diff --git a/frontend/src/index.css b/frontend/src/index.css
index a461c50..15d863b 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1 +1,6 @@
-@import "tailwindcss";
\ No newline at end of file
+@import "tailwindcss";
+
+/* Remove list styling from checklist items */
+li {
+ list-style: none;
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Checklist.tsx b/frontend/src/pages/Checklist.tsx
index 1fa0a19..7877bbe 100644
--- a/frontend/src/pages/Checklist.tsx
+++ b/frontend/src/pages/Checklist.tsx
@@ -4,11 +4,20 @@ import { useSSE } from '../hooks/useSSE'
import ChecklistItem from '../components/ChecklistItem'
import type { ChecklistItem as ChecklistItemType } from '../types'
+interface ItemGroup {
+ title: string
+ items: ChecklistItemType[]
+ isCollapsible?: boolean
+ isCollapsed?: boolean
+ onToggleCollapse?: () => void
+}
+
export default function Checklist() {
const { uuid } = useParams<{ uuid: string }>()
const { items, checkListName, isConnected, error } = useSSE(uuid || '')
const [newItemContent, setNewItemContent] = useState('')
const [isAddingItem, setIsAddingItem] = useState(false)
+ const [completedItemsCollapsed, setCompletedItemsCollapsed] = useState(false)
const buildItemTree = (items: ChecklistItemType[]): ChecklistItemType[] => {
@@ -36,6 +45,79 @@ export default function Checklist() {
return rootItems
}
+ const categorizeItems = (items: ChecklistItemType[]): ItemGroup[] => {
+ const now = new Date()
+
+ // Helper function to check if an item can be completed
+ const canComplete = (item: ChecklistItemType): boolean => {
+ // Check dependencies
+ const dependenciesCompleted = item.dependencies?.every(depId => {
+ const depItem = items.find(i => i.id === depId)
+ return depItem?.checked
+ }) ?? true
+
+ // Check date constraints
+ const notBeforeDate = item.not_before ? new Date(item.not_before) : null
+ const notAfterDate = item.not_after ? new Date(item.not_after) : null
+ const dateConstraintsMet = (!notBeforeDate || now >= notBeforeDate) && (!notAfterDate || now <= notAfterDate)
+
+ return dependenciesCompleted && dateConstraintsMet
+ }
+
+ // Helper function to check if an item is locked by constraints
+ const isLockedByConstraints = (item: ChecklistItemType): boolean => {
+ if (item.checked) return false
+
+ // Check dependencies
+ const dependenciesCompleted = item.dependencies?.every(depId => {
+ const depItem = items.find(i => i.id === depId)
+ return depItem?.checked
+ }) ?? true
+
+ // Check date constraints
+ const notBeforeDate = item.not_before ? new Date(item.not_before) : null
+ const notAfterDate = item.not_after ? new Date(item.not_after) : null
+ const dateConstraintsMet = (!notBeforeDate || now >= notBeforeDate) && (!notAfterDate || now <= notAfterDate)
+
+ return !dependenciesCompleted || !dateConstraintsMet
+ }
+
+ const completedItems = items.filter(item => item.checked)
+ const availableItems = items.filter(item => !item.checked && canComplete(item))
+ const lockedItems = items.filter(item => !item.checked && isLockedByConstraints(item))
+
+ const groups: ItemGroup[] = []
+
+ // Completed items (collapsible)
+ if (completedItems.length > 0) {
+ groups.push({
+ title: `Completed (${completedItems.length})`,
+ items: completedItems,
+ isCollapsible: true,
+ isCollapsed: completedItemsCollapsed,
+ onToggleCollapse: () => setCompletedItemsCollapsed(!completedItemsCollapsed)
+ })
+ }
+
+ // Available items (can be completed)
+ if (availableItems.length > 0) {
+ groups.push({
+ title: `Available (${availableItems.length})`,
+ items: availableItems
+ })
+ }
+
+ // Locked items (cannot be completed due to constraints)
+ if (lockedItems.length > 0) {
+ groups.push({
+ title: `Locked by Constraints (${lockedItems.length})`,
+ items: lockedItems
+ })
+ }
+
+ return groups
+ }
+
const addItem = async (content: string, parentId?: number) => {
if (!content.trim() || !uuid) return
@@ -147,6 +229,117 @@ export default function Checklist() {
))
}
+ const renderGroupedItems = (allItems: ChecklistItemType[]) => {
+ const itemTree = buildItemTree(allItems)
+
+ if (itemTree.length === 0) {
+ return (
+
+
📝
+
No items yet
+
Add your first item above to get started!
+
+ )
+ }
+
+ const groups = categorizeItems(allItems)
+
+ return (
+
+ {groups.map((group, groupIndex) => (
+
+ {/* Group Header */}
+
+
+ {group.title.includes('Completed') && (
+
+ )}
+ {group.title.includes('Available') && (
+
+ )}
+ {group.title.includes('Locked') && (
+
+ )}
+ {group.title}
+
+ {group.isCollapsible && (
+
+ )}
+
+
+ {/* Group Items */}
+ {(!group.isCollapsible || !group.isCollapsed) && (
+
+ {group.items.map(item => {
+ // Find the item with its children from the tree
+ const findItemWithChildren = (items: ChecklistItemType[], targetId: number): ChecklistItemType | null => {
+ for (const item of items) {
+ if (item.id === targetId) {
+ return item
+ }
+ if (item.children) {
+ const found = findItemWithChildren(item.children, targetId)
+ if (found) return found
+ }
+ }
+ return null
+ }
+
+ const itemWithChildren = findItemWithChildren(itemTree, item.id)
+
+ return (
+
+ )
+ })}
+
+ )}
+
+ ))}
+
+ )
+ }
+
if (!uuid) {
return (
@@ -229,17 +422,7 @@ export default function Checklist() {
{/* Items Section */}
- {itemTree.length === 0 ? (
-
-
📝
-
No items yet
-
Add your first item above to get started!
-
- ) : (
-
- {renderItems(itemTree, items)}
-
- )}
+ {renderGroupedItems(items)}