diff --git a/dev-dist/sw.js b/dev-dist/sw.js index ae3c97b..b39aad6 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.9ku5uov2tfg" + "revision": "0.r0dlh3r3ltk" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/src/App.tsx b/src/App.tsx index 6b2373a..17d0cda 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { TooltipProvider } from "@/components/ui/tooltip"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import { AuthProvider } from "@/contexts/AuthContext"; import { OfflineProvider } from "@/contexts/OfflineContext"; +import { TimeTrackingProvider } from "@/contexts/TimeTrackingContext"; import { Suspense, lazy } from "react"; import { InstallPrompt } from "@/components/InstallPrompt"; import { UpdateNotification } from "@/components/UpdateNotification"; @@ -27,26 +28,28 @@ const PageLoader = () => ( const App = () => ( - - - - - }> - - } /> - } /> - } /> - } /> - } /> - {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} - } /> - - - - - - - + + + + + + }> + + } /> + } /> + } /> + } /> + } /> + {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} + } /> + + + + + + + + ); diff --git a/src/contexts/TimeTrackingContext.tsx b/src/contexts/TimeTrackingContext.tsx index 6637df6..07f8a75 100644 --- a/src/contexts/TimeTrackingContext.tsx +++ b/src/contexts/TimeTrackingContext.tsx @@ -1,10 +1,10 @@ import React, { - createContext, - useContext, - useState, - useEffect, - useCallback, - useRef + createContext, + useContext, + useState, + useEffect, + useCallback, + useRef } from "react"; import { DEFAULT_CATEGORIES, TaskCategory } from "@/config/categories"; import { DEFAULT_PROJECTS, ProjectCategory } from "@/config/projects"; @@ -60,15 +60,15 @@ export interface TimeEntry { } export interface InvoiceData { - client: string; - period: { startDate: Date; endDate: Date }; - projects: { [key: string]: { hours: number; rate: number; amount: number } }; - summary: { - totalHours: number; - totalAmount: number; - }; - tasks: (Task & { dayId: string; dayDate: string; dailySummary: string })[]; - dailySummaries: { [dayId: string]: { date: string; summary: string } }; + client: string; + period: { startDate: Date; endDate: Date }; + projects: { [key: string]: { hours: number; rate: number; amount: number } }; + summary: { + totalHours: number; + totalAmount: number; + }; + tasks: (Task & { dayId: string; dayDate: string; dailySummary: string })[]; + dailySummaries: { [dayId: string]: { date: string; summary: string } }; } interface TimeTrackingContextType { @@ -462,15 +462,6 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ // 2. Window close (beforeunload) // 3. Manual sync button - // Track changes to mark as unsaved - useEffect(() => { - // Mark as having unsaved changes whenever state changes - // (but not during initial loading) - if (!loading && dataService) { - setHasUnsavedChanges(true); - } - }, [isDayStarted, dayStartTime, tasks, currentTask, archivedDays, projects, categories, loading, dataService]); - // Save on window close to prevent data loss useEffect(() => { const handleBeforeUnload = (event: BeforeUnloadEvent) => { @@ -498,6 +489,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ const now = startDateTime || new Date(); setIsDayStarted(true); setDayStartTime(now); + setHasUnsavedChanges(true); console.log('Day started at:', now); }; @@ -522,6 +514,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ setCurrentTask(null); } setIsDayStarted(false); + setHasUnsavedChanges(true); console.log('🔚 Day ended - saving state...'); // Save immediately since this is a critical action saveImmediately().then(() => { @@ -568,6 +561,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ setTasks((prev) => [...prev, newTask]); setCurrentTask(newTask); + setHasUnsavedChanges(true); console.log('New task started:', title, 'at', taskStartTime); // Save immediately since this is a critical action saveImmediately(); @@ -580,6 +574,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ if (currentTask?.id === taskId) { setCurrentTask((prev) => (prev ? { ...prev, ...updates } : null)); } + setHasUnsavedChanges(true); console.log('Task updated:', taskId, updates); }; @@ -588,6 +583,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ if (currentTask?.id === taskId) { setCurrentTask(null); } + setHasUnsavedChanges(true); console.log('Task deleted:', taskId); }; @@ -643,6 +639,8 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ }); console.log('✅ Cleared current day state saved'); + setHasUnsavedChanges(false); + // Show success notification to user toast({ title: "Day Archived Successfully", @@ -693,6 +691,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ id: Date.now().toString() }; setProjects((prev) => [...prev, newProject]); + setHasUnsavedChanges(true); console.log('📋 Project added (not saved automatically)'); }; @@ -702,17 +701,20 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ project.id === projectId ? { ...project, ...updates } : project ) ); + setHasUnsavedChanges(true); console.log('📋 Project updated (not saved automatically)'); }; const deleteProject = (projectId: string) => { setProjects((prev) => prev.filter((project) => project.id !== projectId)); + setHasUnsavedChanges(true); console.log('📋 Project deleted (not saved automatically)'); }; const resetProjectsToDefaults = () => { const defaultProjects = convertDefaultProjects(DEFAULT_PROJECTS); setProjects(defaultProjects); + setHasUnsavedChanges(true); }; // Archive management functions @@ -732,6 +734,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ // Then persist to database await dataService.updateArchivedDay(dayId, updates); + setHasUnsavedChanges(false); console.log('✅ Database update complete'); } catch (error) { console.error('❌ Error updating archived day:', error); @@ -778,6 +781,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ // Remove from archive setArchivedDays((prev) => prev.filter((day) => day.id !== dayId)); + setHasUnsavedChanges(true); console.log('Day restored from archive'); }; @@ -788,6 +792,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ id: Date.now().toString() }; setCategories((prev) => [...prev, newCategory]); + setHasUnsavedChanges(true); console.log('🏷️ Category added (not saved automatically)'); }; @@ -800,6 +805,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ category.id === categoryId ? { ...category, ...updates } : category ) ); + setHasUnsavedChanges(true); console.log('🏷️ Category updated (not saved automatically)'); }; @@ -807,6 +813,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ setCategories((prev) => prev.filter((category) => category.id !== categoryId) ); + setHasUnsavedChanges(true); console.log('🏷️ Category deleted (not saved automatically)'); }; @@ -845,13 +852,14 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ setCurrentTask((prev) => prev ? { - ...prev, - startTime: roundedStartTime, - endTime: roundedEndTime - } + ...prev, + startTime: roundedStartTime, + endTime: roundedEndTime + } : null ); } + setHasUnsavedChanges(true); }; const getTotalDayDuration = () => { @@ -959,7 +967,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ }); return Math.round(totalRevenue * 100) / 100; - }; const getBillableHoursForDay = (day: DayRecord): number => { + }; const getBillableHoursForDay = (day: DayRecord): number => { // Create lookup maps for O(1) access (performance optimization) const projectMap = new Map(projects.map(p => [p.name, p])); const categoryMap = new Map(categories.map(c => [c.id, c])); diff --git a/src/pages/Archive.tsx b/src/pages/Archive.tsx index 2f96e13..b475d89 100644 --- a/src/pages/Archive.tsx +++ b/src/pages/Archive.tsx @@ -165,42 +165,42 @@ const ArchiveContent: React.FC = () => { - - -
- {totalBillableHours.toFixed(1)}h -
-
Billable Hours
-
-
- - -
- {totalNonBillableHours.toFixed(1)}h -
-
Non-billable Hours
-
- {totalHoursWorked.toFixed(1)}h total work -
-
-
- - -
- ${totalRevenue.toFixed(2)} -
-
Total Revenue
-
-
- - -
- ${totalBillableHours > 0 ? (totalRevenue / totalBillableHours).toFixed(2) : '0.00'} -
-
Avg Billable Rate
-
-
- + + +
+ {totalBillableHours.toFixed(1)}h +
+
Billable Hours
+
+
+ + +
+ {totalNonBillableHours.toFixed(1)}h +
+
Non-billable Hours
+
+ {totalHoursWorked.toFixed(1)}h total work +
+
+
+ + +
+ ${totalRevenue.toFixed(2)} +
+
Total Revenue
+
+
+ + +
+ ${totalBillableHours > 0 ? (totalRevenue / totalBillableHours).toFixed(2) : '0.00'} +
+
Avg Billable Rate
+
+
+ {/* Archived Days */}
@@ -238,9 +238,7 @@ const ArchiveContent: React.FC = () => { const Archive: React.FC = () => { return ( - - - + ); }; diff --git a/src/pages/Categories.tsx b/src/pages/Categories.tsx index 3e13d5a..a06fc7f 100644 --- a/src/pages/Categories.tsx +++ b/src/pages/Categories.tsx @@ -200,11 +200,10 @@ const CategoryContent: React.FC = () => { onClick={() => setFormData((prev) => ({ ...prev, color })) } - className={`w-6 h-6 rounded-full border-2 hover:scale-110 transition-transform ${ - formData.color === color + className={`w-6 h-6 rounded-full border-2 hover:scale-110 transition-transform ${formData.color === color ? 'border-gray-800' : 'border-gray-300' - }`} + }`} style={{ backgroundColor: color }} /> ))} @@ -277,11 +276,10 @@ const CategoryContent: React.FC = () => {

{category.name}

- + }`}> {category.isBillable !== false ? 'Billable' : 'Non-billable'}
@@ -325,9 +323,7 @@ const CategoryContent: React.FC = () => { const Categories: React.FC = () => { return ( - - - + ); }; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index fd8f8ad..b8c177d 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -99,35 +99,35 @@ const TimeTrackerContent = () => { {/* Main Content */} {!isDayStarted ? ( -
-
-
-

- - Dashboard -

-
- {/* Summary Stats */} -
- - -
- {sortedDays.length} -
-
Days Tracked
-
-
- - -
- {totalHours}h -
-
Total Hours
-
-
+
+
+
+

+ + Dashboard +

+
+ {/* Summary Stats */} +
+ + +
+ {sortedDays.length} +
+
Days Tracked
+
+
+ + +
+ {totalHours}h +
+
Total Hours
+
+
+
-
) : null}
{ ))}
)} - + )}
@@ -199,9 +199,7 @@ const TimeTrackerContent = () => { const Index = () => { return ( - - - + ); }; diff --git a/src/pages/ProjectList.tsx b/src/pages/ProjectList.tsx index 899c486..a4c9313 100644 --- a/src/pages/ProjectList.tsx +++ b/src/pages/ProjectList.tsx @@ -357,9 +357,7 @@ const ProjectContent: React.FC = () => { const ProjectList: React.FC = () => { return ( - - - + ); }; diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 2a337d8..37a00bf 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -226,9 +226,7 @@ const SettingsContent: React.FC = () => { const Settings: React.FC = () => { return ( - - - + ); };