From 8727674c5937df2fe32ca8734c33a34e1a9b1370 Mon Sep 17 00:00:00 2001 From: Adam Jolicoeur Date: Mon, 8 Dec 2025 10:24:41 -0500 Subject: [PATCH] feat: add filtering to archive view --- dev-dist/sw.js | 2 +- src/components/ArchiveFilter.tsx | 166 +++++++++++++++++++++++++++++++ src/pages/Archive.tsx | 137 ++++++++++++++++++------- 3 files changed, 269 insertions(+), 36 deletions(-) create mode 100644 src/components/ArchiveFilter.tsx diff --git a/dev-dist/sw.js b/dev-dist/sw.js index bc3d9a9..044d211 100644 --- a/dev-dist/sw.js +++ b/dev-dist/sw.js @@ -79,7 +79,7 @@ define(['./workbox-21a80088'], (function (workbox) { 'use strict'; */ workbox.precacheAndRoute([{ "url": "index.html", - "revision": "0.qebr9bqg7as" + "revision": "0.v4bl8mgtcdg" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/src/components/ArchiveFilter.tsx b/src/components/ArchiveFilter.tsx new file mode 100644 index 0000000..2921dee --- /dev/null +++ b/src/components/ArchiveFilter.tsx @@ -0,0 +1,166 @@ +import React, { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select'; +import { X, ChevronDown, ChevronUp } from 'lucide-react'; +import { Project } from '@/contexts/TimeTrackingContext'; +import { TaskCategory } from '@/config/categories'; + +export interface ArchiveFilterState { + startDate: string; + endDate: string; + project: string; + category: string; +} + +const ALL_PROJECTS = 'all-projects'; +const ALL_CATEGORIES = 'all-categories'; + +interface ArchiveFilterProps { + filters: ArchiveFilterState; + onFilterChange: (filters: ArchiveFilterState) => void; + projects: Project[]; + categories: TaskCategory[]; +} + +export const ArchiveFilter: React.FC = ({ + filters, + onFilterChange, + projects, + categories +}) => { + const [isExpanded, setIsExpanded] = useState(true); + + const handleReset = () => { + onFilterChange({ + startDate: '', + endDate: '', + project: '', + category: '' + }); + }; + + const isFilterActive = + filters.startDate || filters.endDate || filters.project || filters.category; + + return ( + + +
+
+ + +
+ + {isExpanded && ( +
+ {/* Date Range */} +
+ + + onFilterChange({ ...filters, startDate: e.target.value }) + } + /> +
+ +
+ + + onFilterChange({ ...filters, endDate: e.target.value }) + } + /> +
+ + {/* Project Filter */} +
+ + +
+ + {/* Category Filter */} +
+ + +
+
+ )} +
+
+
+ ); +}; diff --git a/src/pages/Archive.tsx b/src/pages/Archive.tsx index 9142492..2f96e13 100644 --- a/src/pages/Archive.tsx +++ b/src/pages/Archive.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { TimeTrackingProvider, DayRecord @@ -8,6 +8,7 @@ import { ArchiveItem } from '@/components/ArchiveItem'; import { ArchiveEditDialog } from '@/components/ArchiveEditDialog'; import { ExportDialog } from '@/components/ExportDialog'; import { ProjectManagement } from '@/components/ProjectManagement'; +import { ArchiveFilter, ArchiveFilterState } from '@/components/ArchiveFilter'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardTitle } from '@/components/ui/card'; import { Archive as ArchiveIcon, Database } from 'lucide-react'; @@ -18,31 +19,72 @@ import SiteNavigationMenu from '@/components/Navigation'; const ArchiveContent: React.FC = () => { const { archivedDays, - getTotalHoursForPeriod, - getRevenueForPeriod, getHoursWorkedForDay, getBillableHoursForDay, - getNonBillableHoursForDay + getNonBillableHoursForDay, + getRevenueForDay, + projects, + categories } = useTimeTracking(); const [editingDay, setEditingDay] = useState(null); const [showExportDialog, setShowExportDialog] = useState(false); const [showProjectManagement, setShowProjectManagement] = useState(false); + const [filters, setFilters] = useState({ + startDate: "", + endDate: "", + project: "", + category: "" + }); - // Sort archived days from newest to oldest - const sortedDays = [...archivedDays].sort( - (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime() - ); + // Filter and sort archived days + const filteredDays = useMemo(() => { + let filtered = [...archivedDays]; + + // Apply date range filter + if (filters.startDate) { + const startDate = new Date(filters.startDate); + startDate.setHours(0, 0, 0, 0); + filtered = filtered.filter(day => { + const dayDate = new Date(day.startTime); + dayDate.setHours(0, 0, 0, 0); + return dayDate >= startDate; + }); + } + + if (filters.endDate) { + const endDate = new Date(filters.endDate); + endDate.setHours(23, 59, 59, 999); + filtered = filtered.filter(day => { + const dayDate = new Date(day.startTime); + return dayDate <= endDate; + }); + } + + // Apply project filter + if (filters.project) { + filtered = filtered.filter(day => + day.tasks.some(task => task.project === filters.project) + ); + } - // Calculate summary stats - const totalHours = - archivedDays.length > 0 - ? getTotalHoursForPeriod(new Date(0), new Date()) - : 0; - const totalHoursWorked = archivedDays.reduce((sum, day) => sum + getHoursWorkedForDay(day), 0); - const totalBillableHours = archivedDays.reduce((sum, day) => sum + getBillableHoursForDay(day), 0); - const totalNonBillableHours = archivedDays.reduce((sum, day) => sum + getNonBillableHoursForDay(day), 0); - const totalRevenue = - archivedDays.length > 0 ? getRevenueForPeriod(new Date(0), new Date()) : 0; + // Apply category filter + if (filters.category) { + filtered = filtered.filter(day => + day.tasks.some(task => task.category === filters.category) + ); + } + + // Sort from newest to oldest + return filtered.sort( + (a, b) => new Date(b.startTime).getTime() - new Date(a.startTime).getTime() + ); + }, [archivedDays, filters]); + + // Calculate summary stats based on filtered days + const totalHoursWorked = filteredDays.reduce((sum, day) => sum + getHoursWorkedForDay(day), 0); + const totalBillableHours = filteredDays.reduce((sum, day) => sum + getBillableHoursForDay(day), 0); + const totalNonBillableHours = filteredDays.reduce((sum, day) => sum + getNonBillableHoursForDay(day), 0); + const totalRevenue = filteredDays.reduce((sum, day) => sum + getRevenueForDay(day), 0); const handleEdit = (day: DayRecord) => { setEditingDay(day); @@ -73,7 +115,7 @@ const ArchiveContent: React.FC = () => {
- {sortedDays.length === 0 ? ( + {filteredDays.length === 0 && archivedDays.length === 0 ? ( @@ -88,18 +130,41 @@ const ArchiveContent: React.FC = () => { ) : (
- {/* Summary Stats */} -
- - -
- {sortedDays.length} -
-
- Total Days Tracked -
+ {/* Filter Component */} + + + {filteredDays.length === 0 ? ( + + + + No Results Found +

+ No archived days match your filter criteria. +

+
+ ) : ( + <> + {/* Summary Stats */} +
+ + +
+ {filteredDays.length} +
+
+ Days Shown +
+
+
@@ -137,12 +202,14 @@ const ArchiveContent: React.FC = () => {
- {/* Archived Days */} -
- {sortedDays.map((day) => ( - - ))} -
+ {/* Archived Days */} +
+ {filteredDays.map((day) => ( + + ))} +
+ + )}
)}