From 4d19eeaf0c4ec7b35c290a4ee2c72aeda53304d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 14:33:07 +0000 Subject: [PATCH 1/3] Initial plan From 2060faa102e1c030d376353f6a85280ee93b5c72 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 11 Oct 2025 15:04:45 +0000 Subject: [PATCH 2/3] Implement UI enhancements: hover underlines, delete checklist button, category labels, in-page confirmations Co-authored-by: robpitcher <13648061+robpitcher@users.noreply.github.com> --- .github/copilot-instructions.md | 7 +- README.md | 5 +- frontend/src/components/ChecklistDemo.tsx | 6 -- .../src/components/checklist/AddItemForm.tsx | 45 ++++------ .../checklist/ChecklistOverview.tsx | 84 +++++-------------- .../components/checklist/ChecklistPage.tsx | 64 ++++++++++++-- frontend/src/components/checklist/ItemRow.tsx | 75 +++++++++++++---- frontend/src/types/checklist.ts | 4 +- 8 files changed, 163 insertions(+), 127 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 27e2a8a..f2d05a3 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -15,8 +15,11 @@ Aidventure is an MVP web application that helps adventure racers generate and ma - ✅ Complete type definitions for checklists / items (`src/types/checklist.ts`) - ✅ LocalStorage persistence layer with versioning + cross-tab sync - ✅ Zustand state management with full CRUD operations -- ✅ Checklist UI: category grouping, add/edit/delete items, inline editing, bulk complete/reset, progress metrics (see `CHECKLIST_UI.md`) -- ✅ Accessibility basics: keyboard flows, ARIA labels on interactive elements +- ✅ Checklist UI: category-based organization, add/edit/delete items, inline editing, bulk complete/reset, progress metrics +- ✅ Category labels displayed and editable from both checklist view and item edit mode +- ✅ In-page confirmations for all delete actions (checklist and items) +- ✅ Hover underlines on clickable checklist and item names +- ✅ Accessibility basics: keyboard flows, ARIA labels on interactive elements, no popups for confirmations - ✅ Comprehensive tests for storage + state layers (Vitest + RTL) - ✅ Docker setup for consistent development diff --git a/README.md b/README.md index b47bb76..e7320b5 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,10 @@ Aidventure is a React-based web application designed to help adventure racers cr - Complete type system for checklists and items - LocalStorage persistence layer with cross-tab synchronization - Zustand state management with full CRUD operations -- Checklist UI (category grouping, add/edit/delete items, progress, bulk actions) – see `frontend/CHECKLIST_UI.md` +- Checklist UI with category-based organization, inline editing, and delete confirmations +- Category labels displayed and editable from both checklist view and item scope +- In-page confirmations for all delete actions (no popups) +- Visual feedback with hover underlines on clickable elements - Comprehensive test coverage (storage + state management; UI tests upcoming) - Docker development environment with hot reload - Complete linting and formatting setup diff --git a/frontend/src/components/ChecklistDemo.tsx b/frontend/src/components/ChecklistDemo.tsx index 311c379..f864c35 100644 --- a/frontend/src/components/ChecklistDemo.tsx +++ b/frontend/src/components/ChecklistDemo.tsx @@ -32,14 +32,12 @@ export function ChecklistDemo() { await addItem(checklist.id, { name: 'Topographic maps', category: 'Navigation', - priority: 'high', completed: false, }); await addItem(checklist.id, { name: 'Compass', category: 'Navigation', - priority: 'high', completed: false, }); @@ -47,7 +45,6 @@ export function ChecklistDemo() { name: 'Energy gels', category: 'Nutrition', quantity: 10, - priority: 'normal', completed: false, }); @@ -55,14 +52,12 @@ export function ChecklistDemo() { name: 'Water bottles', category: 'Hydration', quantity: 2, - priority: 'high', completed: false, }); await addItem(checklist.id, { name: 'First aid kit', category: 'Emergency', - priority: 'high', completed: false, }); @@ -157,7 +152,6 @@ export function ChecklistDemo() {

{item.category} - {item.priority && ` • ${item.priority}`}

diff --git a/frontend/src/components/checklist/AddItemForm.tsx b/frontend/src/components/checklist/AddItemForm.tsx index 45aae89..cc2141e 100644 --- a/frontend/src/components/checklist/AddItemForm.tsx +++ b/frontend/src/components/checklist/AddItemForm.tsx @@ -3,17 +3,16 @@ import type { Item } from '../../types/checklist'; interface AddItemFormProps { checklistId: string; - category: string; + category?: string; // Optional, not used but kept for backward compatibility onAdd: (checklistId: string, item: Omit) => Promise; onCancel?: () => void; } -export function AddItemForm({ checklistId, category, onAdd, onCancel }: AddItemFormProps) { +export function AddItemForm({ checklistId, onAdd, onCancel }: AddItemFormProps) { const [name, setName] = useState(''); const [notes, setNotes] = useState(''); const [quantity, setQuantity] = useState('1'); - const [priority, setPriority] = useState<'high' | 'normal' | 'optional'>('normal'); - const [selectedCategory, setSelectedCategory] = useState(category); + const [selectedCategory, setSelectedCategory] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const inputRef = useRef(null); @@ -32,10 +31,9 @@ export function AddItemForm({ checklistId, category, onAdd, onCancel }: AddItemF try { await onAdd(checklistId, { name: name.trim(), - category: selectedCategory, + category: selectedCategory.trim() || undefined, notes: notes.trim() || undefined, quantity: quantity ? parseInt(quantity, 10) : undefined, - priority, completed: false, }); @@ -43,7 +41,7 @@ export function AddItemForm({ checklistId, category, onAdd, onCancel }: AddItemF setName(''); setNotes(''); setQuantity('1'); - setPriority('normal'); + setSelectedCategory(''); inputRef.current?.focus(); } finally { setIsSubmitting(false); @@ -92,29 +90,16 @@ export function AddItemForm({ checklistId, category, onAdd, onCancel }: AddItemF aria-label="Notes" /> -
- setQuantity(e.target.value)} - onKeyDown={handleKeyDown} - placeholder="Quantity" - className="w-24 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-accent" - aria-label="Quantity" - min="1" - /> - - -
+ setQuantity(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Quantity" + className="w-full px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-accent" + aria-label="Quantity" + min="1" + />
- - {/* Checklist Content */} -
handleSelectChecklist(checklist.id)}> -

- {checklist.name} -

- -
-
- - {completedItems} of {totalItems} items completed - - {completionPercentage}% -
- -
-
-
+

+ {checklist.name} +

+ +
+
+ + {completedItems} of {totalItems} items completed + + {completionPercentage}%
-
- Created: {formatDate(checklist.createdAt)} - Updated: {formatDate(checklist.updatedAt)} +
+
+ +
+ Created: {formatDate(checklist.createdAt)} + Updated: {formatDate(checklist.updatedAt)} +
); })} diff --git a/frontend/src/components/checklist/ChecklistPage.tsx b/frontend/src/components/checklist/ChecklistPage.tsx index 48f6684..82f954c 100644 --- a/frontend/src/components/checklist/ChecklistPage.tsx +++ b/frontend/src/components/checklist/ChecklistPage.tsx @@ -16,11 +16,13 @@ export function ChecklistPage() { deleteItem, addItem, setCurrentChecklist, + deleteChecklist, } = useChecklistStore(); const [isAddingItem, setIsAddingItem] = useState(false); const [isCreatingChecklist, setIsCreatingChecklist] = useState(false); const [newChecklistName, setNewChecklistName] = useState(''); + const [isDeletingChecklist, setIsDeletingChecklist] = useState(false); useEffect(() => { loadChecklists(); @@ -48,6 +50,22 @@ export function ChecklistPage() { setIsAddingItem(false); }; + const handleDeleteChecklistClick = () => { + setIsDeletingChecklist(true); + }; + + const handleConfirmDeleteChecklist = async () => { + if (currentChecklistId) { + await deleteChecklist(currentChecklistId); + setCurrentChecklist(null); + setIsDeletingChecklist(false); + } + }; + + const handleCancelDeleteChecklist = () => { + setIsDeletingChecklist(false); + }; + if (isLoading && checklists.length === 0) { return (
@@ -93,12 +111,21 @@ export function ChecklistPage() { {currentChecklist?.name || 'Packing Checklist'}
- + {currentChecklist ? ( + + ) : ( + + )}
{currentChecklist && ( @@ -118,6 +145,29 @@ export function ChecklistPage() { )}
+ {/* Delete Checklist Confirmation */} + {isDeletingChecklist && currentChecklist && ( +
+

+ Are you sure you want to delete "{currentChecklist.name}"? This action cannot be undone. +

+
+ + +
+
+ )} + {/* Create Checklist Form */} {isCreatingChecklist && (
@@ -218,7 +268,7 @@ export function ChecklistPage() { {isAddingItem ? ( setIsAddingItem(false)} /> diff --git a/frontend/src/components/checklist/ItemRow.tsx b/frontend/src/components/checklist/ItemRow.tsx index 4ed8a20..1fde5c4 100644 --- a/frontend/src/components/checklist/ItemRow.tsx +++ b/frontend/src/components/checklist/ItemRow.tsx @@ -11,9 +11,11 @@ interface ItemRowProps { export function ItemRow({ item, checklistId, onToggleComplete, onUpdate, onDelete }: ItemRowProps) { const [isEditing, setIsEditing] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); const [editedName, setEditedName] = useState(item.name); const [editedNotes, setEditedNotes] = useState(item.notes || ''); const [editedQuantity, setEditedQuantity] = useState(item.quantity?.toString() || ''); + const [editedCategory, setEditedCategory] = useState(item.category || ''); const inputRef = useRef(null); useEffect(() => { @@ -30,6 +32,7 @@ export function ItemRow({ item, checklistId, onToggleComplete, onUpdate, onDelet await onUpdate(checklistId, item.id, { name: editedName.trim(), + category: editedCategory.trim() || undefined, notes: editedNotes.trim() || undefined, quantity: editedQuantity ? parseInt(editedQuantity, 10) : undefined, }); @@ -40,6 +43,7 @@ export function ItemRow({ item, checklistId, onToggleComplete, onUpdate, onDelet setEditedName(item.name); setEditedNotes(item.notes || ''); setEditedQuantity(item.quantity?.toString() || ''); + setEditedCategory(item.category || ''); setIsEditing(false); }; @@ -53,13 +57,47 @@ export function ItemRow({ item, checklistId, onToggleComplete, onUpdate, onDelet } }; - // TODO: [Accessibility] Using confirm() is not ideal for accessibility. Replace with a proper modal dialog component that supports keyboard navigation and screen readers. - const handleDelete = async () => { - if (confirm(`Delete "${item.name}"?`)) { - await onDelete(checklistId, item.id); - } + // In-page confirmation for delete + const handleDeleteClick = () => { + setIsDeleting(true); + }; + + const handleConfirmDelete = async () => { + await onDelete(checklistId, item.id); }; + const handleCancelDelete = () => { + setIsDeleting(false); + }; + + if (isDeleting) { + return ( +
+
+

+ Delete "{item.name}"? +

+
+ + +
+
+
+ ); + } + if (isEditing) { return (
@@ -74,6 +112,15 @@ export function ItemRow({ item, checklistId, onToggleComplete, onUpdate, onDelet placeholder="Item name" aria-label="Item name" /> + setEditedCategory(e.target.value)} + onKeyDown={handleKeyDown} + className="w-full px-2 py-1 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-accent" + placeholder="Category (optional)" + aria-label="Category" + />

setIsEditing(true)} > {item.name} {item.quantity && ( @@ -138,17 +186,12 @@ export function ItemRow({ item, checklistId, onToggleComplete, onUpdate, onDelet {item.notes && (

{item.notes}

)} - {item.priority && ( + {item.category && ( setIsEditing(true)} + className="inline-block mt-1 px-2 py-0.5 text-xs rounded bg-blue-100 text-blue-600 hover:bg-blue-200 cursor-pointer" > - {item.priority} + {item.category} )}
@@ -173,7 +216,7 @@ export function ItemRow({ item, checklistId, onToggleComplete, onUpdate, onDelet + +
+ + {/* Checklist Content */} +
handleSelectChecklist(checklist.id)}> +

+ {checklist.name} +

@@ -192,6 +252,7 @@ export function ChecklistOverview() { Created: {formatDate(checklist.createdAt)} Updated: {formatDate(checklist.updatedAt)}
+
); })}