@burdenoff/microfe-timecampus 2026.731.2 → 2026.801.1
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/timecampus/TimeCampusRoutes.js +1 -1
- package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js +1 -1
- package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
- package/dist/timecampus/providers/TimeCampusProvider.js +1 -1
- package/dist/timecampus/providers/TimeCampusProvider.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HomePage as e } from "./pages/HomePage.js";
|
|
2
2
|
import { Suspense as t, lazy as n } from "react";
|
|
3
|
-
import { Navigate as r, Route as i, Routes as a } from "react-router
|
|
3
|
+
import { Navigate as r, Route as i, Routes as a } from "react-router";
|
|
4
4
|
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
5
5
|
import { Spinner as c } from "@burdenoff/fe-libs/ui";
|
|
6
6
|
//#region src/timecampus/TimeCampusRoutes.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimeCampusRoutes.js","names":[],"sources":["../../src/timecampus/TimeCampusRoutes.tsx"],"sourcesContent":["/**\n * TimeCampus Routes\n * All route definitions with lazy loading.\n * @module timecampus\n */\n\nimport { lazy, Suspense } from \"react\";\nimport { Routes, Route, Navigate } from \"react-router-dom\";\nimport { Spinner } from \"@burdenoff/fe-libs/ui\";\n\n// Eagerly load the home page\nimport { HomePage } from \"./pages/HomePage\";\n\n// Loading fallback\nfunction PageLoader() {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Spinner className=\"h-8 w-8\" />\n </div>\n );\n}\n\n// ── Dashboard / Analytics / Reports ─────────────────────────────────────────\nconst AnalyticsPage = lazy(() =>\n import(\"./pages/dashboard/AnalyticsPage\").then((m) => ({\n default: m.AnalyticsPage,\n })),\n);\nconst ReportsPage = lazy(() =>\n import(\"./pages/dashboard/ReportsPage\").then((m) => ({\n default: m.ReportsPage,\n })),\n);\n\n// ── Time Tracking ───────────────────────────────────────────────────────────\nconst TimeTrackingPage = lazy(() =>\n import(\"./pages/time/TimeTrackingPage\").then((m) => ({\n default: m.TimeTrackingPage,\n })),\n);\nconst TimerPage = lazy(() =>\n import(\"./pages/time/TimerPage\").then((m) => ({ default: m.TimerPage })),\n);\nconst TimeEntriesPage = lazy(() =>\n import(\"./pages/time/TimeEntriesPage\").then((m) => ({\n default: m.TimeEntriesPage,\n })),\n);\n\n// ── Projects ────────────────────────────────────────────────────────────────\nconst ProjectsPage = lazy(() =>\n import(\"./pages/projects/ProjectsPage\").then((m) => ({\n default: m.ProjectsPage,\n })),\n);\nconst ProjectCreatePage = lazy(() =>\n import(\"./pages/projects/ProjectCreatePage\").then((m) => ({\n default: m.ProjectCreatePage,\n })),\n);\nconst ProjectDetailPage = lazy(() =>\n import(\"./pages/projects/ProjectDetailPage\").then((m) => ({\n default: m.ProjectDetailPage,\n })),\n);\nconst TasksPage = lazy(() =>\n import(\"./pages/projects/TasksPage\").then((m) => ({ default: m.TasksPage })),\n);\n\n// ── Team ────────────────────────────────────────────────────────────────────\nconst TeamPage = lazy(() =>\n import(\"./pages/team/TeamPage\").then((m) => ({ default: m.TeamPage })),\n);\nconst TeamMembersPage = lazy(() =>\n import(\"./pages/team/TeamMembersPage\").then((m) => ({\n default: m.TeamMembersPage,\n })),\n);\nconst TeamAnalyticsPage = lazy(() =>\n import(\"./pages/team/TeamAnalyticsPage\").then((m) => ({\n default: m.TeamAnalyticsPage,\n })),\n);\n\n// ── Pomodoro ────────────────────────────────────────────────────────────────\nconst PomodoroPage = lazy(() =>\n import(\"./pages/pomodoro/PomodoroPage\").then((m) => ({\n default: m.PomodoroPage,\n })),\n);\nconst PomodoroAnalyticsPage = lazy(() =>\n import(\"./pages/pomodoro/PomodoroAnalyticsPage\").then((m) => ({\n default: m.PomodoroAnalyticsPage,\n })),\n);\nconst PomodoroSettingsPage = lazy(() =>\n import(\"./pages/pomodoro/PomodoroSettingsPage\").then((m) => ({\n default: m.PomodoroSettingsPage,\n })),\n);\n\n// ── Meetings ────────────────────────────────────────────────────────────────\nconst MeetingsPage = lazy(() =>\n import(\"./pages/meetings/MeetingsPage\").then((m) => ({\n default: m.MeetingsPage,\n })),\n);\nconst MeetingCreatePage = lazy(() =>\n import(\"./pages/meetings/MeetingCreatePage\").then((m) => ({\n default: m.MeetingCreatePage,\n })),\n);\nconst MeetingDetailPage = lazy(() =>\n import(\"./pages/meetings/MeetingDetailPage\").then((m) => ({\n default: m.MeetingDetailPage,\n })),\n);\nconst MeetingAnalyticsPage = lazy(() =>\n import(\"./pages/meetings/MeetingAnalyticsPage\").then((m) => ({\n default: m.MeetingAnalyticsPage,\n })),\n);\n\n// ── Email Tracking ──────────────────────────────────────────────────────────\nconst EmailTrackingPage = lazy(() =>\n import(\"./pages/email-tracking/EmailTrackingPage\").then((m) => ({\n default: m.EmailTrackingPage,\n })),\n);\nconst EmailAnalyticsPage = lazy(() =>\n import(\"./pages/email-tracking/EmailAnalyticsPage\").then((m) => ({\n default: m.EmailAnalyticsPage,\n })),\n);\n// ── Work Patterns ───────────────────────────────────────────────────────────\nconst WorkPatternsPage = lazy(() =>\n import(\"./pages/work-patterns/WorkPatternsPage\").then((m) => ({\n default: m.WorkPatternsPage,\n })),\n);\n// ── Application Usage ───────────────────────────────────────────────────────\nconst ApplicationUsagePage = lazy(() =>\n import(\"./pages/application-usage/ApplicationUsagePage\").then((m) => ({\n default: m.ApplicationUsagePage,\n })),\n);\n// ── Focus Score ─────────────────────────────────────────────────────────────\nconst FocusScorePage = lazy(() =>\n import(\"./pages/focus-score/FocusScorePage\").then((m) => ({\n default: m.FocusScorePage,\n })),\n);\n// ── Location Tracking ───────────────────────────────────────────────────────\nconst LocationTrackingPage = lazy(() =>\n import(\"./pages/location-tracking/LocationTrackingPage\").then((m) => ({\n default: m.LocationTrackingPage,\n })),\n);\n// ── Geofencing ──────────────────────────────────────────────────────────────\nconst GeofencingPage = lazy(() =>\n import(\"./pages/geofencing/GeofencingPage\").then((m) => ({\n default: m.GeofencingPage,\n })),\n);\n// ── Team Activity ───────────────────────────────────────────────────────────\nconst TeamActivityPage = lazy(() =>\n import(\"./pages/team-activity/TeamActivityPage\").then((m) => ({\n default: m.TeamActivityPage,\n })),\n);\n// ── Resource Planning ───────────────────────────────────────────────────────\nconst ResourcePlanningPage = lazy(() =>\n import(\"./pages/resource-planning/ResourcePlanningPage\").then((m) => ({\n default: m.ResourcePlanningPage,\n })),\n);\n\n// ── Time Approval ───────────────────────────────────────────────────────────\nconst TimeApprovalPage = lazy(() =>\n import(\"./pages/time-approval/TimeApprovalPage\").then((m) => ({\n default: m.TimeApprovalPage,\n })),\n);\n\n// ── Expense Tracking ────────────────────────────────────────────────────────\nconst ExpenseTrackingPage = lazy(() =>\n import(\"./pages/expense-tracking/ExpenseTrackingPage\").then((m) => ({\n default: m.ExpenseTrackingPage,\n })),\n);\n\n// ── Invoicing ────────────────────────────────────────────────────────────────\nconst InvoicingPage = lazy(() =>\n import(\"./pages/invoicing/InvoicingPage\").then((m) => ({\n default: m.InvoicingPage,\n })),\n);\n\n// ── Client Portal ───────────────────────────────────────────────────────────\nconst ClientPortalPage = lazy(() =>\n import(\"./pages/client-portal/ClientPortalPage\").then((m) => ({\n default: m.ClientPortalPage,\n })),\n);\n\n// ── Timeline ────────────────────────────────────────────────────────────────\nconst TimelinePage = lazy(() =>\n import(\"./pages/timeline/TimelinePage\").then((m) => ({\n default: m.TimelinePage,\n })),\n);\n\n// ── Life-time intelligence ──────────────────────────────────────────────────\nconst LifeTimelinePage = lazy(() =>\n import(\"./pages/lifetime/LifeTimelinePage\").then((m) => ({\n default: m.LifeTimelinePage,\n })),\n);\nconst LifeBalancePage = lazy(() =>\n import(\"./pages/lifetime/LifeBalancePage\").then((m) => ({\n default: m.LifeBalancePage,\n })),\n);\nconst FiniteLifePage = lazy(() =>\n import(\"./pages/lifetime/FiniteLifePage\").then((m) => ({\n default: m.FiniteLifePage,\n })),\n);\nconst TimeWellSpentPage = lazy(() =>\n import(\"./pages/lifetime/TimeWellSpentPage\").then((m) => ({\n default: m.TimeWellSpentPage,\n })),\n);\nconst DeadTimeRadarPage = lazy(() =>\n import(\"./pages/lifetime/DeadTimeRadarPage\").then((m) => ({\n default: m.DeadTimeRadarPage,\n })),\n);\nconst ValuesGoalsPage = lazy(() =>\n import(\"./pages/lifetime/ValuesGoalsPage\").then((m) => ({\n default: m.ValuesGoalsPage,\n })),\n);\nconst RecommendationsPage = lazy(() =>\n import(\"./pages/lifetime/RecommendationsPage\").then((m) => ({\n default: m.RecommendationsPage,\n })),\n);\nconst PersonalCorporatePage = lazy(() =>\n import(\"./pages/lifetime/PersonalCorporatePage\").then((m) => ({\n default: m.PersonalCorporatePage,\n })),\n);\n\n// ============================================================================\n// Routes\n// ============================================================================\n\nexport function TimeCampusRoutes() {\n return (\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Dashboard */}\n <Route index element={<HomePage />} />\n <Route path=\"analytics\" element={<AnalyticsPage />} />\n <Route path=\"reports\" element={<ReportsPage />} />\n\n {/* Time Tracking */}\n <Route path=\"time\" element={<TimeTrackingPage />} />\n <Route path=\"time/timer\" element={<TimerPage />} />\n <Route path=\"time/entries\" element={<TimeEntriesPage />} />\n\n {/* Projects */}\n <Route path=\"projects\" element={<ProjectsPage />} />\n <Route path=\"projects/new\" element={<ProjectCreatePage />} />\n <Route path=\"projects/:projectId\" element={<ProjectDetailPage />} />\n <Route path=\"projects/:projectId/tasks\" element={<TasksPage />} />\n\n {/* Team */}\n <Route path=\"team\" element={<TeamPage />} />\n <Route path=\"team/members\" element={<TeamMembersPage />} />\n <Route path=\"team/analytics\" element={<TeamAnalyticsPage />} />\n\n {/* Pomodoro */}\n <Route path=\"pomodoro\" element={<PomodoroPage />} />\n <Route path=\"pomodoro/analytics\" element={<PomodoroAnalyticsPage />} />\n <Route path=\"pomodoro/settings\" element={<PomodoroSettingsPage />} />\n\n {/* Meetings */}\n <Route path=\"meetings\" element={<MeetingsPage />} />\n <Route path=\"meetings/new\" element={<MeetingCreatePage />} />\n <Route path=\"meetings/:id\" element={<MeetingDetailPage />} />\n <Route path=\"meetings/analytics\" element={<MeetingAnalyticsPage />} />\n\n {/* Email Tracking */}\n <Route path=\"email-tracking\" element={<EmailTrackingPage />} />\n <Route\n path=\"email-tracking/analytics\"\n element={<EmailAnalyticsPage />}\n />\n {/* Work Patterns */}\n <Route path=\"work-patterns\" element={<WorkPatternsPage />} />\n {/* Application Usage */}\n <Route path=\"application-usage\" element={<ApplicationUsagePage />} />\n {/* Focus Score */}\n <Route path=\"focus-score\" element={<FocusScorePage />} />\n {/* Location Tracking */}\n <Route path=\"location-tracking\" element={<LocationTrackingPage />} />\n {/* Geofencing */}\n <Route path=\"geofencing\" element={<GeofencingPage />} />\n {/* Team Activity */}\n <Route path=\"team-activity\" element={<TeamActivityPage />} />\n {/* Resource Planning */}\n <Route path=\"resource-planning\" element={<ResourcePlanningPage />} />\n\n {/* Time Approval */}\n <Route path=\"time-approval\" element={<TimeApprovalPage />} />\n\n {/* Expense Tracking */}\n <Route path=\"expense-tracking\" element={<ExpenseTrackingPage />} />\n\n {/* Invoicing */}\n <Route path=\"invoicing\" element={<InvoicingPage />} />\n\n {/* Client Portal */}\n <Route path=\"client-portal\" element={<ClientPortalPage />} />\n\n {/* Timeline */}\n <Route path=\"timeline\" element={<TimelinePage />} />\n\n {/* Life-time intelligence */}\n <Route path=\"life-timeline\" element={<LifeTimelinePage />} />\n <Route path=\"life-balance\" element={<LifeBalancePage />} />\n <Route path=\"finite-life\" element={<FiniteLifePage />} />\n <Route path=\"time-well-spent\" element={<TimeWellSpentPage />} />\n <Route path=\"dead-time-radar\" element={<DeadTimeRadarPage />} />\n <Route path=\"values-goals\" element={<ValuesGoalsPage />} />\n <Route path=\"recommendations\" element={<RecommendationsPage />} />\n <Route path=\"personal-corporate\" element={<PersonalCorporatePage />} />\n\n {/* Catch-all: redirect to dashboard */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TimeCampusRoutes.js","names":[],"sources":["../../src/timecampus/TimeCampusRoutes.tsx"],"sourcesContent":["/**\n * TimeCampus Routes\n * All route definitions with lazy loading.\n * @module timecampus\n */\n\nimport { lazy, Suspense } from \"react\";\nimport { Routes, Route, Navigate } from \"react-router\";\nimport { Spinner } from \"@burdenoff/fe-libs/ui\";\n\n// Eagerly load the home page\nimport { HomePage } from \"./pages/HomePage\";\n\n// Loading fallback\nfunction PageLoader() {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Spinner className=\"h-8 w-8\" />\n </div>\n );\n}\n\n// ── Dashboard / Analytics / Reports ─────────────────────────────────────────\nconst AnalyticsPage = lazy(() =>\n import(\"./pages/dashboard/AnalyticsPage\").then((m) => ({\n default: m.AnalyticsPage,\n })),\n);\nconst ReportsPage = lazy(() =>\n import(\"./pages/dashboard/ReportsPage\").then((m) => ({\n default: m.ReportsPage,\n })),\n);\n\n// ── Time Tracking ───────────────────────────────────────────────────────────\nconst TimeTrackingPage = lazy(() =>\n import(\"./pages/time/TimeTrackingPage\").then((m) => ({\n default: m.TimeTrackingPage,\n })),\n);\nconst TimerPage = lazy(() =>\n import(\"./pages/time/TimerPage\").then((m) => ({ default: m.TimerPage })),\n);\nconst TimeEntriesPage = lazy(() =>\n import(\"./pages/time/TimeEntriesPage\").then((m) => ({\n default: m.TimeEntriesPage,\n })),\n);\n\n// ── Projects ────────────────────────────────────────────────────────────────\nconst ProjectsPage = lazy(() =>\n import(\"./pages/projects/ProjectsPage\").then((m) => ({\n default: m.ProjectsPage,\n })),\n);\nconst ProjectCreatePage = lazy(() =>\n import(\"./pages/projects/ProjectCreatePage\").then((m) => ({\n default: m.ProjectCreatePage,\n })),\n);\nconst ProjectDetailPage = lazy(() =>\n import(\"./pages/projects/ProjectDetailPage\").then((m) => ({\n default: m.ProjectDetailPage,\n })),\n);\nconst TasksPage = lazy(() =>\n import(\"./pages/projects/TasksPage\").then((m) => ({ default: m.TasksPage })),\n);\n\n// ── Team ────────────────────────────────────────────────────────────────────\nconst TeamPage = lazy(() =>\n import(\"./pages/team/TeamPage\").then((m) => ({ default: m.TeamPage })),\n);\nconst TeamMembersPage = lazy(() =>\n import(\"./pages/team/TeamMembersPage\").then((m) => ({\n default: m.TeamMembersPage,\n })),\n);\nconst TeamAnalyticsPage = lazy(() =>\n import(\"./pages/team/TeamAnalyticsPage\").then((m) => ({\n default: m.TeamAnalyticsPage,\n })),\n);\n\n// ── Pomodoro ────────────────────────────────────────────────────────────────\nconst PomodoroPage = lazy(() =>\n import(\"./pages/pomodoro/PomodoroPage\").then((m) => ({\n default: m.PomodoroPage,\n })),\n);\nconst PomodoroAnalyticsPage = lazy(() =>\n import(\"./pages/pomodoro/PomodoroAnalyticsPage\").then((m) => ({\n default: m.PomodoroAnalyticsPage,\n })),\n);\nconst PomodoroSettingsPage = lazy(() =>\n import(\"./pages/pomodoro/PomodoroSettingsPage\").then((m) => ({\n default: m.PomodoroSettingsPage,\n })),\n);\n\n// ── Meetings ────────────────────────────────────────────────────────────────\nconst MeetingsPage = lazy(() =>\n import(\"./pages/meetings/MeetingsPage\").then((m) => ({\n default: m.MeetingsPage,\n })),\n);\nconst MeetingCreatePage = lazy(() =>\n import(\"./pages/meetings/MeetingCreatePage\").then((m) => ({\n default: m.MeetingCreatePage,\n })),\n);\nconst MeetingDetailPage = lazy(() =>\n import(\"./pages/meetings/MeetingDetailPage\").then((m) => ({\n default: m.MeetingDetailPage,\n })),\n);\nconst MeetingAnalyticsPage = lazy(() =>\n import(\"./pages/meetings/MeetingAnalyticsPage\").then((m) => ({\n default: m.MeetingAnalyticsPage,\n })),\n);\n\n// ── Email Tracking ──────────────────────────────────────────────────────────\nconst EmailTrackingPage = lazy(() =>\n import(\"./pages/email-tracking/EmailTrackingPage\").then((m) => ({\n default: m.EmailTrackingPage,\n })),\n);\nconst EmailAnalyticsPage = lazy(() =>\n import(\"./pages/email-tracking/EmailAnalyticsPage\").then((m) => ({\n default: m.EmailAnalyticsPage,\n })),\n);\n// ── Work Patterns ───────────────────────────────────────────────────────────\nconst WorkPatternsPage = lazy(() =>\n import(\"./pages/work-patterns/WorkPatternsPage\").then((m) => ({\n default: m.WorkPatternsPage,\n })),\n);\n// ── Application Usage ───────────────────────────────────────────────────────\nconst ApplicationUsagePage = lazy(() =>\n import(\"./pages/application-usage/ApplicationUsagePage\").then((m) => ({\n default: m.ApplicationUsagePage,\n })),\n);\n// ── Focus Score ─────────────────────────────────────────────────────────────\nconst FocusScorePage = lazy(() =>\n import(\"./pages/focus-score/FocusScorePage\").then((m) => ({\n default: m.FocusScorePage,\n })),\n);\n// ── Location Tracking ───────────────────────────────────────────────────────\nconst LocationTrackingPage = lazy(() =>\n import(\"./pages/location-tracking/LocationTrackingPage\").then((m) => ({\n default: m.LocationTrackingPage,\n })),\n);\n// ── Geofencing ──────────────────────────────────────────────────────────────\nconst GeofencingPage = lazy(() =>\n import(\"./pages/geofencing/GeofencingPage\").then((m) => ({\n default: m.GeofencingPage,\n })),\n);\n// ── Team Activity ───────────────────────────────────────────────────────────\nconst TeamActivityPage = lazy(() =>\n import(\"./pages/team-activity/TeamActivityPage\").then((m) => ({\n default: m.TeamActivityPage,\n })),\n);\n// ── Resource Planning ───────────────────────────────────────────────────────\nconst ResourcePlanningPage = lazy(() =>\n import(\"./pages/resource-planning/ResourcePlanningPage\").then((m) => ({\n default: m.ResourcePlanningPage,\n })),\n);\n\n// ── Time Approval ───────────────────────────────────────────────────────────\nconst TimeApprovalPage = lazy(() =>\n import(\"./pages/time-approval/TimeApprovalPage\").then((m) => ({\n default: m.TimeApprovalPage,\n })),\n);\n\n// ── Expense Tracking ────────────────────────────────────────────────────────\nconst ExpenseTrackingPage = lazy(() =>\n import(\"./pages/expense-tracking/ExpenseTrackingPage\").then((m) => ({\n default: m.ExpenseTrackingPage,\n })),\n);\n\n// ── Invoicing ────────────────────────────────────────────────────────────────\nconst InvoicingPage = lazy(() =>\n import(\"./pages/invoicing/InvoicingPage\").then((m) => ({\n default: m.InvoicingPage,\n })),\n);\n\n// ── Client Portal ───────────────────────────────────────────────────────────\nconst ClientPortalPage = lazy(() =>\n import(\"./pages/client-portal/ClientPortalPage\").then((m) => ({\n default: m.ClientPortalPage,\n })),\n);\n\n// ── Timeline ────────────────────────────────────────────────────────────────\nconst TimelinePage = lazy(() =>\n import(\"./pages/timeline/TimelinePage\").then((m) => ({\n default: m.TimelinePage,\n })),\n);\n\n// ── Life-time intelligence ──────────────────────────────────────────────────\nconst LifeTimelinePage = lazy(() =>\n import(\"./pages/lifetime/LifeTimelinePage\").then((m) => ({\n default: m.LifeTimelinePage,\n })),\n);\nconst LifeBalancePage = lazy(() =>\n import(\"./pages/lifetime/LifeBalancePage\").then((m) => ({\n default: m.LifeBalancePage,\n })),\n);\nconst FiniteLifePage = lazy(() =>\n import(\"./pages/lifetime/FiniteLifePage\").then((m) => ({\n default: m.FiniteLifePage,\n })),\n);\nconst TimeWellSpentPage = lazy(() =>\n import(\"./pages/lifetime/TimeWellSpentPage\").then((m) => ({\n default: m.TimeWellSpentPage,\n })),\n);\nconst DeadTimeRadarPage = lazy(() =>\n import(\"./pages/lifetime/DeadTimeRadarPage\").then((m) => ({\n default: m.DeadTimeRadarPage,\n })),\n);\nconst ValuesGoalsPage = lazy(() =>\n import(\"./pages/lifetime/ValuesGoalsPage\").then((m) => ({\n default: m.ValuesGoalsPage,\n })),\n);\nconst RecommendationsPage = lazy(() =>\n import(\"./pages/lifetime/RecommendationsPage\").then((m) => ({\n default: m.RecommendationsPage,\n })),\n);\nconst PersonalCorporatePage = lazy(() =>\n import(\"./pages/lifetime/PersonalCorporatePage\").then((m) => ({\n default: m.PersonalCorporatePage,\n })),\n);\n\n// ============================================================================\n// Routes\n// ============================================================================\n\nexport function TimeCampusRoutes() {\n return (\n <Suspense fallback={<PageLoader />}>\n <Routes>\n {/* Dashboard */}\n <Route index element={<HomePage />} />\n <Route path=\"analytics\" element={<AnalyticsPage />} />\n <Route path=\"reports\" element={<ReportsPage />} />\n\n {/* Time Tracking */}\n <Route path=\"time\" element={<TimeTrackingPage />} />\n <Route path=\"time/timer\" element={<TimerPage />} />\n <Route path=\"time/entries\" element={<TimeEntriesPage />} />\n\n {/* Projects */}\n <Route path=\"projects\" element={<ProjectsPage />} />\n <Route path=\"projects/new\" element={<ProjectCreatePage />} />\n <Route path=\"projects/:projectId\" element={<ProjectDetailPage />} />\n <Route path=\"projects/:projectId/tasks\" element={<TasksPage />} />\n\n {/* Team */}\n <Route path=\"team\" element={<TeamPage />} />\n <Route path=\"team/members\" element={<TeamMembersPage />} />\n <Route path=\"team/analytics\" element={<TeamAnalyticsPage />} />\n\n {/* Pomodoro */}\n <Route path=\"pomodoro\" element={<PomodoroPage />} />\n <Route path=\"pomodoro/analytics\" element={<PomodoroAnalyticsPage />} />\n <Route path=\"pomodoro/settings\" element={<PomodoroSettingsPage />} />\n\n {/* Meetings */}\n <Route path=\"meetings\" element={<MeetingsPage />} />\n <Route path=\"meetings/new\" element={<MeetingCreatePage />} />\n <Route path=\"meetings/:id\" element={<MeetingDetailPage />} />\n <Route path=\"meetings/analytics\" element={<MeetingAnalyticsPage />} />\n\n {/* Email Tracking */}\n <Route path=\"email-tracking\" element={<EmailTrackingPage />} />\n <Route\n path=\"email-tracking/analytics\"\n element={<EmailAnalyticsPage />}\n />\n {/* Work Patterns */}\n <Route path=\"work-patterns\" element={<WorkPatternsPage />} />\n {/* Application Usage */}\n <Route path=\"application-usage\" element={<ApplicationUsagePage />} />\n {/* Focus Score */}\n <Route path=\"focus-score\" element={<FocusScorePage />} />\n {/* Location Tracking */}\n <Route path=\"location-tracking\" element={<LocationTrackingPage />} />\n {/* Geofencing */}\n <Route path=\"geofencing\" element={<GeofencingPage />} />\n {/* Team Activity */}\n <Route path=\"team-activity\" element={<TeamActivityPage />} />\n {/* Resource Planning */}\n <Route path=\"resource-planning\" element={<ResourcePlanningPage />} />\n\n {/* Time Approval */}\n <Route path=\"time-approval\" element={<TimeApprovalPage />} />\n\n {/* Expense Tracking */}\n <Route path=\"expense-tracking\" element={<ExpenseTrackingPage />} />\n\n {/* Invoicing */}\n <Route path=\"invoicing\" element={<InvoicingPage />} />\n\n {/* Client Portal */}\n <Route path=\"client-portal\" element={<ClientPortalPage />} />\n\n {/* Timeline */}\n <Route path=\"timeline\" element={<TimelinePage />} />\n\n {/* Life-time intelligence */}\n <Route path=\"life-timeline\" element={<LifeTimelinePage />} />\n <Route path=\"life-balance\" element={<LifeBalancePage />} />\n <Route path=\"finite-life\" element={<FiniteLifePage />} />\n <Route path=\"time-well-spent\" element={<TimeWellSpentPage />} />\n <Route path=\"dead-time-radar\" element={<DeadTimeRadarPage />} />\n <Route path=\"values-goals\" element={<ValuesGoalsPage />} />\n <Route path=\"recommendations\" element={<RecommendationsPage />} />\n <Route path=\"personal-corporate\" element={<PersonalCorporatePage />} />\n\n {/* Catch-all: redirect to dashboard */}\n <Route path=\"*\" element={<Navigate to=\"\" replace />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
|
|
@@ -13,7 +13,7 @@ import { ParticipantFormModal as ue } from "./ParticipantFormModal.js";
|
|
|
13
13
|
import { ActionItemFormModal as de } from "./ActionItemFormModal.js";
|
|
14
14
|
import { useState as m } from "react";
|
|
15
15
|
import { CalendarDays as h, Check as g, Clock as fe, Pencil as _, Plus as v, Star as pe, Trash2 as y, UserMinus as b, Users as x } from "lucide-react";
|
|
16
|
-
import { Link as S, useParams as C } from "react-router
|
|
16
|
+
import { Link as S, useParams as C } from "react-router";
|
|
17
17
|
import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
18
18
|
import { NextSteps as D } from "@burdenoff/fe-libs/ui";
|
|
19
19
|
//#region src/timecampus/pages/meetings/MeetingDetailPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeetingDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/MeetingDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, Link } from \"react-router-dom\";\nimport {\n Users,\n Clock,\n Pencil,\n Trash2,\n Plus,\n Star,\n Check,\n UserMinus,\n CalendarDays,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { cn, type BadgeVariant } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetMeetingQuery,\n useUpdateMeetingMutation,\n useDeleteMeetingMutation,\n useRateMeetingMutation,\n useAddParticipantMutation,\n useRemoveParticipantMutation,\n useUpdateParticipantStatusMutation,\n useCreateActionItemMutation,\n useUpdateActionItemMutation,\n useCompleteActionItemMutation,\n useDeleteActionItemMutation,\n type MeetingParticipantFieldsFragment,\n type MeetingActionItemFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport {\n MeetingParticipantStatus,\n MeetingActionItemStatus,\n type UpdateMeetingInput,\n type AddMeetingParticipantInput,\n type CreateActionItemInput,\n type UpdateActionItemInput,\n} from \"@/generated/wspace-types\";\nimport { formatCurrency } from \"../../utils/helpers\";\nimport { MeetingFormModal } from \"./MeetingFormModal\";\nimport { ParticipantFormModal } from \"./ParticipantFormModal\";\nimport { ActionItemFormModal } from \"./ActionItemFormModal\";\n\nconst PARTICIPANT_STATUS_OPTIONS = [\n MeetingParticipantStatus.Pending,\n MeetingParticipantStatus.Accepted,\n MeetingParticipantStatus.Tentative,\n MeetingParticipantStatus.Declined,\n];\n\nconst ACTION_ITEM_VARIANT: Record<MeetingActionItemStatus, BadgeVariant> = {\n [MeetingActionItemStatus.Completed]: \"success\",\n [MeetingActionItemStatus.InProgress]: \"info\",\n [MeetingActionItemStatus.Pending]: \"warning\",\n [MeetingActionItemStatus.Cancelled]: \"muted\",\n};\n\n/** Deterministic 2-char monogram from an opaque participant id. */\nfunction idMonogram(id: string): string {\n return (\n id\n .replace(/[^a-zA-Z0-9]/g, \"\")\n .slice(0, 2)\n .toUpperCase() || \"?\"\n );\n}\n\nfunction prettyStatus(status: string): string {\n const lower = status.replace(/_/g, \" \").toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\nexport function MeetingDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { workspaceId = \"\", currentUser } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n const meetingId = id ?? \"\";\n\n const { data, loading, error, refetch } = useGetMeetingQuery({\n variables: { id: meetingId, workspaceId },\n skip: !workspaceId || !id,\n });\n\n const [updateMeeting, { loading: updating, error: updateError }] =\n useUpdateMeetingMutation();\n const [deleteMeeting, { loading: deletingMeeting }] =\n useDeleteMeetingMutation();\n const [rateMeeting, { loading: rating }] = useRateMeetingMutation();\n const [addParticipant, { loading: addingParticipant, error: addPError }] =\n useAddParticipantMutation();\n const [removeParticipant] = useRemoveParticipantMutation();\n const [updateParticipantStatus] = useUpdateParticipantStatusMutation();\n const [createActionItem, { loading: creatingItem, error: createItemError }] =\n useCreateActionItemMutation();\n const [updateActionItem, { loading: updatingItem, error: updateItemError }] =\n useUpdateActionItemMutation();\n const [completeActionItem] = useCompleteActionItemMutation();\n const [deleteActionItem] = useDeleteActionItemMutation();\n\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const [participantOpen, setParticipantOpen] = useState(false);\n const [removingParticipant, setRemovingParticipant] =\n useState<MeetingParticipantFieldsFragment | null>(null);\n const [actionItemModal, setActionItemModal] = useState<\n | { mode: \"create\" }\n | { mode: \"edit\"; item: MeetingActionItemFieldsFragment }\n | null\n >(null);\n const [deletingItem, setDeletingItem] =\n useState<MeetingActionItemFieldsFragment | null>(null);\n\n const meeting = data?.meeting;\n\n if (loading) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error || !meeting) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n {error\n ? \"Failed to load meeting details. Please try again later.\"\n : \"Meeting not found.\"}\n </div>\n </PageLayout>\n );\n }\n\n const pendingActions = meeting.actionItems.filter(\n (a: MeetingActionItemFieldsFragment) => a.status !== \"COMPLETED\",\n ).length;\n const currentRating = meeting.rating ?? 0;\n\n const handleUpdateMeeting = async (input: UpdateMeetingInput) => {\n await updateMeeting({ variables: { id: meetingId, workspaceId, input } });\n await refetch();\n setEditOpen(false);\n };\n\n const handleDeleteMeeting = async () => {\n await deleteMeeting({ variables: { id: meetingId, workspaceId } });\n setDeleteOpen(false);\n navigate(\"/meetings\");\n };\n\n const handleRate = async (value: number) => {\n await rateMeeting({\n variables: { id: meetingId, workspaceId, rating: value },\n });\n await refetch();\n };\n\n const handleAddParticipant = async (input: AddMeetingParticipantInput) => {\n await addParticipant({\n variables: { meetingId, workspaceId, input },\n });\n await refetch();\n setParticipantOpen(false);\n };\n\n const handleRemoveParticipant = async () => {\n if (!removingParticipant) return;\n await removeParticipant({\n variables: {\n meetingId,\n participantId: removingParticipant.id,\n workspaceId,\n },\n });\n setRemovingParticipant(null);\n await refetch();\n };\n\n const handleParticipantStatus = async (\n participantId: string,\n status: MeetingParticipantStatus,\n ) => {\n await updateParticipantStatus({\n variables: { meetingId, participantId, workspaceId, status },\n });\n await refetch();\n };\n\n const handleCreateItem = async (input: CreateActionItemInput) => {\n await createActionItem({ variables: { meetingId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleUpdateItem = async (\n itemId: string,\n input: UpdateActionItemInput,\n ) => {\n await updateActionItem({ variables: { id: itemId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleCompleteItem = async (itemId: string) => {\n await completeActionItem({ variables: { id: itemId, workspaceId } });\n await refetch();\n };\n\n const handleDeleteItem = async () => {\n if (!deletingItem) return;\n await deleteActionItem({ variables: { id: deletingItem.id, workspaceId } });\n setDeletingItem(null);\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Meeting Details\"\n description={meeting.title}\n purpose=\"The full record of one meeting — who attended, what it cost in people's time, and the action items it produced — so you can follow up on commitments and judge whether the meeting was worth it.\"\n actions={\n <>\n <button\n onClick={() => setEditOpen(true)}\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 <Pencil className=\"h-4 w-4\" />\n Edit\n </button>\n <button\n onClick={() => setDeleteOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/40\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </button>\n </>\n }\n >\n <NextSteps\n storageKey=\"timecampus-meeting-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"all-meetings\",\n label: \"Back to all meetings\",\n description: \"Browse and schedule other meetings.\",\n onClick: () => navigate(\"/meetings\"),\n },\n {\n id: \"analytics\",\n label: \"See meeting analytics\",\n description: \"Understand your overall meeting load and cost.\",\n onClick: () => navigate(\"/meetings/analytics\"),\n },\n ]}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Duration\"\n value={`${meeting.duration ?? 0} min`}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Participants\"\n value={String(meeting.participants.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Cost\"\n value={meeting.cost != null ? formatCurrency(meeting.cost) : \"N/A\"}\n description=\"Based on participant rates\"\n />\n <StatCard\n title=\"Action Items\"\n value={String(meeting.actionItems.length)}\n description={`${pendingActions} pending`}\n />\n </div>\n\n {/* Calendar link — surfaces the wspace-calendar event auto-linked on\n create (MeetingMeta.calendarEventId). Hidden entirely when the\n meeting has no linked event (e.g. calendar-svc was unreachable at\n creation time), rather than showing a dead/broken state. */}\n {meeting.calendarEventId && (\n <Link\n to={`/calendar/event/${meeting.calendarEventId}`}\n className=\"inline-flex w-fit items-center gap-1.5 rounded-full border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken/50 hover:text-text-primary\"\n >\n <CalendarDays className=\"h-3.5 w-3.5\" />\n View in Calendar\n </Link>\n )}\n\n {/* Rating */}\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-1 text-sm font-semibold text-text-primary\">\n Rate this meeting\n </h3>\n <p className=\"mb-3 text-xs text-text-secondary\">\n Was it worth the time? Your rating feeds meeting analytics.\n </p>\n <div className=\"flex items-center gap-1\">\n {[1, 2, 3, 4, 5].map((value) => (\n <button\n key={value}\n onClick={() => handleRate(value)}\n disabled={rating}\n aria-label={`Rate ${value} star${value > 1 ? \"s\" : \"\"}`}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Star\n className={cn(\n \"h-6 w-6\",\n value <= currentRating\n ? \"fill-status-warning-text text-status-warning-text\"\n : \"text-text-secondary\",\n )}\n />\n </button>\n ))}\n {currentRating > 0 && (\n <span className=\"ml-2 text-sm text-text-secondary\">\n {currentRating} / 5\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Participants\n </h3>\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.participants.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.participants.map(\n (p: MeetingParticipantFieldsFragment) => (\n <div\n key={p.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"flex min-w-0 items-center gap-2.5\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-[11px] font-semibold text-primary\">\n {idMonogram(p.userId)}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {p.userId}\n </p>\n <p className=\"text-xs text-text-secondary\">{p.role}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={p.status}\n onChange={(e) =>\n handleParticipantStatus(\n p.id,\n e.target.value as MeetingParticipantStatus,\n )\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary\"\n aria-label=\"Participant status\"\n >\n {PARTICIPANT_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => setRemovingParticipant(p)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Remove participant\"\n >\n <UserMinus className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No participants added\"\n description=\"Add attendees to capture who was in the room and what it cost.\"\n action={\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 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 Add participant\n </button>\n }\n />\n )}\n </div>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Action Items\n </h3>\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.actionItems.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.actionItems.map(\n (item: MeetingActionItemFieldsFragment) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {item.title}\n </p>\n <div className=\"mt-1\">\n <StatusBadge\n label={prettyStatus(item.status)}\n variant={ACTION_ITEM_VARIANT[item.status]}\n />\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n {item.status !== MeetingActionItemStatus.Completed && (\n <button\n onClick={() => handleCompleteItem(item.id)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-success-bg hover:text-status-success-text\"\n aria-label=\"Complete action item\"\n >\n <Check className=\"h-4 w-4\" />\n </button>\n )}\n <button\n onClick={() =>\n setActionItemModal({ mode: \"edit\", item })\n }\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit action item\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeletingItem(item)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Delete action item\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No action items\"\n description=\"Capture follow-ups here so commitments from the meeting don't get lost.\"\n action={\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 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 Add action item\n </button>\n }\n />\n )}\n </div>\n </div>\n\n {/* Edit meeting */}\n {editOpen && (\n <MeetingFormModal\n open\n onClose={() => setEditOpen(false)}\n workspaceId={workspaceId}\n meeting={meeting}\n submitting={updating}\n error={Boolean(updateError)}\n onUpdate={handleUpdateMeeting}\n />\n )}\n\n {/* Delete meeting */}\n <Modal\n open={deleteOpen}\n onClose={() => setDeleteOpen(false)}\n title=\"Delete meeting\"\n description=\"This permanently removes the meeting and its analytics.\"\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={handleDeleteMeeting}\n disabled={deletingMeeting}\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 {deletingMeeting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-medium text-text-primary\">{meeting.title}</span>\n ? This action cannot be undone.\n </p>\n </Modal>\n\n {/* Add participant */}\n {participantOpen && (\n <ParticipantFormModal\n open\n onClose={() => setParticipantOpen(false)}\n defaultUserId={currentUser?.id}\n submitting={addingParticipant}\n error={Boolean(addPError)}\n onSubmit={handleAddParticipant}\n />\n )}\n\n {/* Remove participant confirm */}\n <Modal\n open={Boolean(removingParticipant)}\n onClose={() => setRemovingParticipant(null)}\n title=\"Remove participant\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRemovingParticipant(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={handleRemoveParticipant}\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\"\n >\n Remove\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove{\" \"}\n <span className=\"font-medium text-text-primary\">\n {removingParticipant?.userId}\n </span>{\" \"}\n from this meeting?\n </p>\n </Modal>\n\n {/* Create / edit action item */}\n {actionItemModal && (\n <ActionItemFormModal\n open\n onClose={() => setActionItemModal(null)}\n item={\n actionItemModal.mode === \"edit\" ? actionItemModal.item : undefined\n }\n submitting={\n actionItemModal.mode === \"edit\" ? updatingItem : creatingItem\n }\n error={Boolean(\n actionItemModal.mode === \"edit\" ? updateItemError : createItemError,\n )}\n onCreate={handleCreateItem}\n onUpdate={\n actionItemModal.mode === \"edit\"\n ? (input) => handleUpdateItem(actionItemModal.item.id, input)\n : undefined\n }\n />\n )}\n\n {/* Delete action item confirm */}\n <Modal\n open={Boolean(deletingItem)}\n onClose={() => setDeletingItem(null)}\n title=\"Delete action item\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeletingItem(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={handleDeleteItem}\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\"\n >\n Delete\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Delete action item{\" \"}\n <span className=\"font-medium text-text-primary\">\n {deletingItem?.title}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MeetingDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/meetings/MeetingDetailPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams, Link } from \"react-router\";\nimport {\n Users,\n Clock,\n Pencil,\n Trash2,\n Plus,\n Star,\n Check,\n UserMinus,\n CalendarDays,\n} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { StatusBadge } from \"../../components/StatusBadge\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { cn, type BadgeVariant } from \"../../utils/helpers\";\nimport {\n useTimeCampus,\n useTimeCampusNavigate,\n} from \"../../providers/TimeCampusProvider\";\nimport {\n useGetMeetingQuery,\n useUpdateMeetingMutation,\n useDeleteMeetingMutation,\n useRateMeetingMutation,\n useAddParticipantMutation,\n useRemoveParticipantMutation,\n useUpdateParticipantStatusMutation,\n useCreateActionItemMutation,\n useUpdateActionItemMutation,\n useCompleteActionItemMutation,\n useDeleteActionItemMutation,\n type MeetingParticipantFieldsFragment,\n type MeetingActionItemFieldsFragment,\n} from \"@/generated/wspace-operations\";\nimport {\n MeetingParticipantStatus,\n MeetingActionItemStatus,\n type UpdateMeetingInput,\n type AddMeetingParticipantInput,\n type CreateActionItemInput,\n type UpdateActionItemInput,\n} from \"@/generated/wspace-types\";\nimport { formatCurrency } from \"../../utils/helpers\";\nimport { MeetingFormModal } from \"./MeetingFormModal\";\nimport { ParticipantFormModal } from \"./ParticipantFormModal\";\nimport { ActionItemFormModal } from \"./ActionItemFormModal\";\n\nconst PARTICIPANT_STATUS_OPTIONS = [\n MeetingParticipantStatus.Pending,\n MeetingParticipantStatus.Accepted,\n MeetingParticipantStatus.Tentative,\n MeetingParticipantStatus.Declined,\n];\n\nconst ACTION_ITEM_VARIANT: Record<MeetingActionItemStatus, BadgeVariant> = {\n [MeetingActionItemStatus.Completed]: \"success\",\n [MeetingActionItemStatus.InProgress]: \"info\",\n [MeetingActionItemStatus.Pending]: \"warning\",\n [MeetingActionItemStatus.Cancelled]: \"muted\",\n};\n\n/** Deterministic 2-char monogram from an opaque participant id. */\nfunction idMonogram(id: string): string {\n return (\n id\n .replace(/[^a-zA-Z0-9]/g, \"\")\n .slice(0, 2)\n .toUpperCase() || \"?\"\n );\n}\n\nfunction prettyStatus(status: string): string {\n const lower = status.replace(/_/g, \" \").toLowerCase();\n return lower.charAt(0).toUpperCase() + lower.slice(1);\n}\n\nexport function MeetingDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { workspaceId = \"\", currentUser } = useTimeCampus();\n const navigate = useTimeCampusNavigate();\n const meetingId = id ?? \"\";\n\n const { data, loading, error, refetch } = useGetMeetingQuery({\n variables: { id: meetingId, workspaceId },\n skip: !workspaceId || !id,\n });\n\n const [updateMeeting, { loading: updating, error: updateError }] =\n useUpdateMeetingMutation();\n const [deleteMeeting, { loading: deletingMeeting }] =\n useDeleteMeetingMutation();\n const [rateMeeting, { loading: rating }] = useRateMeetingMutation();\n const [addParticipant, { loading: addingParticipant, error: addPError }] =\n useAddParticipantMutation();\n const [removeParticipant] = useRemoveParticipantMutation();\n const [updateParticipantStatus] = useUpdateParticipantStatusMutation();\n const [createActionItem, { loading: creatingItem, error: createItemError }] =\n useCreateActionItemMutation();\n const [updateActionItem, { loading: updatingItem, error: updateItemError }] =\n useUpdateActionItemMutation();\n const [completeActionItem] = useCompleteActionItemMutation();\n const [deleteActionItem] = useDeleteActionItemMutation();\n\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n const [participantOpen, setParticipantOpen] = useState(false);\n const [removingParticipant, setRemovingParticipant] =\n useState<MeetingParticipantFieldsFragment | null>(null);\n const [actionItemModal, setActionItemModal] = useState<\n | { mode: \"create\" }\n | { mode: \"edit\"; item: MeetingActionItemFieldsFragment }\n | null\n >(null);\n const [deletingItem, setDeletingItem] =\n useState<MeetingActionItemFieldsFragment | null>(null);\n\n const meeting = data?.meeting;\n\n if (loading) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <LoadingSkeleton variant=\"detail\" />\n </PageLayout>\n );\n }\n\n if (error || !meeting) {\n return (\n <PageLayout title=\"Meeting Details\" description={`Meeting ID: ${id}`}>\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n {error\n ? \"Failed to load meeting details. Please try again later.\"\n : \"Meeting not found.\"}\n </div>\n </PageLayout>\n );\n }\n\n const pendingActions = meeting.actionItems.filter(\n (a: MeetingActionItemFieldsFragment) => a.status !== \"COMPLETED\",\n ).length;\n const currentRating = meeting.rating ?? 0;\n\n const handleUpdateMeeting = async (input: UpdateMeetingInput) => {\n await updateMeeting({ variables: { id: meetingId, workspaceId, input } });\n await refetch();\n setEditOpen(false);\n };\n\n const handleDeleteMeeting = async () => {\n await deleteMeeting({ variables: { id: meetingId, workspaceId } });\n setDeleteOpen(false);\n navigate(\"/meetings\");\n };\n\n const handleRate = async (value: number) => {\n await rateMeeting({\n variables: { id: meetingId, workspaceId, rating: value },\n });\n await refetch();\n };\n\n const handleAddParticipant = async (input: AddMeetingParticipantInput) => {\n await addParticipant({\n variables: { meetingId, workspaceId, input },\n });\n await refetch();\n setParticipantOpen(false);\n };\n\n const handleRemoveParticipant = async () => {\n if (!removingParticipant) return;\n await removeParticipant({\n variables: {\n meetingId,\n participantId: removingParticipant.id,\n workspaceId,\n },\n });\n setRemovingParticipant(null);\n await refetch();\n };\n\n const handleParticipantStatus = async (\n participantId: string,\n status: MeetingParticipantStatus,\n ) => {\n await updateParticipantStatus({\n variables: { meetingId, participantId, workspaceId, status },\n });\n await refetch();\n };\n\n const handleCreateItem = async (input: CreateActionItemInput) => {\n await createActionItem({ variables: { meetingId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleUpdateItem = async (\n itemId: string,\n input: UpdateActionItemInput,\n ) => {\n await updateActionItem({ variables: { id: itemId, workspaceId, input } });\n await refetch();\n setActionItemModal(null);\n };\n\n const handleCompleteItem = async (itemId: string) => {\n await completeActionItem({ variables: { id: itemId, workspaceId } });\n await refetch();\n };\n\n const handleDeleteItem = async () => {\n if (!deletingItem) return;\n await deleteActionItem({ variables: { id: deletingItem.id, workspaceId } });\n setDeletingItem(null);\n await refetch();\n };\n\n return (\n <PageLayout\n title=\"Meeting Details\"\n description={meeting.title}\n purpose=\"The full record of one meeting — who attended, what it cost in people's time, and the action items it produced — so you can follow up on commitments and judge whether the meeting was worth it.\"\n actions={\n <>\n <button\n onClick={() => setEditOpen(true)}\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 <Pencil className=\"h-4 w-4\" />\n Edit\n </button>\n <button\n onClick={() => setDeleteOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/40\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </button>\n </>\n }\n >\n <NextSteps\n storageKey=\"timecampus-meeting-detail\"\n className=\"mb-6\"\n steps={[\n {\n id: \"all-meetings\",\n label: \"Back to all meetings\",\n description: \"Browse and schedule other meetings.\",\n onClick: () => navigate(\"/meetings\"),\n },\n {\n id: \"analytics\",\n label: \"See meeting analytics\",\n description: \"Understand your overall meeting load and cost.\",\n onClick: () => navigate(\"/meetings/analytics\"),\n },\n ]}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Duration\"\n value={`${meeting.duration ?? 0} min`}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Participants\"\n value={String(meeting.participants.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Cost\"\n value={meeting.cost != null ? formatCurrency(meeting.cost) : \"N/A\"}\n description=\"Based on participant rates\"\n />\n <StatCard\n title=\"Action Items\"\n value={String(meeting.actionItems.length)}\n description={`${pendingActions} pending`}\n />\n </div>\n\n {/* Calendar link — surfaces the wspace-calendar event auto-linked on\n create (MeetingMeta.calendarEventId). Hidden entirely when the\n meeting has no linked event (e.g. calendar-svc was unreachable at\n creation time), rather than showing a dead/broken state. */}\n {meeting.calendarEventId && (\n <Link\n to={`/calendar/event/${meeting.calendarEventId}`}\n className=\"inline-flex w-fit items-center gap-1.5 rounded-full border border-border-subtle bg-bg-surface px-3 py-1.5 text-xs font-medium text-text-secondary hover:bg-bg-sunken/50 hover:text-text-primary\"\n >\n <CalendarDays className=\"h-3.5 w-3.5\" />\n View in Calendar\n </Link>\n )}\n\n {/* Rating */}\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-1 text-sm font-semibold text-text-primary\">\n Rate this meeting\n </h3>\n <p className=\"mb-3 text-xs text-text-secondary\">\n Was it worth the time? Your rating feeds meeting analytics.\n </p>\n <div className=\"flex items-center gap-1\">\n {[1, 2, 3, 4, 5].map((value) => (\n <button\n key={value}\n onClick={() => handleRate(value)}\n disabled={rating}\n aria-label={`Rate ${value} star${value > 1 ? \"s\" : \"\"}`}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken disabled:opacity-50\"\n >\n <Star\n className={cn(\n \"h-6 w-6\",\n value <= currentRating\n ? \"fill-status-warning-text text-status-warning-text\"\n : \"text-text-secondary\",\n )}\n />\n </button>\n ))}\n {currentRating > 0 && (\n <span className=\"ml-2 text-sm text-text-secondary\">\n {currentRating} / 5\n </span>\n )}\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Participants\n </h3>\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.participants.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.participants.map(\n (p: MeetingParticipantFieldsFragment) => (\n <div\n key={p.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"flex min-w-0 items-center gap-2.5\">\n <span className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-[11px] font-semibold text-primary\">\n {idMonogram(p.userId)}\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {p.userId}\n </p>\n <p className=\"text-xs text-text-secondary\">{p.role}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <select\n value={p.status}\n onChange={(e) =>\n handleParticipantStatus(\n p.id,\n e.target.value as MeetingParticipantStatus,\n )\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary\"\n aria-label=\"Participant status\"\n >\n {PARTICIPANT_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => setRemovingParticipant(p)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Remove participant\"\n >\n <UserMinus className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No participants added\"\n description=\"Add attendees to capture who was in the room and what it cost.\"\n action={\n <button\n onClick={() => setParticipantOpen(true)}\n className=\"inline-flex items-center gap-1 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 Add participant\n </button>\n }\n />\n )}\n </div>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n Action Items\n </h3>\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 rounded-md bg-action-primary-bg px-2.5 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-3.5 w-3.5\" />\n Add\n </button>\n </div>\n {meeting.actionItems.length > 0 ? (\n <div className=\"space-y-2\">\n {meeting.actionItems.map(\n (item: MeetingActionItemFieldsFragment) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-subtle px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm text-text-primary\">\n {item.title}\n </p>\n <div className=\"mt-1\">\n <StatusBadge\n label={prettyStatus(item.status)}\n variant={ACTION_ITEM_VARIANT[item.status]}\n />\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n {item.status !== MeetingActionItemStatus.Completed && (\n <button\n onClick={() => handleCompleteItem(item.id)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-success-bg hover:text-status-success-text\"\n aria-label=\"Complete action item\"\n >\n <Check className=\"h-4 w-4\" />\n </button>\n )}\n <button\n onClick={() =>\n setActionItemModal({ mode: \"edit\", item })\n }\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit action item\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeletingItem(item)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text\"\n aria-label=\"Delete action item\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ),\n )}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No action items\"\n description=\"Capture follow-ups here so commitments from the meeting don't get lost.\"\n action={\n <button\n onClick={() => setActionItemModal({ mode: \"create\" })}\n className=\"inline-flex items-center gap-1 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 Add action item\n </button>\n }\n />\n )}\n </div>\n </div>\n\n {/* Edit meeting */}\n {editOpen && (\n <MeetingFormModal\n open\n onClose={() => setEditOpen(false)}\n workspaceId={workspaceId}\n meeting={meeting}\n submitting={updating}\n error={Boolean(updateError)}\n onUpdate={handleUpdateMeeting}\n />\n )}\n\n {/* Delete meeting */}\n <Modal\n open={deleteOpen}\n onClose={() => setDeleteOpen(false)}\n title=\"Delete meeting\"\n description=\"This permanently removes the meeting and its analytics.\"\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={handleDeleteMeeting}\n disabled={deletingMeeting}\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 {deletingMeeting ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Are you sure you want to delete{\" \"}\n <span className=\"font-medium text-text-primary\">{meeting.title}</span>\n ? This action cannot be undone.\n </p>\n </Modal>\n\n {/* Add participant */}\n {participantOpen && (\n <ParticipantFormModal\n open\n onClose={() => setParticipantOpen(false)}\n defaultUserId={currentUser?.id}\n submitting={addingParticipant}\n error={Boolean(addPError)}\n onSubmit={handleAddParticipant}\n />\n )}\n\n {/* Remove participant confirm */}\n <Modal\n open={Boolean(removingParticipant)}\n onClose={() => setRemovingParticipant(null)}\n title=\"Remove participant\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setRemovingParticipant(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={handleRemoveParticipant}\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\"\n >\n Remove\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove{\" \"}\n <span className=\"font-medium text-text-primary\">\n {removingParticipant?.userId}\n </span>{\" \"}\n from this meeting?\n </p>\n </Modal>\n\n {/* Create / edit action item */}\n {actionItemModal && (\n <ActionItemFormModal\n open\n onClose={() => setActionItemModal(null)}\n item={\n actionItemModal.mode === \"edit\" ? actionItemModal.item : undefined\n }\n submitting={\n actionItemModal.mode === \"edit\" ? updatingItem : creatingItem\n }\n error={Boolean(\n actionItemModal.mode === \"edit\" ? updateItemError : createItemError,\n )}\n onCreate={handleCreateItem}\n onUpdate={\n actionItemModal.mode === \"edit\"\n ? (input) => handleUpdateItem(actionItemModal.item.id, input)\n : undefined\n }\n />\n )}\n\n {/* Delete action item confirm */}\n <Modal\n open={Boolean(deletingItem)}\n onClose={() => setDeletingItem(null)}\n title=\"Delete action item\"\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeletingItem(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={handleDeleteItem}\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\"\n >\n Delete\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Delete action item{\" \"}\n <span className=\"font-medium text-text-primary\">\n {deletingItem?.title}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -12,7 +12,7 @@ import { ProgressMeter as f } from "../../components/charts/ProgressMeter.js";
|
|
|
12
12
|
import "../../components/index.js";
|
|
13
13
|
import { useState as p } from "react";
|
|
14
14
|
import { Archive as ie, ArrowRight as ae, Clock as oe, FolderOpen as m, Pencil as h, Trash2 as g, TrendingUp as se, Users as _ } from "lucide-react";
|
|
15
|
-
import { useParams as v } from "react-router
|
|
15
|
+
import { useParams as v } from "react-router";
|
|
16
16
|
import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
17
17
|
import { NextSteps as S } from "@burdenoff/fe-libs/ui";
|
|
18
18
|
//#region src/timecampus/pages/projects/ProjectDetailPage.tsx
|
|
@@ -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-dom\";\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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"}
|
|
@@ -6,7 +6,7 @@ import { EmptyState as l } from "../../components/EmptyState.js";
|
|
|
6
6
|
import { Modal as u } from "../../components/Modal.js";
|
|
7
7
|
import { useState as d } from "react";
|
|
8
8
|
import { ArrowDown as f, ArrowUp as ee, CheckSquare as te, Pencil as p, Plus as m, Trash2 as h } from "lucide-react";
|
|
9
|
-
import { useParams as g } from "react-router
|
|
9
|
+
import { useParams as g } from "react-router";
|
|
10
10
|
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
11
11
|
import { GlassCard as b } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
//#region src/timecampus/pages/projects/TasksPage.tsx
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TasksPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/TasksPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport {\n Plus,\n CheckSquare,\n Pencil,\n Trash2,\n ArrowUp,\n ArrowDown,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { getPriorityColor } from \"../../utils/helpers\";\nimport {\n useListTasksQuery,\n useCreateTaskMutation,\n useUpdateTaskMutation,\n useUpdateTaskStatusMutation,\n useDeleteTaskMutation,\n useReorderTasksMutation,\n type TaskFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst TASK_STATUSES = [\"todo\", \"in-progress\", \"review\", \"done\"] as const;\nconst TASK_PRIORITIES = [\"low\", \"medium\", \"high\", \"urgent\"] as const;\n\ninterface TaskFormState {\n title: string;\n description: string;\n status: string;\n priority: string;\n assigneeId: string;\n estimatedHours: string;\n dueDate: string;\n}\n\nconst emptyForm: TaskFormState = {\n title: \"\",\n description: \"\",\n status: \"todo\",\n priority: \"medium\",\n assigneeId: \"\",\n estimatedHours: \"\",\n dueDate: \"\",\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 TasksPage() {\n const { projectId } = useParams<{ projectId: string }>();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListTasksQuery({\n variables: { projectId: projectId!, pagination: { limit, offset: 0 } },\n skip: !projectId,\n notifyOnNetworkStatusChange: true,\n });\n\n const tasks = data?.tasks?.items ?? [];\n const hasMore = data?.tasks?.hasMore ?? false;\n\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<TaskFieldsFragment | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<TaskFieldsFragment | null>(\n null,\n );\n const [form, setForm] = useState<TaskFormState>(emptyForm);\n\n const [createTask, { loading: creating, error: createError }] =\n useCreateTaskMutation();\n const [updateTask, { loading: updating, error: updateError }] =\n useUpdateTaskMutation();\n const [updateTaskStatus] = useUpdateTaskStatusMutation();\n const [deleteTask, { loading: deleting, error: deleteError }] =\n useDeleteTaskMutation();\n const [reorderTasks, { loading: reordering }] = useReorderTasksMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (task: TaskFieldsFragment) => {\n setForm({\n title: task.title,\n description: task.description ?? \"\",\n status: task.status,\n priority: task.priority,\n assigneeId: task.assigneeId ?? \"\",\n estimatedHours:\n task.estimatedHours != null ? String(task.estimatedHours) : \"\",\n dueDate: task.dueDate ? task.dueDate.slice(0, 10) : \"\",\n });\n setEditTarget(task);\n };\n\n const handleCreate = async () => {\n if (!projectId || !form.title.trim()) return;\n await createTask({\n variables: {\n input: {\n projectId,\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.title.trim()) return;\n await updateTask({\n variables: {\n id: editTarget.id,\n input: {\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleStatusChange = async (\n task: TaskFieldsFragment,\n status: string,\n ) => {\n await updateTaskStatus({ variables: { id: task.id, status } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteTask({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const handleMove = async (index: number, direction: -1 | 1) => {\n if (!projectId) return;\n const target = index + direction;\n if (target < 0 || target >= tasks.length) return;\n const ordered = tasks.map((task: TaskFieldsFragment) => task.id);\n const [moved] = ordered.splice(index, 1);\n ordered.splice(target, 0, moved);\n await reorderTasks({ variables: { projectId, taskIds: ordered } });\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Title</label>\n <input\n type=\"text\"\n placeholder=\"Task title\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n placeholder=\"Task 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}>Status</label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className={labelClass}>Priority</label>\n <select\n value={form.priority}\n onChange={(e) => setForm({ ...form, priority: e.target.value })}\n className={inputClass}\n >\n {TASK_PRIORITIES.map((p) => (\n <option key={p} value={p}>\n {p}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label className={labelClass}>Assignee ID</label>\n <input\n type=\"text\"\n placeholder=\"User ID\"\n value={form.assigneeId}\n onChange={(e) => setForm({ ...form, assigneeId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Estimated Hours</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}>Due Date</label>\n <input\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm({ ...form, dueDate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout title=\"Tasks\" description={`Tasks for project ${projectId}`}>\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 tasks. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Tasks\"\n description={`Tasks for project ${projectId}`}\n purpose=\"Break a project down into the concrete pieces of work that need doing, then track hours against each one — so you can see what's done, what's blocking, and where effort is actually being spent.\"\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 Add Task\n </button>\n }\n >\n {loading && tasks.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={5} />\n ) : tasks.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No tasks yet\"\n description=\"Add a task to break this project into trackable work.\"\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 Add Task\n </button>\n }\n />\n ) : (\n <>\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {tasks.map((task: TaskFieldsFragment, index: number) => (\n <div\n key={task.id}\n className=\"flex flex-col gap-3 p-4 transition-colors hover:bg-bg-sunken/50 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex items-center gap-3\">\n <CheckSquare\n className={`h-5 w-5 shrink-0 ${task.status === \"done\" ? \"text-status-success-text\" : \"text-text-secondary\"}`}\n />\n <div>\n <p\n className={`text-sm font-medium ${task.status === \"done\" ? \"text-text-secondary line-through\" : \"text-text-primary\"}`}\n >\n {task.title}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {task.assigneeId ?? \"Unassigned\"} ·{\" \"}\n {task.trackedHours}h tracked\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getPriorityColor(task.priority)}`}\n >\n {task.priority}\n </span>\n <select\n value={task.status}\n onChange={(e) =>\n void handleStatusChange(task, e.target.value)\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs font-medium text-text-primary\"\n aria-label=\"Task status\"\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => void handleMove(index, -1)}\n disabled={index === 0 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move up\"\n >\n <ArrowUp className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => void handleMove(index, 1)}\n disabled={index === tasks.length - 1 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move down\"\n >\n <ArrowDown className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => openEdit(task)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit task\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeleteTarget(task)}\n className=\"rounded-md p-1 text-status-error-text hover:bg-status-error-bg\"\n aria-label=\"Delete task\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\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 ? \"Loading…\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title=\"Add Task\"\n description=\"Break this project into a trackable piece of work.\"\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 Cancel\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !form.title.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 Task\"}\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 Failed to create task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title=\"Edit Task\"\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 Cancel\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.title.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 task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title=\"Delete Task\"\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 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 task. 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 {deleteTarget?.title}\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":"TasksPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/TasksPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useParams } from \"react-router\";\nimport {\n Plus,\n CheckSquare,\n Pencil,\n Trash2,\n ArrowUp,\n ArrowDown,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { getPriorityColor } from \"../../utils/helpers\";\nimport {\n useListTasksQuery,\n useCreateTaskMutation,\n useUpdateTaskMutation,\n useUpdateTaskStatusMutation,\n useDeleteTaskMutation,\n useReorderTasksMutation,\n type TaskFieldsFragment,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\n\nconst TASK_STATUSES = [\"todo\", \"in-progress\", \"review\", \"done\"] as const;\nconst TASK_PRIORITIES = [\"low\", \"medium\", \"high\", \"urgent\"] as const;\n\ninterface TaskFormState {\n title: string;\n description: string;\n status: string;\n priority: string;\n assigneeId: string;\n estimatedHours: string;\n dueDate: string;\n}\n\nconst emptyForm: TaskFormState = {\n title: \"\",\n description: \"\",\n status: \"todo\",\n priority: \"medium\",\n assigneeId: \"\",\n estimatedHours: \"\",\n dueDate: \"\",\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 TasksPage() {\n const { projectId } = useParams<{ projectId: string }>();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListTasksQuery({\n variables: { projectId: projectId!, pagination: { limit, offset: 0 } },\n skip: !projectId,\n notifyOnNetworkStatusChange: true,\n });\n\n const tasks = data?.tasks?.items ?? [];\n const hasMore = data?.tasks?.hasMore ?? false;\n\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<TaskFieldsFragment | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<TaskFieldsFragment | null>(\n null,\n );\n const [form, setForm] = useState<TaskFormState>(emptyForm);\n\n const [createTask, { loading: creating, error: createError }] =\n useCreateTaskMutation();\n const [updateTask, { loading: updating, error: updateError }] =\n useUpdateTaskMutation();\n const [updateTaskStatus] = useUpdateTaskStatusMutation();\n const [deleteTask, { loading: deleting, error: deleteError }] =\n useDeleteTaskMutation();\n const [reorderTasks, { loading: reordering }] = useReorderTasksMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (task: TaskFieldsFragment) => {\n setForm({\n title: task.title,\n description: task.description ?? \"\",\n status: task.status,\n priority: task.priority,\n assigneeId: task.assigneeId ?? \"\",\n estimatedHours:\n task.estimatedHours != null ? String(task.estimatedHours) : \"\",\n dueDate: task.dueDate ? task.dueDate.slice(0, 10) : \"\",\n });\n setEditTarget(task);\n };\n\n const handleCreate = async () => {\n if (!projectId || !form.title.trim()) return;\n await createTask({\n variables: {\n input: {\n projectId,\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.title.trim()) return;\n await updateTask({\n variables: {\n id: editTarget.id,\n input: {\n title: form.title.trim(),\n description: form.description.trim() || undefined,\n status: form.status,\n priority: form.priority,\n assigneeId: form.assigneeId.trim() || undefined,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n dueDate: form.dueDate\n ? new Date(form.dueDate).toISOString()\n : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleStatusChange = async (\n task: TaskFieldsFragment,\n status: string,\n ) => {\n await updateTaskStatus({ variables: { id: task.id, status } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteTask({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const handleMove = async (index: number, direction: -1 | 1) => {\n if (!projectId) return;\n const target = index + direction;\n if (target < 0 || target >= tasks.length) return;\n const ordered = tasks.map((task: TaskFieldsFragment) => task.id);\n const [moved] = ordered.splice(index, 1);\n ordered.splice(target, 0, moved);\n await reorderTasks({ variables: { projectId, taskIds: ordered } });\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>Title</label>\n <input\n type=\"text\"\n placeholder=\"Task title\"\n value={form.title}\n onChange={(e) => setForm({ ...form, title: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Description</label>\n <textarea\n placeholder=\"Task 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}>Status</label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className={labelClass}>Priority</label>\n <select\n value={form.priority}\n onChange={(e) => setForm({ ...form, priority: e.target.value })}\n className={inputClass}\n >\n {TASK_PRIORITIES.map((p) => (\n <option key={p} value={p}>\n {p}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div>\n <label className={labelClass}>Assignee ID</label>\n <input\n type=\"text\"\n placeholder=\"User ID\"\n value={form.assigneeId}\n onChange={(e) => setForm({ ...form, assigneeId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>Estimated Hours</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}>Due Date</label>\n <input\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm({ ...form, dueDate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout title=\"Tasks\" description={`Tasks for project ${projectId}`}>\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 tasks. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Tasks\"\n description={`Tasks for project ${projectId}`}\n purpose=\"Break a project down into the concrete pieces of work that need doing, then track hours against each one — so you can see what's done, what's blocking, and where effort is actually being spent.\"\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 Add Task\n </button>\n }\n >\n {loading && tasks.length === 0 ? (\n <LoadingSkeleton variant=\"list\" count={5} />\n ) : tasks.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title=\"No tasks yet\"\n description=\"Add a task to break this project into trackable work.\"\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 Add Task\n </button>\n }\n />\n ) : (\n <>\n <GlassCard treatment=\"glass\" glow className=\"overflow-hidden\">\n <div className=\"divide-y divide-border\">\n {tasks.map((task: TaskFieldsFragment, index: number) => (\n <div\n key={task.id}\n className=\"flex flex-col gap-3 p-4 transition-colors hover:bg-bg-sunken/50 sm:flex-row sm:items-center sm:justify-between\"\n >\n <div className=\"flex items-center gap-3\">\n <CheckSquare\n className={`h-5 w-5 shrink-0 ${task.status === \"done\" ? \"text-status-success-text\" : \"text-text-secondary\"}`}\n />\n <div>\n <p\n className={`text-sm font-medium ${task.status === \"done\" ? \"text-text-secondary line-through\" : \"text-text-primary\"}`}\n >\n {task.title}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {task.assigneeId ?? \"Unassigned\"} ·{\" \"}\n {task.trackedHours}h tracked\n </p>\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getPriorityColor(task.priority)}`}\n >\n {task.priority}\n </span>\n <select\n value={task.status}\n onChange={(e) =>\n void handleStatusChange(task, e.target.value)\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs font-medium text-text-primary\"\n aria-label=\"Task status\"\n >\n {TASK_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n <button\n onClick={() => void handleMove(index, -1)}\n disabled={index === 0 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move up\"\n >\n <ArrowUp className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => void handleMove(index, 1)}\n disabled={index === tasks.length - 1 || reordering}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-40\"\n aria-label=\"Move down\"\n >\n <ArrowDown className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => openEdit(task)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label=\"Edit task\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() => setDeleteTarget(task)}\n className=\"rounded-md p-1 text-status-error-text hover:bg-status-error-bg\"\n aria-label=\"Delete task\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n ))}\n </div>\n </GlassCard>\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 ? \"Loading…\" : \"Load more\"}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title=\"Add Task\"\n description=\"Break this project into a trackable piece of work.\"\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 Cancel\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !form.title.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 Task\"}\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 Failed to create task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title=\"Edit Task\"\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 Cancel\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.title.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 task. Please try again.\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title=\"Delete Task\"\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 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 task. 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 {deleteTarget?.title}\n </span>\n ? This action cannot be undone.\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createContext as e, useCallback as t, useContext as n, useMemo as r } from "react";
|
|
2
|
-
import { MemoryRouter as i, useNavigate as a } from "react-router
|
|
2
|
+
import { MemoryRouter as i, useNavigate as a } from "react-router";
|
|
3
3
|
import { ApolloClient as o, HttpLink as s, InMemoryCache as c, from as l } from "@apollo/client";
|
|
4
4
|
import { onError as u } from "@apollo/client/link/error";
|
|
5
5
|
import { CombinedGraphQLErrors as d } from "@apollo/client/errors";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimeCampusProvider.js","names":[],"sources":["../../../src/timecampus/providers/TimeCampusProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { createContext, useContext, useMemo, useCallback } from \"react\";\nimport {\n MemoryRouter,\n useNavigate as useRouterNavigate,\n} from \"react-router
|
|
1
|
+
{"version":3,"file":"TimeCampusProvider.js","names":[],"sources":["../../../src/timecampus/providers/TimeCampusProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { createContext, useContext, useMemo, useCallback } from \"react\";\nimport {\n MemoryRouter,\n useNavigate as useRouterNavigate,\n} from \"react-router\";\nimport { ApolloClient, InMemoryCache, HttpLink, from } from \"@apollo/client\";\nimport { onError } from \"@apollo/client/link/error\";\nimport { CombinedGraphQLErrors } from \"@apollo/client/errors\";\nimport { setContext } from \"@apollo/client/link/context\";\nimport { ApolloProvider } from \"@apollo/client/react\";\nimport type { TimeCampusRootProps } from \"../types\";\n\n/**\n * Context value for TimeCampus microfrontend\n */\ntype TimeCampusContextValue = TimeCampusRootProps;\n\nconst TimeCampusContext = createContext<TimeCampusContextValue | null>(null);\n\n/**\n * Hook to access TimeCampus context\n */\nexport const useTimeCampus = () => {\n const context = useContext(TimeCampusContext);\n if (!context) {\n throw new Error(\"useTimeCampus must be used within TimeCampusProvider\");\n }\n return context;\n};\n\n/**\n * Hook for navigation within the TimeCampus microfrontend.\n * Uses the shell's navigate function if available, falling back to\n * React Router's useNavigate. Automatically prepends basePath.\n *\n * Usage:\n * const navigate = useTimeCampusNavigate();\n * navigate('/projects/123'); // navigates to /timecampus/projects/123\n */\nexport const useTimeCampusNavigate = () => {\n const { navigate: shellNavigate, basePath } = useTimeCampus();\n const routerNavigate = useRouterNavigate();\n\n return useCallback(\n (path: string) => {\n const fullPath =\n basePath && basePath !== \"/\" ? `${basePath}${path}` : path;\n if (shellNavigate) {\n shellNavigate(fullPath);\n } else {\n routerNavigate(fullPath);\n }\n },\n [shellNavigate, basePath, routerNavigate],\n );\n};\n\n/**\n * Provider component for TimeCampus microfrontend\n * Sets up routing, Apollo Client with auth error detection, and context.\n *\n * The Apollo Client is configured with:\n * - An onError link that detects UNAUTHENTICATED / FORBIDDEN responses and\n * invokes the `onAuthError` prop so the host shell can trigger logout.\n * - A dynamic auth link that reads the token per-request (not baked in at\n * client creation time), so token refreshes take effect immediately.\n */\nexport const TimeCampusProvider: FC<PropsWithChildren<TimeCampusRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n}) => {\n // Build Apollo Client with error link + dynamic auth link\n const apolloClient = useMemo(() => {\n // Error link (Apollo v4 pattern): detect authentication failures\n const errorLink = onError(({ error }) => {\n if (CombinedGraphQLErrors.is(error)) {\n for (const graphQLError of error.errors) {\n const code = graphQLError.extensions?.code;\n if (code === \"UNAUTHENTICATED\" || code === \"FORBIDDEN\") {\n console.warn(\n \"[TimeCampus] Auth error detected — notifying host shell\",\n );\n onAuthError?.();\n return; // Only fire once per batch\n }\n }\n } else if (error) {\n // Network-level errors (e.g., gateway returning 401/403 as non-JSON)\n const msg = error.message ?? \"\";\n if (\n msg.includes(\"401\") ||\n msg.includes(\"403\") ||\n msg.includes(\"Unauthorized\")\n ) {\n console.warn(\n \"[TimeCampus] Auth network error — notifying host shell\",\n );\n onAuthError?.();\n }\n }\n });\n\n // Auth link: dynamically attach the Bearer token per-request\n const authLink = setContext((_, { headers }) => ({\n headers: {\n ...headers,\n ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),\n ...(workspaceToken\n ? { \"X-Workspace-Authorization\": `Bearer ${workspaceToken}` }\n : {}),\n ...(workspaceId ? { \"x-workspace-id\": workspaceId } : {}),\n ...(tenantId ? { \"x-tenant-id\": tenantId } : {}),\n ...(currentUser?.id ? { \"x-actor-id\": currentUser.id } : {}),\n ...(currentUser?.id ? { \"x-actor-type\": \"user\" } : {}),\n },\n }));\n\n const graphqlUri = apiGatewayUrl\n ? apiGatewayUrl.endsWith(\"/graphql\")\n ? apiGatewayUrl\n : `${apiGatewayUrl}/workspaces/graphql`\n : \"/workspaces/graphql\";\n\n const httpLink = new HttpLink({\n uri: graphqlUri,\n });\n\n return new ApolloClient({\n link: from([errorLink, authLink, httpLink]),\n cache: new InMemoryCache(),\n defaultOptions: {\n watchQuery: { fetchPolicy: \"cache-and-network\", errorPolicy: \"all\" },\n query: { errorPolicy: \"all\" },\n mutate: { errorPolicy: \"all\" },\n },\n });\n }, [\n apiGatewayUrl,\n authToken,\n workspaceToken,\n currentUser?.id,\n onAuthError,\n tenantId,\n workspaceId,\n ]);\n\n const contextValue: TimeCampusContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n registerNavItems,\n registerFooterItems,\n onAuthError,\n };\n\n const isIntegrated = !!basePath;\n const content = (\n <TimeCampusContext.Provider value={contextValue}>\n {children}\n </TimeCampusContext.Provider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router and ApolloProvider\n // so we don't create another one. When standalone (no basePath), use MemoryRouter and ApolloProvider.\n return isIntegrated ? (\n content\n ) : (\n <ApolloProvider client={apolloClient}>\n <MemoryRouter>{content}</MemoryRouter>\n </ApolloProvider>\n );\n};\n"],"mappings":";;;;;;;;;AAkBA,IAAM,IAAoB,EAA6C,KAAK,EAK/D,UAAsB;CACjC,IAAM,IAAU,EAAW,EAAkB;AAC7C,KAAI,CAAC,EACH,OAAU,MAAM,uDAAuD;AAEzE,QAAO;GAYI,UAA8B;CACzC,IAAM,EAAE,UAAU,GAAe,gBAAa,GAAe,EACvD,IAAiB,GAAmB;AAE1C,QAAO,GACJ,MAAiB;EAChB,IAAM,IACJ,KAAY,MAAa,MAAM,GAAG,IAAW,MAAS;AACxD,EAAI,IACF,EAAc,EAAS,GAEvB,EAAe,EAAS;IAG5B;EAAC;EAAe;EAAU;EAAe,CAC1C;GAaU,KAAkE,EAC7E,aACA,aACA,aACA,gBACA,gBACA,aACA,kBACA,cACA,mBACA,qBACA,wBACA,qBACI;CAEJ,IAAM,IAAe,QAuDZ,IAAI,EAAa;EACtB,MAAM,EAAK;GAtDK,GAAS,EAAE,eAAY;AACvC,QAAI,EAAsB,GAAG,EAAM,CACjC,MAAK,IAAM,KAAgB,EAAM,QAAQ;KACvC,IAAM,IAAO,EAAa,YAAY;AACtC,SAAI,MAAS,qBAAqB,MAAS,aAAa;AAItD,MAHA,QAAQ,KACN,0DACD,EACD,KAAe;AACf;;;aAGK,GAAO;KAEhB,IAAM,IAAM,EAAM,WAAW;AAC7B,MACE,EAAI,SAAS,MAAM,IACnB,EAAI,SAAS,MAAM,IACnB,EAAI,SAAS,eAAe,MAE5B,QAAQ,KACN,yDACD,EACD,KAAe;;KAGnB;GAGe,GAAY,GAAG,EAAE,kBAAe,EAC/C,SAAS;IACP,GAAG;IACH,GAAI,IAAY,EAAE,eAAe,UAAU,KAAa,GAAG,EAAE;IAC7D,GAAI,IACA,EAAE,6BAA6B,UAAU,KAAkB,GAC3D,EAAE;IACN,GAAI,IAAc,EAAE,kBAAkB,GAAa,GAAG,EAAE;IACxD,GAAI,IAAW,EAAE,eAAe,GAAU,GAAG,EAAE;IAC/C,GAAI,GAAa,KAAK,EAAE,cAAc,EAAY,IAAI,GAAG,EAAE;IAC3D,GAAI,GAAa,KAAK,EAAE,gBAAgB,QAAQ,GAAG,EAAE;IACtD,EACF,EAAE;GAQc,IAAI,EAAS,EAC5B,KAPiB,IACf,EAAc,SAAS,WAAW,GAChC,IACA,GAAG,EAAc,uBACnB,uBAIH,CAAC;GAG0C,CAAC;EAC3C,OAAO,IAAI,GAAe;EAC1B,gBAAgB;GACd,YAAY;IAAE,aAAa;IAAqB,aAAa;IAAO;GACpE,OAAO,EAAE,aAAa,OAAO;GAC7B,QAAQ,EAAE,aAAa,OAAO;GAC/B;EACF,CAAC,EACD;EACD;EACA;EACA;EACA,GAAa;EACb;EACA;EACA;EACD,CAAC,EAEI,IAAuC;EAC3C;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,EAEK,IAAe,CAAC,CAAC,GACjB,IACJ,kBAAC,EAAkB,UAAnB;EAA4B,OAAO;EAChC;EAC0B,CAAA;AAK/B,QAAO,IACL,IAEA,kBAAC,GAAD;EAAgB,QAAQ;YACtB,kBAAC,GAAD,EAAA,UAAe,GAAuB,CAAA;EACvB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-timecampus",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.801.1",
|
|
4
4
|
"description": "Burdenoff TimeCampus Microfrontend - AI-powered time management and productivity platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@apollo/client": "^4.0.11",
|
|
53
|
-
"@burdenoff/fe-libs": "2026.
|
|
53
|
+
"@burdenoff/fe-libs": "2026.801.1",
|
|
54
54
|
"@dnd-kit/core": "^6.3.1",
|
|
55
55
|
"clsx": "^2.1.1",
|
|
56
56
|
"date-fns": "^4.1.0",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"lucide-react": "^0.468.0",
|
|
59
59
|
"react": "^19.0.0",
|
|
60
60
|
"react-dom": "^19.0.0",
|
|
61
|
-
"react-router
|
|
61
|
+
"react-router": "^8.3.0",
|
|
62
62
|
"recharts": "^2.15.0",
|
|
63
63
|
"tailwind-merge": "^3.4.0",
|
|
64
64
|
"zustand": "^5.0.7"
|