+
Todo Items {todoListName && `- ${todoListName}`}
+
+ {error &&
{error}
}
+ {signalRError &&
SignalR error: {signalRError}
}
+ {completionMessage &&
{completionMessage}
}
+ {loading ? (
+
+ ) : items.length === 0 ? (
+
+
No items in this list yet
+
+ ) : (
+
+ {items.map((item) => (
+
+ ))}
+
+ )}
+
+ {items.length > 0 && (
+
+
+
+ {jobStatus && (
+
+ {jobStatus.state === JobState.Queued &&
Job queued, waiting to start...
}
+ {jobStatus.state === JobState.Processing && (
+
Processing: {jobStatus.processedCount}/{jobStatus.totalCount} items completed...
+ )}
+
+ )}
+
+ )}
+
+ )
+}
diff --git a/src/components/TodoListItem.tsx b/src/components/TodoListItem.tsx
new file mode 100644
index 0000000..4500501
--- /dev/null
+++ b/src/components/TodoListItem.tsx
@@ -0,0 +1,113 @@
+import { useState } from 'react'
+import type { TodoList } from '../types'
+import * as React from 'react';
+
+interface TodoListItemProps {
+ list: TodoList
+ isActive: boolean
+ onSelect: (id: number) => void
+ onDelete: (id: number) => void
+ onUpdate: (id: number, name: string) => void
+}
+
+export default function TodoListItem({ list, isActive, onSelect, onDelete, onUpdate }: TodoListItemProps) {
+ const [isEditing, setIsEditing] = useState(false)
+ const [editName, setEditName] = useState(list.name)
+
+ const handleDelete = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ if (confirm(`Are you sure you want to delete "${list.name}"?`)) {
+ onDelete(list.id)
+ }
+ }
+
+ const handleEdit = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ setIsEditing(true)
+ setEditName(list.name)
+ }
+
+ const handleSave = async (e: React.MouseEvent) => {
+ e.stopPropagation()
+ if (editName.trim() && editName !== list.name) {
+ onUpdate(list.id, editName.trim())
+ }
+ setIsEditing(false)
+ }
+
+ const handleCancel = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ setIsEditing(false)
+ setEditName(list.name)
+ }
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ e.preventDefault()
+ if (editName.trim() && editName !== list.name) {
+ onUpdate(list.id, editName.trim())
+ }
+ setIsEditing(false)
+ } else if (e.key === 'Escape') {
+ setIsEditing(false)
+ setEditName(list.name)
+ }
+ }
+
+ return (
+ !isEditing && onSelect(list.id)}
+ >
+ {isEditing ? (
+
setEditName(e.target.value)}
+ onKeyDown={handleKeyDown}
+ onClick={(e) => e.stopPropagation()}
+ autoFocus
+ style={{ flex: 1, marginRight: '10px' }}
+ />
+ ) : (
+
{list.name}
+ )}
+
+ {!isEditing && ({list.incompleteItemCount})}
+ {isEditing ? (
+ <>
+
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+ )
+}
diff --git a/src/components/TodoListsPanel.tsx b/src/components/TodoListsPanel.tsx
new file mode 100644
index 0000000..bf7c7ea
--- /dev/null
+++ b/src/components/TodoListsPanel.tsx
@@ -0,0 +1,55 @@
+import type { TodoList } from '../types'
+import TodoListItem from './TodoListItem'
+import CreateListForm from './CreateListForm'
+
+interface TodoListsPanelProps {
+ lists: TodoList[]
+ selectedListId: number | null
+ loading: boolean
+ error: string | null
+ onCreateList: (name: string) => Promise