From 3aedfb0954286a73dd80667298299dcb226549cd Mon Sep 17 00:00:00 2001 From: Paul Bailey Date: Mon, 1 Dec 2025 19:13:44 +0000 Subject: [PATCH] Implement filtering and sorting functionality - Add getItemsByFilter server action with support for: - Priority filtering (low, medium, high) - Completed status filtering - Tag filtering with compound AND logic - Due date range filtering - Sorting by createdAt, dueDate, priority, and title - Ascending and descending sort order - Handle null due dates in sorting (placed at end) - Implement compound filter logic with AND conditions - Requirements: 6.1, 6.2, 6.3, 6.5 --- lib/actions.ts | 106 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/lib/actions.ts b/lib/actions.ts index e8f958e..ab585b9 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -178,8 +178,8 @@ export async function searchItems(query: string, listId?: string) { const tagMatches = allItems.filter((item) => { const tags = Array.isArray(item.tags) ? item.tags : [] - return tags.some((tag: string) => - tag.toLowerCase().includes(searchTerm) + return tags.some((tag) => + typeof tag === 'string' && tag.toLowerCase().includes(searchTerm) ) }) @@ -190,4 +190,106 @@ export async function searchItems(query: string, listId?: string) { ) return uniqueResults +} + +export async function getItemsByFilter( + listId: string | null, + filters: { + priority?: 'low' | 'medium' | 'high' | null + completed?: boolean | null + tags?: string[] + dueDateRange?: { start: Date | null; end: Date | null } + sortBy?: 'createdAt' | 'dueDate' | 'priority' | 'title' + sortOrder?: 'asc' | 'desc' + } = {} +) { + // Build the where clause with compound filters (AND logic) + const whereClause: any = {} + + // Add list scope if specified + if (listId) { + whereClause.listId = listId + } + + // Filter by priority + if (filters.priority) { + whereClause.priority = filters.priority + } + + // Filter by completed status + if (filters.completed !== undefined && filters.completed !== null) { + whereClause.completed = filters.completed + } + + // Filter by due date range + if (filters.dueDateRange) { + const { start, end } = filters.dueDateRange + if (start || end) { + whereClause.dueDate = {} + if (start) { + whereClause.dueDate.gte = start + } + if (end) { + whereClause.dueDate.lte = end + } + } + } + + // Fetch items from database + const items = await prisma.item.findMany({ + where: whereClause, + include: { + list: true, + }, + }) + + // Filter by tags (client-side since tags are stored as JSON) + let filteredItems = items + if (filters.tags && filters.tags.length > 0) { + filteredItems = items.filter((item) => { + const itemTags = Array.isArray(item.tags) ? item.tags : [] + return filters.tags!.every((filterTag) => + itemTags.some((itemTag) => typeof itemTag === 'string' && itemTag === filterTag) + ) + }) + } + + // Sort items + const sortBy = filters.sortBy || 'createdAt' + const sortOrder = filters.sortOrder || 'desc' + + const sortedItems = [...filteredItems].sort((a, b) => { + let comparison = 0 + + switch (sortBy) { + case 'createdAt': + comparison = a.createdAt.getTime() - b.createdAt.getTime() + break + case 'dueDate': + // Handle null due dates - place at end + if (a.dueDate === null && b.dueDate === null) { + comparison = 0 + } else if (a.dueDate === null) { + comparison = 1 // a goes after b + } else if (b.dueDate === null) { + comparison = -1 // b goes after a + } else { + comparison = a.dueDate.getTime() - b.dueDate.getTime() + } + break + case 'priority': + const priorityOrder = { low: 1, medium: 2, high: 3 } + const aPriority = priorityOrder[a.priority as keyof typeof priorityOrder] || 2 + const bPriority = priorityOrder[b.priority as keyof typeof priorityOrder] || 2 + comparison = aPriority - bPriority + break + case 'title': + comparison = a.title.localeCompare(b.title) + break + } + + return sortOrder === 'asc' ? comparison : -comparison + }) + + return sortedItems } \ No newline at end of file