@open-mercato/core 0.10.1-develop.7764.1.7505d7340b → 0.10.1-develop.7765.1.391b8a0e16

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.
@@ -64,6 +64,10 @@ function RailFact({ label, value }) {
64
64
  function TimesheetProjectDetailPage({ params }) {
65
65
  const projectId = params?.id;
66
66
  const t = useT();
67
+ const translateRef = React.useRef(t);
68
+ React.useEffect(() => {
69
+ translateRef.current = t;
70
+ }, [t]);
67
71
  const scopeVersion = useOrganizationScopeVersion();
68
72
  const { confirm, ConfirmDialogElement } = useConfirmDialog();
69
73
  const router = useRouter();
@@ -247,7 +251,7 @@ function TimesheetProjectDetailPage({ params }) {
247
251
  }
248
252
  if (!cancelled) setAccessDeniedProjectId(null);
249
253
  if (!call.ok) {
250
- throw new Error(t("staff.timesheets.projects.errors.load", "Failed to load project."));
254
+ throw new Error(translateRef.current("staff.timesheets.projects.errors.load", "Failed to load project."));
251
255
  }
252
256
  const payload = call.result;
253
257
  const record = Array.isArray(payload?.items) ? payload.items[0] : null;
@@ -258,7 +262,7 @@ function TimesheetProjectDetailPage({ params }) {
258
262
  if (!cancelled) setProject(record);
259
263
  } catch (loadError) {
260
264
  if (!cancelled) {
261
- setError(loadError instanceof Error ? loadError.message : t("staff.timesheets.projects.errors.load", "Failed to load project."));
265
+ setError(loadError instanceof Error ? loadError.message : translateRef.current("staff.timesheets.projects.errors.load", "Failed to load project."));
262
266
  }
263
267
  } finally {
264
268
  if (!cancelled) {
@@ -271,7 +275,7 @@ function TimesheetProjectDetailPage({ params }) {
271
275
  return () => {
272
276
  cancelled = true;
273
277
  };
274
- }, [projectId, t, scopeVersion]);
278
+ }, [projectId, scopeVersion]);
275
279
  const loadEmployees = React.useCallback(async () => {
276
280
  if (!projectId || accessDenied) return;
277
281
  setEmployeesLoading(true);
@@ -279,7 +283,7 @@ function TimesheetProjectDetailPage({ params }) {
279
283
  const payload = await readApiResultOrThrow(
280
284
  `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=100`,
281
285
  void 0,
282
- { errorMessage: t("staff.timesheets.projects.employees.empty", "No employees assigned yet."), fallback: { items: [], total: 0 } }
286
+ { errorMessage: translateRef.current("staff.timesheets.projects.employees.empty", "No employees assigned yet."), fallback: { items: [], total: 0 } }
283
287
  );
284
288
  const items = Array.isArray(payload.items) ? payload.items : [];
285
289
  const staffMemberIds = items.map((item) => String(item.staff_member_id ?? item.staffMemberId ?? "")).filter((id) => id.length > 0);
@@ -319,7 +323,7 @@ function TimesheetProjectDetailPage({ params }) {
319
323
  } finally {
320
324
  setEmployeesLoading(false);
321
325
  }
322
- }, [projectId, accessDenied, t]);
326
+ }, [projectId, accessDenied]);
323
327
  React.useEffect(() => {
324
328
  void loadEmployees();
325
329
  }, [loadEmployees, reloadToken, scopeVersion]);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/staff/backend/staff/time-tracking/projects/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { LookupSelect } from '@open-mercato/ui/backend/inputs/LookupSelect'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, createCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { ArrowLeft, Plus, Pencil, ChevronDown, Trash2, User, Users } from 'lucide-react'\nimport Link from 'next/link'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { KpiCard, Sparkline } from '@open-mercato/ui/backend/charts'\nimport { ProjectTeamDrawer } from '../../../../../lib/time-tracking-ui/ProjectTeamDrawer'\nimport { NoProjectAccess } from '../../../../../lib/time-tracking-ui/NoProjectAccess'\nimport { ProjectBudgetCell } from '../../../../../lib/timesheets-projects-ui/ProjectBudgetCell'\nimport { computeBudgetBurn } from '../../../../../lib/timesheets-projects/budgetBurn'\nimport { resolveProjectColorHex } from '../../../../../lib/timesheets-ui/colors'\n\nconst logger = createLogger('staff')\n\nconst BACK_HREF = '/backend/staff/time-tracking/projects'\n\nconst EMPLOYEE_MUTATION_CONTEXT_ID = 'staff-time-project-detail:employees'\nconst EMPLOYEE_RESOURCE_KIND = 'staff.time_project_member'\n\ntype EmployeeMutationContext = {\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n}\n\n/**\n * Mirrors `NO_PROJECT_ACCESS_REASON` from\n * `api/timesheets/time-projects/route.ts`. The route module is server-only, so\n * the literal is restated here rather than imported into the client bundle;\n * `__tests__/no-project-access-contract.test.ts` pins the two together.\n */\nconst NO_PROJECT_ACCESS_REASON = 'no_project_access'\n\nconst PROJECT_ENTITY_ID = 'staff:staff_time_project'\n\ntype ProjectDetailInjectionContext = {\n entityId: string\n recordId: string | null\n projectId: string | null\n path: string | null\n retryLastMutation: () => Promise<boolean>\n}\n\ntype ProjectRecord = {\n id: string\n name: string\n code: string\n description?: string | null\n projectType?: string | null\n project_type?: string | null\n startDate?: string | null\n start_date?: string | null\n costCenter?: string | null\n cost_center?: string | null\n status?: string | null\n customerId?: string | null\n customer_id?: string | null\n} & Record<string, unknown>\n\ntype ProjectResponse = {\n items?: ProjectRecord[]\n}\n\nfunction readDenialReason(payload: unknown): string | null {\n if (!payload || typeof payload !== 'object') return null\n const reason = (payload as { reason?: unknown }).reason\n return typeof reason === 'string' ? reason : null\n}\n\ntype EmployeeAssignment = {\n id: string\n staffMemberId: string\n role: string | null\n status: string | null\n assignedStartDate: string | null\n assignedEndDate: string | null\n displayName: string | null\n teamName: string | null\n isAssigner: boolean\n hasUserAccount: boolean | null\n updatedAt: string | null\n}\n\ntype EmployeesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n}\n\ntype StaffMemberRecord = {\n id: string\n display_name?: string\n displayName?: string\n user_id?: string | null\n userId?: string | null\n team?: { id: string; name: string } | null\n}\n\ntype StaffMembersResponse = {\n items?: StaffMemberRecord[]\n}\n\ntype ProjectEntryRow = {\n id: string\n date: string\n description: string | null\n durationMinutes: number\n isBillable: boolean\n taskId: string | null\n staffMemberId: string | null\n}\n\ntype ProjectTaskRow = {\n id: string\n reference: string | null\n title: string\n statusId: string | null\n assigneeStaffMemberId: string | null\n loggedMinutes: number | null\n}\n\n/**\n * A group heading inside the reference rail. The rail replaces three full-width\n * cards, so the groups carry the separation those card borders used to.\n */\nfunction RailGroup({ label, first }: { label: string; first?: boolean }) {\n return (\n <div\n className={`border-b pb-1.5 text-overline font-semibold uppercase tracking-widest text-muted-foreground ${\n first ? '' : 'pt-4'\n }`}\n >\n {label}\n </div>\n )\n}\n\nfunction RailFact({ label, value }: { label: string; value: React.ReactNode }) {\n return (\n <div className=\"flex items-baseline justify-between gap-3 border-b py-2 text-sm last:border-b-0\">\n <dt className=\"shrink-0 text-muted-foreground\">{label}</dt>\n <dd className=\"text-right font-medium tabular-nums\">{value}</dd>\n </div>\n )\n}\n\nexport default function TimesheetProjectDetailPage({ params }: { params?: { id?: string } }) {\n const projectId = params?.id\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n\n const [project, setProject] = React.useState<ProjectRecord | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n // Keyed by project id so a route change to another project never shows the\n // previous project's guard (with a live request button) while the new one loads.\n const [accessDeniedProjectId, setAccessDeniedProjectId] = React.useState<string | null>(null)\n const accessDenied = accessDeniedProjectId !== null && accessDeniedProjectId === projectId\n // The id the last finished load was for. Until it matches the route, the page\n // is still loading \u2014 never the previous project's error or empty state.\n const [settledProjectId, setSettledProjectId] = React.useState<string | null>(null)\n\n const [employees, setEmployees] = React.useState<EmployeeAssignment[]>([])\n const [employeesLoading, setEmployeesLoading] = React.useState(false)\n const [expandedCards, setExpandedCards] = React.useState<Set<string>>(new Set())\n const [reloadToken, setReloadToken] = React.useState(0)\n\n // Tabs govern the working column only \u2014 the details rail stays visible on every\n // tab, because reference facts are what you check *while* reading the tab you\n // are on, not a destination of their own.\n const [activeTab, setActiveTab] = React.useState<string>('team')\n const [recentEntries, setRecentEntries] = React.useState<ProjectEntryRow[]>([])\n const [recentEntriesLoading, setRecentEntriesLoading] = React.useState(false)\n const [projectTasks, setProjectTasks] = React.useState<ProjectTaskRow[]>([])\n const [projectTasksLoading, setProjectTasksLoading] = React.useState(false)\n\n const [canManageProjects, setCanManageProjects] = React.useState(false)\n // Money is gated on the same feature the portfolio gates it on; a rate hidden\n // in the list must not be readable one click deeper.\n const [canSeeMoney, setCanSeeMoney] = React.useState(false)\n\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n try {\n const res = await apiCall<{ ok: boolean; granted: string[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n features: ['staff.timesheets.projects.manage', 'staff.timesheets.rates.view'],\n }),\n })\n if (!cancelled) {\n const granted = new Set(res.result?.granted ?? [])\n setCanManageProjects(granted.has('staff.timesheets.projects.manage'))\n setCanSeeMoney(granted.has('staff.timesheets.rates.view'))\n }\n } catch {\n // default: no manage access, no money\n }\n })()\n return () => { cancelled = true }\n }, [])\n\n const [addDialogOpen, setAddDialogOpen] = React.useState(false)\n const [addStaffMemberId, setAddStaffMemberId] = React.useState<string | null>(null)\n const [addRole, setAddRole] = React.useState('')\n const [addStartDate, setAddStartDate] = React.useState('')\n const [addSaving, setAddSaving] = React.useState(false)\n const [staffLookupError, setStaffLookupError] = React.useState<string | null>(null)\n\n // Fetched per tab rather than up front: a project page opened to check a rate\n // should not pay for two lists nobody looked at.\n React.useEffect(() => {\n if (!projectId || activeTab !== 'time') return\n let cancelled = false\n void (async () => {\n setRecentEntriesLoading(true)\n try {\n const res = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/staff/timesheets/time-entries?projectId=${encodeURIComponent(projectId)}&page=1&pageSize=25&sortField=date&sortDir=desc`,\n )\n if (cancelled || !res.ok) return\n const items = Array.isArray(res.result?.items) ? res.result.items : []\n setRecentEntries(\n items\n .map((row): ProjectEntryRow | null => {\n const id = typeof row.id === 'string' ? row.id : null\n if (!id) return null\n const raw = row as Record<string, unknown>\n const minutes = raw.duration_minutes ?? raw.durationMinutes\n return {\n id,\n date: typeof raw.date === 'string' ? raw.date.slice(0, 10) : '',\n description:\n typeof raw.description === 'string'\n ? raw.description\n : typeof raw.notes === 'string'\n ? raw.notes\n : null,\n durationMinutes: typeof minutes === 'number' ? minutes : Number(minutes ?? 0) || 0,\n isBillable: raw.is_billable === true || raw.isBillable === true,\n taskId: typeof (raw.task_id ?? raw.taskId) === 'string' ? String(raw.task_id ?? raw.taskId) : null,\n staffMemberId:\n typeof (raw.staff_member_id ?? raw.staffMemberId) === 'string'\n ? String(raw.staff_member_id ?? raw.staffMemberId)\n : null,\n }\n })\n .filter((row): row is ProjectEntryRow => row !== null),\n )\n } catch (err) {\n logger.warn('staff.timesheets.projects.detail recent entries failed', { err })\n } finally {\n if (!cancelled) setRecentEntriesLoading(false)\n }\n })()\n return () => { cancelled = true }\n }, [projectId, activeTab, reloadToken])\n\n React.useEffect(() => {\n if (!projectId || activeTab !== 'tasks') return\n let cancelled = false\n void (async () => {\n setProjectTasksLoading(true)\n try {\n const res = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n // `timeProjectId`, not `projectId`: the tasks list schema declares the\n // former and is `.passthrough()`, so the wrong name is accepted, never\n // read, and the tab silently lists every task in the tenant.\n `/api/staff/timesheets/tasks?timeProjectId=${encodeURIComponent(projectId)}&page=1&pageSize=50&sortField=reference&sortDir=asc`,\n )\n if (cancelled || !res.ok) return\n const items = Array.isArray(res.result?.items) ? res.result.items : []\n setProjectTasks(\n items\n .map((row): ProjectTaskRow | null => {\n const id = typeof row.id === 'string' ? row.id : null\n const title = typeof row.title === 'string' ? row.title : null\n if (!id || !title) return null\n const raw = row as Record<string, unknown>\n const logged = raw.loggedMinutes ?? raw.logged_minutes\n return {\n id,\n reference: typeof raw.reference === 'string' ? raw.reference : null,\n title,\n statusId:\n typeof (raw.task_status_id ?? raw.taskStatusId) === 'string'\n ? String(raw.task_status_id ?? raw.taskStatusId)\n : null,\n assigneeStaffMemberId:\n typeof (raw.assignee_staff_member_id ?? raw.assigneeStaffMemberId) === 'string'\n ? String(raw.assignee_staff_member_id ?? raw.assigneeStaffMemberId)\n : null,\n loggedMinutes: typeof logged === 'number' ? logged : null,\n }\n })\n .filter((row): row is ProjectTaskRow => row !== null),\n )\n } catch (err) {\n logger.warn('staff.timesheets.projects.detail tasks failed', { err })\n } finally {\n if (!cancelled) setProjectTasksLoading(false)\n }\n })()\n return () => { cancelled = true }\n }, [projectId, activeTab, reloadToken])\n\n const employeeNameById = React.useMemo(\n () => new Map(employees.map((emp) => [emp.staffMemberId, emp.displayName ?? ''])),\n [employees],\n )\n const activeCount = employees.filter((emp) => emp.status === 'active').length\n const inactiveCount = employees.length - activeCount\n\n const recentEntriesColumns = React.useMemo<ColumnDef<ProjectEntryRow>[]>(\n () => [\n {\n accessorKey: 'date',\n header: t('staff.timesheets.projects.detail.colDate', 'Date'),\n cell: ({ row }) => <span className=\"whitespace-nowrap\">{row.original.date}</span>,\n },\n {\n id: 'person',\n header: t('staff.timesheets.projects.detail.colWho', 'Person'),\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap\">\n {employeeNameById.get(row.original.staffMemberId ?? '') ?? '\u2014'}\n </span>\n ),\n },\n {\n accessorKey: 'description',\n header: t('staff.timesheets.projects.detail.colDescription', 'Description'),\n cell: ({ row }) => (\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate\">{row.original.description ?? '\u2014'}</span>\n {row.original.isBillable ? null : (\n <Badge variant=\"neutral\">\n {t('staff.timesheets.projects.detail.nonBillableTag', 'non-billable')}\n </Badge>\n )}\n </span>\n ),\n },\n {\n id: 'hours',\n header: () => (\n <span className=\"block text-right\">\n {t('staff.timesheets.projects.detail.colHours', 'Hours')}\n </span>\n ),\n cell: ({ row }) => (\n <span className=\"block whitespace-nowrap text-right font-mono tabular-nums\">\n {formatHours(row.original.durationMinutes)}\n </span>\n ),\n },\n ],\n [employeeNameById, t],\n )\n\n // --- Load project ---\n React.useEffect(() => {\n if (!projectId) return\n let cancelled = false\n async function loadProject() {\n setLoading(true)\n setError(null)\n setIsNotFound(false)\n try {\n const queryParams = new URLSearchParams({ page: '1', pageSize: '1', ids: projectId! })\n const call = await apiCall<ProjectResponse>(\n `/api/staff/timesheets/time-projects?${queryParams.toString()}`,\n )\n // Screen 17: the route answers 404 with this discriminator for a project\n // the caller is not a member of, without naming it.\n if (call.status === 404 && readDenialReason(call.result) === NO_PROJECT_ACCESS_REASON) {\n if (!cancelled) setAccessDeniedProjectId(projectId ?? null)\n return\n }\n if (!cancelled) setAccessDeniedProjectId(null)\n if (!call.ok) {\n throw new Error(t('staff.timesheets.projects.errors.load', 'Failed to load project.'))\n }\n const payload = call.result\n const record = Array.isArray(payload?.items) ? payload.items[0] : null\n if (!record) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n if (!cancelled) setProject(record)\n } catch (loadError) {\n if (!cancelled) {\n setError(loadError instanceof Error ? loadError.message : t('staff.timesheets.projects.errors.load', 'Failed to load project.'))\n }\n } finally {\n if (!cancelled) {\n setLoading(false)\n setSettledProjectId(projectId ?? null)\n }\n }\n }\n loadProject()\n return () => { cancelled = true }\n }, [projectId, t, scopeVersion])\n\n // --- Load employees with name resolution ---\n const loadEmployees = React.useCallback(async () => {\n if (!projectId || accessDenied) return\n setEmployeesLoading(true)\n try {\n const payload = await readApiResultOrThrow<EmployeesResponse>(\n `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=100`,\n undefined,\n { errorMessage: t('staff.timesheets.projects.employees.empty', 'No employees assigned yet.'), fallback: { items: [], total: 0 } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n\n const staffMemberIds = items\n .map((item) => String(item.staff_member_id ?? item.staffMemberId ?? ''))\n .filter((id) => id.length > 0)\n\n let staffMap = new Map<string, StaffMemberRecord>()\n if (staffMemberIds.length > 0) {\n try {\n const staffRes = await apiCall<StaffMembersResponse>(\n `/api/staff/team-members?ids=${staffMemberIds.join(',')}&pageSize=100`,\n )\n if (staffRes.ok) {\n const staffItems = Array.isArray(staffRes.result?.items) ? staffRes.result.items : []\n staffMap = new Map(staffItems.map((member) => [member.id, member]))\n }\n } catch {\n // name resolution failed (e.g. 403 for employees) \u2014 show IDs as fallback\n }\n }\n\n const mapped: EmployeeAssignment[] = items.map((item) => {\n const staffMemberId = String(item.staff_member_id ?? item.staffMemberId ?? '')\n const staff = staffMap.get(staffMemberId)\n return {\n id: String(item.id ?? ''),\n staffMemberId,\n role: typeof item.role === 'string' ? item.role : null,\n status: typeof item.status === 'string' ? item.status : null,\n assignedStartDate: String(item.assigned_start_date ?? item.assignedStartDate ?? ''),\n assignedEndDate: typeof (item.assigned_end_date ?? item.assignedEndDate) === 'string'\n ? String(item.assigned_end_date ?? item.assignedEndDate)\n : null,\n displayName: staff?.display_name ?? staff?.displayName ?? null,\n teamName: staff?.team?.name ?? null,\n isAssigner: item.is_assigner === true || item.isAssigner === true,\n hasUserAccount: staff ? Boolean(staff.user_id ?? staff.userId) : null,\n updatedAt: typeof (item.updated_at ?? item.updatedAt) === 'string' ? String(item.updated_at ?? item.updatedAt) : null,\n }\n })\n\n setEmployees(mapped)\n } catch (loadError) {\n logger.error('staff.timesheets.projects.employees.list', { err: loadError })\n } finally {\n setEmployeesLoading(false)\n }\n }, [projectId, accessDenied, t])\n\n React.useEffect(() => {\n void loadEmployees()\n }, [loadEmployees, reloadToken, scopeVersion])\n\n // --- Toggle card expand/collapse ---\n const toggleCard = React.useCallback((employeeId: string) => {\n setExpandedCards((prev) => {\n const next = new Set(prev)\n if (next.has(employeeId)) next.delete(employeeId)\n else next.add(employeeId)\n return next\n })\n }, [])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<EmployeeMutationContext>({\n contextId: EMPLOYEE_MUTATION_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const projectInjectionContext = React.useMemo<ProjectDetailInjectionContext>(\n () => ({\n entityId: PROJECT_ENTITY_ID,\n recordId: projectId ?? null,\n projectId: projectId ?? null,\n path: pathname ?? null,\n retryLastMutation,\n }),\n [pathname, projectId, retryLastMutation],\n )\n\n const { widgets: injectedTabWidgets } = useInjectionWidgets<ProjectDetailInjectionContext>(\n extensionPoints.hosts.projectDetailTabs.spotId,\n { context: projectInjectionContext, triggerOnLoad: true },\n )\n\n const employeeMutationContext = React.useCallback(\n (resourceId: string): EmployeeMutationContext => ({\n formId: EMPLOYEE_MUTATION_CONTEXT_ID,\n resourceKind: EMPLOYEE_RESOURCE_KIND,\n resourceId,\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n // optimistic-lock-exempt: the employee add/remove calls below mutate the\n // time-project \u2194 staff-member assignment junction (createCrud/deleteCrud of a\n // membership row), not a shared editable aggregate. Junction add/remove is exempt\n // from OSS optimistic locking per the data-integrity contract.\n // --- Remove employee ---\n const handleRemoveEmployee = React.useCallback(async (emp: EmployeeAssignment) => {\n const confirmed = await confirm({\n title: t('staff.timesheets.projects.employees.remove', 'Remove'),\n text: t('staff.timesheets.projects.employees.removeConfirm', 'Remove this employee from the project?'),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: () =>\n deleteCrud(\n `staff/timesheets/time-projects/${projectId}/employees`,\n emp.id,\n { errorMessage: t('staff.timesheets.projects.employees.removeError', 'Failed to remove employee.') },\n ),\n context: employeeMutationContext(emp.id),\n mutationPayload: { id: emp.id },\n })\n flash(t('staff.timesheets.projects.employees.removed', 'Employee removed.'), 'success')\n setReloadToken((token) => token + 1)\n } catch (removeError) {\n if (surfaceRecordConflict(removeError, t)) return\n flash(t('staff.timesheets.projects.employees.removeError', 'Failed to remove employee.'), 'error')\n }\n }, [projectId, confirm, employeeMutationContext, runMutation, t])\n\n const handleToggleAssigner = React.useCallback(async (emp: EmployeeAssignment, nextValue: boolean) => {\n const payload = { id: emp.id, isAssigner: nextValue }\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(emp.updatedAt), () =>\n updateCrud(`staff/timesheets/time-projects/${projectId}/employees`, payload, {\n errorMessage: t('staff.time_tracking.assigners.saveError', 'Could not change the assigner setting.'),\n }),\n ),\n context: employeeMutationContext(emp.id),\n mutationPayload: payload,\n })\n flash(\n nextValue\n ? t('staff.time_tracking.assigners.enabled', 'Member is now an assigner.')\n : t('staff.time_tracking.assigners.disabled', 'Member is no longer an assigner.'),\n 'success',\n )\n setReloadToken((token) => token + 1)\n } catch (toggleError) {\n if (surfaceRecordConflict(toggleError, t, { onRefresh: () => setReloadToken((token) => token + 1) })) return\n flash(\n toggleError instanceof Error && toggleError.message\n ? toggleError.message\n : t('staff.time_tracking.assigners.saveError', 'Could not change the assigner setting.'),\n 'error',\n )\n }\n }, [projectId, employeeMutationContext, runMutation, t])\n\n // --- Add employee dialog ---\n const openAddDialog = React.useCallback(() => {\n setAddStaffMemberId(null)\n setAddRole('')\n setAddStartDate(new Date().toISOString().slice(0, 10))\n setStaffLookupError(null)\n setAddDialogOpen(true)\n }, [])\n\n // A failed lookup is not an empty roster: swallowing it here left the picker\n // claiming \"No team members found\" while the request had actually errored.\n const staffLookupErrorMessage = t(\n 'staff.timesheets.projects.employees.loadError',\n 'Could not load team members. Check your connection and try again.',\n )\n\n const fetchStaffMembers = React.useCallback(async (query: string) => {\n const params = new URLSearchParams({ search: query, pageSize: '20', isActive: 'true' })\n try {\n const payload = await readApiResultOrThrow<StaffMembersResponse>(\n `/api/staff/team-members?${params.toString()}`,\n undefined,\n { errorMessage: staffLookupErrorMessage, fallback: { items: [] } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setStaffLookupError(null)\n return items.map((member) => ({\n id: member.id,\n title: member.display_name ?? member.displayName ?? member.id,\n subtitle: member.team?.name ?? null,\n }))\n } catch (lookupError) {\n logger.error('staff.timesheets.projects.employees.lookup', { err: lookupError })\n setStaffLookupError(staffLookupErrorMessage)\n throw lookupError\n }\n }, [staffLookupErrorMessage])\n\n const handleAddEmployee = React.useCallback(async () => {\n if (!projectId || !addStaffMemberId || !addStartDate) return\n const payload = {\n staffMemberId: addStaffMemberId,\n timeProjectId: projectId,\n role: addRole.trim() || null,\n assignedStartDate: addStartDate,\n }\n setAddSaving(true)\n try {\n await runMutation({\n operation: () =>\n createCrud(`staff/timesheets/time-projects/${projectId}/employees`, payload, {\n errorMessage: t('staff.timesheets.projects.employees.addError', 'Failed to add employee.'),\n }),\n context: employeeMutationContext(addStaffMemberId),\n mutationPayload: payload,\n })\n flash(t('staff.timesheets.projects.employees.added', 'Employee added.'), 'success')\n setAddDialogOpen(false)\n setReloadToken((token) => token + 1)\n } catch (addError) {\n if (surfaceRecordConflict(addError, t)) return\n flash(addError instanceof Error ? addError.message : t('staff.timesheets.projects.employees.addError', 'Failed to add employee.'), 'error')\n } finally {\n setAddSaving(false)\n }\n }, [projectId, addStaffMemberId, addRole, addStartDate, employeeMutationContext, runMutation, t])\n\n // Screen 5: the team drawer lives behind `?panel=team` so the project form\n // link and the access-request notification (D-6, `&requesterUserId=`) can both\n // deep-link into it. Assignment is a Team-Leader affordance, so the panel only\n // mounts for holders of `staff.timesheets.projects.manage`.\n const teamPanelRequested = searchParams?.get('panel') === 'team'\n const teamDrawerOpen = teamPanelRequested && canManageProjects\n const requesterUserId = searchParams?.get('requesterUserId') ?? null\n\n const openTeamDrawer = React.useCallback(() => {\n router.replace(`${pathname}?panel=team`)\n }, [pathname, router])\n\n const handleTeamDrawerOpenChange = React.useCallback((next: boolean) => {\n if (next) openTeamDrawer()\n else router.replace(pathname ?? BACK_HREF)\n }, [openTeamDrawer, pathname, router])\n\n const handleTeamSaved = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const handleAddDialogKeyDown = React.useCallback((event: React.KeyboardEvent) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n void handleAddEmployee()\n }\n }, [handleAddEmployee])\n\n // --- Render ---\n // Screen 17 \u2014 the caller may not open this project. The guard state names\n // neither the customer nor the project (note 1), and the surrounding shell\n // (sidebar, topbar) is untouched because the caller still has other projects\n // (note 3). It is checked before `loading` and only cleared by a response that\n // grants access, so a background reload (e.g. the organization scope\n // resolving late) keeps the guard mounted instead of resetting a request the\n // user has just sent.\n if (accessDenied) {\n return <NoProjectAccess timeProjectId={projectId} />\n }\n\n if (loading || settledProjectId !== (projectId ?? null)) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('staff.timesheets.projects.loading', 'Loading project...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('staff.timesheets.projects.errors.notFound', 'Project not found.')}\n backHref={BACK_HREF}\n backLabel={t('staff.timesheets.projects.actions.backToList', 'Back to projects')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !project) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error ?? t('staff.timesheets.projects.errors.load', 'Failed to load project.')} />\n </PageBody>\n </Page>\n )\n }\n\n const projectStatus = project.status ?? 'active'\n const projectType = project.projectType ?? project.project_type ?? null\n const projectStartDate = project.startDate ?? project.start_date ?? null\n const projectCode = project.code\n\n // Everything below already arrives in the list projection this page reads with\n // `?ids=`; the card used to render five of the ~22 columns and drop the rest,\n // so rate, budget, customer and cost centre were only visible by opening Edit.\n const readString = (...keys: string[]): string | null => {\n for (const key of keys) {\n const raw = (project as Record<string, unknown>)[key]\n if (typeof raw === 'string' && raw.trim().length > 0) return raw.trim()\n if (typeof raw === 'number' && Number.isFinite(raw)) return String(raw)\n }\n return null\n }\n const readNumber = (...keys: string[]): number | null => {\n for (const key of keys) {\n const raw = (project as Record<string, unknown>)[key]\n const parsed = typeof raw === 'number' ? raw : typeof raw === 'string' ? Number(raw) : NaN\n if (Number.isFinite(parsed)) return parsed\n }\n return null\n }\n const readBool = (...keys: string[]): boolean | null => {\n for (const key of keys) {\n const raw = (project as Record<string, unknown>)[key]\n if (typeof raw === 'boolean') return raw\n }\n return null\n }\n\n const enrichment = ((project as Record<string, unknown>)._staff ?? {}) as Record<string, unknown>\n const readEnriched = (key: string): number | null => {\n const raw = enrichment[key]\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : null\n }\n\n const customerSnapshot = ((project as Record<string, unknown>).customerSnapshot\n ?? (project as Record<string, unknown>).customer_snapshot) as Record<string, unknown> | null | undefined\n const customerName =\n (typeof customerSnapshot?.name === 'string' ? customerSnapshot.name : null)\n ?? (typeof enrichment.customerName === 'string' ? enrichment.customerName : null)\n const customerId = readString('customerId', 'customer_id')\n const projectColor = readString('color')\n const costCenter = readString('costCenter', 'cost_center')\n const currencyCode = readString('currencyCode', 'currency_code')\n const hourlyRate = readNumber('hourlyRate', 'hourly_rate')\n const billableByDefault = readBool('billableByDefault', 'billable_by_default')\n const createdAt = readString('createdAt', 'created_at')\n const updatedAt = readString('updatedAt', 'updated_at')\n const entryCount = readNumber('entryCount')\n const currencyLocked = readBool('currencyLocked') ?? false\n const lockedEntryCount = readNumber('lockedEntryCount')\n const totalMinutes = readEnriched('totalMinutes') ?? 0\n const billableMinutes = readEnriched('billableMinutes')\n const cost = readEnriched('cost')\n const nonBillableMinutes = billableMinutes === null ? null : Math.max(0, totalMinutes - billableMinutes)\n const billablePercent =\n billableMinutes === null || totalMinutes <= 0 ? null : Math.round((billableMinutes / totalMinutes) * 100)\n const hoursTrend = Array.isArray(enrichment.hoursTrend)\n ? (enrichment.hoursTrend as unknown[]).filter((value): value is number => typeof value === 'number')\n : []\n\n const budgetKind = readString('budgetKind', 'budget_kind')\n const budgetValue = readNumber('budgetValue', 'budget_value')\n const budgetWarnAt = readNumber('budgetWarnAtPercent', 'budget_warn_at_percent')\n const budgetBurn = computeBudgetBurn({\n kind: budgetKind as 'none' | 'hours' | 'amount' | null,\n budgetValue,\n warnAtPercent: budgetWarnAt,\n totalMinutes,\n cost,\n })\n\n const formatHours = (minutes: number | null): string => {\n if (minutes === null) return '\u2014'\n const sign = minutes < 0 ? '-' : ''\n const abs = Math.abs(minutes)\n return `${sign}${Math.floor(abs / 60)}:${String(abs % 60).padStart(2, '0')}`\n }\n const formatDateTime = (value: string | null): string => {\n if (!value) return '\u2014'\n // The query engine returns `2026-05-24 00:00:00+00` \u2014 a space separator and a\n // two-digit offset, neither of which `Date` accepts, so both need widening\n // before parsing or every timestamp renders raw.\n //\n // The offset is only widened when a clock precedes it: anchoring on the\n // trailing `-24` alone also matches the day of a date-only `2026-05-24`,\n // which turns a valid date into `2026-05-24:00` and renders it raw.\n const normalized = value\n .replace(' ', 'T')\n .replace(/(T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?)([+-]\\d{2})$/, '$1$2:00')\n const parsed = new Date(normalized)\n return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString()\n }\n const budgetUsageLabel = budgetBurn\n ? budgetBurn.kind === 'hours'\n ? t('staff.timesheets.projects.detail.budgetHours', '{percent}% of {value} h', {\n percent: budgetBurn.percent,\n value: budgetBurn.budgetValue,\n })\n : t('staff.timesheets.projects.detail.budgetAmount', '{percent}% of {value}', {\n percent: budgetBurn.percent,\n value: formatCurrency(budgetBurn.budgetValue, currencyCode ?? undefined) ?? String(budgetBurn.budgetValue),\n })\n : null\n\n const injectedTabs = injectedTabWidgets\n .filter((widget) => (widget.placement?.kind ?? 'tab') === 'tab')\n .map((widget) => {\n const tabId = widget.placement?.groupId ?? widget.widgetId\n const groupLabel = widget.placement?.groupLabel\n const label = groupLabel ? t(groupLabel, groupLabel) : widget.module.metadata.title ?? tabId\n const priority = typeof widget.placement?.priority === 'number' ? widget.placement.priority : 0\n return { tabId, label, priority, Widget: widget.module.Widget }\n })\n .sort((left, right) => right.priority - left.priority)\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n {/*\n * Identity first: colour, name, and the two states worth knowing before\n * anything else \u2014 is it running, and is the budget in trouble. The code,\n * customer, type and start date sit under it as one meta line rather than\n * four labelled rows, because none of them is a decision on its own.\n */}\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <Button variant=\"ghost\" size=\"icon\" asChild>\n <Link href={BACK_HREF}>\n <ArrowLeft className=\"h-4 w-4\" aria-hidden />\n </Link>\n </Button>\n <div className=\"flex min-w-0 flex-col gap-1.5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className=\"size-3 shrink-0 rounded-sm border border-border\"\n style={{ backgroundColor: resolveProjectColorHex(projectColor, project.name) }}\n aria-hidden\n />\n <h1 className=\"text-xl font-semibold\">{project.name}</h1>\n <Badge\n variant={projectStatus === 'active' ? 'success' : projectStatus === 'on_hold' ? 'warning' : 'neutral'}\n className=\"capitalize\"\n >\n {projectStatus}\n </Badge>\n {canSeeMoney && budgetBurn && budgetBurn.tone !== 'accent' ? (\n <Badge variant={budgetBurn.tone === 'destructive' ? 'destructive' : 'warning'}>\n {t('staff.timesheets.projects.detail.budgetBadge', 'Budget {percent}%', {\n percent: budgetBurn.percent,\n })}\n </Badge>\n ) : null}\n <InjectionSpot\n spotId={extensionPoints.hosts.projectDetailStatusBadges.spotId}\n context={projectInjectionContext}\n data={project}\n />\n </div>\n <div className=\"flex flex-wrap items-center gap-2 text-sm text-muted-foreground\">\n <span className=\"rounded-sm border border-border bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground\">\n {projectCode}\n </span>\n {customerName ? (\n <>\n <span aria-hidden>\u00B7</span>\n {customerId ? (\n <Link className=\"underline underline-offset-2\" href={`/backend/customers/companies/${customerId}`}>\n {customerName}\n </Link>\n ) : (\n <span>{customerName}</span>\n )}\n </>\n ) : null}\n {projectType ? (<><span aria-hidden>\u00B7</span><span>{projectType}</span></>) : null}\n {projectStartDate ? (\n <>\n <span aria-hidden>\u00B7</span>\n <span>\n {t('staff.timesheets.projects.detail.startedOn', 'Started {date}', {\n date: formatDateTime(projectStartDate),\n })}\n </span>\n </>\n ) : null}\n </div>\n </div>\n </div>\n {canManageProjects && (\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={openTeamDrawer}>\n <Users className=\"mr-2 h-4 w-4\" aria-hidden />\n {t('staff.time_tracking.projects.team.manage', 'Manage team')}\n </Button>\n <Button variant=\"outline\" size=\"sm\" asChild>\n <Link href={`/backend/staff/time-tracking/projects/${projectId}/edit`}>\n <Pencil className=\"mr-2 h-4 w-4\" aria-hidden />\n {t('staff.timesheets.projects.form.actions.edit', 'Edit Project')}\n </Link>\n </Button>\n </div>\n )}\n <InjectionSpot\n spotId={extensionPoints.hosts.projectDetailHeader.spotId}\n context={projectInjectionContext}\n data={project}\n />\n </div>\n\n {/*\n * Summary before detail. Four figures across the full width, in the order\n * somebody asks them: how much time, how much of it billable, what it\n * cost, how close the budget is. The three stacked label/value cards this\n * replaces gave all fourteen fields identical weight.\n */}\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-4\">\n <KpiCard\n title={t('staff.timesheets.projects.detail.loggedHours', 'Logged hours')}\n value={totalMinutes / 60}\n formatValue={() => formatHours(totalMinutes)}\n footer={\n hoursTrend.length > 0 ? (\n <Sparkline\n values={hoursTrend}\n ariaLabel={t('staff.timesheets.projects.detail.trendAria', 'Hours per week, last 7 weeks')}\n />\n ) : undefined\n }\n headerAction={\n entryCount !== null ? (\n <span className=\"text-xs text-muted-foreground\">\n {entryCount === 1\n ? t('staff.timesheets.projects.detail.entryCountOne', '1 entry')\n : t('staff.timesheets.projects.detail.entryCountShort', '{count} entries', { count: entryCount })}\n </span>\n ) : undefined\n }\n />\n <KpiCard\n title={t('staff.timesheets.projects.detail.billableHours', 'Billable hours')}\n value={(billableMinutes ?? 0) / 60}\n formatValue={() => formatHours(billableMinutes)}\n headerAction={\n billablePercent !== null ? (\n <Badge variant={billablePercent >= 80 ? 'success' : 'neutral'}>{`${billablePercent}%`}</Badge>\n ) : undefined\n }\n footer={\n nonBillableMinutes !== null ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.timesheets.projects.detail.nonBillable', '{hours} non-billable', {\n hours: formatHours(nonBillableMinutes),\n })}\n </span>\n ) : undefined\n }\n />\n {canSeeMoney ? (\n <KpiCard\n title={t('staff.timesheets.projects.detail.cost', 'Cost to date')}\n value={cost}\n formatValue={(value) => formatCurrency(value, currencyCode ?? undefined) ?? String(value)}\n footer={\n hourlyRate !== null ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.timesheets.projects.detail.costBasis', '{hours} \u00D7 {rate}', {\n hours: formatHours(billableMinutes),\n rate: `${formatCurrency(hourlyRate, currencyCode ?? undefined) ?? hourlyRate}/h`,\n })}\n </span>\n ) : undefined\n }\n />\n ) : null}\n {canSeeMoney ? (\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.form.budget', 'Budget')}\n </span>\n {budgetBurn ? (\n <Badge\n variant={\n budgetBurn.tone === 'destructive'\n ? 'destructive'\n : budgetBurn.tone === 'warning'\n ? 'warning'\n : 'neutral'\n }\n >\n {`${budgetBurn.percent}%`}\n </Badge>\n ) : (\n <Badge variant=\"neutral\">{t('staff.timesheets.projects.detail.noBudget', 'No budget')}</Badge>\n )}\n </div>\n <div className=\"mt-2 space-y-2\">\n <p className=\"text-2xl font-semibold tabular-nums\">\n {budgetBurn ? `${budgetBurn.percent}%` : '\u2014'}\n </p>\n <ProjectBudgetCell\n burn={budgetBurn}\n usageLabel={budgetUsageLabel}\n noBudgetLabel={t('staff.timesheets.projects.detail.budgetHint', 'Set one to track burn')}\n ariaLabel={t('staff.timesheets.projects.detail.budgetUsage', 'Budget usage')}\n />\n </div>\n </div>\n ) : null}\n </div>\n\n {/*\n * Working content left, reference right. Everything static \u2014 rate,\n * currency, cost centre, timestamps \u2014 collapses into one rail instead of\n * three full-width cards, which is what left the right half of a wide\n * screen carrying nothing.\n */}\n <div className=\"grid items-start gap-4 lg:grid-cols-[minmax(0,2fr)_minmax(280px,1fr)]\">\n <Tabs\n value={activeTab}\n onValueChange={setActiveTab}\n variant=\"underline\"\n className=\"min-w-0\"\n >\n <TabsList aria-label={t('staff.timesheets.projects.detail.tabs', 'Project sections')}>\n <TabsTrigger value=\"team\" count={activeCount}>\n {t('staff.timesheets.projects.detail.tabTeam', 'Team')}\n </TabsTrigger>\n <TabsTrigger value=\"time\" count={entryCount ?? undefined}>\n {t('staff.timesheets.projects.detail.tabTime', 'Time')}\n </TabsTrigger>\n <TabsTrigger value=\"tasks\">\n {t('staff.timesheets.projects.detail.tabTasks', 'Tasks')}\n </TabsTrigger>\n {injectedTabs.map((tab) => (\n <TabsTrigger key={tab.tabId} value={tab.tabId}>\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n\n <TabsContent value=\"team\">\n {/* Assigned Employees \u2014 collapsible cards */}\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"sr-only\">\n {t('staff.timesheets.projects.employees.title', 'Assigned Employees')}\n </h2>\n <span />\n {canManageProjects && (\n <Button size=\"sm\" onClick={openAddDialog}>\n <Plus className=\"mr-2 h-4 w-4\" aria-hidden />\n {t('staff.timesheets.projects.add_employee', 'Add Employee')}\n </Button>\n )}\n </div>\n\n {employeesLoading ? (\n <LoadingMessage label={t('staff.timesheets.projects.employees.loading', 'Loading employees...')} />\n ) : employees.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-8 text-center\">\n <User className=\"mx-auto h-8 w-8 text-muted-foreground/50\" aria-hidden />\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('staff.timesheets.projects.employees.empty', 'No employees assigned yet.')}\n </p>\n </div>\n ) : (\n <div className=\"space-y-2\">\n {employees.map((emp) => {\n const isExpanded = expandedCards.has(emp.id)\n const isInactive = emp.status !== 'active'\n return (\n <div\n key={emp.id}\n className={`rounded-lg border ${isInactive ? 'opacity-60' : ''}`}\n >\n {/* Collapsed view: name, role, status badge, start date, expand toggle */}\n <button\n type=\"button\"\n className=\"flex w-full items-center justify-between p-4 text-left hover:bg-muted/50 transition-colors\"\n onClick={() => toggleCard(emp.id)}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted\">\n <User className=\"h-4 w-4 text-muted-foreground\" aria-hidden />\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium truncate\">\n {emp.displayName ?? emp.staffMemberId}\n </p>\n {emp.role ? (\n <p className=\"text-xs text-muted-foreground truncate\">{emp.role}</p>\n ) : null}\n </div>\n </div>\n <div className=\"flex items-center gap-3 shrink-0\">\n {emp.isAssigner ? (\n <Badge variant=\"info\" data-testid={`project-member-assigner-badge-${emp.id}`}>\n {t('staff.time_tracking.assigners.badge', 'Assigner')}\n </Badge>\n ) : null}\n <Badge\n variant={(emp.status ?? 'active') === 'active' ? 'success' : 'neutral'}\n className=\"capitalize\"\n >\n {emp.status ?? 'active'}\n </Badge>\n {emp.assignedStartDate ? (\n <span className=\"text-xs text-muted-foreground hidden sm:inline\">\n {emp.assignedStartDate}\n </span>\n ) : null}\n <ChevronDown className={`h-4 w-4 text-muted-foreground transition-transform ${isExpanded ? 'rotate-180' : ''}`} aria-hidden />\n </div>\n </button>\n\n {/* Expanded view: assignment details */}\n {isExpanded ? (\n <div className=\"border-t px-4 pb-4 pt-3 space-y-3\">\n <dl className=\"grid grid-cols-2 gap-3 text-sm\">\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.role', 'Role')}\n </dt>\n <dd>{emp.role || '-'}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.status', 'Status')}\n </dt>\n <dd className=\"capitalize\">{emp.status ?? 'active'}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.startDate', 'Assignment start')}\n </dt>\n <dd>{emp.assignedStartDate || '-'}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.endDate', 'Assignment end')}\n </dt>\n <dd>{emp.assignedEndDate || '-'}</dd>\n </div>\n {emp.teamName ? (\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.department', 'Department')}\n </dt>\n <dd>{emp.teamName}</dd>\n </div>\n ) : null}\n <div className=\"col-span-2\">\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.time_tracking.assigners.label', 'Assigner')}\n </dt>\n <dd className=\"flex items-center gap-2 pt-1\">\n <Switch\n checked={emp.isAssigner}\n disabled={!canManageProjects || emp.hasUserAccount === false}\n onCheckedChange={(checked) => { void handleToggleAssigner(emp, checked === true) }}\n aria-label={t('staff.time_tracking.assigners.label', 'Assigner')}\n data-testid={`project-member-assigner-switch-${emp.id}`}\n />\n <span className=\"text-xs text-muted-foreground\">\n {emp.hasUserAccount === false\n ? t('staff.time_tracking.assigners.needsUser', 'Only members with a user account can be assigners.')\n : t('staff.time_tracking.assigners.help', 'Assigners are notified about new and released work and can claim unassigned tasks.')}\n </span>\n </dd>\n </div>\n </dl>\n {canManageProjects && (\n <div className=\"flex justify-end\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"text-destructive hover:text-destructive\"\n onClick={() => { void handleRemoveEmployee(emp) }}\n >\n <Trash2 className=\"mr-2 h-3.5 w-3.5\" aria-hidden />\n {t('staff.timesheets.projects.employees.remove', 'Remove')}\n </Button>\n </div>\n )}\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n )}\n </div>\n </TabsContent>\n\n <TabsContent value=\"time\">\n {recentEntriesLoading ? (\n <LoadingMessage label={t('staff.timesheets.projects.detail.loadingTime', 'Loading time entries\u2026')} />\n ) : recentEntries.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-8 text-center\">\n <p className=\"text-sm font-medium\">\n {t('staff.timesheets.projects.detail.noTime', 'No time logged yet')}\n </p>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t('staff.timesheets.projects.detail.noTimeHint', 'Hours logged against this project appear here.')}\n </p>\n </div>\n ) : (\n <DataTable<ProjectEntryRow>\n extensionTableId={extensionPoints.hosts.projectDetailTimeEntriesTable.tableId}\n disableRowClick\n columns={recentEntriesColumns}\n data={recentEntries}\n />\n )}\n </TabsContent>\n\n <TabsContent value=\"tasks\">\n {projectTasksLoading ? (\n <LoadingMessage label={t('staff.timesheets.projects.detail.loadingTasks', 'Loading tasks\u2026')} />\n ) : projectTasks.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-8 text-center\">\n <p className=\"text-sm font-medium\">\n {t('staff.timesheets.projects.detail.noTasks', 'No tasks yet')}\n </p>\n </div>\n ) : (\n <div className=\"divide-y rounded-lg border\">\n {projectTasks.map((task) => (\n <div key={task.id} className=\"flex items-center gap-3 px-3 py-2 text-sm\">\n {task.reference ? (\n <span className=\"shrink-0 rounded-sm border border-border bg-muted px-1.5 py-0.5 font-mono text-xs\">\n {task.reference}\n </span>\n ) : null}\n <span className=\"min-w-0 flex-1 truncate\">{task.title}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">\n {employeeNameById.get(task.assigneeStaffMemberId ?? '') ?? ''}\n </span>\n {task.loggedMinutes !== null ? (\n <span className=\"shrink-0 font-mono text-xs tabular-nums text-muted-foreground\">\n {formatHours(task.loggedMinutes)}\n </span>\n ) : null}\n </div>\n ))}\n </div>\n )}\n </TabsContent>\n\n {injectedTabs.map((tab) => (\n <TabsContent key={tab.tabId} value={tab.tabId}>\n <tab.Widget context={projectInjectionContext} data={project} />\n </TabsContent>\n ))}\n </Tabs>\n\n {/* Reference rail \u2014 one card, grouped, no longer three stacked sections. */}\n <div className=\"rounded-lg border bg-card\">\n <div className=\"border-b px-4 py-3\">\n <h2 className=\"text-sm font-semibold\">\n {t('staff.timesheets.projects.detail.details', 'Details')}\n </h2>\n </div>\n <div className=\"px-4 py-3\">\n {canSeeMoney ? (\n <>\n <RailGroup label={t('staff.timesheets.projects.detail.commercials', 'Billing')} first />\n <dl>\n <RailFact\n label={t('staff.timesheets.projects.form.hourlyRate', 'Hourly rate')}\n value={\n hourlyRate === null\n ? '\u2014'\n : `${formatCurrency(hourlyRate, currencyCode ?? undefined) ?? hourlyRate} / h`\n }\n />\n <RailFact\n label={t('staff.timesheets.projects.form.currency', 'Currency')}\n value={\n <span className=\"inline-flex items-center gap-1.5\">\n {currencyCode ?? '\u2014'}\n {currencyLocked ? (\n <Badge\n variant=\"neutral\"\n title={t(\n 'staff.timesheets.projects.detail.currencyLockedHint',\n 'Locked because hours are already logged against this project.',\n )}\n >\n {t('staff.timesheets.projects.detail.locked', 'locked')}\n </Badge>\n ) : null}\n </span>\n }\n />\n <RailFact\n label={t('staff.timesheets.projects.form.billableByDefault', 'Billable by default')}\n value={\n billableByDefault === null ? (\n '\u2014'\n ) : (\n <Badge variant={billableByDefault ? 'success' : 'neutral'}>\n {billableByDefault\n ? t('staff.timesheets.projects.detail.yes', 'Yes')\n : t('staff.timesheets.projects.detail.no', 'No')}\n </Badge>\n )\n }\n />\n </dl>\n </>\n ) : null}\n\n <RailGroup\n label={t('staff.timesheets.projects.detail.delivery', 'Delivery')}\n first={!canSeeMoney}\n />\n <dl>\n <RailFact\n label={t('staff.timesheets.projects.detail.entryCount', 'Time entries')}\n value={entryCount ?? '\u2014'}\n />\n <RailFact\n label={t('staff.timesheets.projects.detail.lockedEntries', 'Locked in reports')}\n value={lockedEntryCount ?? '\u2014'}\n />\n </dl>\n\n <RailGroup label={t('staff.timesheets.projects.detail.administration', 'Administration')} />\n <dl>\n <RailFact\n label={t('staff.timesheets.projects.form.costCenter', 'Cost center')}\n value={costCenter ?? '\u2014'}\n />\n <RailFact\n label={t('staff.timesheets.projects.form.color', 'Project color')}\n value={\n <span className=\"inline-flex items-center gap-1.5 capitalize\">\n <span\n className=\"size-2.5 rounded-full border border-border\"\n style={{ backgroundColor: resolveProjectColorHex(projectColor, project.name) }}\n aria-hidden\n />\n {projectColor ?? t('staff.timesheets.projects.detail.autoColor', 'Auto')}\n </span>\n }\n />\n <RailFact\n label={t('staff.timesheets.projects.detail.createdAt', 'Created')}\n value={formatDateTime(createdAt)}\n />\n <RailFact\n label={t('staff.timesheets.projects.detail.updatedAt', 'Last updated')}\n value={formatDateTime(updatedAt)}\n />\n </dl>\n\n {project.description ? (\n <>\n <RailGroup label={t('staff.timesheets.projects.form.description', 'Description')} />\n <p className=\"whitespace-pre-wrap pt-2 text-sm text-muted-foreground\">{project.description}</p>\n </>\n ) : null}\n <InjectionSpot\n spotId={extensionPoints.hosts.projectDetailSidebar.spotId}\n context={projectInjectionContext}\n data={project}\n />\n </div>\n </div>\n </div>\n <InjectionSpot\n spotId={extensionPoints.hosts.projectDetailFooter.spotId}\n context={projectInjectionContext}\n data={project}\n />\n </div>\n\n {ConfirmDialogElement}\n\n {projectId && teamDrawerOpen ? (\n <ProjectTeamDrawer\n projectId={projectId}\n projectName={project.name}\n open={teamDrawerOpen}\n onOpenChange={handleTeamDrawerOpenChange}\n preselectUserId={requesterUserId}\n onSaved={handleTeamSaved}\n />\n ) : null}\n\n {/* Add Employee Dialog */}\n <Dialog open={addDialogOpen} onOpenChange={setAddDialogOpen}>\n <DialogContent className=\"sm:max-w-md\" onKeyDown={handleAddDialogKeyDown}>\n <DialogHeader>\n <DialogTitle>{t('staff.timesheets.projects.add_employee', 'Add Employee')}</DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4 pt-2\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('staff.timesheets.projects.employees.selectEmployee', 'Select Employee')}\n </label>\n <LookupSelect\n value={addStaffMemberId}\n onChange={setAddStaffMemberId}\n fetchItems={fetchStaffMembers}\n searchPlaceholder={t('staff.timesheets.projects.employees.searchEmployee', 'Search team members...')}\n emptyLabel={t('staff.timesheets.projects.employees.noResults', 'No team members found')}\n />\n {staffLookupError ? (\n <p className=\"text-xs text-status-error-text\" role=\"alert\">\n {staffLookupError}\n </p>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"add-role\">\n {t('staff.timesheets.projects.employees.roleOnProject', 'Role on Project')}\n </label>\n <Input\n id=\"add-role\"\n value={addRole}\n onChange={(event) => setAddRole(event.target.value)}\n placeholder={t('staff.timesheets.projects.employees.rolePlaceholder', 'e.g. Developer, Designer...')}\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"add-start-date\">\n {t('staff.timesheets.projects.employees.assignmentStartDate', 'Assignment Start Date')}\n </label>\n <Input\n id=\"add-start-date\"\n type=\"date\"\n value={addStartDate}\n onChange={(event) => setAddStartDate(event.target.value)}\n />\n </div>\n <div className=\"flex justify-end gap-2 pt-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => setAddDialogOpen(false)}>\n {t('staff.timesheets.projects.form.actions.cancel', 'Cancel')}\n </Button>\n <Button\n size=\"sm\"\n disabled={addSaving || !addStaffMemberId || !addStartDate}\n onClick={handleAddEmployee}\n >\n {addSaving ? <Spinner className=\"size-4\" /> : null}\n {t('staff.timesheets.projects.add_employee', 'Add Employee')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { LookupSelect } from '@open-mercato/ui/backend/inputs/LookupSelect'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, createCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { ErrorMessage, RecordNotFoundState } from '@open-mercato/ui/backend/detail'\nimport { ArrowLeft, Plus, Pencil, ChevronDown, Trash2, User, Users } from 'lucide-react'\nimport Link from 'next/link'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { KpiCard, Sparkline } from '@open-mercato/ui/backend/charts'\nimport { ProjectTeamDrawer } from '../../../../../lib/time-tracking-ui/ProjectTeamDrawer'\nimport { NoProjectAccess } from '../../../../../lib/time-tracking-ui/NoProjectAccess'\nimport { ProjectBudgetCell } from '../../../../../lib/timesheets-projects-ui/ProjectBudgetCell'\nimport { computeBudgetBurn } from '../../../../../lib/timesheets-projects/budgetBurn'\nimport { resolveProjectColorHex } from '../../../../../lib/timesheets-ui/colors'\n\nconst logger = createLogger('staff')\n\nconst BACK_HREF = '/backend/staff/time-tracking/projects'\n\nconst EMPLOYEE_MUTATION_CONTEXT_ID = 'staff-time-project-detail:employees'\nconst EMPLOYEE_RESOURCE_KIND = 'staff.time_project_member'\n\ntype EmployeeMutationContext = {\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n}\n\n/**\n * Mirrors `NO_PROJECT_ACCESS_REASON` from\n * `api/timesheets/time-projects/route.ts`. The route module is server-only, so\n * the literal is restated here rather than imported into the client bundle;\n * `__tests__/no-project-access-contract.test.ts` pins the two together.\n */\nconst NO_PROJECT_ACCESS_REASON = 'no_project_access'\n\nconst PROJECT_ENTITY_ID = 'staff:staff_time_project'\n\ntype ProjectDetailInjectionContext = {\n entityId: string\n recordId: string | null\n projectId: string | null\n path: string | null\n retryLastMutation: () => Promise<boolean>\n}\n\ntype ProjectRecord = {\n id: string\n name: string\n code: string\n description?: string | null\n projectType?: string | null\n project_type?: string | null\n startDate?: string | null\n start_date?: string | null\n costCenter?: string | null\n cost_center?: string | null\n status?: string | null\n customerId?: string | null\n customer_id?: string | null\n} & Record<string, unknown>\n\ntype ProjectResponse = {\n items?: ProjectRecord[]\n}\n\nfunction readDenialReason(payload: unknown): string | null {\n if (!payload || typeof payload !== 'object') return null\n const reason = (payload as { reason?: unknown }).reason\n return typeof reason === 'string' ? reason : null\n}\n\ntype EmployeeAssignment = {\n id: string\n staffMemberId: string\n role: string | null\n status: string | null\n assignedStartDate: string | null\n assignedEndDate: string | null\n displayName: string | null\n teamName: string | null\n isAssigner: boolean\n hasUserAccount: boolean | null\n updatedAt: string | null\n}\n\ntype EmployeesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n}\n\ntype StaffMemberRecord = {\n id: string\n display_name?: string\n displayName?: string\n user_id?: string | null\n userId?: string | null\n team?: { id: string; name: string } | null\n}\n\ntype StaffMembersResponse = {\n items?: StaffMemberRecord[]\n}\n\ntype ProjectEntryRow = {\n id: string\n date: string\n description: string | null\n durationMinutes: number\n isBillable: boolean\n taskId: string | null\n staffMemberId: string | null\n}\n\ntype ProjectTaskRow = {\n id: string\n reference: string | null\n title: string\n statusId: string | null\n assigneeStaffMemberId: string | null\n loggedMinutes: number | null\n}\n\n/**\n * A group heading inside the reference rail. The rail replaces three full-width\n * cards, so the groups carry the separation those card borders used to.\n */\nfunction RailGroup({ label, first }: { label: string; first?: boolean }) {\n return (\n <div\n className={`border-b pb-1.5 text-overline font-semibold uppercase tracking-widest text-muted-foreground ${\n first ? '' : 'pt-4'\n }`}\n >\n {label}\n </div>\n )\n}\n\nfunction RailFact({ label, value }: { label: string; value: React.ReactNode }) {\n return (\n <div className=\"flex items-baseline justify-between gap-3 border-b py-2 text-sm last:border-b-0\">\n <dt className=\"shrink-0 text-muted-foreground\">{label}</dt>\n <dd className=\"text-right font-medium tabular-nums\">{value}</dd>\n </div>\n )\n}\n\nexport default function TimesheetProjectDetailPage({ params }: { params?: { id?: string } }) {\n const projectId = params?.id\n const t = useT()\n const translateRef = React.useRef(t)\n React.useEffect(() => { translateRef.current = t }, [t])\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n\n const [project, setProject] = React.useState<ProjectRecord | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n // Keyed by project id so a route change to another project never shows the\n // previous project's guard (with a live request button) while the new one loads.\n const [accessDeniedProjectId, setAccessDeniedProjectId] = React.useState<string | null>(null)\n const accessDenied = accessDeniedProjectId !== null && accessDeniedProjectId === projectId\n // The id the last finished load was for. Until it matches the route, the page\n // is still loading \u2014 never the previous project's error or empty state.\n const [settledProjectId, setSettledProjectId] = React.useState<string | null>(null)\n\n const [employees, setEmployees] = React.useState<EmployeeAssignment[]>([])\n const [employeesLoading, setEmployeesLoading] = React.useState(false)\n const [expandedCards, setExpandedCards] = React.useState<Set<string>>(new Set())\n const [reloadToken, setReloadToken] = React.useState(0)\n\n // Tabs govern the working column only \u2014 the details rail stays visible on every\n // tab, because reference facts are what you check *while* reading the tab you\n // are on, not a destination of their own.\n const [activeTab, setActiveTab] = React.useState<string>('team')\n const [recentEntries, setRecentEntries] = React.useState<ProjectEntryRow[]>([])\n const [recentEntriesLoading, setRecentEntriesLoading] = React.useState(false)\n const [projectTasks, setProjectTasks] = React.useState<ProjectTaskRow[]>([])\n const [projectTasksLoading, setProjectTasksLoading] = React.useState(false)\n\n const [canManageProjects, setCanManageProjects] = React.useState(false)\n // Money is gated on the same feature the portfolio gates it on; a rate hidden\n // in the list must not be readable one click deeper.\n const [canSeeMoney, setCanSeeMoney] = React.useState(false)\n\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n try {\n const res = await apiCall<{ ok: boolean; granted: string[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n features: ['staff.timesheets.projects.manage', 'staff.timesheets.rates.view'],\n }),\n })\n if (!cancelled) {\n const granted = new Set(res.result?.granted ?? [])\n setCanManageProjects(granted.has('staff.timesheets.projects.manage'))\n setCanSeeMoney(granted.has('staff.timesheets.rates.view'))\n }\n } catch {\n // default: no manage access, no money\n }\n })()\n return () => { cancelled = true }\n }, [])\n\n const [addDialogOpen, setAddDialogOpen] = React.useState(false)\n const [addStaffMemberId, setAddStaffMemberId] = React.useState<string | null>(null)\n const [addRole, setAddRole] = React.useState('')\n const [addStartDate, setAddStartDate] = React.useState('')\n const [addSaving, setAddSaving] = React.useState(false)\n const [staffLookupError, setStaffLookupError] = React.useState<string | null>(null)\n\n // Fetched per tab rather than up front: a project page opened to check a rate\n // should not pay for two lists nobody looked at.\n React.useEffect(() => {\n if (!projectId || activeTab !== 'time') return\n let cancelled = false\n void (async () => {\n setRecentEntriesLoading(true)\n try {\n const res = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/staff/timesheets/time-entries?projectId=${encodeURIComponent(projectId)}&page=1&pageSize=25&sortField=date&sortDir=desc`,\n )\n if (cancelled || !res.ok) return\n const items = Array.isArray(res.result?.items) ? res.result.items : []\n setRecentEntries(\n items\n .map((row): ProjectEntryRow | null => {\n const id = typeof row.id === 'string' ? row.id : null\n if (!id) return null\n const raw = row as Record<string, unknown>\n const minutes = raw.duration_minutes ?? raw.durationMinutes\n return {\n id,\n date: typeof raw.date === 'string' ? raw.date.slice(0, 10) : '',\n description:\n typeof raw.description === 'string'\n ? raw.description\n : typeof raw.notes === 'string'\n ? raw.notes\n : null,\n durationMinutes: typeof minutes === 'number' ? minutes : Number(minutes ?? 0) || 0,\n isBillable: raw.is_billable === true || raw.isBillable === true,\n taskId: typeof (raw.task_id ?? raw.taskId) === 'string' ? String(raw.task_id ?? raw.taskId) : null,\n staffMemberId:\n typeof (raw.staff_member_id ?? raw.staffMemberId) === 'string'\n ? String(raw.staff_member_id ?? raw.staffMemberId)\n : null,\n }\n })\n .filter((row): row is ProjectEntryRow => row !== null),\n )\n } catch (err) {\n logger.warn('staff.timesheets.projects.detail recent entries failed', { err })\n } finally {\n if (!cancelled) setRecentEntriesLoading(false)\n }\n })()\n return () => { cancelled = true }\n }, [projectId, activeTab, reloadToken])\n\n React.useEffect(() => {\n if (!projectId || activeTab !== 'tasks') return\n let cancelled = false\n void (async () => {\n setProjectTasksLoading(true)\n try {\n const res = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n // `timeProjectId`, not `projectId`: the tasks list schema declares the\n // former and is `.passthrough()`, so the wrong name is accepted, never\n // read, and the tab silently lists every task in the tenant.\n `/api/staff/timesheets/tasks?timeProjectId=${encodeURIComponent(projectId)}&page=1&pageSize=50&sortField=reference&sortDir=asc`,\n )\n if (cancelled || !res.ok) return\n const items = Array.isArray(res.result?.items) ? res.result.items : []\n setProjectTasks(\n items\n .map((row): ProjectTaskRow | null => {\n const id = typeof row.id === 'string' ? row.id : null\n const title = typeof row.title === 'string' ? row.title : null\n if (!id || !title) return null\n const raw = row as Record<string, unknown>\n const logged = raw.loggedMinutes ?? raw.logged_minutes\n return {\n id,\n reference: typeof raw.reference === 'string' ? raw.reference : null,\n title,\n statusId:\n typeof (raw.task_status_id ?? raw.taskStatusId) === 'string'\n ? String(raw.task_status_id ?? raw.taskStatusId)\n : null,\n assigneeStaffMemberId:\n typeof (raw.assignee_staff_member_id ?? raw.assigneeStaffMemberId) === 'string'\n ? String(raw.assignee_staff_member_id ?? raw.assigneeStaffMemberId)\n : null,\n loggedMinutes: typeof logged === 'number' ? logged : null,\n }\n })\n .filter((row): row is ProjectTaskRow => row !== null),\n )\n } catch (err) {\n logger.warn('staff.timesheets.projects.detail tasks failed', { err })\n } finally {\n if (!cancelled) setProjectTasksLoading(false)\n }\n })()\n return () => { cancelled = true }\n }, [projectId, activeTab, reloadToken])\n\n const employeeNameById = React.useMemo(\n () => new Map(employees.map((emp) => [emp.staffMemberId, emp.displayName ?? ''])),\n [employees],\n )\n const activeCount = employees.filter((emp) => emp.status === 'active').length\n const inactiveCount = employees.length - activeCount\n\n const recentEntriesColumns = React.useMemo<ColumnDef<ProjectEntryRow>[]>(\n () => [\n {\n accessorKey: 'date',\n header: t('staff.timesheets.projects.detail.colDate', 'Date'),\n cell: ({ row }) => <span className=\"whitespace-nowrap\">{row.original.date}</span>,\n },\n {\n id: 'person',\n header: t('staff.timesheets.projects.detail.colWho', 'Person'),\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap\">\n {employeeNameById.get(row.original.staffMemberId ?? '') ?? '\u2014'}\n </span>\n ),\n },\n {\n accessorKey: 'description',\n header: t('staff.timesheets.projects.detail.colDescription', 'Description'),\n cell: ({ row }) => (\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate\">{row.original.description ?? '\u2014'}</span>\n {row.original.isBillable ? null : (\n <Badge variant=\"neutral\">\n {t('staff.timesheets.projects.detail.nonBillableTag', 'non-billable')}\n </Badge>\n )}\n </span>\n ),\n },\n {\n id: 'hours',\n header: () => (\n <span className=\"block text-right\">\n {t('staff.timesheets.projects.detail.colHours', 'Hours')}\n </span>\n ),\n cell: ({ row }) => (\n <span className=\"block whitespace-nowrap text-right font-mono tabular-nums\">\n {formatHours(row.original.durationMinutes)}\n </span>\n ),\n },\n ],\n [employeeNameById, t],\n )\n\n // --- Load project ---\n React.useEffect(() => {\n if (!projectId) return\n let cancelled = false\n async function loadProject() {\n setLoading(true)\n setError(null)\n setIsNotFound(false)\n try {\n const queryParams = new URLSearchParams({ page: '1', pageSize: '1', ids: projectId! })\n const call = await apiCall<ProjectResponse>(\n `/api/staff/timesheets/time-projects?${queryParams.toString()}`,\n )\n // Screen 17: the route answers 404 with this discriminator for a project\n // the caller is not a member of, without naming it.\n if (call.status === 404 && readDenialReason(call.result) === NO_PROJECT_ACCESS_REASON) {\n if (!cancelled) setAccessDeniedProjectId(projectId ?? null)\n return\n }\n if (!cancelled) setAccessDeniedProjectId(null)\n if (!call.ok) {\n throw new Error(translateRef.current('staff.timesheets.projects.errors.load', 'Failed to load project.'))\n }\n const payload = call.result\n const record = Array.isArray(payload?.items) ? payload.items[0] : null\n if (!record) {\n if (!cancelled) setIsNotFound(true)\n return\n }\n if (!cancelled) setProject(record)\n } catch (loadError) {\n if (!cancelled) {\n setError(loadError instanceof Error ? loadError.message : translateRef.current('staff.timesheets.projects.errors.load', 'Failed to load project.'))\n }\n } finally {\n if (!cancelled) {\n setLoading(false)\n setSettledProjectId(projectId ?? null)\n }\n }\n }\n loadProject()\n return () => { cancelled = true }\n }, [projectId, scopeVersion])\n\n // --- Load employees with name resolution ---\n const loadEmployees = React.useCallback(async () => {\n if (!projectId || accessDenied) return\n setEmployeesLoading(true)\n try {\n const payload = await readApiResultOrThrow<EmployeesResponse>(\n `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=100`,\n undefined,\n { errorMessage: translateRef.current('staff.timesheets.projects.employees.empty', 'No employees assigned yet.'), fallback: { items: [], total: 0 } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n\n const staffMemberIds = items\n .map((item) => String(item.staff_member_id ?? item.staffMemberId ?? ''))\n .filter((id) => id.length > 0)\n\n let staffMap = new Map<string, StaffMemberRecord>()\n if (staffMemberIds.length > 0) {\n try {\n const staffRes = await apiCall<StaffMembersResponse>(\n `/api/staff/team-members?ids=${staffMemberIds.join(',')}&pageSize=100`,\n )\n if (staffRes.ok) {\n const staffItems = Array.isArray(staffRes.result?.items) ? staffRes.result.items : []\n staffMap = new Map(staffItems.map((member) => [member.id, member]))\n }\n } catch {\n // name resolution failed (e.g. 403 for employees) \u2014 show IDs as fallback\n }\n }\n\n const mapped: EmployeeAssignment[] = items.map((item) => {\n const staffMemberId = String(item.staff_member_id ?? item.staffMemberId ?? '')\n const staff = staffMap.get(staffMemberId)\n return {\n id: String(item.id ?? ''),\n staffMemberId,\n role: typeof item.role === 'string' ? item.role : null,\n status: typeof item.status === 'string' ? item.status : null,\n assignedStartDate: String(item.assigned_start_date ?? item.assignedStartDate ?? ''),\n assignedEndDate: typeof (item.assigned_end_date ?? item.assignedEndDate) === 'string'\n ? String(item.assigned_end_date ?? item.assignedEndDate)\n : null,\n displayName: staff?.display_name ?? staff?.displayName ?? null,\n teamName: staff?.team?.name ?? null,\n isAssigner: item.is_assigner === true || item.isAssigner === true,\n hasUserAccount: staff ? Boolean(staff.user_id ?? staff.userId) : null,\n updatedAt: typeof (item.updated_at ?? item.updatedAt) === 'string' ? String(item.updated_at ?? item.updatedAt) : null,\n }\n })\n\n setEmployees(mapped)\n } catch (loadError) {\n logger.error('staff.timesheets.projects.employees.list', { err: loadError })\n } finally {\n setEmployeesLoading(false)\n }\n }, [projectId, accessDenied])\n\n React.useEffect(() => {\n void loadEmployees()\n }, [loadEmployees, reloadToken, scopeVersion])\n\n // --- Toggle card expand/collapse ---\n const toggleCard = React.useCallback((employeeId: string) => {\n setExpandedCards((prev) => {\n const next = new Set(prev)\n if (next.has(employeeId)) next.delete(employeeId)\n else next.add(employeeId)\n return next\n })\n }, [])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<EmployeeMutationContext>({\n contextId: EMPLOYEE_MUTATION_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const projectInjectionContext = React.useMemo<ProjectDetailInjectionContext>(\n () => ({\n entityId: PROJECT_ENTITY_ID,\n recordId: projectId ?? null,\n projectId: projectId ?? null,\n path: pathname ?? null,\n retryLastMutation,\n }),\n [pathname, projectId, retryLastMutation],\n )\n\n const { widgets: injectedTabWidgets } = useInjectionWidgets<ProjectDetailInjectionContext>(\n extensionPoints.hosts.projectDetailTabs.spotId,\n { context: projectInjectionContext, triggerOnLoad: true },\n )\n\n const employeeMutationContext = React.useCallback(\n (resourceId: string): EmployeeMutationContext => ({\n formId: EMPLOYEE_MUTATION_CONTEXT_ID,\n resourceKind: EMPLOYEE_RESOURCE_KIND,\n resourceId,\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n // optimistic-lock-exempt: the employee add/remove calls below mutate the\n // time-project \u2194 staff-member assignment junction (createCrud/deleteCrud of a\n // membership row), not a shared editable aggregate. Junction add/remove is exempt\n // from OSS optimistic locking per the data-integrity contract.\n // --- Remove employee ---\n const handleRemoveEmployee = React.useCallback(async (emp: EmployeeAssignment) => {\n const confirmed = await confirm({\n title: t('staff.timesheets.projects.employees.remove', 'Remove'),\n text: t('staff.timesheets.projects.employees.removeConfirm', 'Remove this employee from the project?'),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: () =>\n deleteCrud(\n `staff/timesheets/time-projects/${projectId}/employees`,\n emp.id,\n { errorMessage: t('staff.timesheets.projects.employees.removeError', 'Failed to remove employee.') },\n ),\n context: employeeMutationContext(emp.id),\n mutationPayload: { id: emp.id },\n })\n flash(t('staff.timesheets.projects.employees.removed', 'Employee removed.'), 'success')\n setReloadToken((token) => token + 1)\n } catch (removeError) {\n if (surfaceRecordConflict(removeError, t)) return\n flash(t('staff.timesheets.projects.employees.removeError', 'Failed to remove employee.'), 'error')\n }\n }, [projectId, confirm, employeeMutationContext, runMutation, t])\n\n const handleToggleAssigner = React.useCallback(async (emp: EmployeeAssignment, nextValue: boolean) => {\n const payload = { id: emp.id, isAssigner: nextValue }\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(emp.updatedAt), () =>\n updateCrud(`staff/timesheets/time-projects/${projectId}/employees`, payload, {\n errorMessage: t('staff.time_tracking.assigners.saveError', 'Could not change the assigner setting.'),\n }),\n ),\n context: employeeMutationContext(emp.id),\n mutationPayload: payload,\n })\n flash(\n nextValue\n ? t('staff.time_tracking.assigners.enabled', 'Member is now an assigner.')\n : t('staff.time_tracking.assigners.disabled', 'Member is no longer an assigner.'),\n 'success',\n )\n setReloadToken((token) => token + 1)\n } catch (toggleError) {\n if (surfaceRecordConflict(toggleError, t, { onRefresh: () => setReloadToken((token) => token + 1) })) return\n flash(\n toggleError instanceof Error && toggleError.message\n ? toggleError.message\n : t('staff.time_tracking.assigners.saveError', 'Could not change the assigner setting.'),\n 'error',\n )\n }\n }, [projectId, employeeMutationContext, runMutation, t])\n\n // --- Add employee dialog ---\n const openAddDialog = React.useCallback(() => {\n setAddStaffMemberId(null)\n setAddRole('')\n setAddStartDate(new Date().toISOString().slice(0, 10))\n setStaffLookupError(null)\n setAddDialogOpen(true)\n }, [])\n\n // A failed lookup is not an empty roster: swallowing it here left the picker\n // claiming \"No team members found\" while the request had actually errored.\n const staffLookupErrorMessage = t(\n 'staff.timesheets.projects.employees.loadError',\n 'Could not load team members. Check your connection and try again.',\n )\n\n const fetchStaffMembers = React.useCallback(async (query: string) => {\n const params = new URLSearchParams({ search: query, pageSize: '20', isActive: 'true' })\n try {\n const payload = await readApiResultOrThrow<StaffMembersResponse>(\n `/api/staff/team-members?${params.toString()}`,\n undefined,\n { errorMessage: staffLookupErrorMessage, fallback: { items: [] } },\n )\n const items = Array.isArray(payload.items) ? payload.items : []\n setStaffLookupError(null)\n return items.map((member) => ({\n id: member.id,\n title: member.display_name ?? member.displayName ?? member.id,\n subtitle: member.team?.name ?? null,\n }))\n } catch (lookupError) {\n logger.error('staff.timesheets.projects.employees.lookup', { err: lookupError })\n setStaffLookupError(staffLookupErrorMessage)\n throw lookupError\n }\n }, [staffLookupErrorMessage])\n\n const handleAddEmployee = React.useCallback(async () => {\n if (!projectId || !addStaffMemberId || !addStartDate) return\n const payload = {\n staffMemberId: addStaffMemberId,\n timeProjectId: projectId,\n role: addRole.trim() || null,\n assignedStartDate: addStartDate,\n }\n setAddSaving(true)\n try {\n await runMutation({\n operation: () =>\n createCrud(`staff/timesheets/time-projects/${projectId}/employees`, payload, {\n errorMessage: t('staff.timesheets.projects.employees.addError', 'Failed to add employee.'),\n }),\n context: employeeMutationContext(addStaffMemberId),\n mutationPayload: payload,\n })\n flash(t('staff.timesheets.projects.employees.added', 'Employee added.'), 'success')\n setAddDialogOpen(false)\n setReloadToken((token) => token + 1)\n } catch (addError) {\n if (surfaceRecordConflict(addError, t)) return\n flash(addError instanceof Error ? addError.message : t('staff.timesheets.projects.employees.addError', 'Failed to add employee.'), 'error')\n } finally {\n setAddSaving(false)\n }\n }, [projectId, addStaffMemberId, addRole, addStartDate, employeeMutationContext, runMutation, t])\n\n // Screen 5: the team drawer lives behind `?panel=team` so the project form\n // link and the access-request notification (D-6, `&requesterUserId=`) can both\n // deep-link into it. Assignment is a Team-Leader affordance, so the panel only\n // mounts for holders of `staff.timesheets.projects.manage`.\n const teamPanelRequested = searchParams?.get('panel') === 'team'\n const teamDrawerOpen = teamPanelRequested && canManageProjects\n const requesterUserId = searchParams?.get('requesterUserId') ?? null\n\n const openTeamDrawer = React.useCallback(() => {\n router.replace(`${pathname}?panel=team`)\n }, [pathname, router])\n\n const handleTeamDrawerOpenChange = React.useCallback((next: boolean) => {\n if (next) openTeamDrawer()\n else router.replace(pathname ?? BACK_HREF)\n }, [openTeamDrawer, pathname, router])\n\n const handleTeamSaved = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const handleAddDialogKeyDown = React.useCallback((event: React.KeyboardEvent) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n void handleAddEmployee()\n }\n }, [handleAddEmployee])\n\n // --- Render ---\n // Screen 17 \u2014 the caller may not open this project. The guard state names\n // neither the customer nor the project (note 1), and the surrounding shell\n // (sidebar, topbar) is untouched because the caller still has other projects\n // (note 3). It is checked before `loading` and only cleared by a response that\n // grants access, so a background reload (e.g. the organization scope\n // resolving late) keeps the guard mounted instead of resetting a request the\n // user has just sent.\n if (accessDenied) {\n return <NoProjectAccess timeProjectId={projectId} />\n }\n\n if (loading || settledProjectId !== (projectId ?? null)) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('staff.timesheets.projects.loading', 'Loading project...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('staff.timesheets.projects.errors.notFound', 'Project not found.')}\n backHref={BACK_HREF}\n backLabel={t('staff.timesheets.projects.actions.backToList', 'Back to projects')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !project) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error ?? t('staff.timesheets.projects.errors.load', 'Failed to load project.')} />\n </PageBody>\n </Page>\n )\n }\n\n const projectStatus = project.status ?? 'active'\n const projectType = project.projectType ?? project.project_type ?? null\n const projectStartDate = project.startDate ?? project.start_date ?? null\n const projectCode = project.code\n\n // Everything below already arrives in the list projection this page reads with\n // `?ids=`; the card used to render five of the ~22 columns and drop the rest,\n // so rate, budget, customer and cost centre were only visible by opening Edit.\n const readString = (...keys: string[]): string | null => {\n for (const key of keys) {\n const raw = (project as Record<string, unknown>)[key]\n if (typeof raw === 'string' && raw.trim().length > 0) return raw.trim()\n if (typeof raw === 'number' && Number.isFinite(raw)) return String(raw)\n }\n return null\n }\n const readNumber = (...keys: string[]): number | null => {\n for (const key of keys) {\n const raw = (project as Record<string, unknown>)[key]\n const parsed = typeof raw === 'number' ? raw : typeof raw === 'string' ? Number(raw) : NaN\n if (Number.isFinite(parsed)) return parsed\n }\n return null\n }\n const readBool = (...keys: string[]): boolean | null => {\n for (const key of keys) {\n const raw = (project as Record<string, unknown>)[key]\n if (typeof raw === 'boolean') return raw\n }\n return null\n }\n\n const enrichment = ((project as Record<string, unknown>)._staff ?? {}) as Record<string, unknown>\n const readEnriched = (key: string): number | null => {\n const raw = enrichment[key]\n return typeof raw === 'number' && Number.isFinite(raw) ? raw : null\n }\n\n const customerSnapshot = ((project as Record<string, unknown>).customerSnapshot\n ?? (project as Record<string, unknown>).customer_snapshot) as Record<string, unknown> | null | undefined\n const customerName =\n (typeof customerSnapshot?.name === 'string' ? customerSnapshot.name : null)\n ?? (typeof enrichment.customerName === 'string' ? enrichment.customerName : null)\n const customerId = readString('customerId', 'customer_id')\n const projectColor = readString('color')\n const costCenter = readString('costCenter', 'cost_center')\n const currencyCode = readString('currencyCode', 'currency_code')\n const hourlyRate = readNumber('hourlyRate', 'hourly_rate')\n const billableByDefault = readBool('billableByDefault', 'billable_by_default')\n const createdAt = readString('createdAt', 'created_at')\n const updatedAt = readString('updatedAt', 'updated_at')\n const entryCount = readNumber('entryCount')\n const currencyLocked = readBool('currencyLocked') ?? false\n const lockedEntryCount = readNumber('lockedEntryCount')\n const totalMinutes = readEnriched('totalMinutes') ?? 0\n const billableMinutes = readEnriched('billableMinutes')\n const cost = readEnriched('cost')\n const nonBillableMinutes = billableMinutes === null ? null : Math.max(0, totalMinutes - billableMinutes)\n const billablePercent =\n billableMinutes === null || totalMinutes <= 0 ? null : Math.round((billableMinutes / totalMinutes) * 100)\n const hoursTrend = Array.isArray(enrichment.hoursTrend)\n ? (enrichment.hoursTrend as unknown[]).filter((value): value is number => typeof value === 'number')\n : []\n\n const budgetKind = readString('budgetKind', 'budget_kind')\n const budgetValue = readNumber('budgetValue', 'budget_value')\n const budgetWarnAt = readNumber('budgetWarnAtPercent', 'budget_warn_at_percent')\n const budgetBurn = computeBudgetBurn({\n kind: budgetKind as 'none' | 'hours' | 'amount' | null,\n budgetValue,\n warnAtPercent: budgetWarnAt,\n totalMinutes,\n cost,\n })\n\n const formatHours = (minutes: number | null): string => {\n if (minutes === null) return '\u2014'\n const sign = minutes < 0 ? '-' : ''\n const abs = Math.abs(minutes)\n return `${sign}${Math.floor(abs / 60)}:${String(abs % 60).padStart(2, '0')}`\n }\n const formatDateTime = (value: string | null): string => {\n if (!value) return '\u2014'\n // The query engine returns `2026-05-24 00:00:00+00` \u2014 a space separator and a\n // two-digit offset, neither of which `Date` accepts, so both need widening\n // before parsing or every timestamp renders raw.\n //\n // The offset is only widened when a clock precedes it: anchoring on the\n // trailing `-24` alone also matches the day of a date-only `2026-05-24`,\n // which turns a valid date into `2026-05-24:00` and renders it raw.\n const normalized = value\n .replace(' ', 'T')\n .replace(/(T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d+)?)?)([+-]\\d{2})$/, '$1$2:00')\n const parsed = new Date(normalized)\n return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString()\n }\n const budgetUsageLabel = budgetBurn\n ? budgetBurn.kind === 'hours'\n ? t('staff.timesheets.projects.detail.budgetHours', '{percent}% of {value} h', {\n percent: budgetBurn.percent,\n value: budgetBurn.budgetValue,\n })\n : t('staff.timesheets.projects.detail.budgetAmount', '{percent}% of {value}', {\n percent: budgetBurn.percent,\n value: formatCurrency(budgetBurn.budgetValue, currencyCode ?? undefined) ?? String(budgetBurn.budgetValue),\n })\n : null\n\n const injectedTabs = injectedTabWidgets\n .filter((widget) => (widget.placement?.kind ?? 'tab') === 'tab')\n .map((widget) => {\n const tabId = widget.placement?.groupId ?? widget.widgetId\n const groupLabel = widget.placement?.groupLabel\n const label = groupLabel ? t(groupLabel, groupLabel) : widget.module.metadata.title ?? tabId\n const priority = typeof widget.placement?.priority === 'number' ? widget.placement.priority : 0\n return { tabId, label, priority, Widget: widget.module.Widget }\n })\n .sort((left, right) => right.priority - left.priority)\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6\">\n {/*\n * Identity first: colour, name, and the two states worth knowing before\n * anything else \u2014 is it running, and is the budget in trouble. The code,\n * customer, type and start date sit under it as one meta line rather than\n * four labelled rows, because none of them is a decision on its own.\n */}\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <Button variant=\"ghost\" size=\"icon\" asChild>\n <Link href={BACK_HREF}>\n <ArrowLeft className=\"h-4 w-4\" aria-hidden />\n </Link>\n </Button>\n <div className=\"flex min-w-0 flex-col gap-1.5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span\n className=\"size-3 shrink-0 rounded-sm border border-border\"\n style={{ backgroundColor: resolveProjectColorHex(projectColor, project.name) }}\n aria-hidden\n />\n <h1 className=\"text-xl font-semibold\">{project.name}</h1>\n <Badge\n variant={projectStatus === 'active' ? 'success' : projectStatus === 'on_hold' ? 'warning' : 'neutral'}\n className=\"capitalize\"\n >\n {projectStatus}\n </Badge>\n {canSeeMoney && budgetBurn && budgetBurn.tone !== 'accent' ? (\n <Badge variant={budgetBurn.tone === 'destructive' ? 'destructive' : 'warning'}>\n {t('staff.timesheets.projects.detail.budgetBadge', 'Budget {percent}%', {\n percent: budgetBurn.percent,\n })}\n </Badge>\n ) : null}\n <InjectionSpot\n spotId={extensionPoints.hosts.projectDetailStatusBadges.spotId}\n context={projectInjectionContext}\n data={project}\n />\n </div>\n <div className=\"flex flex-wrap items-center gap-2 text-sm text-muted-foreground\">\n <span className=\"rounded-sm border border-border bg-muted px-1.5 py-0.5 font-mono text-xs text-foreground\">\n {projectCode}\n </span>\n {customerName ? (\n <>\n <span aria-hidden>\u00B7</span>\n {customerId ? (\n <Link className=\"underline underline-offset-2\" href={`/backend/customers/companies/${customerId}`}>\n {customerName}\n </Link>\n ) : (\n <span>{customerName}</span>\n )}\n </>\n ) : null}\n {projectType ? (<><span aria-hidden>\u00B7</span><span>{projectType}</span></>) : null}\n {projectStartDate ? (\n <>\n <span aria-hidden>\u00B7</span>\n <span>\n {t('staff.timesheets.projects.detail.startedOn', 'Started {date}', {\n date: formatDateTime(projectStartDate),\n })}\n </span>\n </>\n ) : null}\n </div>\n </div>\n </div>\n {canManageProjects && (\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={openTeamDrawer}>\n <Users className=\"mr-2 h-4 w-4\" aria-hidden />\n {t('staff.time_tracking.projects.team.manage', 'Manage team')}\n </Button>\n <Button variant=\"outline\" size=\"sm\" asChild>\n <Link href={`/backend/staff/time-tracking/projects/${projectId}/edit`}>\n <Pencil className=\"mr-2 h-4 w-4\" aria-hidden />\n {t('staff.timesheets.projects.form.actions.edit', 'Edit Project')}\n </Link>\n </Button>\n </div>\n )}\n <InjectionSpot\n spotId={extensionPoints.hosts.projectDetailHeader.spotId}\n context={projectInjectionContext}\n data={project}\n />\n </div>\n\n {/*\n * Summary before detail. Four figures across the full width, in the order\n * somebody asks them: how much time, how much of it billable, what it\n * cost, how close the budget is. The three stacked label/value cards this\n * replaces gave all fourteen fields identical weight.\n */}\n <div className=\"grid gap-3 sm:grid-cols-2 xl:grid-cols-4\">\n <KpiCard\n title={t('staff.timesheets.projects.detail.loggedHours', 'Logged hours')}\n value={totalMinutes / 60}\n formatValue={() => formatHours(totalMinutes)}\n footer={\n hoursTrend.length > 0 ? (\n <Sparkline\n values={hoursTrend}\n ariaLabel={t('staff.timesheets.projects.detail.trendAria', 'Hours per week, last 7 weeks')}\n />\n ) : undefined\n }\n headerAction={\n entryCount !== null ? (\n <span className=\"text-xs text-muted-foreground\">\n {entryCount === 1\n ? t('staff.timesheets.projects.detail.entryCountOne', '1 entry')\n : t('staff.timesheets.projects.detail.entryCountShort', '{count} entries', { count: entryCount })}\n </span>\n ) : undefined\n }\n />\n <KpiCard\n title={t('staff.timesheets.projects.detail.billableHours', 'Billable hours')}\n value={(billableMinutes ?? 0) / 60}\n formatValue={() => formatHours(billableMinutes)}\n headerAction={\n billablePercent !== null ? (\n <Badge variant={billablePercent >= 80 ? 'success' : 'neutral'}>{`${billablePercent}%`}</Badge>\n ) : undefined\n }\n footer={\n nonBillableMinutes !== null ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.timesheets.projects.detail.nonBillable', '{hours} non-billable', {\n hours: formatHours(nonBillableMinutes),\n })}\n </span>\n ) : undefined\n }\n />\n {canSeeMoney ? (\n <KpiCard\n title={t('staff.timesheets.projects.detail.cost', 'Cost to date')}\n value={cost}\n formatValue={(value) => formatCurrency(value, currencyCode ?? undefined) ?? String(value)}\n footer={\n hourlyRate !== null ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.timesheets.projects.detail.costBasis', '{hours} \u00D7 {rate}', {\n hours: formatHours(billableMinutes),\n rate: `${formatCurrency(hourlyRate, currencyCode ?? undefined) ?? hourlyRate}/h`,\n })}\n </span>\n ) : undefined\n }\n />\n ) : null}\n {canSeeMoney ? (\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.form.budget', 'Budget')}\n </span>\n {budgetBurn ? (\n <Badge\n variant={\n budgetBurn.tone === 'destructive'\n ? 'destructive'\n : budgetBurn.tone === 'warning'\n ? 'warning'\n : 'neutral'\n }\n >\n {`${budgetBurn.percent}%`}\n </Badge>\n ) : (\n <Badge variant=\"neutral\">{t('staff.timesheets.projects.detail.noBudget', 'No budget')}</Badge>\n )}\n </div>\n <div className=\"mt-2 space-y-2\">\n <p className=\"text-2xl font-semibold tabular-nums\">\n {budgetBurn ? `${budgetBurn.percent}%` : '\u2014'}\n </p>\n <ProjectBudgetCell\n burn={budgetBurn}\n usageLabel={budgetUsageLabel}\n noBudgetLabel={t('staff.timesheets.projects.detail.budgetHint', 'Set one to track burn')}\n ariaLabel={t('staff.timesheets.projects.detail.budgetUsage', 'Budget usage')}\n />\n </div>\n </div>\n ) : null}\n </div>\n\n {/*\n * Working content left, reference right. Everything static \u2014 rate,\n * currency, cost centre, timestamps \u2014 collapses into one rail instead of\n * three full-width cards, which is what left the right half of a wide\n * screen carrying nothing.\n */}\n <div className=\"grid items-start gap-4 lg:grid-cols-[minmax(0,2fr)_minmax(280px,1fr)]\">\n <Tabs\n value={activeTab}\n onValueChange={setActiveTab}\n variant=\"underline\"\n className=\"min-w-0\"\n >\n <TabsList aria-label={t('staff.timesheets.projects.detail.tabs', 'Project sections')}>\n <TabsTrigger value=\"team\" count={activeCount}>\n {t('staff.timesheets.projects.detail.tabTeam', 'Team')}\n </TabsTrigger>\n <TabsTrigger value=\"time\" count={entryCount ?? undefined}>\n {t('staff.timesheets.projects.detail.tabTime', 'Time')}\n </TabsTrigger>\n <TabsTrigger value=\"tasks\">\n {t('staff.timesheets.projects.detail.tabTasks', 'Tasks')}\n </TabsTrigger>\n {injectedTabs.map((tab) => (\n <TabsTrigger key={tab.tabId} value={tab.tabId}>\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n\n <TabsContent value=\"team\">\n {/* Assigned Employees \u2014 collapsible cards */}\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"sr-only\">\n {t('staff.timesheets.projects.employees.title', 'Assigned Employees')}\n </h2>\n <span />\n {canManageProjects && (\n <Button size=\"sm\" onClick={openAddDialog}>\n <Plus className=\"mr-2 h-4 w-4\" aria-hidden />\n {t('staff.timesheets.projects.add_employee', 'Add Employee')}\n </Button>\n )}\n </div>\n\n {employeesLoading ? (\n <LoadingMessage label={t('staff.timesheets.projects.employees.loading', 'Loading employees...')} />\n ) : employees.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-8 text-center\">\n <User className=\"mx-auto h-8 w-8 text-muted-foreground/50\" aria-hidden />\n <p className=\"mt-2 text-sm text-muted-foreground\">\n {t('staff.timesheets.projects.employees.empty', 'No employees assigned yet.')}\n </p>\n </div>\n ) : (\n <div className=\"space-y-2\">\n {employees.map((emp) => {\n const isExpanded = expandedCards.has(emp.id)\n const isInactive = emp.status !== 'active'\n return (\n <div\n key={emp.id}\n className={`rounded-lg border ${isInactive ? 'opacity-60' : ''}`}\n >\n {/* Collapsed view: name, role, status badge, start date, expand toggle */}\n <button\n type=\"button\"\n className=\"flex w-full items-center justify-between p-4 text-left hover:bg-muted/50 transition-colors\"\n onClick={() => toggleCard(emp.id)}\n >\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted\">\n <User className=\"h-4 w-4 text-muted-foreground\" aria-hidden />\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium truncate\">\n {emp.displayName ?? emp.staffMemberId}\n </p>\n {emp.role ? (\n <p className=\"text-xs text-muted-foreground truncate\">{emp.role}</p>\n ) : null}\n </div>\n </div>\n <div className=\"flex items-center gap-3 shrink-0\">\n {emp.isAssigner ? (\n <Badge variant=\"info\" data-testid={`project-member-assigner-badge-${emp.id}`}>\n {t('staff.time_tracking.assigners.badge', 'Assigner')}\n </Badge>\n ) : null}\n <Badge\n variant={(emp.status ?? 'active') === 'active' ? 'success' : 'neutral'}\n className=\"capitalize\"\n >\n {emp.status ?? 'active'}\n </Badge>\n {emp.assignedStartDate ? (\n <span className=\"text-xs text-muted-foreground hidden sm:inline\">\n {emp.assignedStartDate}\n </span>\n ) : null}\n <ChevronDown className={`h-4 w-4 text-muted-foreground transition-transform ${isExpanded ? 'rotate-180' : ''}`} aria-hidden />\n </div>\n </button>\n\n {/* Expanded view: assignment details */}\n {isExpanded ? (\n <div className=\"border-t px-4 pb-4 pt-3 space-y-3\">\n <dl className=\"grid grid-cols-2 gap-3 text-sm\">\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.role', 'Role')}\n </dt>\n <dd>{emp.role || '-'}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.status', 'Status')}\n </dt>\n <dd className=\"capitalize\">{emp.status ?? 'active'}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.startDate', 'Assignment start')}\n </dt>\n <dd>{emp.assignedStartDate || '-'}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.endDate', 'Assignment end')}\n </dt>\n <dd>{emp.assignedEndDate || '-'}</dd>\n </div>\n {emp.teamName ? (\n <div>\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.timesheets.projects.employees.department', 'Department')}\n </dt>\n <dd>{emp.teamName}</dd>\n </div>\n ) : null}\n <div className=\"col-span-2\">\n <dt className=\"font-medium text-muted-foreground\">\n {t('staff.time_tracking.assigners.label', 'Assigner')}\n </dt>\n <dd className=\"flex items-center gap-2 pt-1\">\n <Switch\n checked={emp.isAssigner}\n disabled={!canManageProjects || emp.hasUserAccount === false}\n onCheckedChange={(checked) => { void handleToggleAssigner(emp, checked === true) }}\n aria-label={t('staff.time_tracking.assigners.label', 'Assigner')}\n data-testid={`project-member-assigner-switch-${emp.id}`}\n />\n <span className=\"text-xs text-muted-foreground\">\n {emp.hasUserAccount === false\n ? t('staff.time_tracking.assigners.needsUser', 'Only members with a user account can be assigners.')\n : t('staff.time_tracking.assigners.help', 'Assigners are notified about new and released work and can claim unassigned tasks.')}\n </span>\n </dd>\n </div>\n </dl>\n {canManageProjects && (\n <div className=\"flex justify-end\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"text-destructive hover:text-destructive\"\n onClick={() => { void handleRemoveEmployee(emp) }}\n >\n <Trash2 className=\"mr-2 h-3.5 w-3.5\" aria-hidden />\n {t('staff.timesheets.projects.employees.remove', 'Remove')}\n </Button>\n </div>\n )}\n </div>\n ) : null}\n </div>\n )\n })}\n </div>\n )}\n </div>\n </TabsContent>\n\n <TabsContent value=\"time\">\n {recentEntriesLoading ? (\n <LoadingMessage label={t('staff.timesheets.projects.detail.loadingTime', 'Loading time entries\u2026')} />\n ) : recentEntries.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-8 text-center\">\n <p className=\"text-sm font-medium\">\n {t('staff.timesheets.projects.detail.noTime', 'No time logged yet')}\n </p>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t('staff.timesheets.projects.detail.noTimeHint', 'Hours logged against this project appear here.')}\n </p>\n </div>\n ) : (\n <DataTable<ProjectEntryRow>\n extensionTableId={extensionPoints.hosts.projectDetailTimeEntriesTable.tableId}\n disableRowClick\n columns={recentEntriesColumns}\n data={recentEntries}\n />\n )}\n </TabsContent>\n\n <TabsContent value=\"tasks\">\n {projectTasksLoading ? (\n <LoadingMessage label={t('staff.timesheets.projects.detail.loadingTasks', 'Loading tasks\u2026')} />\n ) : projectTasks.length === 0 ? (\n <div className=\"rounded-lg border border-dashed p-8 text-center\">\n <p className=\"text-sm font-medium\">\n {t('staff.timesheets.projects.detail.noTasks', 'No tasks yet')}\n </p>\n </div>\n ) : (\n <div className=\"divide-y rounded-lg border\">\n {projectTasks.map((task) => (\n <div key={task.id} className=\"flex items-center gap-3 px-3 py-2 text-sm\">\n {task.reference ? (\n <span className=\"shrink-0 rounded-sm border border-border bg-muted px-1.5 py-0.5 font-mono text-xs\">\n {task.reference}\n </span>\n ) : null}\n <span className=\"min-w-0 flex-1 truncate\">{task.title}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">\n {employeeNameById.get(task.assigneeStaffMemberId ?? '') ?? ''}\n </span>\n {task.loggedMinutes !== null ? (\n <span className=\"shrink-0 font-mono text-xs tabular-nums text-muted-foreground\">\n {formatHours(task.loggedMinutes)}\n </span>\n ) : null}\n </div>\n ))}\n </div>\n )}\n </TabsContent>\n\n {injectedTabs.map((tab) => (\n <TabsContent key={tab.tabId} value={tab.tabId}>\n <tab.Widget context={projectInjectionContext} data={project} />\n </TabsContent>\n ))}\n </Tabs>\n\n {/* Reference rail \u2014 one card, grouped, no longer three stacked sections. */}\n <div className=\"rounded-lg border bg-card\">\n <div className=\"border-b px-4 py-3\">\n <h2 className=\"text-sm font-semibold\">\n {t('staff.timesheets.projects.detail.details', 'Details')}\n </h2>\n </div>\n <div className=\"px-4 py-3\">\n {canSeeMoney ? (\n <>\n <RailGroup label={t('staff.timesheets.projects.detail.commercials', 'Billing')} first />\n <dl>\n <RailFact\n label={t('staff.timesheets.projects.form.hourlyRate', 'Hourly rate')}\n value={\n hourlyRate === null\n ? '\u2014'\n : `${formatCurrency(hourlyRate, currencyCode ?? undefined) ?? hourlyRate} / h`\n }\n />\n <RailFact\n label={t('staff.timesheets.projects.form.currency', 'Currency')}\n value={\n <span className=\"inline-flex items-center gap-1.5\">\n {currencyCode ?? '\u2014'}\n {currencyLocked ? (\n <Badge\n variant=\"neutral\"\n title={t(\n 'staff.timesheets.projects.detail.currencyLockedHint',\n 'Locked because hours are already logged against this project.',\n )}\n >\n {t('staff.timesheets.projects.detail.locked', 'locked')}\n </Badge>\n ) : null}\n </span>\n }\n />\n <RailFact\n label={t('staff.timesheets.projects.form.billableByDefault', 'Billable by default')}\n value={\n billableByDefault === null ? (\n '\u2014'\n ) : (\n <Badge variant={billableByDefault ? 'success' : 'neutral'}>\n {billableByDefault\n ? t('staff.timesheets.projects.detail.yes', 'Yes')\n : t('staff.timesheets.projects.detail.no', 'No')}\n </Badge>\n )\n }\n />\n </dl>\n </>\n ) : null}\n\n <RailGroup\n label={t('staff.timesheets.projects.detail.delivery', 'Delivery')}\n first={!canSeeMoney}\n />\n <dl>\n <RailFact\n label={t('staff.timesheets.projects.detail.entryCount', 'Time entries')}\n value={entryCount ?? '\u2014'}\n />\n <RailFact\n label={t('staff.timesheets.projects.detail.lockedEntries', 'Locked in reports')}\n value={lockedEntryCount ?? '\u2014'}\n />\n </dl>\n\n <RailGroup label={t('staff.timesheets.projects.detail.administration', 'Administration')} />\n <dl>\n <RailFact\n label={t('staff.timesheets.projects.form.costCenter', 'Cost center')}\n value={costCenter ?? '\u2014'}\n />\n <RailFact\n label={t('staff.timesheets.projects.form.color', 'Project color')}\n value={\n <span className=\"inline-flex items-center gap-1.5 capitalize\">\n <span\n className=\"size-2.5 rounded-full border border-border\"\n style={{ backgroundColor: resolveProjectColorHex(projectColor, project.name) }}\n aria-hidden\n />\n {projectColor ?? t('staff.timesheets.projects.detail.autoColor', 'Auto')}\n </span>\n }\n />\n <RailFact\n label={t('staff.timesheets.projects.detail.createdAt', 'Created')}\n value={formatDateTime(createdAt)}\n />\n <RailFact\n label={t('staff.timesheets.projects.detail.updatedAt', 'Last updated')}\n value={formatDateTime(updatedAt)}\n />\n </dl>\n\n {project.description ? (\n <>\n <RailGroup label={t('staff.timesheets.projects.form.description', 'Description')} />\n <p className=\"whitespace-pre-wrap pt-2 text-sm text-muted-foreground\">{project.description}</p>\n </>\n ) : null}\n <InjectionSpot\n spotId={extensionPoints.hosts.projectDetailSidebar.spotId}\n context={projectInjectionContext}\n data={project}\n />\n </div>\n </div>\n </div>\n <InjectionSpot\n spotId={extensionPoints.hosts.projectDetailFooter.spotId}\n context={projectInjectionContext}\n data={project}\n />\n </div>\n\n {ConfirmDialogElement}\n\n {projectId && teamDrawerOpen ? (\n <ProjectTeamDrawer\n projectId={projectId}\n projectName={project.name}\n open={teamDrawerOpen}\n onOpenChange={handleTeamDrawerOpenChange}\n preselectUserId={requesterUserId}\n onSaved={handleTeamSaved}\n />\n ) : null}\n\n {/* Add Employee Dialog */}\n <Dialog open={addDialogOpen} onOpenChange={setAddDialogOpen}>\n <DialogContent className=\"sm:max-w-md\" onKeyDown={handleAddDialogKeyDown}>\n <DialogHeader>\n <DialogTitle>{t('staff.timesheets.projects.add_employee', 'Add Employee')}</DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4 pt-2\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\">\n {t('staff.timesheets.projects.employees.selectEmployee', 'Select Employee')}\n </label>\n <LookupSelect\n value={addStaffMemberId}\n onChange={setAddStaffMemberId}\n fetchItems={fetchStaffMembers}\n searchPlaceholder={t('staff.timesheets.projects.employees.searchEmployee', 'Search team members...')}\n emptyLabel={t('staff.timesheets.projects.employees.noResults', 'No team members found')}\n />\n {staffLookupError ? (\n <p className=\"text-xs text-status-error-text\" role=\"alert\">\n {staffLookupError}\n </p>\n ) : null}\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"add-role\">\n {t('staff.timesheets.projects.employees.roleOnProject', 'Role on Project')}\n </label>\n <Input\n id=\"add-role\"\n value={addRole}\n onChange={(event) => setAddRole(event.target.value)}\n placeholder={t('staff.timesheets.projects.employees.rolePlaceholder', 'e.g. Developer, Designer...')}\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"add-start-date\">\n {t('staff.timesheets.projects.employees.assignmentStartDate', 'Assignment Start Date')}\n </label>\n <Input\n id=\"add-start-date\"\n type=\"date\"\n value={addStartDate}\n onChange={(event) => setAddStartDate(event.target.value)}\n />\n </div>\n <div className=\"flex justify-end gap-2 pt-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => setAddDialogOpen(false)}>\n {t('staff.timesheets.projects.form.actions.cancel', 'Cancel')}\n </Button>\n <Button\n size=\"sm\"\n disabled={addSaving || !addStaffMemberId || !addStartDate}\n onClick={handleAddEmployee}\n >\n {addSaving ? <Spinner className=\"size-4\" /> : null}\n {t('staff.timesheets.projects.add_employee', 'Add Employee')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["params"]
7
7
  }
@@ -171,6 +171,10 @@ function ProjectTeamDrawer({
171
171
  onSaved
172
172
  }) {
173
173
  const t = useT();
174
+ const translateRef = React.useRef(t);
175
+ React.useEffect(() => {
176
+ translateRef.current = t;
177
+ }, [t]);
174
178
  const currentUserId = useCurrentUserId();
175
179
  const { confirm, ConfirmDialogElement } = useConfirmDialog();
176
180
  const { runMutation, retryLastMutation } = useGuardedMutation({
@@ -227,7 +231,7 @@ function ProjectTeamDrawer({
227
231
  apiCall("/api/staff/timesheets/settings")
228
232
  ]);
229
233
  if (!membersRes.ok) {
230
- setLoadError(t("staff.time_tracking.projects.team.drawer.loadError", "Could not load the project team."));
234
+ setLoadError(translateRef.current("staff.time_tracking.projects.team.drawer.loadError", "Could not load the project team."));
231
235
  return;
232
236
  }
233
237
  const members = readItems(membersRes.result).map(toAssignedMember).filter((member) => member !== null);
@@ -253,11 +257,11 @@ function ProjectTeamDrawer({
253
257
  }
254
258
  } catch (error) {
255
259
  logger.error("staff.time_tracking.projects.team.load failed", { err: error });
256
- setLoadError(t("staff.time_tracking.projects.team.drawer.loadError", "Could not load the project team."));
260
+ setLoadError(translateRef.current("staff.time_tracking.projects.team.drawer.loadError", "Could not load the project team."));
257
261
  } finally {
258
262
  setLoading(false);
259
263
  }
260
- }, [projectId, mergeProfiles, t]);
264
+ }, [projectId, mergeProfiles]);
261
265
  React.useEffect(() => {
262
266
  if (!open) return;
263
267
  void loadTeam();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Lock, Save } from 'lucide-react'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { SearchInput } from '@open-mercato/ui/primitives/search-input'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useCurrentUserId } from '@open-mercato/ui/backend/utils/useCurrentUserId'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n countPendingChanges,\n diffTeamSelection,\n formatProjectMinutes,\n resolveAssignmentState,\n resolveGraceDays,\n selectChangeCountKey,\n toIsoDate,\n type AssignedMember,\n type AssignmentState,\n} from './projectTeamAssignment'\n\nconst logger = createLogger('staff').child({ component: 'ProjectTeamDrawer' })\n\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.project_team'\nconst DIRECTORY_PAGE_SIZE = 100\nconst ENTRIES_PAGE_SIZE = 100\nconst MAX_ENTRY_PAGES = 20\nconst SEARCH_DEBOUNCE_MS = 250\n\ntype ApiRow = Record<string, unknown>\n\ntype PersonProfile = {\n id: string\n name: string\n userId: string | null\n}\n\ntype ProjectHours = {\n minutesByStaffMemberId: Map<string, number>\n partial: boolean\n}\n\nexport type ProjectTeamDrawerProps = {\n projectId: string\n projectName: string\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Pre-checks the person who requested access (notification deep link, D-6). */\n preselectUserId?: string | null\n onSaved?: () => void\n}\n\nfunction readString(row: ApiRow, ...keys: string[]): string | null {\n for (const key of keys) {\n const value = row[key]\n if (typeof value === 'string' && value.trim().length > 0) return value\n if (value instanceof Date) return value.toISOString()\n }\n return null\n}\n\nfunction readNumber(row: ApiRow, ...keys: string[]): number {\n for (const key of keys) {\n const value = row[key]\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length > 0) {\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n }\n }\n return 0\n}\n\nfunction readItems(payload: { items?: unknown } | null | undefined): ApiRow[] {\n const items = payload?.items\n return Array.isArray(items) ? (items as ApiRow[]) : []\n}\n\nfunction toProfile(row: ApiRow): PersonProfile | null {\n const id = readString(row, 'id')\n if (!id) return null\n return {\n id,\n name: readString(row, 'display_name', 'displayName') ?? id,\n userId: readString(row, 'user_id', 'userId'),\n }\n}\n\nfunction toAssignedMember(row: ApiRow): AssignedMember | null {\n const membershipId = readString(row, 'id')\n const staffMemberId = readString(row, 'staff_member_id', 'staffMemberId')\n if (!membershipId || !staffMemberId) return null\n return {\n membershipId,\n staffMemberId,\n role: readString(row, 'role'),\n status: readString(row, 'status'),\n startDate: toIsoDate(readString(row, 'assigned_start_date', 'assignedStartDate')),\n endDate: toIsoDate(readString(row, 'assigned_end_date', 'assignedEndDate')),\n }\n}\n\nfunction todayIsoDate(): string {\n const now = new Date()\n const month = String(now.getMonth() + 1).padStart(2, '0')\n const day = String(now.getDate()).padStart(2, '0')\n return `${now.getFullYear()}-${month}-${day}`\n}\n\n/**\n * Hours logged per person in this project. There is no aggregate endpoint yet,\n * so the drawer pages the (tenant-scoped) entries list and sums client-side;\n * beyond `MAX_ENTRY_PAGES` the totals are reported as a lower bound rather than\n * silently under-reporting.\n */\nasync function loadProjectHours(projectId: string): Promise<ProjectHours> {\n const minutesByStaffMemberId = new Map<string, number>()\n let partial = false\n for (let page = 1; page <= MAX_ENTRY_PAGES; page += 1) {\n const params = new URLSearchParams({\n projectId,\n page: String(page),\n pageSize: String(ENTRIES_PAGE_SIZE),\n })\n const res = await apiCall<{ items?: unknown; total?: number }>(\n `/api/staff/timesheets/time-entries?${params.toString()}`,\n )\n if (!res.ok) return { minutesByStaffMemberId, partial: true }\n const rows = readItems(res.result)\n for (const row of rows) {\n const staffMemberId = readString(row, 'staff_member_id', 'staffMemberId')\n if (!staffMemberId) continue\n const minutes = readNumber(row, 'duration_minutes', 'durationMinutes')\n minutesByStaffMemberId.set(staffMemberId, (minutesByStaffMemberId.get(staffMemberId) ?? 0) + minutes)\n }\n if (rows.length < ENTRIES_PAGE_SIZE) return { minutesByStaffMemberId, partial: false }\n const total = typeof res.result?.total === 'number' ? res.result.total : null\n if (total !== null && page * ENTRIES_PAGE_SIZE >= total) return { minutesByStaffMemberId, partial: false }\n partial = true\n }\n return { minutesByStaffMemberId, partial }\n}\n\ntype TeamPickRowProps = {\n profile: PersonProfile\n hint: string\n checked: boolean\n locked: boolean\n lockedHint: string\n lockLabel: string\n badge: React.ReactNode\n windowLine: React.ReactNode\n endDateControl: React.ReactNode\n selectLabel: string\n onToggle: (next: boolean) => void\n}\n\nfunction TeamPickRow({\n profile,\n hint,\n checked,\n locked,\n lockedHint,\n lockLabel,\n badge,\n windowLine,\n endDateControl,\n selectLabel,\n onToggle,\n}: TeamPickRowProps) {\n return (\n <li className=\"rounded-md border border-border\" data-staff-member-id={profile.id}>\n <label className=\"flex cursor-pointer items-start gap-3 p-3\">\n <span className=\"mt-0.5 shrink-0\">\n <Checkbox\n checked={checked}\n disabled={locked}\n aria-label={locked ? `${selectLabel} \u2014 ${lockedHint}` : selectLabel}\n onCheckedChange={(next) => onToggle(next === true)}\n />\n </span>\n <Avatar label={profile.name} size=\"sm\" />\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">{profile.name}</span>\n <span className=\"text-xs text-muted-foreground\">{hint}</span>\n {windowLine}\n </span>\n <span className=\"flex shrink-0 flex-col items-end gap-1\">\n {locked ? (\n <SimpleTooltip content={lockedHint}>\n <Badge variant=\"neutral\" className=\"gap-1\">\n <Lock className=\"size-3\" aria-hidden />\n {lockLabel}\n </Badge>\n </SimpleTooltip>\n ) : null}\n {badge}\n </span>\n </label>\n {endDateControl}\n </li>\n )\n}\n\nexport function ProjectTeamDrawer({\n projectId,\n projectName,\n open,\n onOpenChange,\n preselectUserId,\n onSaved,\n}: ProjectTeamDrawerProps) {\n const t = useT()\n const currentUserId = useCurrentUserId()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n\n const [loading, setLoading] = React.useState(true)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [assigned, setAssigned] = React.useState<AssignedMember[]>([])\n const [profiles, setProfiles] = React.useState<Map<string, PersonProfile>>(new Map())\n const [directoryIds, setDirectoryIds] = React.useState<string[]>([])\n const [directoryTotal, setDirectoryTotal] = React.useState(0)\n const [selfStaffMemberId, setSelfStaffMemberId] = React.useState<string | null>(null)\n const [graceDays, setGraceDays] = React.useState<number>(resolveGraceDays(null))\n const [hours, setHours] = React.useState<ProjectHours | null>(null)\n const [hoursLoading, setHoursLoading] = React.useState(true)\n const [selected, setSelected] = React.useState<Set<string>>(new Set())\n const [endDrafts, setEndDrafts] = React.useState<Map<string, string | null>>(new Map())\n const [query, setQuery] = React.useState('')\n const [debouncedQuery, setDebouncedQuery] = React.useState('')\n\n const mergeProfiles = React.useCallback((rows: ApiRow[]) => {\n setProfiles((previous) => {\n const next = new Map(previous)\n for (const row of rows) {\n const profile = toProfile(row)\n if (profile) next.set(profile.id, profile)\n }\n return next\n })\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n const handle = setTimeout(() => setDebouncedQuery(query.trim()), SEARCH_DEBOUNCE_MS)\n return () => clearTimeout(handle)\n }, [open, query])\n\n React.useEffect(() => {\n if (!open) {\n setQuery('')\n setDebouncedQuery('')\n }\n }, [open])\n\n const loadTeam = React.useCallback(async () => {\n setLoading(true)\n setLoadError(null)\n try {\n const [membersRes, selfRes, settingsRes] = await Promise.all([\n apiCall<{ items?: unknown }>(\n `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&status=active`,\n ),\n apiCall<{ member?: { id?: string; displayName?: string; userId?: string | null } | null }>(\n '/api/staff/team-members/self',\n ),\n apiCall<{ access?: { assignmentGraceDays?: unknown } | null }>('/api/staff/timesheets/settings'),\n ])\n\n if (!membersRes.ok) {\n setLoadError(t('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))\n return\n }\n\n const members = readItems(membersRes.result)\n .map(toAssignedMember)\n .filter((member): member is AssignedMember => member !== null)\n setAssigned(members)\n setSelected(new Set(members.map((member) => member.staffMemberId)))\n setEndDrafts(new Map(members.map((member) => [member.staffMemberId, member.endDate])))\n\n const selfMember = selfRes.ok ? selfRes.result?.member ?? null : null\n if (selfMember?.id) {\n setSelfStaffMemberId(selfMember.id)\n mergeProfiles([\n { id: selfMember.id, display_name: selfMember.displayName ?? selfMember.id, user_id: selfMember.userId ?? null },\n ])\n }\n\n if (settingsRes.ok) {\n setGraceDays(resolveGraceDays(settingsRes.result?.access?.assignmentGraceDays))\n }\n\n const assignedIds = members.map((member) => member.staffMemberId)\n if (assignedIds.length > 0) {\n const profileRes = await apiCall<{ items?: unknown }>(\n `/api/staff/team-members?ids=${assignedIds.join(',')}&pageSize=${DIRECTORY_PAGE_SIZE}`,\n )\n if (profileRes.ok) mergeProfiles(readItems(profileRes.result))\n }\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.load failed', { err: error })\n setLoadError(t('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))\n } finally {\n setLoading(false)\n }\n }, [projectId, mergeProfiles, t])\n\n React.useEffect(() => {\n if (!open) return\n void loadTeam()\n }, [open, loadTeam])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n setHoursLoading(true)\n void (async () => {\n try {\n const result = await loadProjectHours(projectId)\n if (!cancelled) setHours(result)\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.hours failed', { err: error })\n if (!cancelled) setHours(null)\n } finally {\n if (!cancelled) setHoursLoading(false)\n }\n })()\n return () => { cancelled = true }\n }, [open, projectId])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n void (async () => {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(DIRECTORY_PAGE_SIZE),\n isActive: 'true',\n })\n if (debouncedQuery.length > 0) params.set('search', debouncedQuery)\n const res = await apiCall<{ items?: unknown; total?: number }>(`/api/staff/team-members?${params.toString()}`)\n if (cancelled || !res.ok) return\n const rows = readItems(res.result)\n mergeProfiles(rows)\n setDirectoryIds(rows.map((row) => readString(row, 'id')).filter((id): id is string => id !== null))\n setDirectoryTotal(typeof res.result?.total === 'number' ? res.result.total : rows.length)\n })()\n return () => { cancelled = true }\n }, [open, debouncedQuery, mergeProfiles])\n\n const lockedStaffMemberId = React.useMemo(() => {\n if (selfStaffMemberId) return selfStaffMemberId\n if (!currentUserId) return null\n for (const profile of profiles.values()) {\n if (profile.userId && profile.userId === currentUserId) return profile.id\n }\n return null\n }, [selfStaffMemberId, currentUserId, profiles])\n\n const lockedStaffMemberIds = React.useMemo(\n () => new Set(lockedStaffMemberId ? [lockedStaffMemberId] : []),\n [lockedStaffMemberId],\n )\n\n React.useEffect(() => {\n if (!open || !preselectUserId || loading) return\n for (const profile of profiles.values()) {\n if (profile.userId !== preselectUserId) continue\n setSelected((previous) => (previous.has(profile.id) ? previous : new Set(previous).add(profile.id)))\n return\n }\n }, [open, preselectUserId, loading, profiles])\n\n const assignedByStaffMemberId = React.useMemo(() => {\n const map = new Map<string, AssignedMember>()\n for (const member of assigned) {\n if (!map.has(member.staffMemberId)) map.set(member.staffMemberId, member)\n }\n return map\n }, [assigned])\n\n const changes = React.useMemo(\n () => diffTeamSelection(assigned, { selectedStaffMemberIds: selected, endDateByStaffMemberId: endDrafts }, lockedStaffMemberIds),\n [assigned, selected, endDrafts, lockedStaffMemberIds],\n )\n const pendingChanges = countPendingChanges(changes)\n\n const memberMinutes = React.useCallback(\n (staffMemberId: string): number | null => {\n if (!hours) return null\n return hours.minutesByStaffMemberId.get(staffMemberId) ?? 0\n },\n [hours],\n )\n\n const hoursLabel = React.useCallback(\n (staffMemberId: string): string => {\n if (hoursLoading) return '\u2026'\n const minutes = memberMinutes(staffMemberId)\n if (minutes === null) return formatProjectMinutes(0)\n const formatted = formatProjectMinutes(minutes)\n if (!hours?.partial) return formatted\n return t('staff.time_tracking.projects.team.drawer.hoursAtLeast', 'at least {hours}', { hours: formatted })\n },\n [hours, hoursLoading, memberMinutes, t],\n )\n\n const roleLabel = React.useCallback(\n (staffMemberId: string): string => {\n const member = assignedByStaffMemberId.get(staffMemberId)\n if (member?.role && member.role.trim().length > 0) return member.role\n if (staffMemberId === lockedStaffMemberId) {\n return t('staff.time_tracking.projects.team.drawer.roleLeader', 'Team Leader')\n }\n return t('staff.time_tracking.projects.team.drawer.roleMember', 'Team Member')\n },\n [assignedByStaffMemberId, lockedStaffMemberId, t],\n )\n\n const toggleMember = React.useCallback(\n async (staffMemberId: string, next: boolean) => {\n if (lockedStaffMemberIds.has(staffMemberId)) return\n if (!next && assignedByStaffMemberId.has(staffMemberId)) {\n const profile = profiles.get(staffMemberId)\n const name = profile?.name ?? staffMemberId\n const minutes = memberMinutes(staffMemberId)\n const needsConfirm = minutes === null || minutes > 0\n if (needsConfirm) {\n const text = minutes === null || hoursLoading\n ? t(\n 'staff.time_tracking.projects.team.drawer.removeConfirm.textUnknown',\n '{name} may have time logged in this project \u2014 revoke access?',\n { name },\n )\n : t(\n 'staff.time_tracking.projects.team.drawer.removeConfirm.text',\n '{name} has {hours} in this project \u2014 revoke access?',\n { name, hours: formatProjectMinutes(minutes) },\n )\n const confirmed = await confirm({\n title: t('staff.time_tracking.projects.team.drawer.removeConfirm.title', 'Revoke access?'),\n text,\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n }\n setSelected((previous) => {\n const draft = new Set(previous)\n if (next) draft.add(staffMemberId)\n else draft.delete(staffMemberId)\n return draft\n })\n },\n [assignedByStaffMemberId, confirm, hoursLoading, lockedStaffMemberIds, memberMinutes, profiles, t],\n )\n\n const setEndDate = React.useCallback((staffMemberId: string, value: string | null) => {\n setEndDrafts((previous) => {\n const draft = new Map(previous)\n draft.set(staffMemberId, value && value.length > 0 ? value : null)\n return draft\n })\n }, [])\n\n // Membership rows are an assignment junction, so they are exempt from OSS\n // optimistic locking (data-integrity contract); the drawer never PATCHes the\n // project aggregate itself, so no expected-updated-at header applies here.\n const persistChanges = React.useCallback(async () => {\n const employeesPath = `staff/timesheets/time-projects/${projectId}/employees`\n const startDate = todayIsoDate()\n\n for (const staffMemberId of changes.additions) {\n await createCrud(employeesPath, {\n timeProjectId: projectId,\n staffMemberId,\n assignedStartDate: startDate,\n assignedEndDate: endDrafts.get(staffMemberId) ?? null,\n }, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n\n // Re-dating is an update of the membership row itself (T2.12): the audit\n // trail must read \"assignment end date changed\", not \"member removed,\n // member added\". Only the changed field is sent, so the recorded diff names\n // `assignedEndDate` alone.\n //\n // No optimistic-lock header, deliberately. The endpoint accepts and enforces\n // one (the CRUD factory registers a reader for this entity), but this drawer\n // saves a whole batch \u2014 additions, re-datings and removals \u2014 with no\n // rollback. A per-row version round-trip belongs with a per-row conflict UX;\n // the batch save is not it.\n //\n // What this loop guarantees and what it does not (C6):\n // * GUARANTEED \u2014 the loop stops at the first failure, so no write is\n // attempted after one fails, and `handleSave` reloads the drawer from the\n // server before showing the error, so what the user then sees is the\n // persisted team rather than the optimistic local snapshot.\n // * NOT GUARANTEED \u2014 atomicity. There is no transaction across these three\n // passes: whatever was written before the failure stays written and is\n // not rolled back. The reload therefore also drops the part of the batch\n // that never reached the server; the user re-applies it and saves again.\n for (const update of changes.endDateUpdates) {\n await updateCrud(employeesPath, {\n id: update.member.membershipId,\n assignedEndDate: update.endDate,\n }, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n\n for (const member of changes.removals) {\n await deleteCrud(employeesPath, member.membershipId, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n }, [changes, endDrafts, projectId, t])\n\n const handleSave = React.useCallback(async () => {\n if (saving || pendingChanges === 0) return\n setSaving(true)\n try {\n await runMutation({\n operation: persistChanges,\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: projectId,\n action: 'assign-team',\n retryLastMutation,\n },\n mutationPayload: {\n timeProjectId: projectId,\n additions: changes.additions,\n removals: changes.removals.map((member) => member.membershipId),\n endDateUpdates: changes.endDateUpdates.map((update) => ({\n id: update.member.membershipId,\n assignedEndDate: update.endDate,\n })),\n },\n })\n flash(t('staff.time_tracking.projects.team.drawer.saved', 'Project team updated.'), 'success')\n onSaved?.()\n onOpenChange(false)\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.save failed', { err: error })\n // The batch is not transactional (see `persistChanges`), so the local\n // snapshot can no longer be trusted: re-read the memberships first and\n // only then report, so the error and the rows underneath it agree.\n await loadTeam()\n const reason =\n error instanceof Error && error.message.trim().length > 0\n ? error.message\n : t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.')\n flash(\n t(\n 'staff.time_tracking.projects.team.drawer.partialSaveError',\n '{reason} Saving stopped part-way through, so the team shown below was reloaded from the server \u2014 check it and apply the remaining changes again.',\n { reason },\n ),\n 'error',\n )\n } finally {\n setSaving(false)\n }\n }, [\n changes,\n loadTeam,\n onOpenChange,\n onSaved,\n pendingChanges,\n persistChanges,\n projectId,\n retryLastMutation,\n runMutation,\n saving,\n t,\n ])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n void handleSave()\n }\n },\n [handleSave],\n )\n\n /**\n * The drawer batches the whole team edit and writes nothing until Save, so\n * Escape, the \u00D7, the overlay and Cancel each stand to discard a set of changes\n * the footer is at that moment counting out loud. They all reach `onOpenChange`,\n * so the guard sits there once rather than on each of them.\n */\n const requestClose = React.useCallback(async () => {\n if (saving) return\n if (pendingChanges === 0) {\n onOpenChange(false)\n return\n }\n const confirmed = await confirm({\n title: t('staff.time_tracking.projects.team.drawer.discard.title', 'Discard the team changes?'),\n text: t(\n 'staff.time_tracking.projects.team.drawer.discard.text',\n 'Nothing has been saved yet, so closing now loses every change in this drawer.',\n ),\n confirmText: t('staff.time_tracking.projects.team.drawer.discard.confirm', 'Discard'),\n cancelText: t('staff.time_tracking.projects.team.drawer.discard.cancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (confirmed) onOpenChange(false)\n }, [confirm, onOpenChange, pendingChanges, saving, t])\n\n const matchesQuery = React.useCallback(\n (profile: PersonProfile) => {\n if (debouncedQuery.length === 0) return true\n return profile.name.toLocaleLowerCase().includes(debouncedQuery.toLocaleLowerCase())\n },\n [debouncedQuery],\n )\n\n const assignedRowIds = React.useMemo(() => {\n const ids = new Set<string>(assignedByStaffMemberId.keys())\n if (lockedStaffMemberId) ids.add(lockedStaffMemberId)\n return Array.from(ids)\n }, [assignedByStaffMemberId, lockedStaffMemberId])\n\n const assignedRows = React.useMemo(\n () =>\n assignedRowIds\n .map((id) => profiles.get(id) ?? { id, name: id, userId: null })\n .filter(matchesQuery)\n .sort((left, right) => left.name.localeCompare(right.name)),\n [assignedRowIds, profiles, matchesQuery],\n )\n\n const otherRows = React.useMemo(\n () =>\n directoryIds\n .filter((id) => !assignedRowIds.includes(id))\n .map((id) => profiles.get(id) ?? { id, name: id, userId: null })\n .sort((left, right) => left.name.localeCompare(right.name)),\n [directoryIds, assignedRowIds, profiles],\n )\n\n const changeCountLabel = React.useMemo(() => {\n if (pendingChanges === 0) {\n return t('staff.time_tracking.projects.team.drawer.footer.noChanges', 'No changes')\n }\n const bucket = selectChangeCountKey(pendingChanges)\n if (bucket === 'one') {\n return t('staff.time_tracking.projects.team.drawer.footer.changes.one', '1 change to save', { count: pendingChanges })\n }\n if (bucket === 'few') {\n return t('staff.time_tracking.projects.team.drawer.footer.changes.few', '{count} changes to save', { count: pendingChanges })\n }\n return t('staff.time_tracking.projects.team.drawer.footer.changes.many', '{count} changes to save', { count: pendingChanges })\n }, [pendingChanges, t])\n\n const renderStateBadge = React.useCallback(\n (staffMemberId: string, isAssigned: boolean, isChecked: boolean, state: AssignmentState, endDate: string | null) => {\n if (isAssigned && !isChecked) {\n return (\n <Badge variant=\"error\">\n {t('staff.time_tracking.projects.team.drawer.badges.removing', 'being removed')}\n </Badge>\n )\n }\n if (!isAssigned && isChecked) {\n return (\n <Badge variant=\"success\">\n {t('staff.time_tracking.projects.team.drawer.badges.adding', 'being added')}\n </Badge>\n )\n }\n if (!isAssigned) return null\n if (state === 'expired') {\n return (\n <Badge variant=\"warning\">\n {t('staff.time_tracking.projects.team.drawer.badges.expired', 'expired {date}', { date: endDate ?? '' })}\n </Badge>\n )\n }\n if (state === 'scheduled') {\n return (\n <Badge variant=\"neutral\">\n {t('staff.time_tracking.projects.team.drawer.badges.scheduled', 'from {date}', {\n date: assignedByStaffMemberId.get(staffMemberId)?.startDate ?? '',\n })}\n </Badge>\n )\n }\n return null\n },\n [assignedByStaffMemberId, t],\n )\n\n const renderRow = React.useCallback(\n (profile: PersonProfile) => {\n const member = assignedByStaffMemberId.get(profile.id) ?? null\n const isLocked = lockedStaffMemberIds.has(profile.id)\n const isChecked = isLocked || selected.has(profile.id)\n const draftEndDate = endDrafts.has(profile.id) ? endDrafts.get(profile.id) ?? null : member?.endDate ?? null\n const state = member\n ? resolveAssignmentState({ startDate: member.startDate, endDate: draftEndDate }, graceDays)\n : 'active'\n const role = roleLabel(profile.id)\n const hint = isLocked\n ? t('staff.time_tracking.projects.team.drawer.hintFromRole', '{role} \u00B7 access from role', { role })\n : member\n ? t('staff.time_tracking.projects.team.drawer.hintInProject', '{role} \u00B7 {hours} in this project', {\n role,\n hours: hoursLabel(profile.id),\n })\n : t('staff.time_tracking.projects.team.drawer.hintPerson', '{role} \u00B7 {hours}', {\n role,\n hours: hoursLabel(profile.id),\n })\n\n const windowLine = member && (member.startDate || draftEndDate) ? (\n <span className=\"text-xs text-muted-foreground\">\n {draftEndDate\n ? t('staff.time_tracking.projects.team.drawer.window.range', 'from {start} \u00B7 until {end}', {\n start: member.startDate ?? '',\n end: draftEndDate,\n })\n : t('staff.time_tracking.projects.team.drawer.window.startOnly', 'from {start}', {\n start: member.startDate ?? '',\n })}\n {state === 'expired' ? (\n <span className=\"block text-status-warning-text\">\n {t(\n 'staff.time_tracking.projects.team.drawer.window.expiredHint',\n 'The assignment expired \u2014 this person no longer has access to the project.',\n )}\n </span>\n ) : null}\n </span>\n ) : null\n\n const endDateControl = member && !isLocked ? (\n <div className=\"flex items-center gap-2 border-t border-border px-3 py-2\">\n <label className=\"text-xs text-muted-foreground\" htmlFor={`team-end-date-${profile.id}`}>\n {t('staff.time_tracking.projects.team.drawer.window.label', 'Assignment end')}\n </label>\n <Input\n id={`team-end-date-${profile.id}`}\n type=\"date\"\n size=\"sm\"\n className=\"w-40\"\n value={draftEndDate ?? ''}\n onChange={(event) => setEndDate(profile.id, event.target.value)}\n />\n {draftEndDate ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setEndDate(profile.id, null)}>\n {t('staff.time_tracking.projects.team.drawer.window.clear', 'Clear')}\n </Button>\n ) : null}\n </div>\n ) : null\n\n return (\n <TeamPickRow\n key={profile.id}\n profile={profile}\n hint={hint}\n checked={isChecked}\n locked={isLocked}\n lockLabel={t('staff.time_tracking.projects.team.drawer.badges.always', 'always')}\n lockedHint={t(\n 'staff.time_tracking.projects.team.drawer.lockedHint',\n \"The Team Leader's access comes from the projects permission, not from an assignment, so it cannot be changed here.\",\n )}\n badge={renderStateBadge(profile.id, member !== null, isChecked, state, draftEndDate)}\n windowLine={windowLine}\n endDateControl={endDateControl}\n selectLabel={t('staff.time_tracking.projects.team.drawer.selectPerson', 'Assign {name}', { name: profile.name })}\n onToggle={(next) => { void toggleMember(profile.id, next) }}\n />\n )\n },\n [\n assignedByStaffMemberId,\n endDrafts,\n graceDays,\n hoursLabel,\n lockedStaffMemberIds,\n renderStateBadge,\n roleLabel,\n selected,\n setEndDate,\n t,\n toggleMember,\n ],\n )\n\n return (\n <Drawer\n open={open}\n onOpenChange={(next) => {\n if (next) {\n onOpenChange(true)\n return\n }\n void requestClose()\n }}\n >\n <DrawerContent\n className=\"sm:max-w-lg\"\n onKeyDown={handleKeyDown}\n closeAriaLabel={t('staff.time_tracking.projects.team.drawer.close', 'Close')}\n >\n <DrawerHeader>\n <DrawerTitle>{t('staff.time_tracking.projects.team.drawer.title', 'Project team')}</DrawerTitle>\n <DrawerDescription>{projectName}</DrawerDescription>\n </DrawerHeader>\n <DrawerBody className=\"flex flex-col gap-4\">\n {loading ? (\n <LoadingMessage label={t('staff.time_tracking.projects.team.drawer.loading', 'Loading the team\u2026')} />\n ) : loadError ? (\n <ErrorMessage label={loadError} />\n ) : (\n <>\n <SearchInput\n value={query}\n onChange={setQuery}\n placeholder={t('staff.time_tracking.projects.team.drawer.search', 'Search for a person\u2026')}\n />\n\n <section className=\"flex flex-col gap-2\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.assigned', 'Assigned ({count})', {\n count: assignedRowIds.length,\n })}\n </h3>\n {assignedRows.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.projects.team.empty', 'Nobody is assigned yet.')}\n </p>\n ) : (\n <ul className=\"flex flex-col gap-2\">{assignedRows.map(renderRow)}</ul>\n )}\n </section>\n\n <section className=\"flex flex-col gap-2\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.others', 'Everyone else in the organization')}\n </h3>\n {otherRows.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.othersEmpty', 'No other people found.')}\n </p>\n ) : (\n <ul className=\"flex flex-col gap-2\">{otherRows.map(renderRow)}</ul>\n )}\n {directoryTotal > directoryIds.length ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.projects.team.drawer.directoryTruncated',\n 'Showing the first {count} people \u2014 use the search box to find the rest.',\n { count: directoryIds.length },\n )}\n </p>\n ) : null}\n </section>\n\n <Alert status=\"information\">\n <AlertDescription>\n {t(\n 'staff.time_tracking.projects.team.drawer.info.keepsTime',\n 'Revoking access does not delete logged time \u2014 the entries stay in the project and in reports.',\n )}\n </AlertDescription>\n <AlertDescription>\n {t(\n 'staff.time_tracking.projects.team.drawer.info.immediate',\n 'Access works immediately after saving \u2014 no re-login needed.',\n )}\n </AlertDescription>\n </Alert>\n </>\n )}\n </DrawerBody>\n <DrawerFooter leading={<span className=\"text-xs text-muted-foreground\">{changeCountLabel}</span>}>\n <Button type=\"button\" variant=\"ghost\" onClick={() => { void requestClose() }}>\n {t('staff.time_tracking.projects.team.drawer.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" disabled={saving || pendingChanges === 0} onClick={() => { void handleSave() }}>\n <Save className=\"size-4\" aria-hidden />\n {saving\n ? t('staff.time_tracking.projects.team.drawer.saving', 'Saving\u2026')\n : t('staff.time_tracking.projects.team.drawer.save', 'Save')}\n </Button>\n </DrawerFooter>\n {ConfirmDialogElement}\n </DrawerContent>\n </Drawer>\n )\n}\n\nexport default ProjectTeamDrawer\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Lock, Save } from 'lucide-react'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { SearchInput } from '@open-mercato/ui/primitives/search-input'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useCurrentUserId } from '@open-mercato/ui/backend/utils/useCurrentUserId'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n countPendingChanges,\n diffTeamSelection,\n formatProjectMinutes,\n resolveAssignmentState,\n resolveGraceDays,\n selectChangeCountKey,\n toIsoDate,\n type AssignedMember,\n type AssignmentState,\n} from './projectTeamAssignment'\n\nconst logger = createLogger('staff').child({ component: 'ProjectTeamDrawer' })\n\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.project_team'\nconst DIRECTORY_PAGE_SIZE = 100\nconst ENTRIES_PAGE_SIZE = 100\nconst MAX_ENTRY_PAGES = 20\nconst SEARCH_DEBOUNCE_MS = 250\n\ntype ApiRow = Record<string, unknown>\n\ntype PersonProfile = {\n id: string\n name: string\n userId: string | null\n}\n\ntype ProjectHours = {\n minutesByStaffMemberId: Map<string, number>\n partial: boolean\n}\n\nexport type ProjectTeamDrawerProps = {\n projectId: string\n projectName: string\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Pre-checks the person who requested access (notification deep link, D-6). */\n preselectUserId?: string | null\n onSaved?: () => void\n}\n\nfunction readString(row: ApiRow, ...keys: string[]): string | null {\n for (const key of keys) {\n const value = row[key]\n if (typeof value === 'string' && value.trim().length > 0) return value\n if (value instanceof Date) return value.toISOString()\n }\n return null\n}\n\nfunction readNumber(row: ApiRow, ...keys: string[]): number {\n for (const key of keys) {\n const value = row[key]\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length > 0) {\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n }\n }\n return 0\n}\n\nfunction readItems(payload: { items?: unknown } | null | undefined): ApiRow[] {\n const items = payload?.items\n return Array.isArray(items) ? (items as ApiRow[]) : []\n}\n\nfunction toProfile(row: ApiRow): PersonProfile | null {\n const id = readString(row, 'id')\n if (!id) return null\n return {\n id,\n name: readString(row, 'display_name', 'displayName') ?? id,\n userId: readString(row, 'user_id', 'userId'),\n }\n}\n\nfunction toAssignedMember(row: ApiRow): AssignedMember | null {\n const membershipId = readString(row, 'id')\n const staffMemberId = readString(row, 'staff_member_id', 'staffMemberId')\n if (!membershipId || !staffMemberId) return null\n return {\n membershipId,\n staffMemberId,\n role: readString(row, 'role'),\n status: readString(row, 'status'),\n startDate: toIsoDate(readString(row, 'assigned_start_date', 'assignedStartDate')),\n endDate: toIsoDate(readString(row, 'assigned_end_date', 'assignedEndDate')),\n }\n}\n\nfunction todayIsoDate(): string {\n const now = new Date()\n const month = String(now.getMonth() + 1).padStart(2, '0')\n const day = String(now.getDate()).padStart(2, '0')\n return `${now.getFullYear()}-${month}-${day}`\n}\n\n/**\n * Hours logged per person in this project. There is no aggregate endpoint yet,\n * so the drawer pages the (tenant-scoped) entries list and sums client-side;\n * beyond `MAX_ENTRY_PAGES` the totals are reported as a lower bound rather than\n * silently under-reporting.\n */\nasync function loadProjectHours(projectId: string): Promise<ProjectHours> {\n const minutesByStaffMemberId = new Map<string, number>()\n let partial = false\n for (let page = 1; page <= MAX_ENTRY_PAGES; page += 1) {\n const params = new URLSearchParams({\n projectId,\n page: String(page),\n pageSize: String(ENTRIES_PAGE_SIZE),\n })\n const res = await apiCall<{ items?: unknown; total?: number }>(\n `/api/staff/timesheets/time-entries?${params.toString()}`,\n )\n if (!res.ok) return { minutesByStaffMemberId, partial: true }\n const rows = readItems(res.result)\n for (const row of rows) {\n const staffMemberId = readString(row, 'staff_member_id', 'staffMemberId')\n if (!staffMemberId) continue\n const minutes = readNumber(row, 'duration_minutes', 'durationMinutes')\n minutesByStaffMemberId.set(staffMemberId, (minutesByStaffMemberId.get(staffMemberId) ?? 0) + minutes)\n }\n if (rows.length < ENTRIES_PAGE_SIZE) return { minutesByStaffMemberId, partial: false }\n const total = typeof res.result?.total === 'number' ? res.result.total : null\n if (total !== null && page * ENTRIES_PAGE_SIZE >= total) return { minutesByStaffMemberId, partial: false }\n partial = true\n }\n return { minutesByStaffMemberId, partial }\n}\n\ntype TeamPickRowProps = {\n profile: PersonProfile\n hint: string\n checked: boolean\n locked: boolean\n lockedHint: string\n lockLabel: string\n badge: React.ReactNode\n windowLine: React.ReactNode\n endDateControl: React.ReactNode\n selectLabel: string\n onToggle: (next: boolean) => void\n}\n\nfunction TeamPickRow({\n profile,\n hint,\n checked,\n locked,\n lockedHint,\n lockLabel,\n badge,\n windowLine,\n endDateControl,\n selectLabel,\n onToggle,\n}: TeamPickRowProps) {\n return (\n <li className=\"rounded-md border border-border\" data-staff-member-id={profile.id}>\n <label className=\"flex cursor-pointer items-start gap-3 p-3\">\n <span className=\"mt-0.5 shrink-0\">\n <Checkbox\n checked={checked}\n disabled={locked}\n aria-label={locked ? `${selectLabel} \u2014 ${lockedHint}` : selectLabel}\n onCheckedChange={(next) => onToggle(next === true)}\n />\n </span>\n <Avatar label={profile.name} size=\"sm\" />\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">{profile.name}</span>\n <span className=\"text-xs text-muted-foreground\">{hint}</span>\n {windowLine}\n </span>\n <span className=\"flex shrink-0 flex-col items-end gap-1\">\n {locked ? (\n <SimpleTooltip content={lockedHint}>\n <Badge variant=\"neutral\" className=\"gap-1\">\n <Lock className=\"size-3\" aria-hidden />\n {lockLabel}\n </Badge>\n </SimpleTooltip>\n ) : null}\n {badge}\n </span>\n </label>\n {endDateControl}\n </li>\n )\n}\n\nexport function ProjectTeamDrawer({\n projectId,\n projectName,\n open,\n onOpenChange,\n preselectUserId,\n onSaved,\n}: ProjectTeamDrawerProps) {\n const t = useT()\n const translateRef = React.useRef(t)\n React.useEffect(() => { translateRef.current = t }, [t])\n const currentUserId = useCurrentUserId()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n\n const [loading, setLoading] = React.useState(true)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [assigned, setAssigned] = React.useState<AssignedMember[]>([])\n const [profiles, setProfiles] = React.useState<Map<string, PersonProfile>>(new Map())\n const [directoryIds, setDirectoryIds] = React.useState<string[]>([])\n const [directoryTotal, setDirectoryTotal] = React.useState(0)\n const [selfStaffMemberId, setSelfStaffMemberId] = React.useState<string | null>(null)\n const [graceDays, setGraceDays] = React.useState<number>(resolveGraceDays(null))\n const [hours, setHours] = React.useState<ProjectHours | null>(null)\n const [hoursLoading, setHoursLoading] = React.useState(true)\n const [selected, setSelected] = React.useState<Set<string>>(new Set())\n const [endDrafts, setEndDrafts] = React.useState<Map<string, string | null>>(new Map())\n const [query, setQuery] = React.useState('')\n const [debouncedQuery, setDebouncedQuery] = React.useState('')\n\n const mergeProfiles = React.useCallback((rows: ApiRow[]) => {\n setProfiles((previous) => {\n const next = new Map(previous)\n for (const row of rows) {\n const profile = toProfile(row)\n if (profile) next.set(profile.id, profile)\n }\n return next\n })\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n const handle = setTimeout(() => setDebouncedQuery(query.trim()), SEARCH_DEBOUNCE_MS)\n return () => clearTimeout(handle)\n }, [open, query])\n\n React.useEffect(() => {\n if (!open) {\n setQuery('')\n setDebouncedQuery('')\n }\n }, [open])\n\n const loadTeam = React.useCallback(async () => {\n setLoading(true)\n setLoadError(null)\n try {\n const [membersRes, selfRes, settingsRes] = await Promise.all([\n apiCall<{ items?: unknown }>(\n `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&status=active`,\n ),\n apiCall<{ member?: { id?: string; displayName?: string; userId?: string | null } | null }>(\n '/api/staff/team-members/self',\n ),\n apiCall<{ access?: { assignmentGraceDays?: unknown } | null }>('/api/staff/timesheets/settings'),\n ])\n\n if (!membersRes.ok) {\n setLoadError(translateRef.current('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))\n return\n }\n\n const members = readItems(membersRes.result)\n .map(toAssignedMember)\n .filter((member): member is AssignedMember => member !== null)\n setAssigned(members)\n setSelected(new Set(members.map((member) => member.staffMemberId)))\n setEndDrafts(new Map(members.map((member) => [member.staffMemberId, member.endDate])))\n\n const selfMember = selfRes.ok ? selfRes.result?.member ?? null : null\n if (selfMember?.id) {\n setSelfStaffMemberId(selfMember.id)\n mergeProfiles([\n { id: selfMember.id, display_name: selfMember.displayName ?? selfMember.id, user_id: selfMember.userId ?? null },\n ])\n }\n\n if (settingsRes.ok) {\n setGraceDays(resolveGraceDays(settingsRes.result?.access?.assignmentGraceDays))\n }\n\n const assignedIds = members.map((member) => member.staffMemberId)\n if (assignedIds.length > 0) {\n const profileRes = await apiCall<{ items?: unknown }>(\n `/api/staff/team-members?ids=${assignedIds.join(',')}&pageSize=${DIRECTORY_PAGE_SIZE}`,\n )\n if (profileRes.ok) mergeProfiles(readItems(profileRes.result))\n }\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.load failed', { err: error })\n setLoadError(translateRef.current('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))\n } finally {\n setLoading(false)\n }\n }, [projectId, mergeProfiles])\n\n React.useEffect(() => {\n if (!open) return\n void loadTeam()\n }, [open, loadTeam])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n setHoursLoading(true)\n void (async () => {\n try {\n const result = await loadProjectHours(projectId)\n if (!cancelled) setHours(result)\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.hours failed', { err: error })\n if (!cancelled) setHours(null)\n } finally {\n if (!cancelled) setHoursLoading(false)\n }\n })()\n return () => { cancelled = true }\n }, [open, projectId])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n void (async () => {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(DIRECTORY_PAGE_SIZE),\n isActive: 'true',\n })\n if (debouncedQuery.length > 0) params.set('search', debouncedQuery)\n const res = await apiCall<{ items?: unknown; total?: number }>(`/api/staff/team-members?${params.toString()}`)\n if (cancelled || !res.ok) return\n const rows = readItems(res.result)\n mergeProfiles(rows)\n setDirectoryIds(rows.map((row) => readString(row, 'id')).filter((id): id is string => id !== null))\n setDirectoryTotal(typeof res.result?.total === 'number' ? res.result.total : rows.length)\n })()\n return () => { cancelled = true }\n }, [open, debouncedQuery, mergeProfiles])\n\n const lockedStaffMemberId = React.useMemo(() => {\n if (selfStaffMemberId) return selfStaffMemberId\n if (!currentUserId) return null\n for (const profile of profiles.values()) {\n if (profile.userId && profile.userId === currentUserId) return profile.id\n }\n return null\n }, [selfStaffMemberId, currentUserId, profiles])\n\n const lockedStaffMemberIds = React.useMemo(\n () => new Set(lockedStaffMemberId ? [lockedStaffMemberId] : []),\n [lockedStaffMemberId],\n )\n\n React.useEffect(() => {\n if (!open || !preselectUserId || loading) return\n for (const profile of profiles.values()) {\n if (profile.userId !== preselectUserId) continue\n setSelected((previous) => (previous.has(profile.id) ? previous : new Set(previous).add(profile.id)))\n return\n }\n }, [open, preselectUserId, loading, profiles])\n\n const assignedByStaffMemberId = React.useMemo(() => {\n const map = new Map<string, AssignedMember>()\n for (const member of assigned) {\n if (!map.has(member.staffMemberId)) map.set(member.staffMemberId, member)\n }\n return map\n }, [assigned])\n\n const changes = React.useMemo(\n () => diffTeamSelection(assigned, { selectedStaffMemberIds: selected, endDateByStaffMemberId: endDrafts }, lockedStaffMemberIds),\n [assigned, selected, endDrafts, lockedStaffMemberIds],\n )\n const pendingChanges = countPendingChanges(changes)\n\n const memberMinutes = React.useCallback(\n (staffMemberId: string): number | null => {\n if (!hours) return null\n return hours.minutesByStaffMemberId.get(staffMemberId) ?? 0\n },\n [hours],\n )\n\n const hoursLabel = React.useCallback(\n (staffMemberId: string): string => {\n if (hoursLoading) return '\u2026'\n const minutes = memberMinutes(staffMemberId)\n if (minutes === null) return formatProjectMinutes(0)\n const formatted = formatProjectMinutes(minutes)\n if (!hours?.partial) return formatted\n return t('staff.time_tracking.projects.team.drawer.hoursAtLeast', 'at least {hours}', { hours: formatted })\n },\n [hours, hoursLoading, memberMinutes, t],\n )\n\n const roleLabel = React.useCallback(\n (staffMemberId: string): string => {\n const member = assignedByStaffMemberId.get(staffMemberId)\n if (member?.role && member.role.trim().length > 0) return member.role\n if (staffMemberId === lockedStaffMemberId) {\n return t('staff.time_tracking.projects.team.drawer.roleLeader', 'Team Leader')\n }\n return t('staff.time_tracking.projects.team.drawer.roleMember', 'Team Member')\n },\n [assignedByStaffMemberId, lockedStaffMemberId, t],\n )\n\n const toggleMember = React.useCallback(\n async (staffMemberId: string, next: boolean) => {\n if (lockedStaffMemberIds.has(staffMemberId)) return\n if (!next && assignedByStaffMemberId.has(staffMemberId)) {\n const profile = profiles.get(staffMemberId)\n const name = profile?.name ?? staffMemberId\n const minutes = memberMinutes(staffMemberId)\n const needsConfirm = minutes === null || minutes > 0\n if (needsConfirm) {\n const text = minutes === null || hoursLoading\n ? t(\n 'staff.time_tracking.projects.team.drawer.removeConfirm.textUnknown',\n '{name} may have time logged in this project \u2014 revoke access?',\n { name },\n )\n : t(\n 'staff.time_tracking.projects.team.drawer.removeConfirm.text',\n '{name} has {hours} in this project \u2014 revoke access?',\n { name, hours: formatProjectMinutes(minutes) },\n )\n const confirmed = await confirm({\n title: t('staff.time_tracking.projects.team.drawer.removeConfirm.title', 'Revoke access?'),\n text,\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n }\n setSelected((previous) => {\n const draft = new Set(previous)\n if (next) draft.add(staffMemberId)\n else draft.delete(staffMemberId)\n return draft\n })\n },\n [assignedByStaffMemberId, confirm, hoursLoading, lockedStaffMemberIds, memberMinutes, profiles, t],\n )\n\n const setEndDate = React.useCallback((staffMemberId: string, value: string | null) => {\n setEndDrafts((previous) => {\n const draft = new Map(previous)\n draft.set(staffMemberId, value && value.length > 0 ? value : null)\n return draft\n })\n }, [])\n\n // Membership rows are an assignment junction, so they are exempt from OSS\n // optimistic locking (data-integrity contract); the drawer never PATCHes the\n // project aggregate itself, so no expected-updated-at header applies here.\n const persistChanges = React.useCallback(async () => {\n const employeesPath = `staff/timesheets/time-projects/${projectId}/employees`\n const startDate = todayIsoDate()\n\n for (const staffMemberId of changes.additions) {\n await createCrud(employeesPath, {\n timeProjectId: projectId,\n staffMemberId,\n assignedStartDate: startDate,\n assignedEndDate: endDrafts.get(staffMemberId) ?? null,\n }, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n\n // Re-dating is an update of the membership row itself (T2.12): the audit\n // trail must read \"assignment end date changed\", not \"member removed,\n // member added\". Only the changed field is sent, so the recorded diff names\n // `assignedEndDate` alone.\n //\n // No optimistic-lock header, deliberately. The endpoint accepts and enforces\n // one (the CRUD factory registers a reader for this entity), but this drawer\n // saves a whole batch \u2014 additions, re-datings and removals \u2014 with no\n // rollback. A per-row version round-trip belongs with a per-row conflict UX;\n // the batch save is not it.\n //\n // What this loop guarantees and what it does not (C6):\n // * GUARANTEED \u2014 the loop stops at the first failure, so no write is\n // attempted after one fails, and `handleSave` reloads the drawer from the\n // server before showing the error, so what the user then sees is the\n // persisted team rather than the optimistic local snapshot.\n // * NOT GUARANTEED \u2014 atomicity. There is no transaction across these three\n // passes: whatever was written before the failure stays written and is\n // not rolled back. The reload therefore also drops the part of the batch\n // that never reached the server; the user re-applies it and saves again.\n for (const update of changes.endDateUpdates) {\n await updateCrud(employeesPath, {\n id: update.member.membershipId,\n assignedEndDate: update.endDate,\n }, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n\n for (const member of changes.removals) {\n await deleteCrud(employeesPath, member.membershipId, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n }, [changes, endDrafts, projectId, t])\n\n const handleSave = React.useCallback(async () => {\n if (saving || pendingChanges === 0) return\n setSaving(true)\n try {\n await runMutation({\n operation: persistChanges,\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: projectId,\n action: 'assign-team',\n retryLastMutation,\n },\n mutationPayload: {\n timeProjectId: projectId,\n additions: changes.additions,\n removals: changes.removals.map((member) => member.membershipId),\n endDateUpdates: changes.endDateUpdates.map((update) => ({\n id: update.member.membershipId,\n assignedEndDate: update.endDate,\n })),\n },\n })\n flash(t('staff.time_tracking.projects.team.drawer.saved', 'Project team updated.'), 'success')\n onSaved?.()\n onOpenChange(false)\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.save failed', { err: error })\n // The batch is not transactional (see `persistChanges`), so the local\n // snapshot can no longer be trusted: re-read the memberships first and\n // only then report, so the error and the rows underneath it agree.\n await loadTeam()\n const reason =\n error instanceof Error && error.message.trim().length > 0\n ? error.message\n : t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.')\n flash(\n t(\n 'staff.time_tracking.projects.team.drawer.partialSaveError',\n '{reason} Saving stopped part-way through, so the team shown below was reloaded from the server \u2014 check it and apply the remaining changes again.',\n { reason },\n ),\n 'error',\n )\n } finally {\n setSaving(false)\n }\n }, [\n changes,\n loadTeam,\n onOpenChange,\n onSaved,\n pendingChanges,\n persistChanges,\n projectId,\n retryLastMutation,\n runMutation,\n saving,\n t,\n ])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n void handleSave()\n }\n },\n [handleSave],\n )\n\n /**\n * The drawer batches the whole team edit and writes nothing until Save, so\n * Escape, the \u00D7, the overlay and Cancel each stand to discard a set of changes\n * the footer is at that moment counting out loud. They all reach `onOpenChange`,\n * so the guard sits there once rather than on each of them.\n */\n const requestClose = React.useCallback(async () => {\n if (saving) return\n if (pendingChanges === 0) {\n onOpenChange(false)\n return\n }\n const confirmed = await confirm({\n title: t('staff.time_tracking.projects.team.drawer.discard.title', 'Discard the team changes?'),\n text: t(\n 'staff.time_tracking.projects.team.drawer.discard.text',\n 'Nothing has been saved yet, so closing now loses every change in this drawer.',\n ),\n confirmText: t('staff.time_tracking.projects.team.drawer.discard.confirm', 'Discard'),\n cancelText: t('staff.time_tracking.projects.team.drawer.discard.cancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (confirmed) onOpenChange(false)\n }, [confirm, onOpenChange, pendingChanges, saving, t])\n\n const matchesQuery = React.useCallback(\n (profile: PersonProfile) => {\n if (debouncedQuery.length === 0) return true\n return profile.name.toLocaleLowerCase().includes(debouncedQuery.toLocaleLowerCase())\n },\n [debouncedQuery],\n )\n\n const assignedRowIds = React.useMemo(() => {\n const ids = new Set<string>(assignedByStaffMemberId.keys())\n if (lockedStaffMemberId) ids.add(lockedStaffMemberId)\n return Array.from(ids)\n }, [assignedByStaffMemberId, lockedStaffMemberId])\n\n const assignedRows = React.useMemo(\n () =>\n assignedRowIds\n .map((id) => profiles.get(id) ?? { id, name: id, userId: null })\n .filter(matchesQuery)\n .sort((left, right) => left.name.localeCompare(right.name)),\n [assignedRowIds, profiles, matchesQuery],\n )\n\n const otherRows = React.useMemo(\n () =>\n directoryIds\n .filter((id) => !assignedRowIds.includes(id))\n .map((id) => profiles.get(id) ?? { id, name: id, userId: null })\n .sort((left, right) => left.name.localeCompare(right.name)),\n [directoryIds, assignedRowIds, profiles],\n )\n\n const changeCountLabel = React.useMemo(() => {\n if (pendingChanges === 0) {\n return t('staff.time_tracking.projects.team.drawer.footer.noChanges', 'No changes')\n }\n const bucket = selectChangeCountKey(pendingChanges)\n if (bucket === 'one') {\n return t('staff.time_tracking.projects.team.drawer.footer.changes.one', '1 change to save', { count: pendingChanges })\n }\n if (bucket === 'few') {\n return t('staff.time_tracking.projects.team.drawer.footer.changes.few', '{count} changes to save', { count: pendingChanges })\n }\n return t('staff.time_tracking.projects.team.drawer.footer.changes.many', '{count} changes to save', { count: pendingChanges })\n }, [pendingChanges, t])\n\n const renderStateBadge = React.useCallback(\n (staffMemberId: string, isAssigned: boolean, isChecked: boolean, state: AssignmentState, endDate: string | null) => {\n if (isAssigned && !isChecked) {\n return (\n <Badge variant=\"error\">\n {t('staff.time_tracking.projects.team.drawer.badges.removing', 'being removed')}\n </Badge>\n )\n }\n if (!isAssigned && isChecked) {\n return (\n <Badge variant=\"success\">\n {t('staff.time_tracking.projects.team.drawer.badges.adding', 'being added')}\n </Badge>\n )\n }\n if (!isAssigned) return null\n if (state === 'expired') {\n return (\n <Badge variant=\"warning\">\n {t('staff.time_tracking.projects.team.drawer.badges.expired', 'expired {date}', { date: endDate ?? '' })}\n </Badge>\n )\n }\n if (state === 'scheduled') {\n return (\n <Badge variant=\"neutral\">\n {t('staff.time_tracking.projects.team.drawer.badges.scheduled', 'from {date}', {\n date: assignedByStaffMemberId.get(staffMemberId)?.startDate ?? '',\n })}\n </Badge>\n )\n }\n return null\n },\n [assignedByStaffMemberId, t],\n )\n\n const renderRow = React.useCallback(\n (profile: PersonProfile) => {\n const member = assignedByStaffMemberId.get(profile.id) ?? null\n const isLocked = lockedStaffMemberIds.has(profile.id)\n const isChecked = isLocked || selected.has(profile.id)\n const draftEndDate = endDrafts.has(profile.id) ? endDrafts.get(profile.id) ?? null : member?.endDate ?? null\n const state = member\n ? resolveAssignmentState({ startDate: member.startDate, endDate: draftEndDate }, graceDays)\n : 'active'\n const role = roleLabel(profile.id)\n const hint = isLocked\n ? t('staff.time_tracking.projects.team.drawer.hintFromRole', '{role} \u00B7 access from role', { role })\n : member\n ? t('staff.time_tracking.projects.team.drawer.hintInProject', '{role} \u00B7 {hours} in this project', {\n role,\n hours: hoursLabel(profile.id),\n })\n : t('staff.time_tracking.projects.team.drawer.hintPerson', '{role} \u00B7 {hours}', {\n role,\n hours: hoursLabel(profile.id),\n })\n\n const windowLine = member && (member.startDate || draftEndDate) ? (\n <span className=\"text-xs text-muted-foreground\">\n {draftEndDate\n ? t('staff.time_tracking.projects.team.drawer.window.range', 'from {start} \u00B7 until {end}', {\n start: member.startDate ?? '',\n end: draftEndDate,\n })\n : t('staff.time_tracking.projects.team.drawer.window.startOnly', 'from {start}', {\n start: member.startDate ?? '',\n })}\n {state === 'expired' ? (\n <span className=\"block text-status-warning-text\">\n {t(\n 'staff.time_tracking.projects.team.drawer.window.expiredHint',\n 'The assignment expired \u2014 this person no longer has access to the project.',\n )}\n </span>\n ) : null}\n </span>\n ) : null\n\n const endDateControl = member && !isLocked ? (\n <div className=\"flex items-center gap-2 border-t border-border px-3 py-2\">\n <label className=\"text-xs text-muted-foreground\" htmlFor={`team-end-date-${profile.id}`}>\n {t('staff.time_tracking.projects.team.drawer.window.label', 'Assignment end')}\n </label>\n <Input\n id={`team-end-date-${profile.id}`}\n type=\"date\"\n size=\"sm\"\n className=\"w-40\"\n value={draftEndDate ?? ''}\n onChange={(event) => setEndDate(profile.id, event.target.value)}\n />\n {draftEndDate ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setEndDate(profile.id, null)}>\n {t('staff.time_tracking.projects.team.drawer.window.clear', 'Clear')}\n </Button>\n ) : null}\n </div>\n ) : null\n\n return (\n <TeamPickRow\n key={profile.id}\n profile={profile}\n hint={hint}\n checked={isChecked}\n locked={isLocked}\n lockLabel={t('staff.time_tracking.projects.team.drawer.badges.always', 'always')}\n lockedHint={t(\n 'staff.time_tracking.projects.team.drawer.lockedHint',\n \"The Team Leader's access comes from the projects permission, not from an assignment, so it cannot be changed here.\",\n )}\n badge={renderStateBadge(profile.id, member !== null, isChecked, state, draftEndDate)}\n windowLine={windowLine}\n endDateControl={endDateControl}\n selectLabel={t('staff.time_tracking.projects.team.drawer.selectPerson', 'Assign {name}', { name: profile.name })}\n onToggle={(next) => { void toggleMember(profile.id, next) }}\n />\n )\n },\n [\n assignedByStaffMemberId,\n endDrafts,\n graceDays,\n hoursLabel,\n lockedStaffMemberIds,\n renderStateBadge,\n roleLabel,\n selected,\n setEndDate,\n t,\n toggleMember,\n ],\n )\n\n return (\n <Drawer\n open={open}\n onOpenChange={(next) => {\n if (next) {\n onOpenChange(true)\n return\n }\n void requestClose()\n }}\n >\n <DrawerContent\n className=\"sm:max-w-lg\"\n onKeyDown={handleKeyDown}\n closeAriaLabel={t('staff.time_tracking.projects.team.drawer.close', 'Close')}\n >\n <DrawerHeader>\n <DrawerTitle>{t('staff.time_tracking.projects.team.drawer.title', 'Project team')}</DrawerTitle>\n <DrawerDescription>{projectName}</DrawerDescription>\n </DrawerHeader>\n <DrawerBody className=\"flex flex-col gap-4\">\n {loading ? (\n <LoadingMessage label={t('staff.time_tracking.projects.team.drawer.loading', 'Loading the team\u2026')} />\n ) : loadError ? (\n <ErrorMessage label={loadError} />\n ) : (\n <>\n <SearchInput\n value={query}\n onChange={setQuery}\n placeholder={t('staff.time_tracking.projects.team.drawer.search', 'Search for a person\u2026')}\n />\n\n <section className=\"flex flex-col gap-2\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.assigned', 'Assigned ({count})', {\n count: assignedRowIds.length,\n })}\n </h3>\n {assignedRows.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.projects.team.empty', 'Nobody is assigned yet.')}\n </p>\n ) : (\n <ul className=\"flex flex-col gap-2\">{assignedRows.map(renderRow)}</ul>\n )}\n </section>\n\n <section className=\"flex flex-col gap-2\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.others', 'Everyone else in the organization')}\n </h3>\n {otherRows.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.othersEmpty', 'No other people found.')}\n </p>\n ) : (\n <ul className=\"flex flex-col gap-2\">{otherRows.map(renderRow)}</ul>\n )}\n {directoryTotal > directoryIds.length ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.projects.team.drawer.directoryTruncated',\n 'Showing the first {count} people \u2014 use the search box to find the rest.',\n { count: directoryIds.length },\n )}\n </p>\n ) : null}\n </section>\n\n <Alert status=\"information\">\n <AlertDescription>\n {t(\n 'staff.time_tracking.projects.team.drawer.info.keepsTime',\n 'Revoking access does not delete logged time \u2014 the entries stay in the project and in reports.',\n )}\n </AlertDescription>\n <AlertDescription>\n {t(\n 'staff.time_tracking.projects.team.drawer.info.immediate',\n 'Access works immediately after saving \u2014 no re-login needed.',\n )}\n </AlertDescription>\n </Alert>\n </>\n )}\n </DrawerBody>\n <DrawerFooter leading={<span className=\"text-xs text-muted-foreground\">{changeCountLabel}</span>}>\n <Button type=\"button\" variant=\"ghost\" onClick={() => { void requestClose() }}>\n {t('staff.time_tracking.projects.team.drawer.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" disabled={saving || pendingChanges === 0} onClick={() => { void handleSave() }}>\n <Save className=\"size-4\" aria-hidden />\n {saving\n ? t('staff.time_tracking.projects.team.drawer.saving', 'Saving\u2026')\n : t('staff.time_tracking.projects.team.drawer.save', 'Save')}\n </Button>\n </DrawerFooter>\n {ConfirmDialogElement}\n </DrawerContent>\n </Drawer>\n )\n}\n\nexport default ProjectTeamDrawer\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.10.1-develop.7764.1.7505d7340b",
3
+ "version": "0.10.1-develop.7765.1.391b8a0e16",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -266,16 +266,16 @@
266
266
  "zod": "^4.4.3"
267
267
  },
268
268
  "peerDependencies": {
269
- "@open-mercato/ai-assistant": "0.10.1-develop.7764.1.7505d7340b",
270
- "@open-mercato/shared": "0.10.1-develop.7764.1.7505d7340b",
271
- "@open-mercato/ui": "0.10.1-develop.7764.1.7505d7340b",
269
+ "@open-mercato/ai-assistant": "0.10.1-develop.7765.1.391b8a0e16",
270
+ "@open-mercato/shared": "0.10.1-develop.7765.1.391b8a0e16",
271
+ "@open-mercato/ui": "0.10.1-develop.7765.1.391b8a0e16",
272
272
  "react": "^19.0.0",
273
273
  "react-dom": "^19.0.0"
274
274
  },
275
275
  "devDependencies": {
276
- "@open-mercato/ai-assistant": "0.10.1-develop.7764.1.7505d7340b",
277
- "@open-mercato/shared": "0.10.1-develop.7764.1.7505d7340b",
278
- "@open-mercato/ui": "0.10.1-develop.7764.1.7505d7340b",
276
+ "@open-mercato/ai-assistant": "0.10.1-develop.7765.1.391b8a0e16",
277
+ "@open-mercato/shared": "0.10.1-develop.7765.1.391b8a0e16",
278
+ "@open-mercato/ui": "0.10.1-develop.7765.1.391b8a0e16",
279
279
  "@testing-library/dom": "^10.4.1",
280
280
  "@testing-library/jest-dom": "^7.0.1",
281
281
  "@testing-library/react": "^16.3.3",
@@ -4,6 +4,10 @@ The `staff` module is **optional** and slated for extraction into a standalone `
4
4
 
5
5
  See [`.ai/specs/implemented/2026-05-08-staff-decouple-from-core.md`](../../../../../.ai/specs/implemented/2026-05-08-staff-decouple-from-core.md) for the decoupling plan, and [`BACKWARD_COMPATIBILITY.md`](../../../../../BACKWARD_COMPATIBILITY.md) for the contract-surface taxonomy referenced below.
6
6
 
7
+ ## Always
8
+
9
+ - Preserve unsaved project-team drafts across translation-context updates. Tie data reloads to the project, organization scope, opening the drawer, or an explicit retry; read the current translator for asynchronous error copy without making translator identity a reload trigger.
10
+
7
11
  ## MUST Rules
8
12
 
9
13
  1. **MUST NOT import staff entities (`StaffTeam`, `StaffTeamMember`, etc.) from non-staff core modules.** Use the public surfaces below.
@@ -174,6 +174,8 @@ function RailFact({ label, value }: { label: string; value: React.ReactNode }) {
174
174
  export default function TimesheetProjectDetailPage({ params }: { params?: { id?: string } }) {
175
175
  const projectId = params?.id
176
176
  const t = useT()
177
+ const translateRef = React.useRef(t)
178
+ React.useEffect(() => { translateRef.current = t }, [t])
177
179
  const scopeVersion = useOrganizationScopeVersion()
178
180
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
179
181
  const router = useRouter()
@@ -413,7 +415,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
413
415
  }
414
416
  if (!cancelled) setAccessDeniedProjectId(null)
415
417
  if (!call.ok) {
416
- throw new Error(t('staff.timesheets.projects.errors.load', 'Failed to load project.'))
418
+ throw new Error(translateRef.current('staff.timesheets.projects.errors.load', 'Failed to load project.'))
417
419
  }
418
420
  const payload = call.result
419
421
  const record = Array.isArray(payload?.items) ? payload.items[0] : null
@@ -424,7 +426,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
424
426
  if (!cancelled) setProject(record)
425
427
  } catch (loadError) {
426
428
  if (!cancelled) {
427
- setError(loadError instanceof Error ? loadError.message : t('staff.timesheets.projects.errors.load', 'Failed to load project.'))
429
+ setError(loadError instanceof Error ? loadError.message : translateRef.current('staff.timesheets.projects.errors.load', 'Failed to load project.'))
428
430
  }
429
431
  } finally {
430
432
  if (!cancelled) {
@@ -435,7 +437,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
435
437
  }
436
438
  loadProject()
437
439
  return () => { cancelled = true }
438
- }, [projectId, t, scopeVersion])
440
+ }, [projectId, scopeVersion])
439
441
 
440
442
  // --- Load employees with name resolution ---
441
443
  const loadEmployees = React.useCallback(async () => {
@@ -445,7 +447,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
445
447
  const payload = await readApiResultOrThrow<EmployeesResponse>(
446
448
  `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=100`,
447
449
  undefined,
448
- { errorMessage: t('staff.timesheets.projects.employees.empty', 'No employees assigned yet.'), fallback: { items: [], total: 0 } },
450
+ { errorMessage: translateRef.current('staff.timesheets.projects.employees.empty', 'No employees assigned yet.'), fallback: { items: [], total: 0 } },
449
451
  )
450
452
  const items = Array.isArray(payload.items) ? payload.items : []
451
453
 
@@ -494,7 +496,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
494
496
  } finally {
495
497
  setEmployeesLoading(false)
496
498
  }
497
- }, [projectId, accessDenied, t])
499
+ }, [projectId, accessDenied])
498
500
 
499
501
  React.useEffect(() => {
500
502
  void loadEmployees()
@@ -232,6 +232,8 @@ export function ProjectTeamDrawer({
232
232
  onSaved,
233
233
  }: ProjectTeamDrawerProps) {
234
234
  const t = useT()
235
+ const translateRef = React.useRef(t)
236
+ React.useEffect(() => { translateRef.current = t }, [t])
235
237
  const currentUserId = useCurrentUserId()
236
238
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
237
239
  const { runMutation, retryLastMutation } = useGuardedMutation({
@@ -294,7 +296,7 @@ export function ProjectTeamDrawer({
294
296
  ])
295
297
 
296
298
  if (!membersRes.ok) {
297
- setLoadError(t('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))
299
+ setLoadError(translateRef.current('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))
298
300
  return
299
301
  }
300
302
 
@@ -326,11 +328,11 @@ export function ProjectTeamDrawer({
326
328
  }
327
329
  } catch (error) {
328
330
  logger.error('staff.time_tracking.projects.team.load failed', { err: error })
329
- setLoadError(t('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))
331
+ setLoadError(translateRef.current('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))
330
332
  } finally {
331
333
  setLoading(false)
332
334
  }
333
- }, [projectId, mergeProfiles, t])
335
+ }, [projectId, mergeProfiles])
334
336
 
335
337
  React.useEffect(() => {
336
338
  if (!open) return