@burdenoff/microfe-timecampus 2026.801.1 → 2026.820.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/wspace-operations.d.ts +484 -861
- package/dist/generated/wspace-operations.d.ts.map +1 -1
- package/dist/generated/wspace-operations.js +1377 -1377
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/index.js +5 -3
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/stories/EmptyState.stories.d.ts +0 -1
- package/dist/stories/LoadingSkeleton.stories.d.ts +0 -1
- package/dist/stories/StatCard.stories.d.ts +0 -1
- package/dist/stories/StatusBadge.stories.d.ts +0 -1
- package/dist/timecampus/AdminRoot.d.ts +8 -0
- package/dist/timecampus/AdminRoot.d.ts.map +1 -0
- package/dist/timecampus/AdminRoot.js +12 -0
- package/dist/timecampus/AdminRoot.js.map +1 -0
- package/dist/timecampus/AdminRoutes.d.ts +3 -0
- package/dist/timecampus/AdminRoutes.d.ts.map +1 -0
- package/dist/timecampus/AdminRoutes.js +8 -0
- package/dist/timecampus/AdminRoutes.js.map +1 -0
- package/dist/timecampus/TimeCampusRoot.d.ts +0 -1
- package/dist/timecampus/TimeCampusRoot.js +4 -2
- package/dist/timecampus/TimeCampusRoot.js.map +1 -1
- package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
- package/dist/timecampus/components/ChartCard.d.ts +0 -1
- package/dist/timecampus/components/ChartCard.js.map +1 -1
- package/dist/timecampus/components/DataTable.d.ts +0 -1
- package/dist/timecampus/components/EmptyState.d.ts +0 -1
- package/dist/timecampus/components/EmptyState.js.map +1 -1
- package/dist/timecampus/components/ErrorBoundary.d.ts +0 -1
- package/dist/timecampus/components/ErrorBoundary.js.map +1 -1
- package/dist/timecampus/components/FilterBar.d.ts +0 -1
- package/dist/timecampus/components/LoadingSkeleton.d.ts +0 -1
- package/dist/timecampus/components/LoadingSkeleton.js.map +1 -1
- package/dist/timecampus/components/Modal.d.ts +0 -1
- package/dist/timecampus/components/Modal.js +5 -3
- package/dist/timecampus/components/Modal.js.map +1 -1
- package/dist/timecampus/components/PageLayout.d.ts +0 -1
- package/dist/timecampus/components/PageLayout.js.map +1 -1
- package/dist/timecampus/components/StatCard.d.ts +0 -1
- package/dist/timecampus/components/StatCard.js.map +1 -1
- package/dist/timecampus/components/StatusBadge.d.ts +0 -1
- package/dist/timecampus/components/StatusBadge.js.map +1 -1
- package/dist/timecampus/components/Toast.d.ts +0 -1
- package/dist/timecampus/components/Toast.js.map +1 -1
- package/dist/timecampus/components/charts/BalanceWheel.js.map +1 -1
- package/dist/timecampus/components/charts/BarsChart.d.ts +0 -1
- package/dist/timecampus/components/charts/BarsChart.js.map +1 -1
- package/dist/timecampus/components/charts/ChartParts.d.ts +0 -1
- package/dist/timecampus/components/charts/ChartParts.js.map +1 -1
- package/dist/timecampus/components/charts/DonutChart.d.ts +0 -1
- package/dist/timecampus/components/charts/DonutChart.js.map +1 -1
- package/dist/timecampus/components/charts/Heatmap.d.ts +0 -1
- package/dist/timecampus/components/charts/Heatmap.js +29 -29
- package/dist/timecampus/components/charts/Heatmap.js.map +1 -1
- package/dist/timecampus/components/charts/ProgressMeter.d.ts +0 -1
- package/dist/timecampus/components/charts/ProgressMeter.js +2 -2
- package/dist/timecampus/components/charts/ProgressMeter.js.map +1 -1
- package/dist/timecampus/components/charts/RadialGauge.d.ts +0 -1
- package/dist/timecampus/components/charts/RadialGauge.js +2 -2
- package/dist/timecampus/components/charts/RadialGauge.js.map +1 -1
- package/dist/timecampus/components/charts/TrendChart.d.ts +0 -1
- package/dist/timecampus/components/charts/TrendChart.js.map +1 -1
- package/dist/timecampus/components/charts/WeeksGrid.js +2 -2
- package/dist/timecampus/components/charts/WeeksGrid.js.map +1 -1
- package/dist/timecampus/components/charts/chartTheme.js.map +1 -1
- package/dist/timecampus/components/charts/index.js +10 -9
- package/dist/timecampus/components/index.js +18 -17
- package/dist/timecampus/index.d.ts +3 -1
- package/dist/timecampus/index.d.ts.map +1 -1
- package/dist/timecampus/index.js +8 -5
- package/dist/timecampus/pages/HomePage.d.ts +0 -1
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/admin/AdminPage.d.ts +3 -0
- package/dist/timecampus/pages/admin/AdminPage.d.ts.map +1 -0
- package/dist/timecampus/pages/admin/AdminPage.js +235 -0
- package/dist/timecampus/pages/admin/AdminPage.js.map +1 -0
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.d.ts.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.d.ts.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/AnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/focus-score/FocusScorePage.js.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +9 -6
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/domains.d.ts +0 -1
- package/dist/timecampus/pages/lifetime/domains.js +1 -1
- package/dist/timecampus/pages/lifetime/domains.js.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.d.ts.map +1 -1
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.d.ts +0 -1
- package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js +1 -1
- package/dist/timecampus/pages/meetings/MeetingAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.d.ts +0 -1
- package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.d.ts +0 -1
- package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -1
- package/dist/timecampus/pages/pomodoro/PomodoroAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
- package/dist/timecampus/pages/pomodoro/PomodoroSettingsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.d.ts.map +1 -1
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
- package/dist/timecampus/pages/team-activity/TeamActivityPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.d.ts.map +1 -1
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +4 -4
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.d.ts.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -1
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +1 -1
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
- package/dist/timecampus/providers/TimeCampusProvider.d.ts +0 -1
- package/dist/timecampus/providers/TimeCampusProvider.d.ts.map +1 -1
- package/dist/timecampus/providers/TimeCampusProvider.js +38 -35
- package/dist/timecampus/providers/TimeCampusProvider.js.map +1 -1
- package/dist/timecampus/store/timecampusStore.js.map +1 -1
- package/dist/timecampus/types/index.d.ts +4 -1
- package/dist/timecampus/types/index.d.ts.map +1 -1
- package/dist/timecampus/utils/helpers.d.ts +0 -1
- package/dist/timecampus/utils/helpers.js.map +1 -1
- package/dist/types/assistant.d.ts +0 -1
- package/dist/utils/pageContext.js +1 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +60 -57
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PomodoroPage.js","names":[],"sources":["../../../../src/timecampus/pages/pomodoro/PomodoroPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from \"react\";\nimport {\n Play,\n Pause,\n SkipForward,\n RotateCcw,\n Target,\n Plus,\n Pencil,\n Trash2,\n} from \"lucide-react\";\nimport { GlassCard, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetActivePomodoroSessionQuery,\n useGetPomodoroStatsQuery,\n useStartPomodoroSessionMutation,\n useCompletePomodoroSessionMutation,\n useListPomodoroGoalsQuery,\n useCreatePomodoroGoalMutation,\n useUpdatePomodoroGoalMutation,\n useDeletePomodoroGoalMutation,\n type PomodoroGoalFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { PomodoroGoalType } from \"@/generated/wspace-types\";\n\ninterface GoalFormState {\n type: PomodoroGoalType;\n target: string;\n period: string;\n startDate: string;\n endDate: string;\n}\n\nconst inputClasses =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClasses = \"block text-sm font-medium text-text-primary\";\n\nfunction toDateInput(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 10);\n}\n\nfunction todayInput(): string {\n const d = new Date();\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 10);\n}\n\nfunction futureInput(days: number): string {\n const d = new Date();\n d.setDate(d.getDate() + days);\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 10);\n}\n\nfunction toGoalState(goal?: PomodoroGoalFieldsFragment): GoalFormState {\n return {\n type: goal?.type ?? PomodoroGoalType.Daily,\n target: goal?.target != null ? String(goal.target) : \"8\",\n period: goal?.period ?? \"DAILY\",\n startDate: goal?.startDate ? toDateInput(goal.startDate) : todayInput(),\n endDate: goal?.endDate ? toDateInput(goal.endDate) : futureInput(0),\n };\n}\n\nexport function PomodoroPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetPomodoroStatsQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n\n const {\n data: activeData,\n loading: activeLoading,\n error: activeError,\n } = useGetActivePomodoroSessionQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n\n const [startSession] = useStartPomodoroSessionMutation();\n const [completeSession] = useCompletePomodoroSessionMutation();\n\n // Focus goals\n const { data: goalsData, refetch: refetchGoals } = useListPomodoroGoalsQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n const goals = goalsData?.pomodoroGoals ?? [];\n\n const [createGoal, { loading: creatingGoal, error: createGoalError }] =\n useCreatePomodoroGoalMutation();\n const [updateGoal, { loading: updatingGoal, error: updateGoalError }] =\n useUpdatePomodoroGoalMutation();\n const [deleteGoal, { loading: deletingGoal, error: deleteGoalError }] =\n useDeletePomodoroGoalMutation();\n\n const [goalModalOpen, setGoalModalOpen] = useState(false);\n const [editingGoal, setEditingGoal] =\n useState<PomodoroGoalFieldsFragment | null>(null);\n const [deletingGoalEntry, setDeletingGoalEntry] =\n useState<PomodoroGoalFieldsFragment | null>(null);\n const [goalForm, setGoalForm] = useState<GoalFormState>(toGoalState());\n\n const setGoalField = <K extends keyof GoalFormState>(\n key: K,\n v: GoalFormState[K],\n ) => setGoalForm((f) => ({ ...f, [key]: v }));\n\n const openCreateGoal = () => {\n setEditingGoal(null);\n setGoalForm(toGoalState());\n setGoalModalOpen(true);\n };\n\n const openEditGoal = (goal: PomodoroGoalFieldsFragment) => {\n setEditingGoal(goal);\n setGoalForm(toGoalState(goal));\n setGoalModalOpen(true);\n };\n\n const handleSaveGoal = async () => {\n if (!workspaceId) return;\n const target = Number(goalForm.target);\n if (!target || target <= 0) return;\n if (editingGoal) {\n await updateGoal({\n variables: {\n id: editingGoal.id,\n workspaceId,\n input: {\n type: goalForm.type,\n target,\n period: goalForm.period.trim() || goalForm.type,\n startDate: new Date(goalForm.startDate).toISOString(),\n endDate: new Date(goalForm.endDate).toISOString(),\n },\n },\n });\n } else {\n await createGoal({\n variables: {\n input: {\n workspaceId,\n type: goalForm.type,\n target,\n period: goalForm.period.trim() || goalForm.type,\n startDate: new Date(goalForm.startDate).toISOString(),\n endDate: new Date(goalForm.endDate).toISOString(),\n },\n },\n });\n }\n setGoalModalOpen(false);\n await refetchGoals();\n };\n\n const handleDeleteGoal = async () => {\n if (!deletingGoalEntry || !workspaceId) return;\n await deleteGoal({\n variables: { id: deletingGoalEntry.id, workspaceId },\n });\n setDeletingGoalEntry(null);\n await refetchGoals();\n };\n\n const stats = statsData?.pomodoroStats;\n const activeSession = activeData?.activePomodoroSession;\n\n const [timerSeconds, setTimerSeconds] = useState(25 * 60);\n const [isRunning, setIsRunning] = useState(false);\n\n useEffect(() => {\n if (activeSession && !activeSession.completed) {\n const elapsed = Math.floor(\n (Date.now() - new Date(activeSession.startTime).getTime()) / 1000,\n );\n const duration = activeSession.duration ?? 25 * 60;\n const remaining = Math.max(0, duration - elapsed);\n setTimerSeconds(remaining);\n setIsRunning(remaining > 0);\n }\n }, [activeSession]);\n\n useEffect(() => {\n if (!isRunning || timerSeconds <= 0) return;\n const interval = setInterval(() => {\n setTimerSeconds((prev) => {\n if (prev <= 1) {\n setIsRunning(false);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n return () => clearInterval(interval);\n }, [isRunning, timerSeconds]);\n\n const formatTimer = (seconds: number) => {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${String(m).padStart(2, \"0\")}:${String(s).padStart(2, \"0\")}`;\n };\n\n const handleStart = useCallback(async () => {\n if (!workspaceId) return;\n if (activeSession && !activeSession.completed) {\n setIsRunning(true);\n return;\n }\n await startSession({\n variables: { input: { workspaceId, type: \"FOCUS\" as never } },\n refetchQueries: [\"GetActivePomodoroSession\", \"GetPomodoroStats\"],\n });\n setTimerSeconds(25 * 60);\n setIsRunning(true);\n }, [workspaceId, activeSession, startSession]);\n\n const handleComplete = useCallback(async () => {\n if (!activeSession || !workspaceId) return;\n await completeSession({\n variables: { id: activeSession.id, workspaceId },\n refetchQueries: [\"GetActivePomodoroSession\", \"GetPomodoroStats\"],\n });\n setIsRunning(false);\n setTimerSeconds(25 * 60);\n }, [activeSession, workspaceId, completeSession]);\n\n const handleReset = () => {\n setIsRunning(false);\n setTimerSeconds(25 * 60);\n };\n\n if (statsLoading || activeLoading) {\n return (\n <PageLayout\n title=\"Pomodoro Timer\"\n description=\"Focus with the Pomodoro Technique\"\n >\n <LoadingSkeleton variant=\"card\" count={3} />\n </PageLayout>\n );\n }\n\n if (statsError || activeError) {\n return (\n <PageLayout\n title=\"Pomodoro Timer\"\n description=\"Focus with the Pomodoro Technique\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load pomodoro data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const totalFocusH = Math.floor((stats?.totalFocusMinutes ?? 0) / 60);\n const totalFocusM = (stats?.totalFocusMinutes ?? 0) % 60;\n\n return (\n <PageLayout\n title=\"Pomodoro Timer\"\n description=\"Focus with the Pomodoro Technique\"\n purpose=\"Work in focused 25-minute sprints with short breaks — the Pomodoro technique helps you beat procrastination and build a streak of deep-work sessions you can actually measure.\"\n >\n <NextSteps\n storageKey=\"timecampus-pomodoro\"\n className=\"mb-6\"\n steps={[\n {\n id: \"start\",\n label: \"Start a focus session\",\n description: \"Begin a 25-minute sprint on one task.\",\n onClick: () => handleStart(),\n },\n {\n id: \"analytics\",\n label: \"Review your pomodoro stats\",\n description: \"See streaks, completion rate and focus time.\",\n onClick: () => navigate(\"/pomodoro/analytics\"),\n },\n {\n id: \"settings\",\n label: \"Tune your intervals\",\n description: \"Adjust focus and break lengths to fit you.\",\n onClick: () => navigate(\"/pomodoro/settings\"),\n },\n ]}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n title=\"Sessions Today\"\n value={String(stats?.completedSessions ?? 0)}\n description={`Total: ${stats?.totalSessions ?? 0} sessions`}\n />\n <StatCard\n title=\"Focus Time\"\n value={`${totalFocusH}h ${totalFocusM}m`}\n trend={{\n value: stats?.completionRate ?? 0,\n label: \"completion rate\",\n }}\n />\n <StatCard\n title=\"Current Streak\"\n value={`${stats?.streakDays ?? 0} days`}\n description={`Avg: ${stats?.averageSessionMinutes ?? 0}m per session`}\n />\n </div>\n\n <div className=\"mx-auto max-w-md\">\n <GlassCard treatment=\"glass\" glow className=\"p-8 text-center\">\n <div className=\"mb-2\">\n <span className=\"inline-flex rounded-full bg-action-primary-bg/10 px-3 py-1 text-xs font-medium text-primary\">\n {activeSession ? \"Focus Session\" : \"Ready\"}\n </span>\n </div>\n <p className=\"text-7xl font-mono font-bold text-text-primary\">\n {formatTimer(timerSeconds)}\n </p>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {stats\n ? `${stats.completedSessions} of ${stats.totalSessions} sessions`\n : \"No sessions yet\"}\n </p>\n <div className=\"mt-6 flex items-center justify-center gap-3\">\n {!isRunning ? (\n <button\n onClick={handleStart}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-6 py-3 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Play className=\"h-4 w-4\" />\n Start\n </button>\n ) : (\n <button\n onClick={() => setIsRunning(false)}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-6 py-3 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Pause className=\"h-4 w-4\" />\n Pause\n </button>\n )}\n <button\n onClick={handleComplete}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-3 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <SkipForward className=\"h-4 w-4\" />\n Skip\n </button>\n <button\n onClick={handleReset}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-3 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <RotateCcw className=\"h-4 w-4\" />\n Reset\n </button>\n </div>\n </GlassCard>\n </div>\n\n {/* Focus Goals */}\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Target className=\"h-4 w-4\" />\n Focus Goals\n </h3>\n <button\n onClick={openCreateGoal}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Goal\n </button>\n </div>\n {goals.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No focus goals yet. Set a daily or weekly target to build a\n consistent focus habit.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {goals.map((goal: PomodoroGoalFieldsFragment) => {\n const pct = goal.target\n ? Math.min(100, Math.round((goal.current / goal.target) * 100))\n : 0;\n return (\n <div\n key={goal.id}\n className=\"rounded-lg border border-border-subtle p-4\"\n >\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm font-medium capitalize text-text-primary\">\n {goal.type.toLowerCase()} goal\n </p>\n <p className=\"text-xs text-text-secondary\">\n {goal.current} of {goal.target} sessions\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {pct}%\n </span>\n <button\n onClick={() => openEditGoal(goal)}\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit goal\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeletingGoalEntry(goal)}\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Delete goal\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n <div className=\"mt-3 h-2 w-full rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n )}\n </GlassCard>\n\n {/* Goal create/edit modal */}\n <Modal\n open={goalModalOpen}\n onClose={() => setGoalModalOpen(false)}\n title={editingGoal ? \"Edit Focus Goal\" : \"Add Focus Goal\"}\n description=\"Set a target number of focus sessions\"\n footer={\n <>\n <button\n onClick={() => setGoalModalOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSaveGoal}\n disabled={\n creatingGoal || updatingGoal || Number(goalForm.target) <= 0\n }\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creatingGoal || updatingGoal\n ? \"Saving...\"\n : editingGoal\n ? \"Save Changes\"\n : \"Add Goal\"}\n </button>\n </>\n }\n >\n {(createGoalError || updateGoalError) && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to save goal. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className={labelClasses}>Goal Type</label>\n <select\n value={goalForm.type}\n onChange={(e) => {\n const type = e.target.value as PomodoroGoalType;\n setGoalForm((f) => ({\n ...f,\n type,\n period: type,\n endDate:\n type === PomodoroGoalType.Daily\n ? futureInput(0)\n : type === PomodoroGoalType.Weekly\n ? futureInput(7)\n : futureInput(30),\n }));\n }}\n className={inputClasses}\n >\n <option value={PomodoroGoalType.Daily}>Daily</option>\n <option value={PomodoroGoalType.Weekly}>Weekly</option>\n <option value={PomodoroGoalType.Monthly}>Monthly</option>\n </select>\n </div>\n <div>\n <label className={labelClasses}>Target Sessions</label>\n <input\n type=\"number\"\n min=\"1\"\n placeholder=\"8\"\n value={goalForm.target}\n onChange={(e) => setGoalField(\"target\", e.target.value)}\n className={inputClasses}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClasses}>Start Date</label>\n <input\n type=\"date\"\n value={goalForm.startDate}\n onChange={(e) => setGoalField(\"startDate\", e.target.value)}\n className={inputClasses}\n />\n </div>\n <div>\n <label className={labelClasses}>End Date</label>\n <input\n type=\"date\"\n value={goalForm.endDate}\n onChange={(e) => setGoalField(\"endDate\", e.target.value)}\n className={inputClasses}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n {/* Goal delete confirm modal */}\n <Modal\n open={deletingGoalEntry !== null}\n onClose={() => setDeletingGoalEntry(null)}\n title=\"Delete Focus Goal\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeletingGoalEntry(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteGoal}\n disabled={deletingGoal}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deletingGoal ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n {deleteGoalError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to delete goal. Please try again.\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete this{\" \"}\n {deletingGoalEntry?.type.toLowerCase()} focus goal? This action cannot\n be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PomodoroPage.js","names":[],"sources":["../../../../src/timecampus/pages/pomodoro/PomodoroPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from \"react\";\nimport {\n Play,\n Pause,\n SkipForward,\n RotateCcw,\n Target,\n Plus,\n Pencil,\n Trash2,\n} from \"lucide-react\";\nimport { GlassCard, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetActivePomodoroSessionQuery,\n useGetPomodoroStatsQuery,\n useStartPomodoroSessionMutation,\n useCompletePomodoroSessionMutation,\n useListPomodoroGoalsQuery,\n useCreatePomodoroGoalMutation,\n useUpdatePomodoroGoalMutation,\n useDeletePomodoroGoalMutation,\n type PomodoroGoalFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport { PomodoroGoalType } from \"@/generated/wspace-types\";\n\ninterface GoalFormState {\n type: PomodoroGoalType;\n target: string;\n period: string;\n startDate: string;\n endDate: string;\n}\n\nconst inputClasses =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClasses = \"block text-sm font-medium text-text-primary\";\n\nfunction toDateInput(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return \"\";\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 10);\n}\n\nfunction todayInput(): string {\n const d = new Date();\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 10);\n}\n\nfunction futureInput(days: number): string {\n const d = new Date();\n d.setDate(d.getDate() + days);\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60000);\n return local.toISOString().slice(0, 10);\n}\n\nfunction toGoalState(goal?: PomodoroGoalFieldsFragment): GoalFormState {\n return {\n type: goal?.type ?? PomodoroGoalType.Daily,\n target: goal?.target != null ? String(goal.target) : \"8\",\n period: goal?.period ?? \"DAILY\",\n startDate: goal?.startDate ? toDateInput(goal.startDate) : todayInput(),\n endDate: goal?.endDate ? toDateInput(goal.endDate) : futureInput(0),\n };\n}\n\nexport function PomodoroPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n\n const {\n data: statsData,\n loading: statsLoading,\n error: statsError,\n } = useGetPomodoroStatsQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n\n const {\n data: activeData,\n loading: activeLoading,\n error: activeError,\n } = useGetActivePomodoroSessionQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n\n const [startSession] = useStartPomodoroSessionMutation();\n const [completeSession] = useCompletePomodoroSessionMutation();\n\n // Focus goals\n const { data: goalsData, refetch: refetchGoals } = useListPomodoroGoalsQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n const goals = goalsData?.pomodoroGoals ?? [];\n\n const [createGoal, { loading: creatingGoal, error: createGoalError }] =\n useCreatePomodoroGoalMutation();\n const [updateGoal, { loading: updatingGoal, error: updateGoalError }] =\n useUpdatePomodoroGoalMutation();\n const [deleteGoal, { loading: deletingGoal, error: deleteGoalError }] =\n useDeletePomodoroGoalMutation();\n\n const [goalModalOpen, setGoalModalOpen] = useState(false);\n const [editingGoal, setEditingGoal] =\n useState<PomodoroGoalFieldsFragment | null>(null);\n const [deletingGoalEntry, setDeletingGoalEntry] =\n useState<PomodoroGoalFieldsFragment | null>(null);\n const [goalForm, setGoalForm] = useState<GoalFormState>(toGoalState());\n\n const setGoalField = <K extends keyof GoalFormState>(\n key: K,\n v: GoalFormState[K],\n ) => setGoalForm((f) => ({ ...f, [key]: v }));\n\n const openCreateGoal = () => {\n setEditingGoal(null);\n setGoalForm(toGoalState());\n setGoalModalOpen(true);\n };\n\n const openEditGoal = (goal: PomodoroGoalFieldsFragment) => {\n setEditingGoal(goal);\n setGoalForm(toGoalState(goal));\n setGoalModalOpen(true);\n };\n\n const handleSaveGoal = async () => {\n if (!workspaceId) return;\n const target = Number(goalForm.target);\n if (!target || target <= 0) return;\n if (editingGoal) {\n await updateGoal({\n variables: {\n id: editingGoal.id,\n workspaceId,\n input: {\n type: goalForm.type,\n target,\n period: goalForm.period.trim() || goalForm.type,\n startDate: new Date(goalForm.startDate).toISOString(),\n endDate: new Date(goalForm.endDate).toISOString(),\n },\n },\n });\n } else {\n await createGoal({\n variables: {\n input: {\n workspaceId,\n type: goalForm.type,\n target,\n period: goalForm.period.trim() || goalForm.type,\n startDate: new Date(goalForm.startDate).toISOString(),\n endDate: new Date(goalForm.endDate).toISOString(),\n },\n },\n });\n }\n setGoalModalOpen(false);\n await refetchGoals();\n };\n\n const handleDeleteGoal = async () => {\n if (!deletingGoalEntry || !workspaceId) return;\n await deleteGoal({\n variables: { id: deletingGoalEntry.id, workspaceId },\n });\n setDeletingGoalEntry(null);\n await refetchGoals();\n };\n\n const stats = statsData?.pomodoroStats;\n const activeSession = activeData?.activePomodoroSession;\n\n const [timerSeconds, setTimerSeconds] = useState(25 * 60);\n const [isRunning, setIsRunning] = useState(false);\n\n useEffect(() => {\n if (activeSession && !activeSession.completed) {\n const elapsed = Math.floor(\n (Date.now() - new Date(activeSession.startTime).getTime()) / 1000,\n );\n const duration = activeSession.duration ?? 25 * 60;\n const remaining = Math.max(0, duration - elapsed);\n setTimerSeconds(remaining);\n setIsRunning(remaining > 0);\n }\n }, [activeSession]);\n\n useEffect(() => {\n if (!isRunning || timerSeconds <= 0) return;\n const interval = setInterval(() => {\n setTimerSeconds((prev) => {\n if (prev <= 1) {\n setIsRunning(false);\n return 0;\n }\n return prev - 1;\n });\n }, 1000);\n return () => clearInterval(interval);\n }, [isRunning, timerSeconds]);\n\n const formatTimer = (seconds: number) => {\n const m = Math.floor(seconds / 60);\n const s = seconds % 60;\n return `${String(m).padStart(2, \"0\")}:${String(s).padStart(2, \"0\")}`;\n };\n\n const handleStart = useCallback(async () => {\n if (!workspaceId) return;\n if (activeSession && !activeSession.completed) {\n setIsRunning(true);\n return;\n }\n await startSession({\n variables: { input: { workspaceId, type: \"FOCUS\" as never } },\n refetchQueries: [\"GetActivePomodoroSession\", \"GetPomodoroStats\"],\n });\n setTimerSeconds(25 * 60);\n setIsRunning(true);\n }, [workspaceId, activeSession, startSession]);\n\n const handleComplete = useCallback(async () => {\n if (!activeSession || !workspaceId) return;\n await completeSession({\n variables: { id: activeSession.id, workspaceId },\n refetchQueries: [\"GetActivePomodoroSession\", \"GetPomodoroStats\"],\n });\n setIsRunning(false);\n setTimerSeconds(25 * 60);\n }, [activeSession, workspaceId, completeSession]);\n\n const handleReset = () => {\n setIsRunning(false);\n setTimerSeconds(25 * 60);\n };\n\n if (statsLoading || activeLoading) {\n return (\n <PageLayout\n title=\"Pomodoro Timer\"\n description=\"Focus with the Pomodoro Technique\"\n >\n <LoadingSkeleton variant=\"card\" count={3} />\n </PageLayout>\n );\n }\n\n if (statsError || activeError) {\n return (\n <PageLayout\n title=\"Pomodoro Timer\"\n description=\"Focus with the Pomodoro Technique\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load pomodoro data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const totalFocusH = Math.floor((stats?.totalFocusMinutes ?? 0) / 60);\n const totalFocusM = (stats?.totalFocusMinutes ?? 0) % 60;\n\n return (\n <PageLayout\n title=\"Pomodoro Timer\"\n description=\"Focus with the Pomodoro Technique\"\n purpose=\"Work in focused 25-minute sprints with short breaks — the Pomodoro technique helps you beat procrastination and build a streak of deep-work sessions you can actually measure.\"\n >\n <NextSteps\n storageKey=\"timecampus-pomodoro\"\n className=\"mb-6\"\n steps={[\n {\n id: \"start\",\n label: \"Start a focus session\",\n description: \"Begin a 25-minute sprint on one task.\",\n onClick: () => handleStart(),\n },\n {\n id: \"analytics\",\n label: \"Review your pomodoro stats\",\n description: \"See streaks, completion rate and focus time.\",\n onClick: () => navigate(\"/pomodoro/analytics\"),\n },\n {\n id: \"settings\",\n label: \"Tune your intervals\",\n description: \"Adjust focus and break lengths to fit you.\",\n onClick: () => navigate(\"/pomodoro/settings\"),\n },\n ]}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <StatCard\n title=\"Sessions Today\"\n value={String(stats?.completedSessions ?? 0)}\n description={`Total: ${stats?.totalSessions ?? 0} sessions`}\n />\n <StatCard\n title=\"Focus Time\"\n value={`${totalFocusH}h ${totalFocusM}m`}\n trend={{\n value: stats?.completionRate ?? 0,\n label: \"completion rate\",\n }}\n />\n <StatCard\n title=\"Current Streak\"\n value={`${stats?.streakDays ?? 0} days`}\n description={`Avg: ${stats?.averageSessionMinutes ?? 0}m per session`}\n />\n </div>\n\n <div className=\"mx-auto max-w-md\">\n <GlassCard treatment=\"glass\" glow className=\"p-8 text-center\">\n <div className=\"mb-2\">\n <span className=\"inline-flex rounded-full bg-action-primary-bg/10 px-3 py-1 text-xs font-medium text-primary\">\n {activeSession ? \"Focus Session\" : \"Ready\"}\n </span>\n </div>\n <p className=\"text-7xl font-mono font-bold text-text-primary\">\n {formatTimer(timerSeconds)}\n </p>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {stats\n ? `${stats.completedSessions} of ${stats.totalSessions} sessions`\n : \"No sessions yet\"}\n </p>\n <div className=\"mt-6 flex items-center justify-center gap-3\">\n {!isRunning ? (\n <button\n onClick={handleStart}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-6 py-3 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Play className=\"h-4 w-4\" />\n Start\n </button>\n ) : (\n <button\n onClick={() => setIsRunning(false)}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-6 py-3 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Pause className=\"h-4 w-4\" />\n Pause\n </button>\n )}\n <button\n onClick={handleComplete}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-3 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <SkipForward className=\"h-4 w-4\" />\n Skip\n </button>\n <button\n onClick={handleReset}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle px-4 py-3 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <RotateCcw className=\"h-4 w-4\" />\n Reset\n </button>\n </div>\n </GlassCard>\n </div>\n\n {/* Focus Goals */}\n <GlassCard treatment=\"glass\" className=\"p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Target className=\"h-4 w-4\" />\n Focus Goals\n </h3>\n <button\n onClick={openCreateGoal}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n Add Goal\n </button>\n </div>\n {goals.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No focus goals yet. Set a daily or weekly target to build a\n consistent focus habit.\n </p>\n ) : (\n <div className=\"space-y-3\">\n {goals.map((goal: PomodoroGoalFieldsFragment) => {\n const pct = goal.target\n ? Math.min(100, Math.round((goal.current / goal.target) * 100))\n : 0;\n return (\n <div\n key={goal.id}\n className=\"rounded-lg border border-border-subtle p-4\"\n >\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm font-medium capitalize text-text-primary\">\n {goal.type.toLowerCase()} goal\n </p>\n <p className=\"text-xs text-text-secondary\">\n {goal.current} of {goal.target} sessions\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">\n {pct}%\n </span>\n <button\n onClick={() => openEditGoal(goal)}\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit goal\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeletingGoalEntry(goal)}\n className=\"rounded-md p-1.5 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Delete goal\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n <div className=\"mt-3 h-2 w-full rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{ width: `${pct}%` }}\n />\n </div>\n </div>\n );\n })}\n </div>\n )}\n </GlassCard>\n\n {/* Goal create/edit modal */}\n <Modal\n open={goalModalOpen}\n onClose={() => setGoalModalOpen(false)}\n title={editingGoal ? \"Edit Focus Goal\" : \"Add Focus Goal\"}\n description=\"Set a target number of focus sessions\"\n footer={\n <>\n <button\n onClick={() => setGoalModalOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSaveGoal}\n disabled={\n creatingGoal || updatingGoal || Number(goalForm.target) <= 0\n }\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creatingGoal || updatingGoal\n ? \"Saving...\"\n : editingGoal\n ? \"Save Changes\"\n : \"Add Goal\"}\n </button>\n </>\n }\n >\n {(createGoalError || updateGoalError) && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to save goal. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className={labelClasses}>Goal Type</label>\n <select\n value={goalForm.type}\n onChange={(e) => {\n const type = e.target.value as PomodoroGoalType;\n setGoalForm((f) => ({\n ...f,\n type,\n period: type,\n endDate:\n type === PomodoroGoalType.Daily\n ? futureInput(0)\n : type === PomodoroGoalType.Weekly\n ? futureInput(7)\n : futureInput(30),\n }));\n }}\n className={inputClasses}\n >\n <option value={PomodoroGoalType.Daily}>Daily</option>\n <option value={PomodoroGoalType.Weekly}>Weekly</option>\n <option value={PomodoroGoalType.Monthly}>Monthly</option>\n </select>\n </div>\n <div>\n <label className={labelClasses}>Target Sessions</label>\n <input\n type=\"number\"\n min=\"1\"\n placeholder=\"8\"\n value={goalForm.target}\n onChange={(e) => setGoalField(\"target\", e.target.value)}\n className={inputClasses}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClasses}>Start Date</label>\n <input\n type=\"date\"\n value={goalForm.startDate}\n onChange={(e) => setGoalField(\"startDate\", e.target.value)}\n className={inputClasses}\n />\n </div>\n <div>\n <label className={labelClasses}>End Date</label>\n <input\n type=\"date\"\n value={goalForm.endDate}\n onChange={(e) => setGoalField(\"endDate\", e.target.value)}\n className={inputClasses}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n {/* Goal delete confirm modal */}\n <Modal\n open={deletingGoalEntry !== null}\n onClose={() => setDeletingGoalEntry(null)}\n title=\"Delete Focus Goal\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeletingGoalEntry(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDeleteGoal}\n disabled={deletingGoal}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deletingGoal ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n {deleteGoalError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to delete goal. Please try again.\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete this{\" \"}\n {deletingGoalEntry?.type.toLowerCase()} focus goal? This action cannot\n be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PomodoroSettingsPage.js","names":[],"sources":["../../../../src/timecampus/pages/pomodoro/PomodoroSettingsPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from \"react\";\nimport { Settings } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetPomodoroSettingsQuery,\n useUpdatePomodoroSettingsMutation,\n} from \"@/generated/wspace-operations\";\n\nexport function PomodoroSettingsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const { data, loading, error } = useGetPomodoroSettingsQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n\n const [updateSettings, { loading: saving }] =\n useUpdatePomodoroSettingsMutation();\n\n const settings = data?.pomodoroSettings;\n\n const [focusDuration, setFocusDuration] = useState(25);\n const [shortBreak, setShortBreak] = useState(5);\n const [longBreak, setLongBreak] = useState(15);\n const [soundEnabled, setSoundEnabled] = useState(true);\n const [notificationsEnabled, setNotificationsEnabled] = useState(true);\n const [autoStartBreaks, setAutoStartBreaks] = useState(false);\n\n useEffect(() => {\n if (settings) {\n setFocusDuration(settings.focusDuration);\n setShortBreak(settings.shortBreakDuration);\n setLongBreak(settings.longBreakDuration);\n setSoundEnabled(settings.soundEnabled);\n setNotificationsEnabled(settings.notificationsEnabled);\n setAutoStartBreaks(settings.autoStartBreaks);\n }\n }, [settings]);\n\n const handleSave = useCallback(async () => {\n if (!workspaceId) return;\n await updateSettings({\n variables: {\n input: {\n workspaceId,\n focusDuration,\n shortBreakDuration: shortBreak,\n longBreakDuration: longBreak,\n soundEnabled,\n notificationsEnabled,\n autoStartBreaks,\n },\n },\n refetchQueries: [\"GetPomodoroSettings\"],\n });\n }, [\n workspaceId,\n focusDuration,\n shortBreak,\n longBreak,\n soundEnabled,\n notificationsEnabled,\n autoStartBreaks,\n updateSettings,\n ]);\n\n if (loading) {\n return (\n <PageLayout\n title=\"Pomodoro Settings\"\n description=\"Configure your Pomodoro timer preferences\"\n >\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Pomodoro Settings\"\n description=\"Configure your Pomodoro timer preferences\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load settings. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Pomodoro Settings\"\n description=\"Configure your Pomodoro timer preferences\"\n purpose=\"Shape the Pomodoro technique around how you actually work — set focus and break lengths and decide how you're nudged — so the timer fits your rhythm instead of fighting it.\"\n >\n <div className=\"mx-auto max-w-2xl space-y-6\">\n <GlassCard treatment=\"glass\" glow className=\"p-6\">\n <h3 className=\"mb-4 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Settings className=\"h-4 w-4\" />\n Timer Durations\n </h3>\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n Focus Duration\n </p>\n <p className=\"text-xs text-text-secondary\">\n Length of each focus session\n </p>\n </div>\n <input\n type=\"number\"\n value={focusDuration}\n onChange={(e) => setFocusDuration(Number(e.target.value))}\n className=\"w-20 rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm text-text-primary text-right\"\n />\n </div>\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n Short Break\n </p>\n <p className=\"text-xs text-text-secondary\">\n Break between focus sessions\n </p>\n </div>\n <input\n type=\"number\"\n value={shortBreak}\n onChange={(e) => setShortBreak(Number(e.target.value))}\n className=\"w-20 rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm text-text-primary text-right\"\n />\n </div>\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n Long Break\n </p>\n <p className=\"text-xs text-text-secondary\">\n After every 4 focus sessions\n </p>\n </div>\n <input\n type=\"number\"\n value={longBreak}\n onChange={(e) => setLongBreak(Number(e.target.value))}\n className=\"w-20 rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm text-text-primary text-right\"\n />\n </div>\n </div>\n </GlassCard>\n\n <div className=\"rounded-xl border border-border-seam bg-bg-surface shadow-elevation-1 p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Notifications\n </h3>\n <div className=\"space-y-3\">\n <label className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-primary\">Sound alerts</span>\n <input\n type=\"checkbox\"\n checked={soundEnabled}\n onChange={(e) => setSoundEnabled(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </label>\n <label className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-primary\">\n Desktop notifications\n </span>\n <input\n type=\"checkbox\"\n checked={notificationsEnabled}\n onChange={(e) => setNotificationsEnabled(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </label>\n <label className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-primary\">\n Auto-start breaks\n </span>\n <input\n type=\"checkbox\"\n checked={autoStartBreaks}\n onChange={(e) => setAutoStartBreaks(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </label>\n </div>\n </div>\n\n <div className=\"flex justify-end\">\n <button\n onClick={handleSave}\n disabled={saving}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {saving ? \"Saving...\" : \"Save Settings\"}\n </button>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PomodoroSettingsPage.js","names":[],"sources":["../../../../src/timecampus/pages/pomodoro/PomodoroSettingsPage.tsx"],"sourcesContent":["import { useState, useEffect, useCallback } from \"react\";\nimport { Settings } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { useTimeCampus } from \"../../providers/TimeCampusProvider\";\nimport {\n useGetPomodoroSettingsQuery,\n useUpdatePomodoroSettingsMutation,\n} from \"@/generated/wspace-operations\";\n\nexport function PomodoroSettingsPage() {\n const { workspaceId = \"\" } = useTimeCampus();\n\n const { data, loading, error } = useGetPomodoroSettingsQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n });\n\n const [updateSettings, { loading: saving }] =\n useUpdatePomodoroSettingsMutation();\n\n const settings = data?.pomodoroSettings;\n\n const [focusDuration, setFocusDuration] = useState(25);\n const [shortBreak, setShortBreak] = useState(5);\n const [longBreak, setLongBreak] = useState(15);\n const [soundEnabled, setSoundEnabled] = useState(true);\n const [notificationsEnabled, setNotificationsEnabled] = useState(true);\n const [autoStartBreaks, setAutoStartBreaks] = useState(false);\n\n useEffect(() => {\n if (settings) {\n setFocusDuration(settings.focusDuration);\n setShortBreak(settings.shortBreakDuration);\n setLongBreak(settings.longBreakDuration);\n setSoundEnabled(settings.soundEnabled);\n setNotificationsEnabled(settings.notificationsEnabled);\n setAutoStartBreaks(settings.autoStartBreaks);\n }\n }, [settings]);\n\n const handleSave = useCallback(async () => {\n if (!workspaceId) return;\n await updateSettings({\n variables: {\n input: {\n workspaceId,\n focusDuration,\n shortBreakDuration: shortBreak,\n longBreakDuration: longBreak,\n soundEnabled,\n notificationsEnabled,\n autoStartBreaks,\n },\n },\n refetchQueries: [\"GetPomodoroSettings\"],\n });\n }, [\n workspaceId,\n focusDuration,\n shortBreak,\n longBreak,\n soundEnabled,\n notificationsEnabled,\n autoStartBreaks,\n updateSettings,\n ]);\n\n if (loading) {\n return (\n <PageLayout\n title=\"Pomodoro Settings\"\n description=\"Configure your Pomodoro timer preferences\"\n >\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title=\"Pomodoro Settings\"\n description=\"Configure your Pomodoro timer preferences\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load settings. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Pomodoro Settings\"\n description=\"Configure your Pomodoro timer preferences\"\n purpose=\"Shape the Pomodoro technique around how you actually work — set focus and break lengths and decide how you're nudged — so the timer fits your rhythm instead of fighting it.\"\n >\n <div className=\"mx-auto max-w-2xl space-y-6\">\n <GlassCard treatment=\"glass\" glow className=\"p-6\">\n <h3 className=\"mb-4 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <Settings className=\"h-4 w-4\" />\n Timer Durations\n </h3>\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n Focus Duration\n </p>\n <p className=\"text-xs text-text-secondary\">\n Length of each focus session\n </p>\n </div>\n <input\n type=\"number\"\n value={focusDuration}\n onChange={(e) => setFocusDuration(Number(e.target.value))}\n className=\"w-20 rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm text-text-primary text-right\"\n />\n </div>\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n Short Break\n </p>\n <p className=\"text-xs text-text-secondary\">\n Break between focus sessions\n </p>\n </div>\n <input\n type=\"number\"\n value={shortBreak}\n onChange={(e) => setShortBreak(Number(e.target.value))}\n className=\"w-20 rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm text-text-primary text-right\"\n />\n </div>\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm font-medium text-text-primary\">\n Long Break\n </p>\n <p className=\"text-xs text-text-secondary\">\n After every 4 focus sessions\n </p>\n </div>\n <input\n type=\"number\"\n value={longBreak}\n onChange={(e) => setLongBreak(Number(e.target.value))}\n className=\"w-20 rounded-md border border-border-subtle bg-bg-surface px-3 py-1.5 text-sm text-text-primary text-right\"\n />\n </div>\n </div>\n </GlassCard>\n\n <div className=\"rounded-xl border border-border-seam bg-bg-surface shadow-elevation-1 p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Notifications\n </h3>\n <div className=\"space-y-3\">\n <label className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-primary\">Sound alerts</span>\n <input\n type=\"checkbox\"\n checked={soundEnabled}\n onChange={(e) => setSoundEnabled(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </label>\n <label className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-primary\">\n Desktop notifications\n </span>\n <input\n type=\"checkbox\"\n checked={notificationsEnabled}\n onChange={(e) => setNotificationsEnabled(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </label>\n <label className=\"flex items-center justify-between\">\n <span className=\"text-sm text-text-primary\">\n Auto-start breaks\n </span>\n <input\n type=\"checkbox\"\n checked={autoStartBreaks}\n onChange={(e) => setAutoStartBreaks(e.target.checked)}\n className=\"h-4 w-4 rounded border-border-subtle\"\n />\n </label>\n </div>\n </div>\n\n <div className=\"flex justify-end\">\n <button\n onClick={handleSave}\n disabled={saving}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {saving ? \"Saving...\" : \"Save Settings\"}\n </button>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectCreatePage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectCreatePage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { useCreateProjectMutation } from \"@/generated/wspace-operations\";\n\nexport function ProjectCreatePage() {\n const navigate = useTimeCampusNavigate();\n const [name, setName] = useState(\"\");\n const [clientId, setClientId] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [estimatedHours, setEstimatedHours] = useState(\"\");\n const [hourlyRate, setHourlyRate] = useState(\"\");\n\n const [createProject, { loading: creating, error }] =\n useCreateProjectMutation();\n\n const handleSubmit = async () => {\n if (!name.trim()) return;\n await createProject({\n variables: {\n input: {\n name: name.trim(),\n description: description.trim() || undefined,\n clientId: clientId.trim() || undefined,\n estimatedHours: estimatedHours ? Number(estimatedHours) : undefined,\n hourlyRate: hourlyRate ? Number(hourlyRate) : undefined,\n },\n },\n });\n navigate(\"/projects\");\n };\n\n return (\n <PageLayout\n title=\"Create Project\"\n description=\"Set up a new project for time tracking\"\n purpose=\"Define a new project — its client, estimate and rate — so the time your team logs against it rolls up into accurate progress and billing from day one.\"\n actions={\n <button\n onClick={() => navigate(\"/projects\")}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Projects\n </button>\n }\n >\n <GlassCard treatment=\"glass\" glow className=\"mx-auto max-w-2xl p-6\">\n {error && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to create project. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Project Name\n </label>\n <input\n type=\"text\"\n placeholder=\"Enter project name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Client\n </label>\n <input\n type=\"text\"\n placeholder=\"Client name\"\n value={clientId}\n onChange={(e) => setClientId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description\n </label>\n <textarea\n placeholder=\"Project description\"\n rows={3}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Estimated Hours\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={estimatedHours}\n onChange={(e) => setEstimatedHours(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Hourly Rate\n </label>\n <input\n type=\"number\"\n placeholder=\"0.00\"\n value={hourlyRate}\n onChange={(e) => setHourlyRate(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n </div>\n <div className=\"flex justify-end gap-2 pt-4\">\n <button\n onClick={() => navigate(\"/projects\")}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSubmit}\n disabled={creating || !name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating ? \"Creating...\" : \"Create Project\"}\n </button>\n </div>\n </div>\n </GlassCard>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,IAAoB;CAClC,IAAM,IAAW,
|
|
1
|
+
{"version":3,"file":"ProjectCreatePage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectCreatePage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { ArrowLeft } from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { useCreateProjectMutation } from \"@/generated/wspace-operations\";\n\nexport function ProjectCreatePage() {\n const navigate = useTimeCampusNavigate();\n const [name, setName] = useState(\"\");\n const [clientId, setClientId] = useState(\"\");\n const [description, setDescription] = useState(\"\");\n const [estimatedHours, setEstimatedHours] = useState(\"\");\n const [hourlyRate, setHourlyRate] = useState(\"\");\n\n const [createProject, { loading: creating, error }] =\n useCreateProjectMutation();\n\n const handleSubmit = async () => {\n if (!name.trim()) return;\n await createProject({\n variables: {\n input: {\n name: name.trim(),\n description: description.trim() || undefined,\n clientId: clientId.trim() || undefined,\n estimatedHours: estimatedHours ? Number(estimatedHours) : undefined,\n hourlyRate: hourlyRate ? Number(hourlyRate) : undefined,\n },\n },\n });\n navigate(\"/projects\");\n };\n\n return (\n <PageLayout\n title=\"Create Project\"\n description=\"Set up a new project for time tracking\"\n purpose=\"Define a new project — its client, estimate and rate — so the time your team logs against it rolls up into accurate progress and billing from day one.\"\n actions={\n <button\n onClick={() => navigate(\"/projects\")}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Back to Projects\n </button>\n }\n >\n <GlassCard treatment=\"glass\" glow className=\"mx-auto max-w-2xl p-6\">\n {error && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to create project. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Project Name\n </label>\n <input\n type=\"text\"\n placeholder=\"Enter project name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Client\n </label>\n <input\n type=\"text\"\n placeholder=\"Client name\"\n value={clientId}\n onChange={(e) => setClientId(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description\n </label>\n <textarea\n placeholder=\"Project description\"\n rows={3}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\"\n />\n </div>\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Estimated Hours\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={estimatedHours}\n onChange={(e) => setEstimatedHours(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Hourly Rate\n </label>\n <input\n type=\"number\"\n placeholder=\"0.00\"\n value={hourlyRate}\n onChange={(e) => setHourlyRate(e.target.value)}\n className=\"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary\"\n />\n </div>\n </div>\n <div className=\"flex justify-end gap-2 pt-4\">\n <button\n onClick={() => navigate(\"/projects\")}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleSubmit}\n disabled={creating || !name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating ? \"Creating...\" : \"Create Project\"}\n </button>\n </div>\n </div>\n </GlassCard>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams } from \"react-router\";\nimport {\n FolderOpen,\n Clock,\n Users,\n TrendingUp,\n Pencil,\n Archive,\n Trash2,\n ArrowRight,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport {\n ChartCard,\n BarsChart,\n ProgressMeter,\n StatusBadge,\n type ChartDatum,\n} from \"../../components\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { formatHours, formatCurrency, formatDate } from \"../../utils/helpers\";\nimport {\n useGetProjectQuery,\n useGetProjectStatsQuery,\n useUpdateProjectMutation,\n useArchiveProjectMutation,\n useDeleteProjectMutation,\n type TaskFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PROJECT_STATUSES = [\n \"active\",\n \"on-hold\",\n \"completed\",\n \"archived\",\n] as const;\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nexport function ProjectDetailPage() {\n const { projectId } = useParams<{ projectId: string }>();\n const navigate = useTimeCampusNavigate();\n\n const {\n data: projectData,\n loading: projectLoading,\n error: projectError,\n refetch,\n } = useGetProjectQuery({\n variables: { id: projectId! },\n skip: !projectId,\n });\n\n const { data: statsData, loading: statsLoading } = useGetProjectStatsQuery({\n variables: { id: projectId! },\n skip: !projectId,\n });\n\n const project = projectData?.project;\n const stats = statsData?.projectStats;\n\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const [form, setForm] = useState({\n name: \"\",\n clientId: \"\",\n description: \"\",\n status: \"active\",\n estimatedHours: \"\",\n hourlyRate: \"\",\n });\n\n const [updateProject, { loading: updating, error: updateError }] =\n useUpdateProjectMutation();\n const [archiveProject, { loading: archiving }] = useArchiveProjectMutation();\n const [deleteProject, { loading: deleting, error: deleteError }] =\n useDeleteProjectMutation();\n\n const openEdit = () => {\n if (!project) return;\n setForm({\n name: project.name,\n clientId: project.clientId ?? \"\",\n description: project.description ?? \"\",\n status: project.status,\n estimatedHours:\n project.estimatedHours != null ? String(project.estimatedHours) : \"\",\n hourlyRate: project.hourlyRate != null ? String(project.hourlyRate) : \"\",\n });\n setEditOpen(true);\n };\n\n const handleUpdate = async () => {\n if (!projectId || !form.name.trim()) return;\n await updateProject({\n variables: {\n id: projectId,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setEditOpen(false);\n await refetch();\n };\n\n const handleArchive = async () => {\n if (!projectId) return;\n await archiveProject({ variables: { id: projectId } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!projectId) return;\n await deleteProject({ variables: { id: projectId } });\n setDeleteOpen(false);\n navigate(\"/projects\");\n };\n\n if (projectError) {\n return (\n <PageLayout\n title=\"Project Details\"\n description={`Project ID: ${projectId}`}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load project details. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n if (projectLoading || statsLoading) {\n return (\n <PageLayout\n title=\"Project Details\"\n description={`Project ID: ${projectId}`}\n >\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n const currency = project?.currency ?? \"USD\";\n const tasks = project?.tasks ?? [];\n const budgetUsed = stats?.budgetUsed ?? 0;\n const budgetTotal =\n project?.budget ?? (stats?.budgetUsed ?? 0) + (stats?.budgetRemaining ?? 0);\n const estimatedHours = project?.estimatedHours ?? 0;\n const trackedHours = stats?.totalHours ?? project?.trackedHours ?? 0;\n const completedTasks = stats?.completedTasks ?? 0;\n const totalTasks = stats?.totalTasks ?? tasks.length;\n\n const hoursFmt = (v: number) => `${Math.round(v)}h`;\n\n // Tracked vs estimated hours for the tasks that carry any time — real\n // per-task figures from the project's `tasks` connection, no fabrication.\n const taskHoursData: ChartDatum[] = tasks\n .filter(\n (task: TaskFieldsFragment) =>\n task.trackedHours > 0 || (task.estimatedHours ?? 0) > 0,\n )\n .sort(\n (a: TaskFieldsFragment, b: TaskFieldsFragment) =>\n b.trackedHours - a.trackedHours,\n )\n .slice(0, 8)\n .map((task: TaskFieldsFragment) => ({\n task: task.title,\n tracked: task.trackedHours,\n estimated: task.estimatedHours ?? 0,\n }));\n\n const orderedTasks = [...tasks]\n .sort((a: TaskFieldsFragment, b: TaskFieldsFragment) => a.order - b.order)\n .slice(0, 8);\n\n return (\n <PageLayout\n title={project?.name ?? \"Project Details\"}\n description={project?.description ?? `Project ID: ${projectId}`}\n purpose=\"See everything happening on this project in one place — logged hours, task progress and budget burn — so you know whether it's on track and where the time is going before it overruns.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n onClick={openEdit}\n disabled={!project}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <Pencil className=\"h-4 w-4\" />\n Edit\n </button>\n {project?.status !== \"archived\" && (\n <button\n onClick={() => void handleArchive()}\n disabled={!project || archiving}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <Archive className=\"h-4 w-4\" />\n {archiving ? \"Archiving…\" : \"Archive\"}\n </button>\n )}\n <button\n onClick={() => setDeleteOpen(true)}\n disabled={!project}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-status-error-bg/40 px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </button>\n </div>\n }\n >\n <NextSteps\n storageKey=\"timecampus-project-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"log-time\",\n label: \"Log time to this project\",\n description: \"Start the timer or add a manual entry for this work.\",\n onClick: () => navigate(\"/time/timer\"),\n },\n {\n id: \"view-tasks\",\n label: \"Review tasks\",\n description: \"Break the project into trackable tasks.\",\n onClick: () => navigate(`/projects/${projectId ?? \"\"}/tasks`),\n },\n {\n id: \"open-reports\",\n label: \"Open reports\",\n description: \"Analyse this project's time and budget over time.\",\n onClick: () => navigate(\"/reports\"),\n },\n ]}\n />\n <div className=\"space-y-6\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {project?.status && (\n <StatusBadge label={project.status} status={project.status} dot />\n )}\n {project?.clientId && (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n <Users className=\"h-3.5 w-3.5\" />\n {project.clientId}\n </span>\n )}\n {project?.createdAt && (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n Started {formatDate(project.createdAt)}\n </span>\n )}\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Tracked Hours\"\n value={formatHours(trackedHours)}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Tasks\"\n value={`${completedTasks} / ${totalTasks}`}\n icon={<Users className=\"h-5 w-5\" />}\n description=\"completed / total\"\n />\n <StatCard\n title=\"Progress\"\n value={\n totalTasks\n ? `${Math.round((completedTasks / totalTasks) * 100)}%`\n : \"0%\"\n }\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Budget Used\"\n value={formatCurrency(budgetUsed, currency)}\n icon={<FolderOpen className=\"h-5 w-5\" />}\n description={\n stats?.budgetRemaining != null\n ? `${formatCurrency(stats.budgetRemaining, currency)} remaining`\n : undefined\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Budget & effort\"\n description=\"Spend and hours against the plan\"\n >\n <div className=\"space-y-5\">\n <ProgressMeter\n label=\"Budget spent\"\n value={budgetUsed}\n max={budgetTotal > 0 ? budgetTotal : Math.max(budgetUsed, 1)}\n format={(v) => formatCurrency(v, currency)}\n />\n <ProgressMeter\n label=\"Hours tracked\"\n value={trackedHours}\n max={\n estimatedHours > 0\n ? estimatedHours\n : Math.max(trackedHours, 1)\n }\n format={(v) => formatHours(v)}\n />\n <ProgressMeter\n label=\"Tasks completed\"\n higherIsWorse={false}\n value={completedTasks}\n max={totalTasks > 0 ? totalTasks : 1}\n format={(v) => String(Math.round(v))}\n />\n </div>\n </ChartCard>\n\n <ChartCard\n title=\"Time distribution\"\n description=\"Tracked vs estimated hours by task\"\n >\n <BarsChart\n data={taskHoursData}\n xKey=\"task\"\n orientation=\"horizontal\"\n height={280}\n categoryWidth={120}\n valueFormat={hoursFmt}\n series={[\n { key: \"tracked\", label: \"Tracked\" },\n { key: \"estimated\", label: \"Estimated\" },\n ]}\n emptyLabel=\"No task hours logged yet\"\n />\n </ChartCard>\n </div>\n\n <ChartCard\n title=\"Tasks\"\n description={`${completedTasks} of ${totalTasks} complete`}\n actions={\n <button\n onClick={() => navigate(`/projects/${projectId ?? \"\"}/tasks`)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2.5 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n View all\n <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n }\n >\n {orderedTasks.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No tasks yet. Break this project into trackable tasks to see\n progress here.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {orderedTasks.map((task: TaskFieldsFragment) => (\n <li\n key={task.id}\n className=\"flex flex-col gap-2 py-3 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {task.title}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {formatHours(task.trackedHours)} tracked\n {task.estimatedHours\n ? ` / ${formatHours(task.estimatedHours)} est`\n : \"\"}\n {task.dueDate ? ` · due ${formatDate(task.dueDate)}` : \"\"}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge\n label={task.priority}\n priority={task.priority}\n />\n <StatusBadge label={task.status} status={task.status} />\n </div>\n </li>\n ))}\n </ul>\n )}\n </ChartCard>\n </div>\n\n {/* Edit modal */}\n <Modal\n open={editOpen}\n onClose={() => setEditOpen(false)}\n title=\"Edit Project\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {updating ? \"Saving…\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to update project. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Project Name</label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>Client</label>\n <input\n type=\"text\"\n value={form.clientId}\n onChange={(e) => setForm({ ...form, clientId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Status</label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {PROJECT_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n rows={3}\n value={form.description}\n onChange={(e) =>\n setForm({ ...form, description: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>Estimated Hours</label>\n <input\n type=\"number\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Hourly Rate</label>\n <input\n type=\"number\"\n value={form.hourlyRate}\n onChange={(e) =>\n setForm({ ...form, hourlyRate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteOpen}\n onClose={() => setDeleteOpen(false)}\n title=\"Delete Project\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deleting ? \"Deleting…\" : \"Delete\"}\n </button>\n </>\n }\n >\n {deleteError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to delete project. Please try again.\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {project?.name}\n </span>\n ? This action cannot be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams } from \"react-router\";\nimport {\n FolderOpen,\n Clock,\n Users,\n TrendingUp,\n Pencil,\n Archive,\n Trash2,\n ArrowRight,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport {\n ChartCard,\n BarsChart,\n ProgressMeter,\n StatusBadge,\n type ChartDatum,\n} from \"../../components\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { formatHours, formatCurrency, formatDate } from \"../../utils/helpers\";\nimport {\n useGetProjectQuery,\n useGetProjectStatsQuery,\n useUpdateProjectMutation,\n useArchiveProjectMutation,\n useDeleteProjectMutation,\n type TaskFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PROJECT_STATUSES = [\n \"active\",\n \"on-hold\",\n \"completed\",\n \"archived\",\n] as const;\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nexport function ProjectDetailPage() {\n const { projectId } = useParams<{ projectId: string }>();\n const navigate = useTimeCampusNavigate();\n\n const {\n data: projectData,\n loading: projectLoading,\n error: projectError,\n refetch,\n } = useGetProjectQuery({\n variables: { id: projectId! },\n skip: !projectId,\n });\n\n const { data: statsData, loading: statsLoading } = useGetProjectStatsQuery({\n variables: { id: projectId! },\n skip: !projectId,\n });\n\n const project = projectData?.project;\n const stats = statsData?.projectStats;\n\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const [form, setForm] = useState({\n name: \"\",\n clientId: \"\",\n description: \"\",\n status: \"active\",\n estimatedHours: \"\",\n hourlyRate: \"\",\n });\n\n const [updateProject, { loading: updating, error: updateError }] =\n useUpdateProjectMutation();\n const [archiveProject, { loading: archiving }] = useArchiveProjectMutation();\n const [deleteProject, { loading: deleting, error: deleteError }] =\n useDeleteProjectMutation();\n\n const openEdit = () => {\n if (!project) return;\n setForm({\n name: project.name,\n clientId: project.clientId ?? \"\",\n description: project.description ?? \"\",\n status: project.status,\n estimatedHours:\n project.estimatedHours != null ? String(project.estimatedHours) : \"\",\n hourlyRate: project.hourlyRate != null ? String(project.hourlyRate) : \"\",\n });\n setEditOpen(true);\n };\n\n const handleUpdate = async () => {\n if (!projectId || !form.name.trim()) return;\n await updateProject({\n variables: {\n id: projectId,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setEditOpen(false);\n await refetch();\n };\n\n const handleArchive = async () => {\n if (!projectId) return;\n await archiveProject({ variables: { id: projectId } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!projectId) return;\n await deleteProject({ variables: { id: projectId } });\n setDeleteOpen(false);\n navigate(\"/projects\");\n };\n\n if (projectError) {\n return (\n <PageLayout\n title=\"Project Details\"\n description={`Project ID: ${projectId}`}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n Failed to load project details. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n if (projectLoading || statsLoading) {\n return (\n <PageLayout\n title=\"Project Details\"\n description={`Project ID: ${projectId}`}\n >\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n const currency = project?.currency ?? \"USD\";\n const tasks = project?.tasks ?? [];\n const budgetUsed = stats?.budgetUsed ?? 0;\n const budgetTotal =\n project?.budget ?? (stats?.budgetUsed ?? 0) + (stats?.budgetRemaining ?? 0);\n const estimatedHours = project?.estimatedHours ?? 0;\n const trackedHours = stats?.totalHours ?? project?.trackedHours ?? 0;\n const completedTasks = stats?.completedTasks ?? 0;\n const totalTasks = stats?.totalTasks ?? tasks.length;\n\n const hoursFmt = (v: number) => `${Math.round(v)}h`;\n\n // Tracked vs estimated hours for the tasks that carry any time — real\n // per-task figures from the project's `tasks` connection, no fabrication.\n const taskHoursData: ChartDatum[] = tasks\n .filter(\n (task: TaskFieldsFragment) =>\n task.trackedHours > 0 || (task.estimatedHours ?? 0) > 0,\n )\n .sort(\n (a: TaskFieldsFragment, b: TaskFieldsFragment) =>\n b.trackedHours - a.trackedHours,\n )\n .slice(0, 8)\n .map((task: TaskFieldsFragment) => ({\n task: task.title,\n tracked: task.trackedHours,\n estimated: task.estimatedHours ?? 0,\n }));\n\n const orderedTasks = [...tasks]\n .sort((a: TaskFieldsFragment, b: TaskFieldsFragment) => a.order - b.order)\n .slice(0, 8);\n\n return (\n <PageLayout\n title={project?.name ?? \"Project Details\"}\n description={project?.description ?? `Project ID: ${projectId}`}\n purpose=\"See everything happening on this project in one place — logged hours, task progress and budget burn — so you know whether it's on track and where the time is going before it overruns.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <button\n onClick={openEdit}\n disabled={!project}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <Pencil className=\"h-4 w-4\" />\n Edit\n </button>\n {project?.status !== \"archived\" && (\n <button\n onClick={() => void handleArchive()}\n disabled={!project || archiving}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n <Archive className=\"h-4 w-4\" />\n {archiving ? \"Archiving…\" : \"Archive\"}\n </button>\n )}\n <button\n onClick={() => setDeleteOpen(true)}\n disabled={!project}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-status-error-bg/40 px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </button>\n </div>\n }\n >\n <NextSteps\n storageKey=\"timecampus-project-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"log-time\",\n label: \"Log time to this project\",\n description: \"Start the timer or add a manual entry for this work.\",\n onClick: () => navigate(\"/time/timer\"),\n },\n {\n id: \"view-tasks\",\n label: \"Review tasks\",\n description: \"Break the project into trackable tasks.\",\n onClick: () => navigate(`/projects/${projectId ?? \"\"}/tasks`),\n },\n {\n id: \"open-reports\",\n label: \"Open reports\",\n description: \"Analyse this project's time and budget over time.\",\n onClick: () => navigate(\"/reports\"),\n },\n ]}\n />\n <div className=\"space-y-6\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {project?.status && (\n <StatusBadge label={project.status} status={project.status} dot />\n )}\n {project?.clientId && (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n <Users className=\"h-3.5 w-3.5\" />\n {project.clientId}\n </span>\n )}\n {project?.createdAt && (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-secondary\">\n Started {formatDate(project.createdAt)}\n </span>\n )}\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Tracked Hours\"\n value={formatHours(trackedHours)}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Tasks\"\n value={`${completedTasks} / ${totalTasks}`}\n icon={<Users className=\"h-5 w-5\" />}\n description=\"completed / total\"\n />\n <StatCard\n title=\"Progress\"\n value={\n totalTasks\n ? `${Math.round((completedTasks / totalTasks) * 100)}%`\n : \"0%\"\n }\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Budget Used\"\n value={formatCurrency(budgetUsed, currency)}\n icon={<FolderOpen className=\"h-5 w-5\" />}\n description={\n stats?.budgetRemaining != null\n ? `${formatCurrency(stats.budgetRemaining, currency)} remaining`\n : undefined\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Budget & effort\"\n description=\"Spend and hours against the plan\"\n >\n <div className=\"space-y-5\">\n <ProgressMeter\n label=\"Budget spent\"\n value={budgetUsed}\n max={budgetTotal > 0 ? budgetTotal : Math.max(budgetUsed, 1)}\n format={(v) => formatCurrency(v, currency)}\n />\n <ProgressMeter\n label=\"Hours tracked\"\n value={trackedHours}\n max={\n estimatedHours > 0\n ? estimatedHours\n : Math.max(trackedHours, 1)\n }\n format={(v) => formatHours(v)}\n />\n <ProgressMeter\n label=\"Tasks completed\"\n higherIsWorse={false}\n value={completedTasks}\n max={totalTasks > 0 ? totalTasks : 1}\n format={(v) => String(Math.round(v))}\n />\n </div>\n </ChartCard>\n\n <ChartCard\n title=\"Time distribution\"\n description=\"Tracked vs estimated hours by task\"\n >\n <BarsChart\n data={taskHoursData}\n xKey=\"task\"\n orientation=\"horizontal\"\n height={280}\n categoryWidth={120}\n valueFormat={hoursFmt}\n series={[\n { key: \"tracked\", label: \"Tracked\" },\n { key: \"estimated\", label: \"Estimated\" },\n ]}\n emptyLabel=\"No task hours logged yet\"\n />\n </ChartCard>\n </div>\n\n <ChartCard\n title=\"Tasks\"\n description={`${completedTasks} of ${totalTasks} complete`}\n actions={\n <button\n onClick={() => navigate(`/projects/${projectId ?? \"\"}/tasks`)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-2.5 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n View all\n <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n }\n >\n {orderedTasks.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No tasks yet. Break this project into trackable tasks to see\n progress here.\n </p>\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {orderedTasks.map((task: TaskFieldsFragment) => (\n <li\n key={task.id}\n className=\"flex flex-col gap-2 py-3 first:pt-0 last:pb-0 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {task.title}\n </p>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {formatHours(task.trackedHours)} tracked\n {task.estimatedHours\n ? ` / ${formatHours(task.estimatedHours)} est`\n : \"\"}\n {task.dueDate ? ` · due ${formatDate(task.dueDate)}` : \"\"}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge\n label={task.priority}\n priority={task.priority}\n />\n <StatusBadge label={task.status} status={task.status} />\n </div>\n </li>\n ))}\n </ul>\n )}\n </ChartCard>\n </div>\n\n {/* Edit modal */}\n <Modal\n open={editOpen}\n onClose={() => setEditOpen(false)}\n title=\"Edit Project\"\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {updating ? \"Saving…\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to update project. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Project Name</label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>Client</label>\n <input\n type=\"text\"\n value={form.clientId}\n onChange={(e) => setForm({ ...form, clientId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Status</label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {PROJECT_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n rows={3}\n value={form.description}\n onChange={(e) =>\n setForm({ ...form, description: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>Estimated Hours</label>\n <input\n type=\"number\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Hourly Rate</label>\n <input\n type=\"number\"\n value={form.hourlyRate}\n onChange={(e) =>\n setForm({ ...form, hourlyRate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n </div>\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteOpen}\n onClose={() => setDeleteOpen(false)}\n title=\"Delete Project\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n Cancel\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deleting ? \"Deleting…\" : \"Delete\"}\n </button>\n </>\n }\n >\n {deleteError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to delete project. Please try again.\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {project?.name}\n </span>\n ? This action cannot be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { Plus, Pencil, Trash2, Archive } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { getStatusColor, formatHours } from \"../../utils/helpers\";\nimport {\n useListProjectsQuery,\n useCreateProjectMutation,\n useUpdateProjectMutation,\n useArchiveProjectMutation,\n useDeleteProjectMutation,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\ntype ProjectItem = NonNullable<ListProjectsQuery[\"projects\"]>[\"items\"][number];\n\nconst PAGE_SIZE = 25;\n\nconst PROJECT_STATUSES = [\n \"active\",\n \"on-hold\",\n \"completed\",\n \"archived\",\n] as const;\n\ninterface ProjectFormState {\n name: string;\n clientId: string;\n description: string;\n status: string;\n estimatedHours: string;\n hourlyRate: string;\n}\n\nconst emptyForm: ProjectFormState = {\n name: \"\",\n clientId: \"\",\n description: \"\",\n status: \"active\",\n estimatedHours: \"\",\n hourlyRate: \"\",\n};\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nexport function ProjectsPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const navigate = useTimeCampusNavigate();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListProjectsQuery({\n variables: { pagination: { limit, offset: 0 } },\n notifyOnNetworkStatusChange: true,\n });\n\n const projects = data?.projects?.items ?? [];\n const hasMore = data?.projects?.hasMore ?? false;\n\n // Modal state\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<ProjectItem | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<ProjectItem | null>(null);\n const [form, setForm] = useState<ProjectFormState>(emptyForm);\n\n const [createProject, { loading: creating, error: createError }] =\n useCreateProjectMutation();\n const [updateProject, { loading: updating, error: updateError }] =\n useUpdateProjectMutation();\n const [archiveProject, { loading: archiving }] = useArchiveProjectMutation();\n const [deleteProject, { loading: deleting, error: deleteError }] =\n useDeleteProjectMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (project: ProjectItem) => {\n setForm({\n name: project.name,\n clientId: project.clientId ?? \"\",\n description: project.description ?? \"\",\n status: project.status,\n estimatedHours:\n project.estimatedHours != null ? String(project.estimatedHours) : \"\",\n hourlyRate: project.hourlyRate != null ? String(project.hourlyRate) : \"\",\n });\n setEditTarget(project);\n };\n\n const handleCreate = async () => {\n if (!form.name.trim()) return;\n await createProject({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.name.trim()) return;\n await updateProject({\n variables: {\n id: editTarget.id,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleArchive = async (project: ProjectItem) => {\n await archiveProject({ variables: { id: project.id } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteProject({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.name\", \"Project Name\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Enter project name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.client\", \"Client\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Client name\"\n value={form.clientId}\n onChange={(e) => setForm({ ...form, clientId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.status\", \"Status\")}\n </label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {PROJECT_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.description\", \"Description\")}\n </label>\n <textarea\n placeholder=\"Project description\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.estimatedHours\", \"Estimated Hours\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.hourlyRate\", \"Hourly Rate\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0.00\"\n value={form.hourlyRate}\n onChange={(e) => setForm({ ...form, hourlyRate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.loadFailed\",\n \"Failed to load projects. Please try again later.\",\n )}\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n purpose={t(\n \"timecampus.projects.purpose\",\n \"Group your work into projects so every hour you track rolls up to the right client and budget — use it to estimate effort, watch progress against the plan, and bill accurately.\",\n )}\n actions={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n >\n {loading && projects.length === 0 ? (\n <LoadingSkeleton variant=\"card\" count={4} />\n ) : projects.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title={t(\"timecampus.projects.empty.title\", \"No projects yet\")}\n description={t(\n \"timecampus.projects.empty\",\n \"No projects yet. Create your first project to get started.\",\n )}\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {projects.map((project: ProjectItem) => (\n <div\n key={project.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/projects/${project.id}`)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n navigate(`/projects/${project.id}`);\n }\n }}\n className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div\n className=\"h-3 w-3 rounded-full\"\n style={{\n backgroundColor:\n project.color ?? \"var(--color-accent-blue)\",\n }}\n />\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {project.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {project.clientId ??\n t(\"timecampus.projects.card.noClient\", \"No client\")}\n </p>\n </div>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(project.status)}`}\n >\n {project.status}\n </span>\n </div>\n <div className=\"mt-4\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {t(\"timecampus.projects.card.progress\", \"Progress\")}\n </span>\n <span>\n {project.estimatedHours\n ? Math.round(\n (project.trackedHours / project.estimatedHours) *\n 100,\n )\n : 0}\n %\n </span>\n </div>\n <div className=\"mt-1 h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{\n width: `${project.estimatedHours ? Math.min(Math.round((project.trackedHours / project.estimatedHours) * 100), 100) : 0}%`,\n }}\n />\n </div>\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {formatHours(project.trackedHours)} /{\" \"}\n {formatHours(project.estimatedHours ?? 0)}\n </span>\n <span>\n {project.tasks?.length ?? 0}{\" \"}\n {t(\"timecampus.projects.card.tasks\", \"tasks\")}\n </span>\n </div>\n <div className=\"mt-4 flex items-center justify-end gap-1 border-t border-border-subtle pt-3\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n openEdit(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={t(\"timecampus.projects.actions.edit\", \"Edit\")}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.edit\", \"Edit\")}\n </button>\n {project.status !== \"archived\" && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n void handleArchive(project);\n }}\n disabled={archiving}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50\"\n aria-label={t(\n \"timecampus.projects.actions.archive\",\n \"Archive\",\n )}\n >\n <Archive className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.archive\", \"Archive\")}\n </button>\n )}\n <button\n onClick={(e) => {\n e.stopPropagation();\n setDeleteTarget(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-status-error-text hover:bg-status-error-bg\"\n aria-label={t(\n \"timecampus.projects.actions.delete\",\n \"Delete\",\n )}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.delete\", \"Delete\")}\n </button>\n </div>\n </div>\n ))}\n </div>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={() => setLimit((prev) => prev + PAGE_SIZE)}\n disabled={loading}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loading\n ? t(\"timecampus.common.loading\", \"Loading…\")\n : t(\"timecampus.common.loadMore\", \"Load more\")}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title={t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n description={t(\n \"timecampus.projects.form.createHint\",\n \"Define a new project for time tracking.\",\n )}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating\n ? t(\"timecampus.common.creating\", \"Creating…\")\n : t(\"timecampus.projects.actions.create\", \"Create Project\")}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.createFailed\",\n \"Failed to create project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title={t(\"timecampus.projects.actions.editProject\", \"Edit Project\")}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {updating\n ? t(\"timecampus.common.saving\", \"Saving…\")\n : t(\"timecampus.common.saveChanges\", \"Save Changes\")}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.updateFailed\",\n \"Failed to update project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title={t(\"timecampus.projects.actions.deleteProject\", \"Delete Project\")}\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deleting\n ? t(\"timecampus.common.deleting\", \"Deleting…\")\n : t(\"timecampus.common.delete\", \"Delete\")}\n </button>\n </>\n }\n >\n {deleteError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.deleteFailed\",\n \"Failed to delete project. Please try again.\",\n )}\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n {t(\n \"timecampus.projects.deleteConfirm\",\n \"Are you sure you want to delete\",\n )}{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {deleteTarget?.name}\n </span>\n ? {t(\"timecampus.common.cannotUndo\", \"This action cannot be undone.\")}\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectsPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { Plus, Pencil, Trash2, Archive } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { getStatusColor, formatHours } from \"../../utils/helpers\";\nimport {\n useListProjectsQuery,\n useCreateProjectMutation,\n useUpdateProjectMutation,\n useArchiveProjectMutation,\n useDeleteProjectMutation,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\ntype ProjectItem = NonNullable<ListProjectsQuery[\"projects\"]>[\"items\"][number];\n\nconst PAGE_SIZE = 25;\n\nconst PROJECT_STATUSES = [\n \"active\",\n \"on-hold\",\n \"completed\",\n \"archived\",\n] as const;\n\ninterface ProjectFormState {\n name: string;\n clientId: string;\n description: string;\n status: string;\n estimatedHours: string;\n hourlyRate: string;\n}\n\nconst emptyForm: ProjectFormState = {\n name: \"\",\n clientId: \"\",\n description: \"\",\n status: \"active\",\n estimatedHours: \"\",\n hourlyRate: \"\",\n};\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nexport function ProjectsPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const navigate = useTimeCampusNavigate();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListProjectsQuery({\n variables: { pagination: { limit, offset: 0 } },\n notifyOnNetworkStatusChange: true,\n });\n\n const projects = data?.projects?.items ?? [];\n const hasMore = data?.projects?.hasMore ?? false;\n\n // Modal state\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<ProjectItem | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<ProjectItem | null>(null);\n const [form, setForm] = useState<ProjectFormState>(emptyForm);\n\n const [createProject, { loading: creating, error: createError }] =\n useCreateProjectMutation();\n const [updateProject, { loading: updating, error: updateError }] =\n useUpdateProjectMutation();\n const [archiveProject, { loading: archiving }] = useArchiveProjectMutation();\n const [deleteProject, { loading: deleting, error: deleteError }] =\n useDeleteProjectMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (project: ProjectItem) => {\n setForm({\n name: project.name,\n clientId: project.clientId ?? \"\",\n description: project.description ?? \"\",\n status: project.status,\n estimatedHours:\n project.estimatedHours != null ? String(project.estimatedHours) : \"\",\n hourlyRate: project.hourlyRate != null ? String(project.hourlyRate) : \"\",\n });\n setEditTarget(project);\n };\n\n const handleCreate = async () => {\n if (!form.name.trim()) return;\n await createProject({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.name.trim()) return;\n await updateProject({\n variables: {\n id: editTarget.id,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleArchive = async (project: ProjectItem) => {\n await archiveProject({ variables: { id: project.id } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteProject({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.name\", \"Project Name\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Enter project name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.client\", \"Client\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Client name\"\n value={form.clientId}\n onChange={(e) => setForm({ ...form, clientId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.status\", \"Status\")}\n </label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {PROJECT_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.description\", \"Description\")}\n </label>\n <textarea\n placeholder=\"Project description\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.estimatedHours\", \"Estimated Hours\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.hourlyRate\", \"Hourly Rate\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0.00\"\n value={form.hourlyRate}\n onChange={(e) => setForm({ ...form, hourlyRate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.loadFailed\",\n \"Failed to load projects. Please try again later.\",\n )}\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n purpose={t(\n \"timecampus.projects.purpose\",\n \"Group your work into projects so every hour you track rolls up to the right client and budget — use it to estimate effort, watch progress against the plan, and bill accurately.\",\n )}\n actions={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n >\n {loading && projects.length === 0 ? (\n <LoadingSkeleton variant=\"card\" count={4} />\n ) : projects.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title={t(\"timecampus.projects.empty.title\", \"No projects yet\")}\n description={t(\n \"timecampus.projects.empty\",\n \"No projects yet. Create your first project to get started.\",\n )}\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {projects.map((project: ProjectItem) => (\n <div\n key={project.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/projects/${project.id}`)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n navigate(`/projects/${project.id}`);\n }\n }}\n className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div\n className=\"h-3 w-3 rounded-full\"\n style={{\n backgroundColor:\n project.color ?? \"var(--color-accent-blue)\",\n }}\n />\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {project.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {project.clientId ??\n t(\"timecampus.projects.card.noClient\", \"No client\")}\n </p>\n </div>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(project.status)}`}\n >\n {project.status}\n </span>\n </div>\n <div className=\"mt-4\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {t(\"timecampus.projects.card.progress\", \"Progress\")}\n </span>\n <span>\n {project.estimatedHours\n ? Math.round(\n (project.trackedHours / project.estimatedHours) *\n 100,\n )\n : 0}\n %\n </span>\n </div>\n <div className=\"mt-1 h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{\n width: `${project.estimatedHours ? Math.min(Math.round((project.trackedHours / project.estimatedHours) * 100), 100) : 0}%`,\n }}\n />\n </div>\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {formatHours(project.trackedHours)} /{\" \"}\n {formatHours(project.estimatedHours ?? 0)}\n </span>\n <span>\n {project.tasks?.length ?? 0}{\" \"}\n {t(\"timecampus.projects.card.tasks\", \"tasks\")}\n </span>\n </div>\n <div className=\"mt-4 flex items-center justify-end gap-1 border-t border-border-subtle pt-3\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n openEdit(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={t(\"timecampus.projects.actions.edit\", \"Edit\")}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.edit\", \"Edit\")}\n </button>\n {project.status !== \"archived\" && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n void handleArchive(project);\n }}\n disabled={archiving}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50\"\n aria-label={t(\n \"timecampus.projects.actions.archive\",\n \"Archive\",\n )}\n >\n <Archive className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.archive\", \"Archive\")}\n </button>\n )}\n <button\n onClick={(e) => {\n e.stopPropagation();\n setDeleteTarget(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-status-error-text hover:bg-status-error-bg\"\n aria-label={t(\n \"timecampus.projects.actions.delete\",\n \"Delete\",\n )}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.delete\", \"Delete\")}\n </button>\n </div>\n </div>\n ))}\n </div>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={() => setLimit((prev) => prev + PAGE_SIZE)}\n disabled={loading}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loading\n ? t(\"timecampus.common.loading\", \"Loading…\")\n : t(\"timecampus.common.loadMore\", \"Load more\")}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title={t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n description={t(\n \"timecampus.projects.form.createHint\",\n \"Define a new project for time tracking.\",\n )}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating\n ? t(\"timecampus.common.creating\", \"Creating…\")\n : t(\"timecampus.projects.actions.create\", \"Create Project\")}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.createFailed\",\n \"Failed to create project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title={t(\"timecampus.projects.actions.editProject\", \"Edit Project\")}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {updating\n ? t(\"timecampus.common.saving\", \"Saving…\")\n : t(\"timecampus.common.saveChanges\", \"Save Changes\")}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.updateFailed\",\n \"Failed to update project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title={t(\"timecampus.projects.actions.deleteProject\", \"Delete Project\")}\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deleting\n ? t(\"timecampus.common.deleting\", \"Deleting…\")\n : t(\"timecampus.common.delete\", \"Delete\")}\n </button>\n </>\n }\n >\n {deleteError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.deleteFailed\",\n \"Failed to delete project. Please try again.\",\n )}\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n {t(\n \"timecampus.projects.deleteConfirm\",\n \"Are you sure you want to delete\",\n )}{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {deleteTarget?.name}\n </span>\n ? {t(\"timecampus.common.cannotUndo\", \"This action cannot be undone.\")}\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|