@burdenoff/microfe-timecampus 2026.830.2 → 2026.903.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/components/index.js +4 -4
- package/dist/timecampus/pages/HomePage.d.ts.map +1 -1
- package/dist/timecampus/pages/HomePage.js +142 -131
- package/dist/timecampus/pages/HomePage.js.map +1 -1
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js +7 -7
- package/dist/timecampus/pages/application-usage/ApplicationUsagePage.js.map +1 -1
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js +7 -7
- package/dist/timecampus/pages/client-portal/ClientPortalPage.js.map +1 -1
- package/dist/timecampus/pages/dashboard/ReportsPage.js +13 -13
- package/dist/timecampus/pages/email-tracking/EmailTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js +7 -7
- package/dist/timecampus/pages/expense-tracking/ExpenseTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/focus-score/FocusScorePage.js +13 -13
- package/dist/timecampus/pages/geofencing/GeofencingPage.js +135 -135
- package/dist/timecampus/pages/geofencing/GeofencingPage.js.map +1 -1
- package/dist/timecampus/pages/invoicing/InvoicingPage.js +7 -7
- package/dist/timecampus/pages/invoicing/InvoicingPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/DeadTimeRadarPage.js +8 -8
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js +4 -4
- package/dist/timecampus/pages/lifetime/FiniteLifePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js +104 -104
- package/dist/timecampus/pages/lifetime/LifeBalancePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js +7 -7
- package/dist/timecampus/pages/lifetime/LifeTimelinePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js +33 -33
- package/dist/timecampus/pages/lifetime/PersonalCorporatePage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js +80 -80
- package/dist/timecampus/pages/lifetime/RecommendationsPage.js.map +1 -1
- package/dist/timecampus/pages/lifetime/TimeWellSpentPage.js +16 -16
- package/dist/timecampus/pages/lifetime/ValuesGoalsPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js +11 -11
- package/dist/timecampus/pages/location-tracking/LocationTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js +14 -14
- package/dist/timecampus/pages/meetings/MeetingDetailPage.js.map +1 -1
- package/dist/timecampus/pages/meetings/MeetingsPage.js +12 -12
- package/dist/timecampus/pages/projects/ProjectsPage.js +5 -5
- package/dist/timecampus/pages/projects/ProjectsPage.js.map +1 -1
- package/dist/timecampus/pages/projects/TasksPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js +8 -8
- package/dist/timecampus/pages/resource-planning/ResourcePlanningPage.js.map +1 -1
- package/dist/timecampus/pages/team/TeamMembersPage.js +8 -8
- package/dist/timecampus/pages/team-activity/TeamActivityPage.js +13 -13
- package/dist/timecampus/pages/time/TimeEntriesPage.js +5 -5
- package/dist/timecampus/pages/time/TimeEntriesPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimeTrackingPage.js +138 -138
- package/dist/timecampus/pages/time/TimeTrackingPage.js.map +1 -1
- package/dist/timecampus/pages/time/TimerPage.js +14 -14
- package/dist/timecampus/pages/time/TimerPage.js.map +1 -1
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js +26 -26
- package/dist/timecampus/pages/time-approval/TimeApprovalPage.js.map +1 -1
- package/dist/timecampus/pages/timeline/TimelinePage.js +8 -8
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js +79 -79
- package/dist/timecampus/pages/work-patterns/WorkPatternsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { Plus, Pencil, Trash2, Archive } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { getStatusColor, formatHours } from \"../../utils/helpers\";\nimport {\n useListProjectsQuery,\n useCreateProjectMutation,\n useUpdateProjectMutation,\n useArchiveProjectMutation,\n useDeleteProjectMutation,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\ntype ProjectItem = NonNullable<ListProjectsQuery[\"projects\"]>[\"items\"][number];\n\nconst PAGE_SIZE = 25;\n\nconst PROJECT_STATUSES = [\n \"active\",\n \"on-hold\",\n \"completed\",\n \"archived\",\n] as const;\n\ninterface ProjectFormState {\n name: string;\n clientId: string;\n description: string;\n status: string;\n estimatedHours: string;\n hourlyRate: string;\n}\n\nconst emptyForm: ProjectFormState = {\n name: \"\",\n clientId: \"\",\n description: \"\",\n status: \"active\",\n estimatedHours: \"\",\n hourlyRate: \"\",\n};\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nexport function ProjectsPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const navigate = useTimeCampusNavigate();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListProjectsQuery({\n variables: { pagination: { limit, offset: 0 } },\n notifyOnNetworkStatusChange: true,\n });\n\n const projects = data?.projects?.items ?? [];\n const hasMore = data?.projects?.hasMore ?? false;\n\n // Modal state\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<ProjectItem | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<ProjectItem | null>(null);\n const [form, setForm] = useState<ProjectFormState>(emptyForm);\n\n const [createProject, { loading: creating, error: createError }] =\n useCreateProjectMutation();\n const [updateProject, { loading: updating, error: updateError }] =\n useUpdateProjectMutation();\n const [archiveProject, { loading: archiving }] = useArchiveProjectMutation();\n const [deleteProject, { loading: deleting, error: deleteError }] =\n useDeleteProjectMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (project: ProjectItem) => {\n setForm({\n name: project.name,\n clientId: project.clientId ?? \"\",\n description: project.description ?? \"\",\n status: project.status,\n estimatedHours:\n project.estimatedHours != null ? String(project.estimatedHours) : \"\",\n hourlyRate: project.hourlyRate != null ? String(project.hourlyRate) : \"\",\n });\n setEditTarget(project);\n };\n\n const handleCreate = async () => {\n if (!form.name.trim()) return;\n await createProject({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.name.trim()) return;\n await updateProject({\n variables: {\n id: editTarget.id,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleArchive = async (project: ProjectItem) => {\n await archiveProject({ variables: { id: project.id } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteProject({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.name\", \"Project Name\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Enter project name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.client\", \"Client\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Client name\"\n value={form.clientId}\n onChange={(e) => setForm({ ...form, clientId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.status\", \"Status\")}\n </label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {PROJECT_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.description\", \"Description\")}\n </label>\n <textarea\n placeholder=\"Project description\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.estimatedHours\", \"Estimated Hours\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.hourlyRate\", \"Hourly Rate\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0.00\"\n value={form.hourlyRate}\n onChange={(e) => setForm({ ...form, hourlyRate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.loadFailed\",\n \"Failed to load projects. Please try again later.\",\n )}\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n purpose={t(\n \"timecampus.projects.purpose\",\n \"Group your work into projects so every hour you track rolls up to the right client and budget — use it to estimate effort, watch progress against the plan, and bill accurately.\",\n )}\n actions={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n >\n {loading && projects.length === 0 ? (\n <LoadingSkeleton variant=\"card\" count={4} />\n ) : projects.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title={t(\"timecampus.projects.empty.title\", \"No projects yet\")}\n description={t(\n \"timecampus.projects.empty\",\n \"No projects yet. Create your first project to get started.\",\n )}\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {projects.map((project: ProjectItem) => (\n <div\n key={project.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/projects/${project.id}`)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n navigate(`/projects/${project.id}`);\n }\n }}\n className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div\n className=\"h-3 w-3 rounded-full\"\n style={{\n backgroundColor:\n project.color ?? \"var(--color-accent-blue)\",\n }}\n />\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {project.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {project.clientId ??\n t(\"timecampus.projects.card.noClient\", \"No client\")}\n </p>\n </div>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(project.status)}`}\n >\n {project.status}\n </span>\n </div>\n <div className=\"mt-4\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {t(\"timecampus.projects.card.progress\", \"Progress\")}\n </span>\n <span>\n {project.estimatedHours\n ? Math.round(\n (project.trackedHours / project.estimatedHours) *\n 100,\n )\n : 0}\n %\n </span>\n </div>\n <div className=\"mt-1 h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{\n width: `${project.estimatedHours ? Math.min(Math.round((project.trackedHours / project.estimatedHours) * 100), 100) : 0}%`,\n }}\n />\n </div>\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {formatHours(project.trackedHours)} /{\" \"}\n {formatHours(project.estimatedHours ?? 0)}\n </span>\n <span>\n {project.tasks?.length ?? 0}{\" \"}\n {t(\"timecampus.projects.card.tasks\", \"tasks\")}\n </span>\n </div>\n <div className=\"mt-4 flex items-center justify-end gap-1 border-t border-border-subtle pt-3\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n openEdit(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={t(\"timecampus.projects.actions.edit\", \"Edit\")}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.edit\", \"Edit\")}\n </button>\n {project.status !== \"archived\" && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n void handleArchive(project);\n }}\n disabled={archiving}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50\"\n aria-label={t(\n \"timecampus.projects.actions.archive\",\n \"Archive\",\n )}\n >\n <Archive className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.archive\", \"Archive\")}\n </button>\n )}\n <button\n onClick={(e) => {\n e.stopPropagation();\n setDeleteTarget(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-status-error-text hover:bg-status-error-bg\"\n aria-label={t(\n \"timecampus.projects.actions.delete\",\n \"Delete\",\n )}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.delete\", \"Delete\")}\n </button>\n </div>\n </div>\n ))}\n </div>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={() => setLimit((prev) => prev + PAGE_SIZE)}\n disabled={loading}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loading\n ? t(\"timecampus.common.loading\", \"Loading…\")\n : t(\"timecampus.common.loadMore\", \"Load more\")}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title={t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n description={t(\n \"timecampus.projects.form.createHint\",\n \"Define a new project for time tracking.\",\n )}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating\n ? t(\"timecampus.common.creating\", \"Creating…\")\n : t(\"timecampus.projects.actions.create\", \"Create Project\")}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.createFailed\",\n \"Failed to create project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title={t(\"timecampus.projects.actions.editProject\", \"Edit Project\")}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {updating\n ? t(\"timecampus.common.saving\", \"Saving…\")\n : t(\"timecampus.common.saveChanges\", \"Save Changes\")}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.updateFailed\",\n \"Failed to update project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title={t(\"timecampus.projects.actions.deleteProject\", \"Delete Project\")}\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deleting\n ? t(\"timecampus.common.deleting\", \"Deleting…\")\n : t(\"timecampus.common.delete\", \"Delete\")}\n </button>\n </>\n }\n >\n {deleteError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.deleteFailed\",\n \"Failed to delete project. Please try again.\",\n )}\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n {t(\n \"timecampus.projects.deleteConfirm\",\n \"Are you sure you want to delete\",\n )}{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {deleteTarget?.name}\n </span>\n ? {t(\"timecampus.common.cannotUndo\", \"This action cannot be undone.\")}\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ProjectsPage.js","names":[],"sources":["../../../../src/timecampus/pages/projects/ProjectsPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { Plus, Pencil, Trash2, Archive } from \"lucide-react\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport { useTimeCampusNavigate } from \"../../providers/TimeCampusProvider\";\nimport { getStatusColor, formatHours } from \"../../utils/helpers\";\nimport {\n useListProjectsQuery,\n useCreateProjectMutation,\n useUpdateProjectMutation,\n useArchiveProjectMutation,\n useDeleteProjectMutation,\n type ListProjectsQuery,\n} from \"@/generated/wspace-operations\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\n\ntype ProjectItem = NonNullable<ListProjectsQuery[\"projects\"]>[\"items\"][number];\n\nconst PAGE_SIZE = 25;\n\nconst PROJECT_STATUSES = [\n \"active\",\n \"on-hold\",\n \"completed\",\n \"archived\",\n] as const;\n\ninterface ProjectFormState {\n name: string;\n clientId: string;\n description: string;\n status: string;\n estimatedHours: string;\n hourlyRate: string;\n}\n\nconst emptyForm: ProjectFormState = {\n name: \"\",\n clientId: \"\",\n description: \"\",\n status: \"active\",\n estimatedHours: \"\",\n hourlyRate: \"\",\n};\n\nconst inputClass =\n \"mt-1 w-full rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary\";\nconst labelClass = \"block text-sm font-medium text-text-primary\";\n\nexport function ProjectsPage() {\n const { t: translate } = useI18n();\n const t = (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n const navigate = useTimeCampusNavigate();\n\n const [limit, setLimit] = useState(PAGE_SIZE);\n const { data, loading, error, refetch } = useListProjectsQuery({\n variables: { pagination: { limit, offset: 0 } },\n notifyOnNetworkStatusChange: true,\n });\n\n const projects = data?.projects?.items ?? [];\n const hasMore = data?.projects?.hasMore ?? false;\n\n // Modal state\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<ProjectItem | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<ProjectItem | null>(null);\n const [form, setForm] = useState<ProjectFormState>(emptyForm);\n\n const [createProject, { loading: creating, error: createError }] =\n useCreateProjectMutation();\n const [updateProject, { loading: updating, error: updateError }] =\n useUpdateProjectMutation();\n const [archiveProject, { loading: archiving }] = useArchiveProjectMutation();\n const [deleteProject, { loading: deleting, error: deleteError }] =\n useDeleteProjectMutation();\n\n const openCreate = () => {\n setForm(emptyForm);\n setCreateOpen(true);\n };\n\n const openEdit = (project: ProjectItem) => {\n setForm({\n name: project.name,\n clientId: project.clientId ?? \"\",\n description: project.description ?? \"\",\n status: project.status,\n estimatedHours:\n project.estimatedHours != null ? String(project.estimatedHours) : \"\",\n hourlyRate: project.hourlyRate != null ? String(project.hourlyRate) : \"\",\n });\n setEditTarget(project);\n };\n\n const handleCreate = async () => {\n if (!form.name.trim()) return;\n await createProject({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setCreateOpen(false);\n await refetch();\n };\n\n const handleUpdate = async () => {\n if (!editTarget || !form.name.trim()) return;\n await updateProject({\n variables: {\n id: editTarget.id,\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n clientId: form.clientId.trim() || undefined,\n status: form.status,\n estimatedHours: form.estimatedHours\n ? Number(form.estimatedHours)\n : undefined,\n hourlyRate: form.hourlyRate ? Number(form.hourlyRate) : undefined,\n },\n },\n });\n setEditTarget(null);\n await refetch();\n };\n\n const handleArchive = async (project: ProjectItem) => {\n await archiveProject({ variables: { id: project.id } });\n await refetch();\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) return;\n await deleteProject({ variables: { id: deleteTarget.id } });\n setDeleteTarget(null);\n await refetch();\n };\n\n const formFields = (\n <div className=\"space-y-4\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.name\", \"Project Name\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Enter project name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.client\", \"Client\")}\n </label>\n <input\n type=\"text\"\n placeholder=\"Client name\"\n value={form.clientId}\n onChange={(e) => setForm({ ...form, clientId: e.target.value })}\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.status\", \"Status\")}\n </label>\n <select\n value={form.status}\n onChange={(e) => setForm({ ...form, status: e.target.value })}\n className={inputClass}\n >\n {PROJECT_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.description\", \"Description\")}\n </label>\n <textarea\n placeholder=\"Project description\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n className={inputClass}\n />\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.estimatedHours\", \"Estimated Hours\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={form.estimatedHours}\n onChange={(e) =>\n setForm({ ...form, estimatedHours: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className={labelClass}>\n {t(\"timecampus.projects.form.hourlyRate\", \"Hourly Rate\")}\n </label>\n <input\n type=\"number\"\n placeholder=\"0.00\"\n value={form.hourlyRate}\n onChange={(e) => setForm({ ...form, hourlyRate: e.target.value })}\n className={inputClass}\n />\n </div>\n </div>\n </div>\n );\n\n if (error) {\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n >\n <div className=\"rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.loadFailed\",\n \"Failed to load projects. Please try again later.\",\n )}\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={t(\"timecampus.projects.title\", \"Projects\")}\n description={t(\n \"timecampus.projects.description\",\n \"Manage and track project time\",\n )}\n purpose={t(\n \"timecampus.projects.purpose\",\n \"Group your work into projects so every hour you track rolls up to the right client and budget — use it to estimate effort, watch progress against the plan, and bill accurately.\",\n )}\n actions={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n >\n {loading && projects.length === 0 ? (\n <LoadingSkeleton variant=\"card\" count={4} />\n ) : projects.length === 0 ? (\n <EmptyState\n illustration=\"empty-generic\"\n title={t(\"timecampus.projects.empty.title\", \"No projects yet\")}\n description={t(\n \"timecampus.projects.empty\",\n \"No projects yet. Create your first project to get started.\",\n )}\n action={\n <button\n onClick={openCreate}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n </button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {projects.map((project: ProjectItem) => (\n <div\n key={project.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigate(`/projects/${project.id}`)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n navigate(`/projects/${project.id}`);\n }\n }}\n className=\"rounded-xl border border-border-seam bg-gloss-card shadow-pop p-5 transition-colors hover:bg-bg-sunken/50 cursor-pointer\"\n >\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3\">\n <div\n className=\"h-3 w-3 rounded-full\"\n style={{\n backgroundColor:\n project.color ?? \"var(--color-accent-blue)\",\n }}\n />\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {project.name}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {project.clientId ??\n t(\"timecampus.projects.card.noClient\", \"No client\")}\n </p>\n </div>\n </div>\n <span\n className={`inline-flex rounded-full px-2 py-0.5 text-xs font-medium ${getStatusColor(project.status)}`}\n >\n {project.status}\n </span>\n </div>\n <div className=\"mt-4\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {t(\"timecampus.projects.card.progress\", \"Progress\")}\n </span>\n <span>\n {project.estimatedHours\n ? Math.round(\n (project.trackedHours / project.estimatedHours) *\n 100,\n )\n : 0}\n %\n </span>\n </div>\n <div className=\"mt-1 h-2 rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-action-primary-bg\"\n style={{\n width: `${project.estimatedHours ? Math.min(Math.round((project.trackedHours / project.estimatedHours) * 100), 100) : 0}%`,\n }}\n />\n </div>\n </div>\n <div className=\"mt-3 flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {formatHours(project.trackedHours)} /{\" \"}\n {formatHours(project.estimatedHours ?? 0)}\n </span>\n <span>\n {project.tasks?.length ?? 0}{\" \"}\n {t(\"timecampus.projects.card.tasks\", \"tasks\")}\n </span>\n </div>\n <div className=\"mt-4 flex items-center justify-end gap-1 border-t border-border-subtle pt-3\">\n <button\n onClick={(e) => {\n e.stopPropagation();\n openEdit(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n aria-label={t(\"timecampus.projects.actions.edit\", \"Edit\")}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.edit\", \"Edit\")}\n </button>\n {project.status !== \"archived\" && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n void handleArchive(project);\n }}\n disabled={archiving}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-text-secondary hover:bg-bg-sunken hover:text-text-primary disabled:opacity-50\"\n aria-label={t(\n \"timecampus.projects.actions.archive\",\n \"Archive\",\n )}\n >\n <Archive className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.archive\", \"Archive\")}\n </button>\n )}\n <button\n onClick={(e) => {\n e.stopPropagation();\n setDeleteTarget(project);\n }}\n className=\"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium text-status-error-text hover:bg-status-error-bg\"\n aria-label={t(\n \"timecampus.projects.actions.delete\",\n \"Delete\",\n )}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n {t(\"timecampus.projects.actions.delete\", \"Delete\")}\n </button>\n </div>\n </div>\n ))}\n </div>\n {hasMore && (\n <div className=\"mt-6 flex justify-center\">\n <button\n onClick={() => setLimit((prev) => prev + PAGE_SIZE)}\n disabled={loading}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50 disabled:opacity-50\"\n >\n {loading\n ? t(\"timecampus.common.loading\", \"Loading…\")\n : t(\"timecampus.common.loadMore\", \"Load more\")}\n </button>\n </div>\n )}\n </>\n )}\n\n {/* Create modal */}\n <Modal\n open={createOpen}\n onClose={() => setCreateOpen(false)}\n title={t(\"timecampus.projects.actions.newProject\", \"New Project\")}\n description={t(\n \"timecampus.projects.form.createHint\",\n \"Define a new project for time tracking.\",\n )}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setCreateOpen(false)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleCreate}\n disabled={creating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {creating\n ? t(\"timecampus.common.creating\", \"Creating…\")\n : t(\"timecampus.projects.actions.create\", \"Create Project\")}\n </button>\n </>\n }\n >\n {createError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.createFailed\",\n \"Failed to create project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Edit modal */}\n <Modal\n open={editTarget !== null}\n onClose={() => setEditTarget(null)}\n title={t(\"timecampus.projects.actions.editProject\", \"Edit Project\")}\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setEditTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleUpdate}\n disabled={updating || !form.name.trim()}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n {updating\n ? t(\"timecampus.common.saving\", \"Saving…\")\n : t(\"timecampus.common.saveChanges\", \"Save Changes\")}\n </button>\n </>\n }\n >\n {updateError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.updateFailed\",\n \"Failed to update project. Please try again.\",\n )}\n </div>\n )}\n {formFields}\n </Modal>\n\n {/* Delete confirm modal */}\n <Modal\n open={deleteTarget !== null}\n onClose={() => setDeleteTarget(null)}\n title={t(\"timecampus.projects.actions.deleteProject\", \"Delete Project\")}\n size=\"sm\"\n footer={\n <>\n <button\n onClick={() => setDeleteTarget(null)}\n className=\"rounded-md border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n {t(\"timecampus.common.cancel\", \"Cancel\")}\n </button>\n <button\n onClick={handleDelete}\n disabled={deleting}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50\"\n >\n {deleting\n ? t(\"timecampus.common.deleting\", \"Deleting…\")\n : t(\"timecampus.common.delete\", \"Delete\")}\n </button>\n </>\n }\n >\n {deleteError && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n {t(\n \"timecampus.projects.error.deleteFailed\",\n \"Failed to delete project. Please try again.\",\n )}\n </div>\n )}\n <p className=\"text-sm text-text-secondary\">\n {t(\n \"timecampus.projects.deleteConfirm\",\n \"Are you sure you want to delete\",\n )}{\" \"}\n <span className=\"font-semibold text-text-primary\">\n {deleteTarget?.name}\n </span>\n ? {t(\"timecampus.common.cannotUndo\", \"This action cannot be undone.\")}\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getPriorityColor as e } from "../../utils/helpers.js";
|
|
2
2
|
import { LoadingSkeleton as t } from "../../components/LoadingSkeleton.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { EmptyState as n } from "../../components/EmptyState.js";
|
|
4
|
+
import { useCreateTaskMutation as r, useDeleteTaskMutation as i, useListTasksQuery as a, useReorderTasksMutation as o, useUpdateTaskMutation as s, useUpdateTaskStatusMutation as c } from "../../../generated/wspace-operations.js";
|
|
5
|
+
import { PageLayout as l } from "../../components/PageLayout.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";
|
|
@@ -30,7 +30,7 @@ var x = 25, S = [
|
|
|
30
30
|
dueDate: ""
|
|
31
31
|
}, w = "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", T = "block text-sm font-medium text-text-primary";
|
|
32
32
|
function E() {
|
|
33
|
-
let { projectId: E } = g(), [D, re] = d(x), { data: O, loading: k, error: ie, refetch: A } =
|
|
33
|
+
let { projectId: E } = g(), [D, re] = d(x), { data: O, loading: k, error: ie, refetch: A } = a({
|
|
34
34
|
variables: {
|
|
35
35
|
projectId: E,
|
|
36
36
|
pagination: {
|
|
@@ -40,7 +40,7 @@ function E() {
|
|
|
40
40
|
},
|
|
41
41
|
skip: !E,
|
|
42
42
|
notifyOnNetworkStatusChange: !0
|
|
43
|
-
}), j = O?.tasks?.items ?? [], M = O?.tasks?.hasMore ?? !1, [N, P] = d(!1), [F, I] = d(null), [L, R] = d(null), [z, B] = d(C), [V, { loading: H, error: U }] =
|
|
43
|
+
}), j = O?.tasks?.items ?? [], M = O?.tasks?.hasMore ?? !1, [N, P] = d(!1), [F, I] = d(null), [L, R] = d(null), [z, B] = d(C), [V, { loading: H, error: U }] = r(), [W, { loading: G, error: K }] = s(), [q] = c(), [J, { loading: Y, error: ae }] = i(), [oe, { loading: X }] = o(), Z = () => {
|
|
44
44
|
B(C), P(!0);
|
|
45
45
|
}, se = (e) => {
|
|
46
46
|
B({
|
|
@@ -198,14 +198,14 @@ function E() {
|
|
|
198
198
|
})
|
|
199
199
|
]
|
|
200
200
|
});
|
|
201
|
-
return ie ? /* @__PURE__ */ v(
|
|
201
|
+
return ie ? /* @__PURE__ */ v(l, {
|
|
202
202
|
title: "Tasks",
|
|
203
203
|
description: `Tasks for project ${E}`,
|
|
204
204
|
children: /* @__PURE__ */ v("div", {
|
|
205
205
|
className: "rounded-xl border border-status-error-bg/40 bg-status-error-bg p-6 text-sm text-status-error-text",
|
|
206
206
|
children: "Failed to load tasks. Please try again later."
|
|
207
207
|
})
|
|
208
|
-
}) : /* @__PURE__ */ y(
|
|
208
|
+
}) : /* @__PURE__ */ y(l, {
|
|
209
209
|
title: "Tasks",
|
|
210
210
|
description: `Tasks for project ${E}`,
|
|
211
211
|
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.",
|
|
@@ -218,7 +218,7 @@ function E() {
|
|
|
218
218
|
k && j.length === 0 ? /* @__PURE__ */ v(t, {
|
|
219
219
|
variant: "list",
|
|
220
220
|
count: 5
|
|
221
|
-
}) : j.length === 0 ? /* @__PURE__ */ v(
|
|
221
|
+
}) : j.length === 0 ? /* @__PURE__ */ v(n, {
|
|
222
222
|
illustration: "empty-generic",
|
|
223
223
|
title: "No tasks yet",
|
|
224
224
|
description: "Add a task to break this project into trackable work.",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { formatHours as e, getInitials as t } from "../../utils/helpers.js";
|
|
2
2
|
import { StatCard as n } from "../../components/StatCard.js";
|
|
3
3
|
import { LoadingSkeleton as r } from "../../components/LoadingSkeleton.js";
|
|
4
|
-
import {
|
|
4
|
+
import { EmptyState as i } from "../../components/EmptyState.js";
|
|
5
|
+
import { useBulkCreateAllocationsMutation as ee, useCreateAllocationMutation as te, useCreateResourcePlanMutation as ne, useDeleteAllocationMutation as re, useDeleteResourcePlanMutation as ie, useGetCapacityOverviewQuery as ae, useListResourcePlansQuery as oe, useUpdateAllocationMutation as se, useUpdateResourcePlanMutation as ce } from "../../../generated/wspace-operations.js";
|
|
5
6
|
import { PageLayout as a } from "../../components/PageLayout.js";
|
|
6
|
-
import { StatusBadge as
|
|
7
|
-
import { EmptyState as le } from "../../components/EmptyState.js";
|
|
7
|
+
import { StatusBadge as le } from "../../components/StatusBadge.js";
|
|
8
8
|
import { Modal as o } from "../../components/Modal.js";
|
|
9
9
|
import { ChartCard as s } from "../../components/ChartCard.js";
|
|
10
10
|
import { DonutChart as ue } from "../../components/charts/DonutChart.js";
|
|
@@ -33,13 +33,13 @@ var ye = 25, h = "mt-1 w-full rounded-md border border-border-subtle bg-bg-surfa
|
|
|
33
33
|
notes: ""
|
|
34
34
|
});
|
|
35
35
|
function b() {
|
|
36
|
-
let b = /* @__PURE__ */ new Date(), be = new Date(b.getFullYear(), b.getMonth(), b.getDate()).toISOString(), xe = new Date(b.getFullYear(), b.getMonth(), b.getDate() + 7).toISOString(), { data: x, loading: Se, error: Ce, refetch: we, fetchMore: Te } =
|
|
36
|
+
let b = /* @__PURE__ */ new Date(), be = new Date(b.getFullYear(), b.getMonth(), b.getDate()).toISOString(), xe = new Date(b.getFullYear(), b.getMonth(), b.getDate() + 7).toISOString(), { data: x, loading: Se, error: Ce, refetch: we, fetchMore: Te } = oe({ variables: { pagination: {
|
|
37
37
|
limit: ye,
|
|
38
38
|
offset: 0
|
|
39
|
-
} } }), { data: Ee, refetch: De } =
|
|
39
|
+
} } }), { data: Ee, refetch: De } = ae({ variables: {
|
|
40
40
|
startDate: be,
|
|
41
41
|
endDate: xe
|
|
42
|
-
} }), [Oe, { loading: S, error: ke }] =
|
|
42
|
+
} }), [Oe, { loading: S, error: ke }] = ne(), [Ae, { loading: C, error: je }] = ce(), [Me, { loading: w }] = ie(), [Ne, { loading: T }] = te(), [Pe, { loading: E }] = ee(), [Fe, { loading: D, error: Ie }] = se(), [Le, { loading: O }] = re(), [Re, k] = c(!1), [A, ze] = c(null), [j, M] = c(v()), [N, P] = c(null), [F, I] = c(null), [L, R] = c([]), [Be, z] = c(!1), [B, V] = c(null), [H, U] = c(null), [W, G] = c(null), [K, q] = c(!1), J = x?.resourcePlans?.items ?? [], Ve = x?.resourcePlans?.hasMore ?? !1, Y = Ee?.capacityOverview, X = async () => {
|
|
43
43
|
await Promise.all([we(), De()]);
|
|
44
44
|
}, He = () => {
|
|
45
45
|
ze(null), M(v()), k(!0);
|
|
@@ -256,7 +256,7 @@ function b() {
|
|
|
256
256
|
})]
|
|
257
257
|
})]
|
|
258
258
|
}),
|
|
259
|
-
J.length === 0 ? /* @__PURE__ */ p(
|
|
259
|
+
J.length === 0 ? /* @__PURE__ */ p(i, {
|
|
260
260
|
illustration: "empty-people",
|
|
261
261
|
title: "No resource plans yet",
|
|
262
262
|
description: "Create a resource plan to start allocating team members and balancing workload across the week.",
|
|
@@ -276,7 +276,7 @@ function b() {
|
|
|
276
276
|
children: [/* @__PURE__ */ p("p", {
|
|
277
277
|
className: "text-sm font-semibold text-text-primary",
|
|
278
278
|
children: n.name
|
|
279
|
-
}), /* @__PURE__ */ p(
|
|
279
|
+
}), /* @__PURE__ */ p(le, {
|
|
280
280
|
label: n.status,
|
|
281
281
|
status: n.status
|
|
282
282
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourcePlanningPage.js","names":[],"sources":["../../../../src/timecampus/pages/resource-planning/ResourcePlanningPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n CalendarRange,\n Users,\n TrendingUp,\n Plus,\n Pencil,\n Trash2,\n UserPlus,\n AlertTriangle,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n ChartCard,\n DonutChart,\n RadialGauge,\n ProgressMeter,\n StatusBadge,\n type MeterTone,\n} from \"../../components\";\nimport { getInitials, formatHours } from \"../../utils/helpers\";\nimport {\n useListResourcePlansQuery,\n useGetCapacityOverviewQuery,\n useCreateResourcePlanMutation,\n useUpdateResourcePlanMutation,\n useDeleteResourcePlanMutation,\n useCreateAllocationMutation,\n useUpdateAllocationMutation,\n useDeleteAllocationMutation,\n useBulkCreateAllocationsMutation,\n type ResourcePlanFieldsFragment,\n type ResourceAllocationFieldsFragment,\n type ListResourcePlansQuery,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\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\";\n\nconst toDateInput = (iso: string) => iso.slice(0, 10);\nconst toIso = (dateStr: string) => new Date(dateStr).toISOString();\n\ninterface PlanFormState {\n name: string;\n description: string;\n startDate: string;\n endDate: string;\n status: string;\n}\n\ninterface AllocationRow {\n userId: string;\n allocatedHours: string;\n startDate: string;\n endDate: string;\n notes: string;\n}\n\ninterface AllocationEditState {\n userId: string;\n allocatedHours: string;\n actualHours: string;\n startDate: string;\n endDate: string;\n notes: string;\n}\n\nconst emptyPlanForm = (): PlanFormState => {\n const now = new Date();\n const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 7);\n return {\n name: \"\",\n description: \"\",\n startDate: toDateInput(start.toISOString()),\n endDate: toDateInput(end.toISOString()),\n status: \"active\",\n };\n};\n\nconst emptyAllocationRow = (\n plan?: ResourcePlanFieldsFragment,\n): AllocationRow => ({\n userId: \"\",\n allocatedHours: \"\",\n startDate: plan ? toDateInput(plan.startDate) : \"\",\n endDate: plan ? toDateInput(plan.endDate) : \"\",\n notes: \"\",\n});\n\nexport function ResourcePlanningPage() {\n const now = new Date();\n const startDate = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate(),\n ).toISOString();\n const endDate = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate() + 7,\n ).toISOString();\n\n const {\n data: plansData,\n loading: plansLoading,\n error: plansError,\n refetch: refetchPlans,\n fetchMore,\n } = useListResourcePlansQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n });\n const { data: capacityData, refetch: refetchCapacity } =\n useGetCapacityOverviewQuery({ variables: { startDate, endDate } });\n\n const [createPlan, { loading: creatingPlan, error: createPlanError }] =\n useCreateResourcePlanMutation();\n const [updatePlan, { loading: updatingPlan, error: updatePlanError }] =\n useUpdateResourcePlanMutation();\n const [deletePlan, { loading: deletingPlan }] =\n useDeleteResourcePlanMutation();\n const [createAllocation, { loading: creatingAlloc }] =\n useCreateAllocationMutation();\n const [bulkCreateAllocations, { loading: bulkCreating }] =\n useBulkCreateAllocationsMutation();\n const [\n updateAllocation,\n { loading: updatingAlloc, error: updateAllocError },\n ] = useUpdateAllocationMutation();\n const [deleteAllocation, { loading: deletingAlloc }] =\n useDeleteAllocationMutation();\n\n const [planModalOpen, setPlanModalOpen] = useState(false);\n const [editingPlan, setEditingPlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n const [planForm, setPlanForm] = useState<PlanFormState>(emptyPlanForm());\n const [confirmDeletePlan, setConfirmDeletePlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n\n const [allocModalPlan, setAllocModalPlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n const [allocRows, setAllocRows] = useState<AllocationRow[]>([]);\n const [allocError, setAllocError] = useState(false);\n\n const [editingAlloc, setEditingAlloc] =\n useState<ResourceAllocationFieldsFragment | null>(null);\n const [allocEditForm, setAllocEditForm] =\n useState<AllocationEditState | null>(null);\n const [confirmDeleteAlloc, setConfirmDeleteAlloc] =\n useState<ResourceAllocationFieldsFragment | null>(null);\n\n const [loadingMore, setLoadingMore] = useState(false);\n\n const plans = plansData?.resourcePlans?.items ?? [];\n const hasMore = plansData?.resourcePlans?.hasMore ?? false;\n const capacity = capacityData?.capacityOverview;\n\n const refetchAll = async () => {\n await Promise.all([refetchPlans(), refetchCapacity()]);\n };\n\n // -- Plan handlers --------------------------------------------------------\n const openCreatePlan = () => {\n setEditingPlan(null);\n setPlanForm(emptyPlanForm());\n setPlanModalOpen(true);\n };\n\n const openEditPlan = (plan: ResourcePlanFieldsFragment) => {\n setEditingPlan(plan);\n setPlanForm({\n name: plan.name,\n description: plan.description ?? \"\",\n startDate: toDateInput(plan.startDate),\n endDate: toDateInput(plan.endDate),\n status: plan.status,\n });\n setPlanModalOpen(true);\n };\n\n const handleSubmitPlan = async () => {\n if (!planForm.name.trim()) return;\n if (editingPlan) {\n await updatePlan({\n variables: {\n id: editingPlan.id,\n input: {\n name: planForm.name.trim(),\n description: planForm.description.trim() || undefined,\n startDate: toIso(planForm.startDate),\n endDate: toIso(planForm.endDate),\n status: planForm.status,\n },\n },\n });\n } else {\n await createPlan({\n variables: {\n input: {\n name: planForm.name.trim(),\n description: planForm.description.trim() || undefined,\n startDate: toIso(planForm.startDate),\n endDate: toIso(planForm.endDate),\n },\n },\n });\n }\n setPlanModalOpen(false);\n await refetchAll();\n };\n\n const handleDeletePlan = async () => {\n if (!confirmDeletePlan) return;\n await deletePlan({ variables: { id: confirmDeletePlan.id } });\n setConfirmDeletePlan(null);\n await refetchAll();\n };\n\n // -- Allocation handlers --------------------------------------------------\n const openAddAllocation = (plan: ResourcePlanFieldsFragment) => {\n setAllocModalPlan(plan);\n setAllocRows([emptyAllocationRow(plan)]);\n setAllocError(false);\n };\n\n const updateAllocRow = (index: number, patch: Partial<AllocationRow>) => {\n setAllocRows((rows) =>\n rows.map((r, i) => (i === index ? { ...r, ...patch } : r)),\n );\n };\n\n const addAllocRow = () => {\n setAllocRows((rows) => [\n ...rows,\n emptyAllocationRow(allocModalPlan ?? undefined),\n ]);\n };\n\n const removeAllocRow = (index: number) => {\n setAllocRows((rows) => rows.filter((_, i) => i !== index));\n };\n\n const allocRowsValid = allocRows.every(\n (r) =>\n r.userId.trim() !== \"\" &&\n r.allocatedHours.trim() !== \"\" &&\n r.startDate !== \"\" &&\n r.endDate !== \"\",\n );\n\n const handleSubmitAllocations = async () => {\n if (!allocModalPlan || !allocRowsValid || allocRows.length === 0) return;\n setAllocError(false);\n try {\n if (allocRows.length === 1) {\n const r = allocRows[0];\n await createAllocation({\n variables: {\n input: {\n resourcePlanId: allocModalPlan.id,\n userId: r.userId.trim(),\n allocatedHours: Number(r.allocatedHours),\n startDate: toIso(r.startDate),\n endDate: toIso(r.endDate),\n notes: r.notes.trim() || undefined,\n },\n },\n });\n } else {\n await bulkCreateAllocations({\n variables: {\n planId: allocModalPlan.id,\n allocations: allocRows.map((r) => ({\n userId: r.userId.trim(),\n allocatedHours: Number(r.allocatedHours),\n startDate: toIso(r.startDate),\n endDate: toIso(r.endDate),\n notes: r.notes.trim() || undefined,\n })),\n },\n });\n }\n setAllocModalPlan(null);\n await refetchAll();\n } catch {\n setAllocError(true);\n }\n };\n\n const openEditAllocation = (alloc: ResourceAllocationFieldsFragment) => {\n setEditingAlloc(alloc);\n setAllocEditForm({\n userId: alloc.userId,\n allocatedHours: String(alloc.allocatedHours),\n actualHours: String(alloc.actualHours),\n startDate: toDateInput(alloc.startDate),\n endDate: toDateInput(alloc.endDate),\n notes: alloc.notes ?? \"\",\n });\n };\n\n const handleSubmitAllocEdit = async () => {\n if (!editingAlloc || !allocEditForm) return;\n await updateAllocation({\n variables: {\n id: editingAlloc.id,\n input: {\n userId: allocEditForm.userId.trim(),\n allocatedHours: Number(allocEditForm.allocatedHours),\n actualHours: Number(allocEditForm.actualHours),\n startDate: toIso(allocEditForm.startDate),\n endDate: toIso(allocEditForm.endDate),\n notes: allocEditForm.notes.trim() || undefined,\n },\n },\n });\n setEditingAlloc(null);\n setAllocEditForm(null);\n await refetchAll();\n };\n\n const handleDeleteAllocation = async () => {\n if (!confirmDeleteAlloc) return;\n await deleteAllocation({ variables: { id: confirmDeleteAlloc.id } });\n setConfirmDeleteAlloc(null);\n await refetchAll();\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: plans.length } },\n updateQuery: (\n prev: ListResourcePlansQuery,\n { fetchMoreResult }: { fetchMoreResult?: ListResourcePlansQuery },\n ) => {\n if (!fetchMoreResult) return prev;\n return {\n resourcePlans: {\n ...fetchMoreResult.resourcePlans,\n items: [\n ...prev.resourcePlans.items,\n ...fetchMoreResult.resourcePlans.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const newPlanButton = (\n <button\n onClick={openCreatePlan}\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 New Resource Plan\n </button>\n );\n\n if (plansLoading) {\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n >\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (plansError) {\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load resource planning data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const utilizationRate = capacity?.utilizationRate ?? 0;\n const overallocatedUsers = capacity?.overallocatedUsers ?? [];\n const overallocatedCount = overallocatedUsers.length;\n const availableHours = capacity?.availableCapacity ?? 0;\n const allocatedHours = capacity?.allocatedCapacity ?? 0;\n const totalHours = capacity?.totalCapacity ?? 0;\n const hasCapacity =\n !!capacity &&\n (totalHours > 0 || allocatedHours > 0 || overallocatedCount > 0);\n\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n purpose=\"Match the work ahead to the people you have — see who's over- or under-booked and how much capacity is free this week — so you can commit to deadlines you can actually hit without burning anyone out.\"\n actions={newPlanButton}\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Team Utilization\"\n value={`${Math.round(utilizationRate)}%`}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Over-allocated\"\n value={String(overallocatedCount)}\n icon={<Users className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-error-bg text-status-error-text\"\n />\n <StatCard\n title=\"Active Plans\"\n value={String(plans.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Available Capacity\"\n value={`${Math.round(availableHours)}h`}\n icon={<CalendarRange className=\"h-5 w-5\" />}\n description=\"This week\"\n />\n </div>\n\n {hasCapacity && (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Capacity split\"\n description=\"Allocated vs available hours this week\"\n >\n <DonutChart\n data={[\n { name: \"Allocated\", value: allocatedHours },\n { name: \"Available\", value: availableHours },\n ]}\n centerLabel=\"Total\"\n centerValue={`${Math.round(totalHours)}h`}\n valueFormat={(v) => `${Math.round(v)}h`}\n emptyLabel=\"No capacity data yet\"\n />\n </ChartCard>\n <ChartCard\n title=\"Utilization & over-allocation\"\n description=\"Team utilization and who is over-booked this week\"\n >\n <RadialGauge\n value={Math.round(utilizationRate)}\n max={100}\n label=\"Utilization\"\n sublabel=\"this week\"\n format={(v) => `${Math.round(v)}%`}\n />\n <div className=\"mt-4\">\n <p className=\"mb-2 text-xs font-medium text-text-secondary\">\n Over-allocated ({overallocatedCount})\n </p>\n {overallocatedCount === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No one is over-allocated this week.\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {overallocatedUsers.map((userId: string) => (\n <span\n key={userId}\n className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg px-2.5 py-0.5 text-xs font-medium text-status-warning-text\"\n >\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {userId}\n </span>\n ))}\n </div>\n )}\n </div>\n </ChartCard>\n </div>\n )}\n\n {plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No resource plans yet\"\n description=\"Create a resource plan to start allocating team members and balancing workload across the week.\"\n action={newPlanButton}\n />\n ) : (\n <div className=\"space-y-6\">\n {plans.map((plan: ResourcePlanFieldsFragment) => (\n <GlassCard\n key={plan.id}\n treatment=\"glass\"\n className=\"overflow-hidden\"\n >\n <div className=\"flex flex-col gap-3 border-b border-border-seam p-5 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {plan.name}\n </p>\n <StatusBadge label={plan.status} status={plan.status} />\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {plan.startDate.slice(0, 10)} to{\" \"}\n {plan.endDate.slice(0, 10)} ·{\" \"}\n {plan.allocations.length}{\" \"}\n {plan.allocations.length === 1 ? \"person\" : \"people\"}\n </p>\n <div className=\"mt-3 max-w-sm\">\n <ProgressMeter\n size=\"sm\"\n label=\"Logged vs allocated\"\n value={plan.totalActualHours}\n max={\n plan.totalAllocatedHours > 0\n ? plan.totalAllocatedHours\n : Math.max(plan.totalActualHours, 1)\n }\n format={(v) => formatHours(v)}\n />\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n onClick={() => openAddAllocation(plan)}\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 <UserPlus className=\"h-3.5 w-3.5\" />\n Add Allocation\n </button>\n <button\n onClick={() => openEditPlan(plan)}\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 <Pencil className=\"h-3.5 w-3.5\" />\n Edit\n </button>\n <button\n onClick={() => setConfirmDeletePlan(plan)}\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-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n Delete\n </button>\n </div>\n </div>\n\n {plan.allocations.length === 0 ? (\n <div className=\"p-5 text-sm text-text-secondary\">\n No allocations yet. Use “Add Allocation” to\n assign team members.\n </div>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs text-text-secondary\">\n <th className=\"whitespace-nowrap px-5 py-2.5 font-medium\">\n Member\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 font-medium\">\n Window\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 text-right font-medium\">\n Allocated\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 text-right font-medium\">\n Actual\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 font-medium\">\n Utilization\n </th>\n <th className=\"whitespace-nowrap px-5 py-2.5 text-right font-medium\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {plan.allocations.map(\n (alloc: ResourceAllocationFieldsFragment) => {\n const utilization =\n alloc.utilizationRate ??\n (alloc.allocatedHours > 0\n ? Math.round(\n (alloc.actualHours / alloc.allocatedHours) *\n 100,\n )\n : 0);\n const tone: MeterTone =\n utilization > 100\n ? \"danger\"\n : utilization < 70\n ? \"warning\"\n : \"success\";\n const toneText =\n tone === \"danger\"\n ? \"text-status-error-text\"\n : tone === \"warning\"\n ? \"text-status-warning-text\"\n : \"text-status-success-text\";\n return (\n <tr\n key={alloc.id}\n className=\"transition-colors hover:bg-bg-sunken/50\"\n >\n <td className=\"whitespace-nowrap px-5 py-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(alloc.userId)}\n </div>\n <span className=\"font-medium text-text-primary\">\n {alloc.userId}\n </span>\n </div>\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-text-secondary\">\n {alloc.startDate.slice(0, 10)} to{\" \"}\n {alloc.endDate.slice(0, 10)}\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-right font-medium text-text-primary\">\n {formatHours(alloc.allocatedHours)}\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-right text-text-secondary\">\n {formatHours(alloc.actualHours)}\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex w-40 items-center gap-2\">\n <ProgressMeter\n size=\"sm\"\n value={utilization}\n max={100}\n tone={tone}\n showValue={false}\n className=\"flex-1\"\n />\n <span\n className={`shrink-0 text-xs font-medium tabular-nums ${toneText}`}\n >\n {utilization}%\n </span>\n </div>\n </td>\n <td className=\"whitespace-nowrap px-5 py-3\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n onClick={() => openEditAllocation(alloc)}\n aria-label=\"Edit allocation\"\n className=\"rounded-md border border-border-subtle p-1.5 text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() =>\n setConfirmDeleteAlloc(alloc)\n }\n aria-label=\"Delete allocation\"\n className=\"rounded-md border border-border-subtle p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </td>\n </tr>\n );\n },\n )}\n </tbody>\n </table>\n </div>\n )}\n </GlassCard>\n ))}\n {hasMore && (\n <div className=\"flex justify-center\">\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\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 {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Plan create/edit modal */}\n <Modal\n open={planModalOpen}\n onClose={() => setPlanModalOpen(false)}\n title={editingPlan ? \"Edit Resource Plan\" : \"New Resource Plan\"}\n description=\"Set the window this plan covers, then allocate people to it.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setPlanModalOpen(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={handleSubmitPlan}\n disabled={creatingPlan || updatingPlan || !planForm.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 {creatingPlan || updatingPlan\n ? \"Saving...\"\n : editingPlan\n ? \"Save Changes\"\n : \"Create Plan\"}\n </button>\n </>\n }\n >\n {(createPlanError || updatePlanError) && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to save resource plan. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Plan Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Q3 Delivery\"\n value={planForm.name}\n onChange={(e) =>\n setPlanForm({ ...planForm, name: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description (optional)\n </label>\n <textarea\n rows={2}\n placeholder=\"What this plan covers\"\n value={planForm.description}\n onChange={(e) =>\n setPlanForm({ ...planForm, 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=\"block text-sm font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={planForm.startDate}\n onChange={(e) =>\n setPlanForm({ ...planForm, startDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={planForm.endDate}\n onChange={(e) =>\n setPlanForm({ ...planForm, endDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n {editingPlan && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Status\n </label>\n <select\n value={planForm.status}\n onChange={(e) =>\n setPlanForm({ ...planForm, status: e.target.value })\n }\n className={inputClass}\n >\n <option value=\"active\">Active</option>\n <option value=\"draft\">Draft</option>\n <option value=\"completed\">Completed</option>\n <option value=\"archived\">Archived</option>\n </select>\n </div>\n )}\n </div>\n </Modal>\n\n {/* Add allocation(s) modal */}\n <Modal\n open={allocModalPlan !== null}\n onClose={() => setAllocModalPlan(null)}\n title=\"Add Allocation\"\n description={\n allocModalPlan\n ? `Assign team members to \"${allocModalPlan.name}\".`\n : undefined\n }\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setAllocModalPlan(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={handleSubmitAllocations}\n disabled={creatingAlloc || bulkCreating || !allocRowsValid}\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 {creatingAlloc || bulkCreating\n ? \"Saving...\"\n : allocRows.length > 1\n ? `Add ${allocRows.length} Allocations`\n : \"Add Allocation\"}\n </button>\n </>\n }\n >\n {allocError && (\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 add allocations. Please check the values and try again.\n </div>\n )}\n <div className=\"space-y-4\">\n {allocRows.map((row, index) => (\n <div\n key={index}\n className=\"rounded-lg border border-border-subtle p-3\"\n >\n <div className=\"mb-2 flex items-center justify-between\">\n <span className=\"text-xs font-medium text-text-secondary\">\n Allocation {index + 1}\n </span>\n {allocRows.length > 1 && (\n <button\n onClick={() => removeAllocRow(index)}\n className=\"text-xs font-medium text-status-error-text hover:underline\"\n >\n Remove\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n User ID\n </label>\n <input\n type=\"text\"\n placeholder=\"User identifier\"\n value={row.userId}\n onChange={(e) =>\n updateAllocRow(index, { userId: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n Allocated Hours\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={row.allocatedHours}\n onChange={(e) =>\n updateAllocRow(index, {\n allocatedHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={row.startDate}\n onChange={(e) =>\n updateAllocRow(index, { startDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={row.endDate}\n onChange={(e) =>\n updateAllocRow(index, { endDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"block text-xs font-medium text-text-primary\">\n Notes (optional)\n </label>\n <input\n type=\"text\"\n placeholder=\"Context for this allocation\"\n value={row.notes}\n onChange={(e) =>\n updateAllocRow(index, { notes: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n </div>\n ))}\n <button\n onClick={addAllocRow}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Plus className=\"h-4 w-4\" />\n Add another person\n </button>\n </div>\n </Modal>\n\n {/* Edit allocation modal */}\n <Modal\n open={editingAlloc !== null && allocEditForm !== null}\n onClose={() => {\n setEditingAlloc(null);\n setAllocEditForm(null);\n }}\n title=\"Edit Allocation\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => {\n setEditingAlloc(null);\n setAllocEditForm(null);\n }}\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={handleSubmitAllocEdit}\n disabled={updatingAlloc}\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 {updatingAlloc ? \"Saving...\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateAllocError && (\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 allocation. Please try again.\n </div>\n )}\n {allocEditForm && (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n User ID\n </label>\n <input\n type=\"text\"\n value={allocEditForm.userId}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n userId: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Allocated Hours\n </label>\n <input\n type=\"number\"\n value={allocEditForm.allocatedHours}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n allocatedHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Actual Hours\n </label>\n <input\n type=\"number\"\n value={allocEditForm.actualHours}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n actualHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div />\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={allocEditForm.startDate}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n startDate: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={allocEditForm.endDate}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n endDate: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"block text-sm font-medium text-text-primary\">\n Notes (optional)\n </label>\n <input\n type=\"text\"\n value={allocEditForm.notes}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n notes: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n </div>\n )}\n </Modal>\n\n {/* Delete plan confirm */}\n <Modal\n open={confirmDeletePlan !== null}\n onClose={() => setConfirmDeletePlan(null)}\n title=\"Delete Resource Plan\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDeletePlan(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={handleDeletePlan}\n disabled={deletingPlan}\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/80 disabled:opacity-50\"\n >\n {deletingPlan ? \"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\">\n {confirmDeletePlan?.name}\n </span>\n ? All its allocations will be removed.\n </p>\n </Modal>\n\n {/* Delete allocation confirm */}\n <Modal\n open={confirmDeleteAlloc !== null}\n onClose={() => setConfirmDeleteAlloc(null)}\n title=\"Delete Allocation\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDeleteAlloc(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={handleDeleteAllocation}\n disabled={deletingAlloc}\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/80 disabled:opacity-50\"\n >\n {deletingAlloc ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove the allocation for{\" \"}\n <span className=\"font-medium text-text-primary\">\n {confirmDeleteAlloc?.userId}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ResourcePlanningPage.js","names":[],"sources":["../../../../src/timecampus/pages/resource-planning/ResourcePlanningPage.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport {\n CalendarRange,\n Users,\n TrendingUp,\n Plus,\n Pencil,\n Trash2,\n UserPlus,\n AlertTriangle,\n} from \"lucide-react\";\nimport { GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../../components/PageLayout\";\nimport { StatCard } from \"../../components/StatCard\";\nimport { EmptyState } from \"../../components/EmptyState\";\nimport { LoadingSkeleton } from \"../../components/LoadingSkeleton\";\nimport { Modal } from \"../../components/Modal\";\nimport {\n ChartCard,\n DonutChart,\n RadialGauge,\n ProgressMeter,\n StatusBadge,\n type MeterTone,\n} from \"../../components\";\nimport { getInitials, formatHours } from \"../../utils/helpers\";\nimport {\n useListResourcePlansQuery,\n useGetCapacityOverviewQuery,\n useCreateResourcePlanMutation,\n useUpdateResourcePlanMutation,\n useDeleteResourcePlanMutation,\n useCreateAllocationMutation,\n useUpdateAllocationMutation,\n useDeleteAllocationMutation,\n useBulkCreateAllocationsMutation,\n type ResourcePlanFieldsFragment,\n type ResourceAllocationFieldsFragment,\n type ListResourcePlansQuery,\n} from \"@/generated/wspace-operations\";\n\nconst PAGE_SIZE = 25;\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\";\n\nconst toDateInput = (iso: string) => iso.slice(0, 10);\nconst toIso = (dateStr: string) => new Date(dateStr).toISOString();\n\ninterface PlanFormState {\n name: string;\n description: string;\n startDate: string;\n endDate: string;\n status: string;\n}\n\ninterface AllocationRow {\n userId: string;\n allocatedHours: string;\n startDate: string;\n endDate: string;\n notes: string;\n}\n\ninterface AllocationEditState {\n userId: string;\n allocatedHours: string;\n actualHours: string;\n startDate: string;\n endDate: string;\n notes: string;\n}\n\nconst emptyPlanForm = (): PlanFormState => {\n const now = new Date();\n const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());\n const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 7);\n return {\n name: \"\",\n description: \"\",\n startDate: toDateInput(start.toISOString()),\n endDate: toDateInput(end.toISOString()),\n status: \"active\",\n };\n};\n\nconst emptyAllocationRow = (\n plan?: ResourcePlanFieldsFragment,\n): AllocationRow => ({\n userId: \"\",\n allocatedHours: \"\",\n startDate: plan ? toDateInput(plan.startDate) : \"\",\n endDate: plan ? toDateInput(plan.endDate) : \"\",\n notes: \"\",\n});\n\nexport function ResourcePlanningPage() {\n const now = new Date();\n const startDate = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate(),\n ).toISOString();\n const endDate = new Date(\n now.getFullYear(),\n now.getMonth(),\n now.getDate() + 7,\n ).toISOString();\n\n const {\n data: plansData,\n loading: plansLoading,\n error: plansError,\n refetch: refetchPlans,\n fetchMore,\n } = useListResourcePlansQuery({\n variables: { pagination: { limit: PAGE_SIZE, offset: 0 } },\n });\n const { data: capacityData, refetch: refetchCapacity } =\n useGetCapacityOverviewQuery({ variables: { startDate, endDate } });\n\n const [createPlan, { loading: creatingPlan, error: createPlanError }] =\n useCreateResourcePlanMutation();\n const [updatePlan, { loading: updatingPlan, error: updatePlanError }] =\n useUpdateResourcePlanMutation();\n const [deletePlan, { loading: deletingPlan }] =\n useDeleteResourcePlanMutation();\n const [createAllocation, { loading: creatingAlloc }] =\n useCreateAllocationMutation();\n const [bulkCreateAllocations, { loading: bulkCreating }] =\n useBulkCreateAllocationsMutation();\n const [\n updateAllocation,\n { loading: updatingAlloc, error: updateAllocError },\n ] = useUpdateAllocationMutation();\n const [deleteAllocation, { loading: deletingAlloc }] =\n useDeleteAllocationMutation();\n\n const [planModalOpen, setPlanModalOpen] = useState(false);\n const [editingPlan, setEditingPlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n const [planForm, setPlanForm] = useState<PlanFormState>(emptyPlanForm());\n const [confirmDeletePlan, setConfirmDeletePlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n\n const [allocModalPlan, setAllocModalPlan] =\n useState<ResourcePlanFieldsFragment | null>(null);\n const [allocRows, setAllocRows] = useState<AllocationRow[]>([]);\n const [allocError, setAllocError] = useState(false);\n\n const [editingAlloc, setEditingAlloc] =\n useState<ResourceAllocationFieldsFragment | null>(null);\n const [allocEditForm, setAllocEditForm] =\n useState<AllocationEditState | null>(null);\n const [confirmDeleteAlloc, setConfirmDeleteAlloc] =\n useState<ResourceAllocationFieldsFragment | null>(null);\n\n const [loadingMore, setLoadingMore] = useState(false);\n\n const plans = plansData?.resourcePlans?.items ?? [];\n const hasMore = plansData?.resourcePlans?.hasMore ?? false;\n const capacity = capacityData?.capacityOverview;\n\n const refetchAll = async () => {\n await Promise.all([refetchPlans(), refetchCapacity()]);\n };\n\n // -- Plan handlers --------------------------------------------------------\n const openCreatePlan = () => {\n setEditingPlan(null);\n setPlanForm(emptyPlanForm());\n setPlanModalOpen(true);\n };\n\n const openEditPlan = (plan: ResourcePlanFieldsFragment) => {\n setEditingPlan(plan);\n setPlanForm({\n name: plan.name,\n description: plan.description ?? \"\",\n startDate: toDateInput(plan.startDate),\n endDate: toDateInput(plan.endDate),\n status: plan.status,\n });\n setPlanModalOpen(true);\n };\n\n const handleSubmitPlan = async () => {\n if (!planForm.name.trim()) return;\n if (editingPlan) {\n await updatePlan({\n variables: {\n id: editingPlan.id,\n input: {\n name: planForm.name.trim(),\n description: planForm.description.trim() || undefined,\n startDate: toIso(planForm.startDate),\n endDate: toIso(planForm.endDate),\n status: planForm.status,\n },\n },\n });\n } else {\n await createPlan({\n variables: {\n input: {\n name: planForm.name.trim(),\n description: planForm.description.trim() || undefined,\n startDate: toIso(planForm.startDate),\n endDate: toIso(planForm.endDate),\n },\n },\n });\n }\n setPlanModalOpen(false);\n await refetchAll();\n };\n\n const handleDeletePlan = async () => {\n if (!confirmDeletePlan) return;\n await deletePlan({ variables: { id: confirmDeletePlan.id } });\n setConfirmDeletePlan(null);\n await refetchAll();\n };\n\n // -- Allocation handlers --------------------------------------------------\n const openAddAllocation = (plan: ResourcePlanFieldsFragment) => {\n setAllocModalPlan(plan);\n setAllocRows([emptyAllocationRow(plan)]);\n setAllocError(false);\n };\n\n const updateAllocRow = (index: number, patch: Partial<AllocationRow>) => {\n setAllocRows((rows) =>\n rows.map((r, i) => (i === index ? { ...r, ...patch } : r)),\n );\n };\n\n const addAllocRow = () => {\n setAllocRows((rows) => [\n ...rows,\n emptyAllocationRow(allocModalPlan ?? undefined),\n ]);\n };\n\n const removeAllocRow = (index: number) => {\n setAllocRows((rows) => rows.filter((_, i) => i !== index));\n };\n\n const allocRowsValid = allocRows.every(\n (r) =>\n r.userId.trim() !== \"\" &&\n r.allocatedHours.trim() !== \"\" &&\n r.startDate !== \"\" &&\n r.endDate !== \"\",\n );\n\n const handleSubmitAllocations = async () => {\n if (!allocModalPlan || !allocRowsValid || allocRows.length === 0) return;\n setAllocError(false);\n try {\n if (allocRows.length === 1) {\n const r = allocRows[0];\n await createAllocation({\n variables: {\n input: {\n resourcePlanId: allocModalPlan.id,\n userId: r.userId.trim(),\n allocatedHours: Number(r.allocatedHours),\n startDate: toIso(r.startDate),\n endDate: toIso(r.endDate),\n notes: r.notes.trim() || undefined,\n },\n },\n });\n } else {\n await bulkCreateAllocations({\n variables: {\n planId: allocModalPlan.id,\n allocations: allocRows.map((r) => ({\n userId: r.userId.trim(),\n allocatedHours: Number(r.allocatedHours),\n startDate: toIso(r.startDate),\n endDate: toIso(r.endDate),\n notes: r.notes.trim() || undefined,\n })),\n },\n });\n }\n setAllocModalPlan(null);\n await refetchAll();\n } catch {\n setAllocError(true);\n }\n };\n\n const openEditAllocation = (alloc: ResourceAllocationFieldsFragment) => {\n setEditingAlloc(alloc);\n setAllocEditForm({\n userId: alloc.userId,\n allocatedHours: String(alloc.allocatedHours),\n actualHours: String(alloc.actualHours),\n startDate: toDateInput(alloc.startDate),\n endDate: toDateInput(alloc.endDate),\n notes: alloc.notes ?? \"\",\n });\n };\n\n const handleSubmitAllocEdit = async () => {\n if (!editingAlloc || !allocEditForm) return;\n await updateAllocation({\n variables: {\n id: editingAlloc.id,\n input: {\n userId: allocEditForm.userId.trim(),\n allocatedHours: Number(allocEditForm.allocatedHours),\n actualHours: Number(allocEditForm.actualHours),\n startDate: toIso(allocEditForm.startDate),\n endDate: toIso(allocEditForm.endDate),\n notes: allocEditForm.notes.trim() || undefined,\n },\n },\n });\n setEditingAlloc(null);\n setAllocEditForm(null);\n await refetchAll();\n };\n\n const handleDeleteAllocation = async () => {\n if (!confirmDeleteAlloc) return;\n await deleteAllocation({ variables: { id: confirmDeleteAlloc.id } });\n setConfirmDeleteAlloc(null);\n await refetchAll();\n };\n\n const handleLoadMore = async () => {\n setLoadingMore(true);\n try {\n await fetchMore({\n variables: { pagination: { limit: PAGE_SIZE, offset: plans.length } },\n updateQuery: (\n prev: ListResourcePlansQuery,\n { fetchMoreResult }: { fetchMoreResult?: ListResourcePlansQuery },\n ) => {\n if (!fetchMoreResult) return prev;\n return {\n resourcePlans: {\n ...fetchMoreResult.resourcePlans,\n items: [\n ...prev.resourcePlans.items,\n ...fetchMoreResult.resourcePlans.items,\n ],\n },\n };\n },\n });\n } finally {\n setLoadingMore(false);\n }\n };\n\n const newPlanButton = (\n <button\n onClick={openCreatePlan}\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 New Resource Plan\n </button>\n );\n\n if (plansLoading) {\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n >\n <LoadingSkeleton variant=\"list\" count={5} />\n </PageLayout>\n );\n }\n\n if (plansError) {\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n >\n <div className=\"rounded-lg border border-status-error-bg/40 bg-status-error-bg p-4 text-sm text-status-error-text\">\n Failed to load resource planning data. Please try again later.\n </div>\n </PageLayout>\n );\n }\n\n const utilizationRate = capacity?.utilizationRate ?? 0;\n const overallocatedUsers = capacity?.overallocatedUsers ?? [];\n const overallocatedCount = overallocatedUsers.length;\n const availableHours = capacity?.availableCapacity ?? 0;\n const allocatedHours = capacity?.allocatedCapacity ?? 0;\n const totalHours = capacity?.totalCapacity ?? 0;\n const hasCapacity =\n !!capacity &&\n (totalHours > 0 || allocatedHours > 0 || overallocatedCount > 0);\n\n return (\n <PageLayout\n title=\"Resource Planning\"\n description=\"Plan and optimize team resource allocation\"\n purpose=\"Match the work ahead to the people you have — see who's over- or under-booked and how much capacity is free this week — so you can commit to deadlines you can actually hit without burning anyone out.\"\n actions={newPlanButton}\n >\n <div className=\"space-y-6\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n featured\n title=\"Team Utilization\"\n value={`${Math.round(utilizationRate)}%`}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Over-allocated\"\n value={String(overallocatedCount)}\n icon={<Users className=\"h-5 w-5\" />}\n iconBgColor=\"bg-status-error-bg text-status-error-text\"\n />\n <StatCard\n title=\"Active Plans\"\n value={String(plans.length)}\n icon={<Users className=\"h-5 w-5\" />}\n />\n <StatCard\n title=\"Available Capacity\"\n value={`${Math.round(availableHours)}h`}\n icon={<CalendarRange className=\"h-5 w-5\" />}\n description=\"This week\"\n />\n </div>\n\n {hasCapacity && (\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <ChartCard\n title=\"Capacity split\"\n description=\"Allocated vs available hours this week\"\n >\n <DonutChart\n data={[\n { name: \"Allocated\", value: allocatedHours },\n { name: \"Available\", value: availableHours },\n ]}\n centerLabel=\"Total\"\n centerValue={`${Math.round(totalHours)}h`}\n valueFormat={(v) => `${Math.round(v)}h`}\n emptyLabel=\"No capacity data yet\"\n />\n </ChartCard>\n <ChartCard\n title=\"Utilization & over-allocation\"\n description=\"Team utilization and who is over-booked this week\"\n >\n <RadialGauge\n value={Math.round(utilizationRate)}\n max={100}\n label=\"Utilization\"\n sublabel=\"this week\"\n format={(v) => `${Math.round(v)}%`}\n />\n <div className=\"mt-4\">\n <p className=\"mb-2 text-xs font-medium text-text-secondary\">\n Over-allocated ({overallocatedCount})\n </p>\n {overallocatedCount === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No one is over-allocated this week.\n </p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {overallocatedUsers.map((userId: string) => (\n <span\n key={userId}\n className=\"inline-flex items-center gap-1 rounded-full bg-status-warning-bg px-2.5 py-0.5 text-xs font-medium text-status-warning-text\"\n >\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {userId}\n </span>\n ))}\n </div>\n )}\n </div>\n </ChartCard>\n </div>\n )}\n\n {plans.length === 0 ? (\n <EmptyState\n illustration=\"empty-people\"\n title=\"No resource plans yet\"\n description=\"Create a resource plan to start allocating team members and balancing workload across the week.\"\n action={newPlanButton}\n />\n ) : (\n <div className=\"space-y-6\">\n {plans.map((plan: ResourcePlanFieldsFragment) => (\n <GlassCard\n key={plan.id}\n treatment=\"glass\"\n className=\"overflow-hidden\"\n >\n <div className=\"flex flex-col gap-3 border-b border-border-seam p-5 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {plan.name}\n </p>\n <StatusBadge label={plan.status} status={plan.status} />\n </div>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {plan.startDate.slice(0, 10)} to{\" \"}\n {plan.endDate.slice(0, 10)} ·{\" \"}\n {plan.allocations.length}{\" \"}\n {plan.allocations.length === 1 ? \"person\" : \"people\"}\n </p>\n <div className=\"mt-3 max-w-sm\">\n <ProgressMeter\n size=\"sm\"\n label=\"Logged vs allocated\"\n value={plan.totalActualHours}\n max={\n plan.totalAllocatedHours > 0\n ? plan.totalAllocatedHours\n : Math.max(plan.totalActualHours, 1)\n }\n format={(v) => formatHours(v)}\n />\n </div>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n onClick={() => openAddAllocation(plan)}\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 <UserPlus className=\"h-3.5 w-3.5\" />\n Add Allocation\n </button>\n <button\n onClick={() => openEditPlan(plan)}\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 <Pencil className=\"h-3.5 w-3.5\" />\n Edit\n </button>\n <button\n onClick={() => setConfirmDeletePlan(plan)}\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-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n Delete\n </button>\n </div>\n </div>\n\n {plan.allocations.length === 0 ? (\n <div className=\"p-5 text-sm text-text-secondary\">\n No allocations yet. Use “Add Allocation” to\n assign team members.\n </div>\n ) : (\n <div className=\"overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs text-text-secondary\">\n <th className=\"whitespace-nowrap px-5 py-2.5 font-medium\">\n Member\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 font-medium\">\n Window\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 text-right font-medium\">\n Allocated\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 text-right font-medium\">\n Actual\n </th>\n <th className=\"whitespace-nowrap px-4 py-2.5 font-medium\">\n Utilization\n </th>\n <th className=\"whitespace-nowrap px-5 py-2.5 text-right font-medium\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-seam\">\n {plan.allocations.map(\n (alloc: ResourceAllocationFieldsFragment) => {\n const utilization =\n alloc.utilizationRate ??\n (alloc.allocatedHours > 0\n ? Math.round(\n (alloc.actualHours / alloc.allocatedHours) *\n 100,\n )\n : 0);\n const tone: MeterTone =\n utilization > 100\n ? \"danger\"\n : utilization < 70\n ? \"warning\"\n : \"success\";\n const toneText =\n tone === \"danger\"\n ? \"text-status-error-text\"\n : tone === \"warning\"\n ? \"text-status-warning-text\"\n : \"text-status-success-text\";\n return (\n <tr\n key={alloc.id}\n className=\"transition-colors hover:bg-bg-sunken/50\"\n >\n <td className=\"whitespace-nowrap px-5 py-3\">\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/10 text-sm font-medium text-primary\">\n {getInitials(alloc.userId)}\n </div>\n <span className=\"font-medium text-text-primary\">\n {alloc.userId}\n </span>\n </div>\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-text-secondary\">\n {alloc.startDate.slice(0, 10)} to{\" \"}\n {alloc.endDate.slice(0, 10)}\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-right font-medium text-text-primary\">\n {formatHours(alloc.allocatedHours)}\n </td>\n <td className=\"whitespace-nowrap px-4 py-3 text-right text-text-secondary\">\n {formatHours(alloc.actualHours)}\n </td>\n <td className=\"px-4 py-3\">\n <div className=\"flex w-40 items-center gap-2\">\n <ProgressMeter\n size=\"sm\"\n value={utilization}\n max={100}\n tone={tone}\n showValue={false}\n className=\"flex-1\"\n />\n <span\n className={`shrink-0 text-xs font-medium tabular-nums ${toneText}`}\n >\n {utilization}%\n </span>\n </div>\n </td>\n <td className=\"whitespace-nowrap px-5 py-3\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n onClick={() => openEditAllocation(alloc)}\n aria-label=\"Edit allocation\"\n className=\"rounded-md border border-border-subtle p-1.5 text-text-primary hover:bg-bg-sunken/50\"\n >\n <Pencil className=\"h-4 w-4\" />\n </button>\n <button\n onClick={() =>\n setConfirmDeleteAlloc(alloc)\n }\n aria-label=\"Delete allocation\"\n className=\"rounded-md border border-border-subtle p-1.5 text-status-error-text hover:bg-status-error-bg\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n </td>\n </tr>\n );\n },\n )}\n </tbody>\n </table>\n </div>\n )}\n </GlassCard>\n ))}\n {hasMore && (\n <div className=\"flex justify-center\">\n <button\n onClick={handleLoadMore}\n disabled={loadingMore}\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 {loadingMore ? \"Loading...\" : \"Load more\"}\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Plan create/edit modal */}\n <Modal\n open={planModalOpen}\n onClose={() => setPlanModalOpen(false)}\n title={editingPlan ? \"Edit Resource Plan\" : \"New Resource Plan\"}\n description=\"Set the window this plan covers, then allocate people to it.\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => setPlanModalOpen(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={handleSubmitPlan}\n disabled={creatingPlan || updatingPlan || !planForm.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 {creatingPlan || updatingPlan\n ? \"Saving...\"\n : editingPlan\n ? \"Save Changes\"\n : \"Create Plan\"}\n </button>\n </>\n }\n >\n {(createPlanError || updatePlanError) && (\n <div className=\"mb-4 rounded-lg border border-status-error-bg/40 bg-status-error-bg p-3 text-sm text-status-error-text\">\n Failed to save resource plan. Please try again.\n </div>\n )}\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Plan Name\n </label>\n <input\n type=\"text\"\n placeholder=\"e.g. Q3 Delivery\"\n value={planForm.name}\n onChange={(e) =>\n setPlanForm({ ...planForm, name: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Description (optional)\n </label>\n <textarea\n rows={2}\n placeholder=\"What this plan covers\"\n value={planForm.description}\n onChange={(e) =>\n setPlanForm({ ...planForm, 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=\"block text-sm font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={planForm.startDate}\n onChange={(e) =>\n setPlanForm({ ...planForm, startDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={planForm.endDate}\n onChange={(e) =>\n setPlanForm({ ...planForm, endDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n {editingPlan && (\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Status\n </label>\n <select\n value={planForm.status}\n onChange={(e) =>\n setPlanForm({ ...planForm, status: e.target.value })\n }\n className={inputClass}\n >\n <option value=\"active\">Active</option>\n <option value=\"draft\">Draft</option>\n <option value=\"completed\">Completed</option>\n <option value=\"archived\">Archived</option>\n </select>\n </div>\n )}\n </div>\n </Modal>\n\n {/* Add allocation(s) modal */}\n <Modal\n open={allocModalPlan !== null}\n onClose={() => setAllocModalPlan(null)}\n title=\"Add Allocation\"\n description={\n allocModalPlan\n ? `Assign team members to \"${allocModalPlan.name}\".`\n : undefined\n }\n size=\"xl\"\n footer={\n <>\n <button\n onClick={() => setAllocModalPlan(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={handleSubmitAllocations}\n disabled={creatingAlloc || bulkCreating || !allocRowsValid}\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 {creatingAlloc || bulkCreating\n ? \"Saving...\"\n : allocRows.length > 1\n ? `Add ${allocRows.length} Allocations`\n : \"Add Allocation\"}\n </button>\n </>\n }\n >\n {allocError && (\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 add allocations. Please check the values and try again.\n </div>\n )}\n <div className=\"space-y-4\">\n {allocRows.map((row, index) => (\n <div\n key={index}\n className=\"rounded-lg border border-border-subtle p-3\"\n >\n <div className=\"mb-2 flex items-center justify-between\">\n <span className=\"text-xs font-medium text-text-secondary\">\n Allocation {index + 1}\n </span>\n {allocRows.length > 1 && (\n <button\n onClick={() => removeAllocRow(index)}\n className=\"text-xs font-medium text-status-error-text hover:underline\"\n >\n Remove\n </button>\n )}\n </div>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n User ID\n </label>\n <input\n type=\"text\"\n placeholder=\"User identifier\"\n value={row.userId}\n onChange={(e) =>\n updateAllocRow(index, { userId: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n Allocated Hours\n </label>\n <input\n type=\"number\"\n placeholder=\"0\"\n value={row.allocatedHours}\n onChange={(e) =>\n updateAllocRow(index, {\n allocatedHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={row.startDate}\n onChange={(e) =>\n updateAllocRow(index, { startDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={row.endDate}\n onChange={(e) =>\n updateAllocRow(index, { endDate: e.target.value })\n }\n className={inputClass}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"block text-xs font-medium text-text-primary\">\n Notes (optional)\n </label>\n <input\n type=\"text\"\n placeholder=\"Context for this allocation\"\n value={row.notes}\n onChange={(e) =>\n updateAllocRow(index, { notes: e.target.value })\n }\n className={inputClass}\n />\n </div>\n </div>\n </div>\n ))}\n <button\n onClick={addAllocRow}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary hover:bg-bg-sunken/50\"\n >\n <Plus className=\"h-4 w-4\" />\n Add another person\n </button>\n </div>\n </Modal>\n\n {/* Edit allocation modal */}\n <Modal\n open={editingAlloc !== null && allocEditForm !== null}\n onClose={() => {\n setEditingAlloc(null);\n setAllocEditForm(null);\n }}\n title=\"Edit Allocation\"\n size=\"lg\"\n footer={\n <>\n <button\n onClick={() => {\n setEditingAlloc(null);\n setAllocEditForm(null);\n }}\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={handleSubmitAllocEdit}\n disabled={updatingAlloc}\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 {updatingAlloc ? \"Saving...\" : \"Save Changes\"}\n </button>\n </>\n }\n >\n {updateAllocError && (\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 allocation. Please try again.\n </div>\n )}\n {allocEditForm && (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n User ID\n </label>\n <input\n type=\"text\"\n value={allocEditForm.userId}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n userId: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Allocated Hours\n </label>\n <input\n type=\"number\"\n value={allocEditForm.allocatedHours}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n allocatedHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Actual Hours\n </label>\n <input\n type=\"number\"\n value={allocEditForm.actualHours}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n actualHours: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div />\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n Start Date\n </label>\n <input\n type=\"date\"\n value={allocEditForm.startDate}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n startDate: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary\">\n End Date\n </label>\n <input\n type=\"date\"\n value={allocEditForm.endDate}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n endDate: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n <div className=\"sm:col-span-2\">\n <label className=\"block text-sm font-medium text-text-primary\">\n Notes (optional)\n </label>\n <input\n type=\"text\"\n value={allocEditForm.notes}\n onChange={(e) =>\n setAllocEditForm({\n ...allocEditForm,\n notes: e.target.value,\n })\n }\n className={inputClass}\n />\n </div>\n </div>\n )}\n </Modal>\n\n {/* Delete plan confirm */}\n <Modal\n open={confirmDeletePlan !== null}\n onClose={() => setConfirmDeletePlan(null)}\n title=\"Delete Resource Plan\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDeletePlan(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={handleDeletePlan}\n disabled={deletingPlan}\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/80 disabled:opacity-50\"\n >\n {deletingPlan ? \"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\">\n {confirmDeletePlan?.name}\n </span>\n ? All its allocations will be removed.\n </p>\n </Modal>\n\n {/* Delete allocation confirm */}\n <Modal\n open={confirmDeleteAlloc !== null}\n onClose={() => setConfirmDeleteAlloc(null)}\n title=\"Delete Allocation\"\n description=\"This action cannot be undone.\"\n footer={\n <>\n <button\n onClick={() => setConfirmDeleteAlloc(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={handleDeleteAllocation}\n disabled={deletingAlloc}\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/80 disabled:opacity-50\"\n >\n {deletingAlloc ? \"Deleting...\" : \"Delete\"}\n </button>\n </>\n }\n >\n <p className=\"text-sm text-text-secondary\">\n Remove the allocation for{\" \"}\n <span className=\"font-medium text-text-primary\">\n {confirmDeleteAlloc?.userId}\n </span>\n ?\n </p>\n </Modal>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|