@burdenoff/microfe-timecampus 2026.626.2 → 2026.703.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/generated/wspace-operations.js +996 -816
  2. package/dist/generated/wspace-operations.js.map +1 -1
  3. package/dist/generated/wspace-types.js +28 -0
  4. package/dist/generated/wspace-types.js.map +1 -0
  5. package/dist/index.js +4 -4
  6. package/dist/timecampus/TimeCampusRoutes.js +16 -12
  7. package/dist/timecampus/TimeCampusRoutes.js.map +1 -1
  8. package/dist/timecampus/components/Modal.js +65 -0
  9. package/dist/timecampus/components/Modal.js.map +1 -0
  10. package/dist/timecampus/components/StatusBadge.js +28 -0
  11. package/dist/timecampus/components/StatusBadge.js.map +1 -0
  12. package/dist/timecampus/components/Toast.js +87 -0
  13. package/dist/timecampus/components/Toast.js.map +1 -0
  14. package/dist/timecampus/index.js +1 -1
  15. package/dist/timecampus/pages/HomePage.js +143 -135
  16. package/dist/timecampus/pages/HomePage.js.map +1 -1
  17. package/dist/timecampus/pages/applets/AppletStorePage.js +14 -19
  18. package/dist/timecampus/pages/applets/AppletStorePage.js.map +1 -1
  19. package/dist/timecampus/pages/applets/InstalledAppletsPage.js +49 -37
  20. package/dist/timecampus/pages/applets/InstalledAppletsPage.js.map +1 -1
  21. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +315 -87
  22. package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
  23. package/dist/timecampus/pages/channels/ChannelsPage.js +340 -46
  24. package/dist/timecampus/pages/channels/ChannelsPage.js.map +1 -1
  25. package/dist/timecampus/pages/client-portal/ClientPortalPage.js +374 -92
  26. package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
  27. package/dist/timecampus/pages/dashboard/ReportsPage.js +70 -37
  28. package/dist/timecampus/pages/dashboard/ReportsPage.js.map +1 -1
  29. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +366 -60
  30. package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js.map +1 -1
  31. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +461 -87
  32. package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
  33. package/dist/timecampus/pages/geofencing/GeofencingPage.js +314 -55
  34. package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
  35. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js +30 -27
  36. package/dist/timecampus/pages/industry-solutions/IndustrySolutionsPage.js.map +1 -1
  37. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +269 -74
  38. package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
  39. package/dist/timecampus/pages/meetings/ActionItemFormModal.js +127 -0
  40. package/dist/timecampus/pages/meetings/ActionItemFormModal.js.map +1 -0
  41. package/dist/timecampus/pages/meetings/MeetingCreatePage.js +62 -52
  42. package/dist/timecampus/pages/meetings/MeetingCreatePage.js.map +1 -1
  43. package/dist/timecampus/pages/meetings/MeetingDetailPage.js +361 -74
  44. package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
  45. package/dist/timecampus/pages/meetings/MeetingFormModal.js +156 -0
  46. package/dist/timecampus/pages/meetings/MeetingFormModal.js.map +1 -0
  47. package/dist/timecampus/pages/meetings/MeetingsPage.js +175 -74
  48. package/dist/timecampus/pages/meetings/MeetingsPage.js.map +1 -1
  49. package/dist/timecampus/pages/meetings/ParticipantFormModal.js +105 -0
  50. package/dist/timecampus/pages/meetings/ParticipantFormModal.js.map +1 -0
  51. package/dist/timecampus/pages/pomodoro/PomodoroPage.js +307 -67
  52. package/dist/timecampus/pages/pomodoro/PomodoroPage.js.map +1 -1
  53. package/dist/timecampus/pages/projects/ProjectCreatePage.js +51 -48
  54. package/dist/timecampus/pages/projects/ProjectCreatePage.js.map +1 -1
  55. package/dist/timecampus/pages/projects/ProjectDetailPage.js +248 -51
  56. package/dist/timecampus/pages/projects/ProjectDetailPage.js.map +1 -1
  57. package/dist/timecampus/pages/projects/ProjectsPage.js +354 -87
  58. package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
  59. package/dist/timecampus/pages/projects/TasksPage.js +367 -65
  60. package/dist/timecampus/pages/projects/TasksPage.js.map +1 -1
  61. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +674 -104
  62. package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
  63. package/dist/timecampus/pages/team/TeamAnalyticsPage.js +46 -34
  64. package/dist/timecampus/pages/team/TeamAnalyticsPage.js.map +1 -1
  65. package/dist/timecampus/pages/team/TeamMembersPage.js +93 -69
  66. package/dist/timecampus/pages/team/TeamMembersPage.js.map +1 -1
  67. package/dist/timecampus/pages/team/TeamPage.js +4 -3
  68. package/dist/timecampus/pages/team/TeamPage.js.map +1 -1
  69. package/dist/timecampus/pages/templates/TemplateDetailPage.js +72 -53
  70. package/dist/timecampus/pages/templates/TemplateDetailPage.js.map +1 -1
  71. package/dist/timecampus/pages/templates/TemplatesPage.js +40 -35
  72. package/dist/timecampus/pages/templates/TemplatesPage.js.map +1 -1
  73. package/dist/timecampus/pages/time/TimeEntriesPage.js +484 -97
  74. package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
  75. package/dist/timecampus/pages/time/TimeTrackingPage.js +210 -99
  76. package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
  77. package/dist/timecampus/pages/time/TimerPage.js +229 -97
  78. package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
  79. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +315 -110
  80. package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
  81. package/dist/timecampus/pages/timeline/TimelinePage.js +250 -0
  82. package/dist/timecampus/pages/timeline/TimelinePage.js.map +1 -0
  83. package/dist/timecampus/pages/widgets/WidgetDetailPage.js +158 -76
  84. package/dist/timecampus/pages/widgets/WidgetDetailPage.js.map +1 -1
  85. package/dist/timecampus/pages/widgets/WidgetsPage.js +181 -52
  86. package/dist/timecampus/pages/widgets/WidgetsPage.js.map +1 -1
  87. package/dist/timecampus/pages/workflows/WorkflowsPage.js +212 -61
  88. package/dist/timecampus/pages/workflows/WorkflowsPage.js.map +1 -1
  89. package/dist/timecampus/store/timecampusStore.js +13 -1
  90. package/dist/timecampus/store/timecampusStore.js.map +1 -1
  91. package/dist/timecampus/utils/helpers.js +20 -10
  92. package/dist/timecampus/utils/helpers.js.map +1 -1
  93. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectDetailPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectDetailPage.tsx"],"sourcesContent":["import { useParams } from \"react-router-dom\";\nimport { FolderOpen, Clock, Users, TrendingUp } from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { formatHours, formatCurrency } from \"../../utils/helpers\";\nimport {\n useGetProjectQuery,\n useGetProjectStatsQuery,\n} from \"@/generated/wspace-operations\";\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 } = 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 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 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 >\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=\"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(stats?.totalHours ?? project?.trackedHours ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Tasks\"\n value={`${stats?.completedTasks ?? 0} / ${stats?.totalTasks ?? 0}`}\n icon={<Users className=\"h-5 w-5\" />}\n description=\"completed / total\"\n />\n <StatCard\n title=\"Progress\"\n value={\n stats?.totalTasks\n ? `${Math.round(((stats?.completedTasks ?? 0) / stats.totalTasks) * 100)}%`\n : \"0%\"\n }\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Budget Used\"\n value={formatCurrency(\n stats?.budgetUsed ?? 0,\n project?.currency ?? \"USD\",\n )}\n icon={<FolderOpen className=\"h-5 w-5\" />}\n description={\n stats?.budgetRemaining != null\n ? `${formatCurrency(stats.budgetRemaining, project?.currency ?? \"USD\")} remaining`\n : undefined\n }\n />\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 <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Recent Activity\n </h3>\n <div className=\"flex h-48 items-center justify-center rounded-lg border border-dashed border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n Activity timeline will render here\n </p>\n </div>\n </div>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Time Distribution\n </h3>\n <div className=\"flex h-48 items-center justify-center rounded-lg border border-dashed border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n Chart will render here\n </p>\n </div>\n </div>\n </div>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;AAaA,SAAgB,IAAoB;CAClC,IAAM,EAAE,iBAAc,GAAkC,EAClD,IAAW,GAAuB,EAElC,EACJ,MAAM,GACN,SAAS,GACT,OAAO,MACL,EAAmB;EACrB,WAAW,EAAE,IAAI,GAAY;EAC7B,MAAM,CAAC;EACR,CAAC,EAEI,EAAE,MAAM,GAAW,SAAS,MAAiB,EAAwB;EACzE,WAAW,EAAE,IAAI,GAAY;EAC7B,MAAM,CAAC;EACR,CAAC,EAEI,IAAU,GAAa,SACvB,IAAQ,GAAW;AA0BzB,QAxBI,IAEA,kBAAC,GAAD;EACE,OAAM;EACN,aAAa,eAAe;YAE5B,kBAAC,OAAD;GAAK,WAAU;aAAoG;GAE7G,CAAA;EACK,CAAA,GAIb,KAAkB,IAElB,kBAAC,GAAD;EACE,OAAM;EACN,aAAa,eAAe;YAE5B,kBAAC,GAAD,EAAiB,SAAQ,UAAW,CAAA;EACzB,CAAA,GAKf,kBAAC,GAAD;EACE,OAAO,GAAS,QAAQ;EACxB,aAAa,GAAS,eAAe,eAAe;EACpD,SAAQ;YAHV;GAKE,kBAAC,GAAD;IACE,YAAW;IACX,WAAU;IACV,OAAO;KACL;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,eAAe,EAAS,cAAc;MACvC;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,eAAe,EAAS,aAAa,KAAa,GAAG,QAAQ;MAC9D;KACD;MACE,IAAI;MACJ,OAAO;MACP,aAAa;MACb,eAAe,EAAS,WAAW;MACpC;KACF;IACD,CAAA;GACF,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,UAAA;MACA,OAAM;MACN,OAAO,EAAY,GAAO,cAAc,GAAS,gBAAgB,EAAE;MACnE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,GAAG,GAAO,kBAAkB,EAAE,KAAK,GAAO,cAAc;MAC/D,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,aAAY;MACZ,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OACE,GAAO,aACH,GAAG,KAAK,OAAQ,GAAO,kBAAkB,KAAK,EAAM,aAAc,IAAI,CAAC,KACvE;MAEN,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;MACxC,CAAA;KACF,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EACL,GAAO,cAAc,GACrB,GAAS,YAAY,MACtB;MACD,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;MACxC,aACE,GAAO,mBAAmB,OAEtB,KAAA,IADA,GAAG,EAAe,EAAM,iBAAiB,GAAS,YAAY,MAAM,CAAC;MAG3E,CAAA;KACE;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA+C;MAExD,CAAA,EACL,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAEvC,CAAA;MACA,CAAA,CACF;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBAA+C;MAExD,CAAA,EACL,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAEvC,CAAA;MACA,CAAA,CACF;OACF;;GACK"}
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} from \"lucide-react\";\nimport { NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { formatHours, formatCurrency } from \"../../utils/helpers\";\nimport {\n useGetProjectQuery,\n useGetProjectStatsQuery,\n useUpdateProjectMutation,\n useArchiveProjectMutation,\n useDeleteProjectMutation,\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 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=\"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(stats?.totalHours ?? project?.trackedHours ?? 0)}\n icon={<Clock className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Tasks\"\n value={`${stats?.completedTasks ?? 0} / ${stats?.totalTasks ?? 0}`}\n icon={<Users className=\"h-5 w-5\" />}\n description=\"completed / total\"\n />\n <StatCard\n title=\"Progress\"\n value={\n stats?.totalTasks\n ? `${Math.round(((stats?.completedTasks ?? 0) / stats.totalTasks) * 100)}%`\n : \"0%\"\n }\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Budget Used\"\n value={formatCurrency(\n stats?.budgetUsed ?? 0,\n project?.currency ?? \"USD\",\n )}\n icon={<FolderOpen className=\"h-5 w-5\" />}\n description={\n stats?.budgetRemaining != null\n ? `${formatCurrency(stats.budgetRemaining, project?.currency ?? \"USD\")} remaining`\n : undefined\n }\n />\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 <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Recent Activity\n </h3>\n <div className=\"flex h-48 items-center justify-center rounded-lg border border-dashed border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n Activity timeline will render here\n </p>\n </div>\n </div>\n <div className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-6\">\n <h3 className=\"mb-4 text-sm font-semibold text-text-primary\">\n Time Distribution\n </h3>\n <div className=\"flex h-48 items-center justify-center rounded-lg border border-dashed border-border-subtle\">\n <p className=\"text-sm text-text-secondary\">\n Chart will render here\n </p>\n </div>\n </div>\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,99 +1,366 @@
1
- import { formatHours as e, getStatusColor as t } from "../../utils/helpers.js";
2
- import { LoadingSkeleton as n } from "../../components/LoadingSkeleton.js";
3
- import { useListProjectsQuery as r } from "../../../generated/wspace-operations.js";
4
- import { PageLayout as i } from "../../components/PageLayout.js";
5
- import { EmptyState as a } from "../../components/EmptyState.js";
6
- import { Plus as o } from "lucide-react";
7
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
8
- import { useI18n as l } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
1
+ import { useTimeCampusNavigate as e } from "../../providers/TimeCampusProvider.js";
2
+ import { formatHours as t, getStatusColor as n } from "../../utils/helpers.js";
3
+ import { LoadingSkeleton as ee } from "../../components/LoadingSkeleton.js";
4
+ import { useArchiveProjectMutation as r, useCreateProjectMutation as i, useDeleteProjectMutation as a, useListProjectsQuery as o, useUpdateProjectMutation as s } from "../../../generated/wspace-operations.js";
5
+ import { PageLayout as c } from "../../components/PageLayout.js";
6
+ import { EmptyState as te } from "../../components/EmptyState.js";
7
+ import { Modal as l } from "../../components/Modal.js";
8
+ import { useState as u } from "react";
9
+ import { Archive as d, Pencil as f, Plus as p, Trash2 as m } from "lucide-react";
10
+ import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
11
+ import { useI18n as v } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
12
  //#region src/timecampus/pages/projects/ProjectsPage.tsx
10
- function u() {
11
- let { t: u } = l(), d = (e, t) => {
12
- let n = u(e);
13
+ var y = 25, b = [
14
+ "active",
15
+ "on-hold",
16
+ "completed",
17
+ "archived"
18
+ ], x = {
19
+ name: "",
20
+ clientId: "",
21
+ description: "",
22
+ status: "active",
23
+ estimatedHours: "",
24
+ hourlyRate: ""
25
+ }, S = "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", C = "block text-sm font-medium text-text-primary";
26
+ function w() {
27
+ let { t: w } = v(), T = (e, t) => {
28
+ let n = w(e);
13
29
  return n === e ? t : n;
14
- }, { data: f, loading: p, error: m } = r({ variables: { limit: 50 } }), h = f?.projects?.items ?? [];
15
- return m ? /* @__PURE__ */ s(i, {
16
- title: d("timecampus.projects.title", "Projects"),
17
- description: d("timecampus.projects.description", "Manage and track project time"),
18
- children: /* @__PURE__ */ s("div", {
30
+ }, E = e(), [D, O] = u(y), { data: k, loading: A, error: ne, refetch: j } = o({
31
+ variables: {
32
+ limit: D,
33
+ offset: 0
34
+ },
35
+ notifyOnNetworkStatusChange: !0
36
+ }), M = k?.projects?.items ?? [], N = k?.projects?.hasMore ?? !1, [P, F] = u(!1), [I, L] = u(null), [R, z] = u(null), [B, V] = u(x), [H, { loading: U, error: W }] = i(), [G, { loading: K, error: q }] = s(), [J, { loading: Y }] = r(), [X, { loading: Z, error: re }] = a(), Q = () => {
37
+ V(x), F(!0);
38
+ }, ie = (e) => {
39
+ V({
40
+ name: e.name,
41
+ clientId: e.clientId ?? "",
42
+ description: e.description ?? "",
43
+ status: e.status,
44
+ estimatedHours: e.estimatedHours == null ? "" : String(e.estimatedHours),
45
+ hourlyRate: e.hourlyRate == null ? "" : String(e.hourlyRate)
46
+ }), L(e);
47
+ }, ae = async () => {
48
+ B.name.trim() && (await H({ variables: { input: {
49
+ name: B.name.trim(),
50
+ description: B.description.trim() || void 0,
51
+ clientId: B.clientId.trim() || void 0,
52
+ status: B.status,
53
+ estimatedHours: B.estimatedHours ? Number(B.estimatedHours) : void 0,
54
+ hourlyRate: B.hourlyRate ? Number(B.hourlyRate) : void 0
55
+ } } }), F(!1), await j());
56
+ }, oe = async () => {
57
+ !I || !B.name.trim() || (await G({ variables: {
58
+ id: I.id,
59
+ input: {
60
+ name: B.name.trim(),
61
+ description: B.description.trim() || void 0,
62
+ clientId: B.clientId.trim() || void 0,
63
+ status: B.status,
64
+ estimatedHours: B.estimatedHours ? Number(B.estimatedHours) : void 0,
65
+ hourlyRate: B.hourlyRate ? Number(B.hourlyRate) : void 0
66
+ }
67
+ } }), L(null), await j());
68
+ }, se = async (e) => {
69
+ await J({ variables: { id: e.id } }), await j();
70
+ }, ce = async () => {
71
+ R && (await X({ variables: { id: R.id } }), z(null), await j());
72
+ }, $ = /* @__PURE__ */ _("div", {
73
+ className: "space-y-4",
74
+ children: [
75
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
76
+ className: C,
77
+ children: T("timecampus.projects.form.name", "Project Name")
78
+ }), /* @__PURE__ */ g("input", {
79
+ type: "text",
80
+ placeholder: "Enter project name",
81
+ value: B.name,
82
+ onChange: (e) => V({
83
+ ...B,
84
+ name: e.target.value
85
+ }),
86
+ className: S
87
+ })] }),
88
+ /* @__PURE__ */ _("div", {
89
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
90
+ children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
91
+ className: C,
92
+ children: T("timecampus.projects.form.client", "Client")
93
+ }), /* @__PURE__ */ g("input", {
94
+ type: "text",
95
+ placeholder: "Client name",
96
+ value: B.clientId,
97
+ onChange: (e) => V({
98
+ ...B,
99
+ clientId: e.target.value
100
+ }),
101
+ className: S
102
+ })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
103
+ className: C,
104
+ children: T("timecampus.projects.form.status", "Status")
105
+ }), /* @__PURE__ */ g("select", {
106
+ value: B.status,
107
+ onChange: (e) => V({
108
+ ...B,
109
+ status: e.target.value
110
+ }),
111
+ className: S,
112
+ children: b.map((e) => /* @__PURE__ */ g("option", {
113
+ value: e,
114
+ children: e
115
+ }, e))
116
+ })] })]
117
+ }),
118
+ /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
119
+ className: C,
120
+ children: T("timecampus.projects.form.description", "Description")
121
+ }), /* @__PURE__ */ g("textarea", {
122
+ placeholder: "Project description",
123
+ rows: 3,
124
+ value: B.description,
125
+ onChange: (e) => V({
126
+ ...B,
127
+ description: e.target.value
128
+ }),
129
+ className: S
130
+ })] }),
131
+ /* @__PURE__ */ _("div", {
132
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
133
+ children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
134
+ className: C,
135
+ children: T("timecampus.projects.form.estimatedHours", "Estimated Hours")
136
+ }), /* @__PURE__ */ g("input", {
137
+ type: "number",
138
+ placeholder: "0",
139
+ value: B.estimatedHours,
140
+ onChange: (e) => V({
141
+ ...B,
142
+ estimatedHours: e.target.value
143
+ }),
144
+ className: S
145
+ })] }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("label", {
146
+ className: C,
147
+ children: T("timecampus.projects.form.hourlyRate", "Hourly Rate")
148
+ }), /* @__PURE__ */ g("input", {
149
+ type: "number",
150
+ placeholder: "0.00",
151
+ value: B.hourlyRate,
152
+ onChange: (e) => V({
153
+ ...B,
154
+ hourlyRate: e.target.value
155
+ }),
156
+ className: S
157
+ })] })]
158
+ })
159
+ ]
160
+ });
161
+ return ne ? /* @__PURE__ */ g(c, {
162
+ title: T("timecampus.projects.title", "Projects"),
163
+ description: T("timecampus.projects.description", "Manage and track project time"),
164
+ children: /* @__PURE__ */ g("div", {
19
165
  className: "rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text",
20
- children: d("timecampus.projects.error.loadFailed", "Failed to load projects. Please try again later.")
166
+ children: T("timecampus.projects.error.loadFailed", "Failed to load projects. Please try again later.")
21
167
  })
22
- }) : /* @__PURE__ */ s(i, {
23
- title: d("timecampus.projects.title", "Projects"),
24
- description: d("timecampus.projects.description", "Manage and track project time"),
25
- purpose: d("timecampus.projects.purpose", "Group your work into projects so every hour you track rolls up to the right client and budget — use it to estimate effort, watch progress against the plan, and bill accurately."),
26
- actions: /* @__PURE__ */ c("button", {
168
+ }) : /* @__PURE__ */ _(c, {
169
+ title: T("timecampus.projects.title", "Projects"),
170
+ description: T("timecampus.projects.description", "Manage and track project time"),
171
+ purpose: T("timecampus.projects.purpose", "Group your work into projects so every hour you track rolls up to the right client and budget — use it to estimate effort, watch progress against the plan, and bill accurately."),
172
+ actions: /* @__PURE__ */ _("button", {
173
+ onClick: Q,
27
174
  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",
28
- children: [/* @__PURE__ */ s(o, { className: "h-4 w-4" }), d("timecampus.projects.actions.newProject", "New Project")]
175
+ children: [/* @__PURE__ */ g(p, { className: "h-4 w-4" }), T("timecampus.projects.actions.newProject", "New Project")]
29
176
  }),
30
- children: p ? /* @__PURE__ */ s(n, {
31
- variant: "card",
32
- count: 4
33
- }) : h.length === 0 ? /* @__PURE__ */ s(a, {
34
- illustration: "empty-generic",
35
- title: d("timecampus.projects.empty.title", "No projects yet"),
36
- description: d("timecampus.projects.empty", "No projects yet. Create your first project to get started."),
37
- action: /* @__PURE__ */ c("button", {
38
- 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",
39
- children: [/* @__PURE__ */ s(o, { className: "h-4 w-4" }), d("timecampus.projects.actions.newProject", "New Project")]
177
+ children: [
178
+ A && M.length === 0 ? /* @__PURE__ */ g(ee, {
179
+ variant: "card",
180
+ count: 4
181
+ }) : M.length === 0 ? /* @__PURE__ */ g(te, {
182
+ illustration: "empty-generic",
183
+ title: T("timecampus.projects.empty.title", "No projects yet"),
184
+ description: T("timecampus.projects.empty", "No projects yet. Create your first project to get started."),
185
+ action: /* @__PURE__ */ _("button", {
186
+ onClick: Q,
187
+ 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",
188
+ children: [/* @__PURE__ */ g(p, { className: "h-4 w-4" }), T("timecampus.projects.actions.newProject", "New Project")]
189
+ })
190
+ }) : /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("div", {
191
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
192
+ children: M.map((e) => /* @__PURE__ */ _("div", {
193
+ role: "button",
194
+ tabIndex: 0,
195
+ onClick: () => E(`/projects/${e.id}`),
196
+ onKeyDown: (t) => {
197
+ (t.key === "Enter" || t.key === " ") && (t.preventDefault(), E(`/projects/${e.id}`));
198
+ },
199
+ className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer",
200
+ children: [
201
+ /* @__PURE__ */ _("div", {
202
+ className: "flex items-start justify-between",
203
+ children: [/* @__PURE__ */ _("div", {
204
+ className: "flex items-center gap-3",
205
+ children: [/* @__PURE__ */ g("div", {
206
+ className: "h-3 w-3 rounded-full",
207
+ style: { backgroundColor: e.color ?? "var(--color-accent-blue)" }
208
+ }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
209
+ className: "text-sm font-semibold text-text-primary",
210
+ children: e.name
211
+ }), /* @__PURE__ */ g("p", {
212
+ className: "text-xs text-text-secondary",
213
+ children: e.clientId ?? T("timecampus.projects.card.noClient", "No client")
214
+ })] })]
215
+ }), /* @__PURE__ */ g("span", {
216
+ className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${n(e.status)}`,
217
+ children: e.status
218
+ })]
219
+ }),
220
+ /* @__PURE__ */ _("div", {
221
+ className: "mt-4",
222
+ children: [/* @__PURE__ */ _("div", {
223
+ className: "flex items-center justify-between text-xs text-text-secondary",
224
+ children: [/* @__PURE__ */ g("span", { children: T("timecampus.projects.card.progress", "Progress") }), /* @__PURE__ */ _("span", { children: [e.estimatedHours ? Math.round(e.trackedHours / e.estimatedHours * 100) : 0, "%"] })]
225
+ }), /* @__PURE__ */ g("div", {
226
+ className: "mt-1 h-2 rounded-full bg-bg-sunken",
227
+ children: /* @__PURE__ */ g("div", {
228
+ className: "h-2 rounded-full bg-action-primary-bg",
229
+ style: { width: `${e.estimatedHours ? Math.min(Math.round(e.trackedHours / e.estimatedHours * 100), 100) : 0}%` }
230
+ })
231
+ })]
232
+ }),
233
+ /* @__PURE__ */ _("div", {
234
+ className: "mt-3 flex items-center justify-between text-xs text-text-secondary",
235
+ children: [/* @__PURE__ */ _("span", { children: [
236
+ t(e.trackedHours),
237
+ " /",
238
+ " ",
239
+ t(e.estimatedHours ?? 0)
240
+ ] }), /* @__PURE__ */ _("span", { children: [
241
+ e.tasks?.length ?? 0,
242
+ " ",
243
+ T("timecampus.projects.card.tasks", "tasks")
244
+ ] })]
245
+ }),
246
+ /* @__PURE__ */ _("div", {
247
+ className: "mt-4 flex items-center justify-end gap-1 border-t border-border-subtle pt-3",
248
+ children: [
249
+ /* @__PURE__ */ _("button", {
250
+ onClick: (t) => {
251
+ t.stopPropagation(), ie(e);
252
+ },
253
+ className: "inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary",
254
+ "aria-label": T("timecampus.projects.actions.edit", "Edit"),
255
+ children: [/* @__PURE__ */ g(f, { className: "h-3.5 w-3.5" }), T("timecampus.projects.actions.edit", "Edit")]
256
+ }),
257
+ e.status !== "archived" && /* @__PURE__ */ _("button", {
258
+ onClick: (t) => {
259
+ t.stopPropagation(), se(e);
260
+ },
261
+ disabled: Y,
262
+ className: "inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50",
263
+ "aria-label": T("timecampus.projects.actions.archive", "Archive"),
264
+ children: [/* @__PURE__ */ g(d, { className: "h-3.5 w-3.5" }), T("timecampus.projects.actions.archive", "Archive")]
265
+ }),
266
+ /* @__PURE__ */ _("button", {
267
+ onClick: (t) => {
268
+ t.stopPropagation(), z(e);
269
+ },
270
+ className: "inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-status-error-text hover:bg-status-error-bg",
271
+ "aria-label": T("timecampus.projects.actions.delete", "Delete"),
272
+ children: [/* @__PURE__ */ g(m, { className: "h-3.5 w-3.5" }), T("timecampus.projects.actions.delete", "Delete")]
273
+ })
274
+ ]
275
+ })
276
+ ]
277
+ }, e.id))
278
+ }), N && /* @__PURE__ */ g("div", {
279
+ className: "mt-6 flex justify-center",
280
+ children: /* @__PURE__ */ g("button", {
281
+ onClick: () => O((e) => e + y),
282
+ disabled: A,
283
+ 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",
284
+ children: A ? T("timecampus.common.loading", "Loading…") : T("timecampus.common.loadMore", "Load more")
285
+ })
286
+ })] }),
287
+ /* @__PURE__ */ _(l, {
288
+ open: P,
289
+ onClose: () => F(!1),
290
+ title: T("timecampus.projects.actions.newProject", "New Project"),
291
+ description: T("timecampus.projects.form.createHint", "Define a new project for time tracking."),
292
+ size: "xl",
293
+ footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
294
+ onClick: () => F(!1),
295
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
296
+ children: T("timecampus.common.cancel", "Cancel")
297
+ }), /* @__PURE__ */ g("button", {
298
+ onClick: ae,
299
+ disabled: U || !B.name.trim(),
300
+ 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",
301
+ children: U ? T("timecampus.common.creating", "Creating…") : T("timecampus.projects.actions.create", "Create Project")
302
+ })] }),
303
+ children: [W && /* @__PURE__ */ g("div", {
304
+ className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
305
+ children: T("timecampus.projects.error.createFailed", "Failed to create project. Please try again.")
306
+ }), $]
307
+ }),
308
+ /* @__PURE__ */ _(l, {
309
+ open: I !== null,
310
+ onClose: () => L(null),
311
+ title: T("timecampus.projects.actions.editProject", "Edit Project"),
312
+ size: "xl",
313
+ footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
314
+ onClick: () => L(null),
315
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
316
+ children: T("timecampus.common.cancel", "Cancel")
317
+ }), /* @__PURE__ */ g("button", {
318
+ onClick: oe,
319
+ disabled: K || !B.name.trim(),
320
+ 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",
321
+ children: K ? T("timecampus.common.saving", "Saving…") : T("timecampus.common.saveChanges", "Save Changes")
322
+ })] }),
323
+ children: [q && /* @__PURE__ */ g("div", {
324
+ className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
325
+ children: T("timecampus.projects.error.updateFailed", "Failed to update project. Please try again.")
326
+ }), $]
327
+ }),
328
+ /* @__PURE__ */ _(l, {
329
+ open: R !== null,
330
+ onClose: () => z(null),
331
+ title: T("timecampus.projects.actions.deleteProject", "Delete Project"),
332
+ size: "sm",
333
+ footer: /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("button", {
334
+ onClick: () => z(null),
335
+ className: "rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50",
336
+ children: T("timecampus.common.cancel", "Cancel")
337
+ }), /* @__PURE__ */ g("button", {
338
+ onClick: ce,
339
+ disabled: Z,
340
+ 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",
341
+ children: Z ? T("timecampus.common.deleting", "Deleting…") : T("timecampus.common.delete", "Delete")
342
+ })] }),
343
+ children: [re && /* @__PURE__ */ g("div", {
344
+ className: "mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text",
345
+ children: T("timecampus.projects.error.deleteFailed", "Failed to delete project. Please try again.")
346
+ }), /* @__PURE__ */ _("p", {
347
+ className: "text-sm text-text-secondary",
348
+ children: [
349
+ T("timecampus.projects.deleteConfirm", "Are you sure you want to delete"),
350
+ " ",
351
+ /* @__PURE__ */ g("span", {
352
+ className: "font-semibold text-text-primary",
353
+ children: R?.name
354
+ }),
355
+ "? ",
356
+ T("timecampus.common.cannotUndo", "This action cannot be undone.")
357
+ ]
358
+ })]
40
359
  })
41
- }) : /* @__PURE__ */ s("div", {
42
- className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
43
- children: h.map((n) => /* @__PURE__ */ c("div", {
44
- className: "rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer",
45
- children: [
46
- /* @__PURE__ */ c("div", {
47
- className: "flex items-start justify-between",
48
- children: [/* @__PURE__ */ c("div", {
49
- className: "flex items-center gap-3",
50
- children: [/* @__PURE__ */ s("div", {
51
- className: "h-3 w-3 rounded-full",
52
- style: { backgroundColor: n.color ?? "var(--color-accent-blue)" }
53
- }), /* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("p", {
54
- className: "text-sm font-semibold text-text-primary",
55
- children: n.name
56
- }), /* @__PURE__ */ s("p", {
57
- className: "text-xs text-text-secondary",
58
- children: n.clientId ?? d("timecampus.projects.card.noClient", "No client")
59
- })] })]
60
- }), /* @__PURE__ */ s("span", {
61
- className: `inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${t(n.status)}`,
62
- children: n.status
63
- })]
64
- }),
65
- /* @__PURE__ */ c("div", {
66
- className: "mt-4",
67
- children: [/* @__PURE__ */ c("div", {
68
- className: "flex items-center justify-between text-xs text-text-secondary",
69
- children: [/* @__PURE__ */ s("span", { children: d("timecampus.projects.card.progress", "Progress") }), /* @__PURE__ */ c("span", { children: [n.estimatedHours ? Math.round(n.trackedHours / n.estimatedHours * 100) : 0, "%"] })]
70
- }), /* @__PURE__ */ s("div", {
71
- className: "mt-1 h-2 rounded-full bg-bg-sunken",
72
- children: /* @__PURE__ */ s("div", {
73
- className: "h-2 rounded-full bg-action-primary-bg",
74
- style: { width: `${n.estimatedHours ? Math.min(Math.round(n.trackedHours / n.estimatedHours * 100), 100) : 0}%` }
75
- })
76
- })]
77
- }),
78
- /* @__PURE__ */ c("div", {
79
- className: "mt-3 flex items-center justify-between text-xs text-text-secondary",
80
- children: [/* @__PURE__ */ c("span", { children: [
81
- e(n.trackedHours),
82
- " /",
83
- " ",
84
- e(n.estimatedHours ?? 0)
85
- ] }), /* @__PURE__ */ c("span", { children: [
86
- n.tasks?.length ?? 0,
87
- " ",
88
- d("timecampus.projects.card.tasks", "tasks")
89
- ] })]
90
- })
91
- ]
92
- }, n.id))
93
- })
360
+ ]
94
361
  });
95
362
  }
96
363
  //#endregion
97
- export { u as ProjectsPage };
364
+ export { w as ProjectsPage };
98
365
 
99
366
  //# sourceMappingURL=ProjectsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectsPage.tsx"],"sourcesContent":["import { Plus } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { getStatusColor, formatHours } from \"../../utils/helpers\";\nimport {\n useListProjectsQuery,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\ntype ProjectItem = NonNullable<ListProjectsQuery[\"projects\"]>[\"items\"][number];\n\nexport function ProjectsPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const { data, loading, error } = useListProjectsQuery({\n variables: { limit: 50 },\n });\n\n const projects = data?.projects?.items ?? [];\n\n if (error) {\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.loadFailed\",\n \"Failed to load projects. Please try again later.\",\n )}\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n purpose={t(\n \"timecampus.projects.purpose\",\n \"Group your work into projects so every hour you track rolls up to the right client and budget — use it to estimate effort, watch progress against the plan, and bill accurately.\",\n )}\n actions={\n <button 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 <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n >\n {loading ? (\n <LoadingSkeleton variant=\"card\" count={4} />\n ) : projects.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title={t(\"timecampus.projects.empty.title\", \"No projects yet\")}\n description={t(\n \"timecampus.projects.empty\",\n \"No projects yet. Create your first project to get started.\",\n )}\n action={\n <button 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 <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {projects.map((project: ProjectItem) => (\n <div\n key={project.id}\n className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div\n className=\"h-3 w-3 rounded-full\"\n style={{\n backgroundColor:\n project.color ?? \"var(--color-accent-blue)\",\n }}\n />\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {project.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {project.clientId ??\n t(\"timecampus.projects.card.noClient\", \"No client\")}\n </p>\n </div>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(project.status)}`}\n >\n {project.status}\n </span>\n </div>\n <div className=\"mt-4\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {t(\"timecampus.projects.card.progress\", \"Progress\")}\n </span>\n <span>\n {project.estimatedHours\n ? Math.round(\n (project.trackedHours / project.estimatedHours) * 100,\n )\n : 0}\n %\n </span>\n </div>\n <div className=\"mt-1 h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{\n width: `${project.estimatedHours ? Math.min(Math.round((project.trackedHours / project.estimatedHours) * 100), 100) : 0}%`,\n }}\n />\n </div>\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {formatHours(project.trackedHours)} /{\" \"}\n {formatHours(project.estimatedHours ?? 0)}\n </span>\n <span>\n {project.tasks?.length ?? 0}{\" \"}\n {t(\"timecampus.projects.card.tasks\", \"tasks\")}\n </span>\n </div>\n </div>\n ))}\n </div>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;AAaA,SAAgB,IAAe;CAC7B,IAAM,EAAE,GAAG,MAAc,GAAS,EAC5B,KAAK,GAAa,MAA6B;EACnD,IAAM,IAAQ,EAAU,EAAI;AAC5B,SAAO,MAAU,IAAM,IAAW;IAE9B,EAAE,SAAM,YAAS,aAAU,EAAqB,EACpD,WAAW,EAAE,OAAO,IAAI,EACzB,CAAC,EAEI,IAAW,GAAM,UAAU,SAAS,EAAE;AAqB5C,QAnBI,IAEA,kBAAC,GAAD;EACE,OAAO,EAAE,6BAA6B,WAAW;EACjD,aAAa,EACX,mCACA,gCACD;YAED,kBAAC,OAAD;GAAK,WAAU;aACZ,EACC,wCACA,mDACD;GACG,CAAA;EACK,CAAA,GAKf,kBAAC,GAAD;EACE,OAAO,EAAE,6BAA6B,WAAW;EACjD,aAAa,EACX,mCACA,gCACD;EACD,SAAS,EACP,+BACA,mLACD;EACD,SACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC3B,EAAE,0CAA0C,cAAc,CACpD;;YAGV,IACC,kBAAC,GAAD;GAAiB,SAAQ;GAAO,OAAO;GAAK,CAAA,GAC1C,EAAS,WAAW,IACtB,kBAAC,GAAD;GACE,cAAa;GACb,OAAO,EAAE,mCAAmC,kBAAkB;GAC9D,aAAa,EACX,6BACA,6DACD;GACD,QACE,kBAAC,UAAD;IAAQ,WAAU;cAAlB,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC3B,EAAE,0CAA0C,cAAc,CACpD;;GAEX,CAAA,GAEF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAS,KAAK,MACb,kBAAC,OAAD;IAEE,WAAU;cAFZ;KAIE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD;QACE,WAAU;QACV,OAAO,EACL,iBACE,EAAQ,SAAS,4BACpB;QACD,CAAA,EACF,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAQ;QACP,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAQ,YACP,EAAE,qCAAqC,YAAY;QACnD,CAAA,CACA,EAAA,CAAA,CACF;UACN,kBAAC,QAAD;OACE,WAAW,4DAA4D,EAAe,EAAQ,OAAO;iBAEpG,EAAQ;OACJ,CAAA,CACH;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD,EAAA,UACG,EAAE,qCAAqC,WAAW,EAC9C,CAAA,EACP,kBAAC,QAAD,EAAA,UAAA,CACG,EAAQ,iBACL,KAAK,MACF,EAAQ,eAAe,EAAQ,iBAAkB,IACnD,GACD,GAAE,IAED,EAAA,CAAA,CACH;UACN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QACE,WAAU;QACV,OAAO,EACL,OAAO,GAAG,EAAQ,iBAAiB,KAAK,IAAI,KAAK,MAAO,EAAQ,eAAe,EAAQ,iBAAkB,IAAI,EAAE,IAAI,GAAG,EAAE,IACzH;QACD,CAAA;OACE,CAAA,CACF;;KACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD,EAAA,UAAA;OACG,EAAY,EAAQ,aAAa;OAAC;OAAG;OACrC,EAAY,EAAQ,kBAAkB,EAAE;OACpC,EAAA,CAAA,EACP,kBAAC,QAAD,EAAA,UAAA;OACG,EAAQ,OAAO,UAAU;OAAG;OAC5B,EAAE,kCAAkC,QAAQ;OACxC,EAAA,CAAA,CACH;;KACF;MA7DC,EAAQ,GA6DT,CACN;GACE,CAAA;EAEG,CAAA"}
1
+ {"version":3,"file":"ProjectsPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { Plus, Pencil, Trash2, Archive } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { getStatusColor, formatHours } from \"../../utils/helpers\";\nimport {\n useListProjectsQuery,\n useCreateProjectMutation,\n useUpdateProjectMutation,\n useArchiveProjectMutation,\n useDeleteProjectMutation,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\ntype ProjectItem = NonNullable<ListProjectsQuery[\"projects\"]>[\"items\"][number];\n\nconst PAGE_SIZE = 25;\n\nconst PROJECT_STATUSES = [\n \"active\",\n \"on-hold\",\n \"completed\",\n \"archived\",\n] as const;\n\ninterface ProjectFormState {\n name: string;\n clientId: string;\n description: string;\n status: string;\n estimatedHours: string;\n hourlyRate: string;\n}\n\nconst emptyForm: ProjectFormState = {\n name: \"\",\n clientId: \"\",\n description: \"\",\n status: \"active\",\n estimatedHours: \"\",\n hourlyRate: \"\",\n};\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nexport function ProjectsPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const navigate = useTimeCampusNavigate();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListProjectsQuery({\n variables: { limit, offset: 0 },\n notifyOnNetworkStatusChange: true,\n });\n\n const projects = data?.projects?.items ?? [];\n const hasMore = data?.projects?.hasMore ?? false;\n\n // Modal state\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<ProjectItem | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<ProjectItem | null>(null);\n const [form, setForm] = useState<ProjectFormState>(emptyForm);\n\n const [createProject, { loading: creating, error: createError }] =\n useCreateProjectMutation();\n const [updateProject, { loading: updating, error: updateError }] =\n useUpdateProjectMutation();\n const [archiveProject, { loading: archiving }] = useArchiveProjectMutation();\n const [deleteProject, { loading: deleting, error: deleteError }] =\n useDeleteProjectMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (project: ProjectItem) => {\n setForm({\n name: project.name,\n clientId: project.clientId ?? \"\",\n description: project.description ?? \"\",\n status: project.status,\n estimatedHours:\n project.estimatedHours != null ? String(project.estimatedHours) : \"\",\n hourlyRate: project.hourlyRate != null ? String(project.hourlyRate) : \"\",\n });\n setEditTarget(project);\n };\n\n const handleCreate = async () => {\n if (!form.name.trim()) return;\n await createProject({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.name.trim()) return;\n await updateProject({\n variables: {\n id: editTarget.id,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleArchive = async (project: ProjectItem) => {\n await archiveProject({ variables: { id: project.id } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteProject({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.name\", \"Project Name\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Enter project name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.client\", \"Client\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Client name\"\n value={form.clientId}\n onChange={(e) => setForm({ ...form, clientId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.status\", \"Status\")}\n </label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {PROJECT_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.description\", \"Description\")}\n </label>\n <textarea\n placeholder=\"Project description\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.estimatedHours\", \"Estimated Hours\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.hourlyRate\", \"Hourly Rate\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0.00\"\n value={form.hourlyRate}\n onChange={(e) => setForm({ ...form, hourlyRate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.loadFailed\",\n \"Failed to load projects. Please try again later.\",\n )}\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n purpose={t(\n \"timecampus.projects.purpose\",\n \"Group your work into projects so every hour you track rolls up to the right client and budget — use it to estimate effort, watch progress against the plan, and bill accurately.\",\n )}\n actions={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n >\n {loading && projects.length === 0 ? (\n <LoadingSkeleton variant=\"card\" count={4} />\n ) : projects.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title={t(\"timecampus.projects.empty.title\", \"No projects yet\")}\n description={t(\n \"timecampus.projects.empty\",\n \"No projects yet. Create your first project to get started.\",\n )}\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {projects.map((project: ProjectItem) => (\n <div\n key={project.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/projects/${project.id}`)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n navigate(`/projects/${project.id}`);\n }\n }}\n className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div\n className=\"h-3 w-3 rounded-full\"\n style={{\n backgroundColor:\n project.color ?? \"var(--color-accent-blue)\",\n }}\n />\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {project.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {project.clientId ??\n t(\"timecampus.projects.card.noClient\", \"No client\")}\n </p>\n </div>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(project.status)}`}\n >\n {project.status}\n </span>\n </div>\n <div className=\"mt-4\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {t(\"timecampus.projects.card.progress\", \"Progress\")}\n </span>\n <span>\n {project.estimatedHours\n ? Math.round(\n (project.trackedHours / project.estimatedHours) *\n 100,\n )\n : 0}\n %\n </span>\n </div>\n <div className=\"mt-1 h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{\n width: `${project.estimatedHours ? Math.min(Math.round((project.trackedHours / project.estimatedHours) * 100), 100) : 0}%`,\n }}\n />\n </div>\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {formatHours(project.trackedHours)} /{\" \"}\n {formatHours(project.estimatedHours ?? 0)}\n </span>\n <span>\n {project.tasks?.length ?? 0}{\" \"}\n {t(\"timecampus.projects.card.tasks\", \"tasks\")}\n </span>\n </div>\n <div className=\"mt-4 flex items-center justify-end gap-1 border-t border-border-subtle pt-3\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n openEdit(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={t(\"timecampus.projects.actions.edit\", \"Edit\")}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.edit\", \"Edit\")}\n </button>\n {project.status !== \"archived\" && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n void handleArchive(project);\n }}\n disabled={archiving}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50\"\n aria-label={t(\n \"timecampus.projects.actions.archive\",\n \"Archive\",\n )}\n >\n <Archive className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.archive\", \"Archive\")}\n </button>\n )}\n <button\n onClick={(e) => {\n e.stopPropagation();\n setDeleteTarget(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-status-error-text hover:bg-status-error-bg\"\n aria-label={t(\n \"timecampus.projects.actions.delete\",\n \"Delete\",\n )}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.delete\", \"Delete\")}\n </button>\n </div>\n </div>\n ))}\n </div>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={() => setLimit((prev) => prev + PAGE_SIZE)}\n disabled={loading}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loading\n ? t(\"timecampus.common.loading\", \"Loading…\")\n : t(\"timecampus.common.loadMore\", \"Load more\")}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title={t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n description={t(\n \"timecampus.projects.form.createHint\",\n \"Define a new project for time tracking.\",\n )}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating\n ? t(\"timecampus.common.creating\", \"Creating…\")\n : t(\"timecampus.projects.actions.create\", \"Create Project\")}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.createFailed\",\n \"Failed to create project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title={t(\"timecampus.projects.actions.editProject\", \"Edit Project\")}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {updating\n ? t(\"timecampus.common.saving\", \"Saving…\")\n : t(\"timecampus.common.saveChanges\", \"Save Changes\")}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.updateFailed\",\n \"Failed to update project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title={t(\"timecampus.projects.actions.deleteProject\", \"Delete Project\")}\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deleting\n ? t(\"timecampus.common.deleting\", \"Deleting…\")\n : t(\"timecampus.common.delete\", \"Delete\")}\n </button>\n </>\n }\n >\n {deleteError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.deleteFailed\",\n \"Failed to delete project. Please try again.\",\n )}\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n {t(\n \"timecampus.projects.deleteConfirm\",\n \"Are you sure you want to delete\",\n )}{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {deleteTarget?.name}\n </span>\n ? {t(\"timecampus.common.cannotUndo\", \"This action cannot be undone.\")}\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}