@open-mercato/core 0.9.1-develop.7464.1.9a392fca41 → 0.9.1-develop.7467.1.3b2bb92d47

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.
@@ -1,4 +1,4 @@
1
- [build:core] found 5550 entry points
1
+ [build:core] found 5551 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 248 entry points
4
4
  [build:core:generated] built successfully
@@ -36,6 +36,7 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
36
36
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
37
37
  import { createLogger } from "@open-mercato/shared/lib/logger";
38
38
  import { TimeEntryDialog } from "../../../../lib/time-tracking-ui/TimeEntryDialog.js";
39
+ import { REPORTS_PATH, reportDetailHref } from "../../../../lib/time-tracking-ui/reportLinks.js";
39
40
  import { TimeEntryDurationCell } from "../../../../lib/time-tracking-ui/TimeEntryDurationCell.js";
40
41
  import { TimeEntriesSummaryFooter } from "../../../../lib/time-tracking-ui/TimeEntriesSummaryFooter.js";
41
42
  import {
@@ -65,7 +66,6 @@ const RATES_FEATURE = "staff.timesheets.rates.view";
65
66
  const MANAGE_OWN_FEATURE = "staff.timesheets.manage_own";
66
67
  const MANAGE_ALL_FEATURE = "staff.timesheets.manage_all";
67
68
  const DEEP_LINK_PARAMS = ["taskId", "ids"];
68
- const REPORTS_PATH = "/backend/staff/time-tracking/reports";
69
69
  const MUTATION_CONTEXT_ID = "staff.time_tracking.entriesList";
70
70
  const RESOURCE_KIND = "staff.timesheets.time_entry";
71
71
  function readUndoToken(response) {
@@ -924,7 +924,7 @@ function TimeTrackingEntriesPage() {
924
924
  {
925
925
  id: "show-report",
926
926
  label: labels.showReport,
927
- href: row.lockedReportId ? `${REPORTS_PATH}?id=${encodeURIComponent(row.lockedReportId)}` : REPORTS_PATH
927
+ href: row.lockedReportId ? reportDetailHref(row.lockedReportId) : REPORTS_PATH
928
928
  }
929
929
  ]
930
930
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/staff/backend/staff/time-tracking/entries/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\n/**\n * Screen 10 \u2014 the working list of time entries.\n *\n * The five notes on the mockup are the five decisions this page exists to make,\n * and none of them is decoration:\n *\n * 1. **The `Czas` cell edits in place** (US-D5). Correcting a duration is the\n * single most common thing anybody does to a logged entry, so it costs one\n * click, not a dialog round trip. The field is the shared `DurationInput`\n * in its compact variant, so `1h 40m` means here exactly what it means in\n * the entry dialog and the timesheet grid.\n * 2. **Delete is optimistic and reversible** (US-D5). The row leaves the table\n * immediately and the platform's undo bar \u2014 fed by the `x-om-operation`\n * header the command layer already attaches to the response \u2014 offers\n * \"Cofnij\" for a few seconds. The restore is the command's own `undo`,\n * never a row rebuilt from client state, which is why an entry restored\n * this way comes back with its tags, its rounding and its audit trail\n * intact. The page watches the operation store and refetches the moment its\n * token is undone, so the row reappears from the server.\n * 3. **A rate override is visible on the row** (US-F2). Without the badge two\n * rows with the same duration and different costs look like a bug.\n * 4. **A locked row is inert** (US-G3): no checkbox, no edit, no delete, a\n * lock badge, and exactly one action \u2014 the report that froze it. Unlocking\n * happens in the report (screen 15), not here.\n * 5. **`Koszt` is empty for a non-billable row, not `0,00`.** The list route\n * already answers `null`; a zero would read as free work rather than as\n * work that is out of scope.\n *\n * The page is also the landing spot for two deep links \u2014 the task drawer's\n * \"show all entries\" (`?taskId=`) and the entry dialog's jump to an overlapping\n * entry (`?ids=`). Either one replaces the default \"my entries, this week\"\n * seeding and shows up as a removable chip, because a list that quietly ignored\n * the link would answer a question nobody asked.\n *\n * Two things beyond the notes. Money columns exist only for a holder of\n * `staff.timesheets.rates.view` \u2014 the response has no `cost` for anybody else,\n * so the column is absent rather than blanked. And the footer never adds two\n * currencies together; see `summarizeTimeEntries`.\n */\n\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport * as React from 'react'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { Copy, Lock, Plus } from 'lucide-react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterOverlay'\nimport {\n EntryFilterChips,\n EntryFilterPresets,\n matchActivePreset,\n type AppliedFilterChip,\n type EntryFilterPreset,\n} from '../../../../lib/time-tracking-ui/EntryFilterBar'\nimport {\n apiCall,\n apiCallOrThrow,\n readApiResultOrThrow,\n withScopedApiRequestHeaders,\n} from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOperationStore } from '@open-mercato/ui/backend/operations/store'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { deserializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { TimeEntryDialog } from '../../../../lib/time-tracking-ui/TimeEntryDialog'\nimport { TimeEntryDurationCell } from '../../../../lib/time-tracking-ui/TimeEntryDurationCell'\nimport { TimeEntriesSummaryFooter } from '../../../../lib/time-tracking-ui/TimeEntriesSummaryFooter'\nimport {\n collectDirectoryIds,\n currentWeekRange,\n formatEntryClockRange,\n formatEntryDay,\n isEntryLockedError,\n readCopyDayTargetConflict,\n summarizeTimeEntries,\n toTimeEntryListRow,\n type TimeEntryDirectory,\n type TimeEntryListRow,\n} from '../../../../lib/time-tracking-ui/timeEntryListData'\nimport {\n readRowItems,\n shiftIsoDate,\n todayIsoDate,\n toProjectOption,\n toTaskOption,\n} from '../../../../lib/time-tracking-ui/timeEntryDialogState'\nimport { formatDuration } from '../../../../lib/time-tracking/duration'\n\nconst logger = createLogger('staff').child({ component: 'time-tracking/entries' })\n\nconst PAGE_SIZE = 50\nconst DIRECTORY_PAGE_SIZE = 100\nconst ENTRIES_API_PATH = 'staff/timesheets/time-entries'\nconst ENTRIES_API = `/api/${ENTRIES_API_PATH}`\nconst RATES_FEATURE = 'staff.timesheets.rates.view'\nconst MANAGE_OWN_FEATURE = 'staff.timesheets.manage_own'\nconst MANAGE_ALL_FEATURE = 'staff.timesheets.manage_all'\n/**\n * Query parameters other screens use to hand this list a question: the task\n * drawer's \"show every entry for this task\" (`?taskId=`) and the entry dialog's\n * jump to a conflicting entry (`?ids=`). Both are read once on mount and become\n * ordinary filter values, so they carry a removable chip like anything else.\n */\nconst DEEP_LINK_PARAMS = ['taskId', 'ids'] as const\nconst REPORTS_PATH = '/backend/staff/time-tracking/reports'\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.entriesList'\nconst RESOURCE_KIND = 'staff.timesheets.time_entry'\n\ntype EntriesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n totalIsCapped?: boolean\n totalPages?: number\n}\n\ntype CopyDayResponse = {\n copied?: number\n toDate?: string\n created?: Array<Record<string, unknown>>\n skipped?: Array<Record<string, unknown>>\n}\n\ntype MutationContext = {\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n}\n\nfunction readUndoToken(response: Response | null | undefined): string | null {\n const header = response?.headers?.get?.('x-om-operation') ?? null\n return deserializeOperationMetadata(header)?.undoToken ?? null\n}\n\nasync function loadDirectory(\n taskIds: readonly string[],\n projectIds: readonly string[],\n): Promise<TimeEntryDirectory> {\n const taskTitles = new Map<string, string>()\n const projectLabels = new Map<string, string>()\n const requests: Array<Promise<void>> = []\n\n if (taskIds.length > 0) {\n const params = new URLSearchParams({ page: '1', pageSize: String(DIRECTORY_PAGE_SIZE), ids: taskIds.join(',') })\n requests.push(\n readApiResultOrThrow<Record<string, unknown>>(`/api/staff/timesheets/tasks?${params.toString()}`, undefined, {\n allowNullResult: true,\n }).then((payload) => {\n for (const row of readRowItems(payload)) {\n const option = toTaskOption(row)\n if (option) taskTitles.set(option.id, option.title)\n }\n }),\n )\n }\n\n if (projectIds.length > 0) {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(DIRECTORY_PAGE_SIZE),\n ids: projectIds.join(','),\n })\n requests.push(\n readApiResultOrThrow<Record<string, unknown>>(\n `/api/staff/timesheets/time-projects?${params.toString()}`,\n undefined,\n { allowNullResult: true },\n ).then((payload) => {\n for (const row of readRowItems(payload)) {\n const option = toProjectOption(row)\n if (!option) continue\n projectLabels.set(option.id, option.customerName ? `${option.customerName} \u2014 ${option.name}` : option.name)\n }\n }),\n )\n }\n\n await Promise.all(requests)\n return { taskTitles, projectLabels }\n}\n\nexport default function TimeTrackingEntriesPage() {\n const t = useT()\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const scopeVersion = useOrganizationScopeVersion()\n const { payload } = useBackendChrome()\n const canSeeMoney = hasFeature(payload?.grantedFeatures, RATES_FEATURE)\n const canManage = hasFeature(payload?.grantedFeatures, MANAGE_OWN_FEATURE)\n // Reading somebody else's entries is `manage_all`, not `manage_own`: the list\n // route narrows every other caller back to their own rows, so offering them a\n // person filter promises a lookup the server will answer with nothing.\n const canManageAll = hasFeature(payload?.grantedFeatures, MANAGE_ALL_FEATURE)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const deepLinkTaskId = searchParams?.get('taskId')?.trim() ?? ''\n const deepLinkEntryIds = searchParams?.get('ids')?.trim() ?? ''\n\n const defaultRange = React.useMemo(() => currentWeekRange(), [])\n const [filterValues, setFilterValues] = React.useState<FilterValues>(() => {\n // A deep link is the entire question the reader arrived with, so it replaces\n // the default week instead of being intersected with it \u2014 an entry logged\n // last month would otherwise be filtered straight back out of the list the\n // link exists to show.\n if (deepLinkTaskId) return { taskId: deepLinkTaskId }\n if (deepLinkEntryIds) return { ids: deepLinkEntryIds }\n return { period: { from: defaultRange.from, to: defaultRange.to } }\n })\n // The list opens on your own timesheet. Someone holding `manage_all` sees every\n // entry in the organization otherwise, which is a page about other people.\n const [selfStaffMemberId, setSelfStaffMemberId] = React.useState<string | null>(null)\n const [projectOptions, setProjectOptions] = React.useState<{ value: string; label: string }[]>([])\n const [customerOptions, setCustomerOptions] = React.useState<{ value: string; label: string }[]>([])\n const [tagOptions, setTagOptions] = React.useState<{ value: string; label: string }[]>([])\n const [peopleOptions, setPeopleOptions] = React.useState<{ value: string; label: string }[]>([])\n // A deep link also outranks the \"my entries\" seeding the self lookup performs\n // once it resolves; the entry being pointed at is frequently someone else's.\n const personSeededRef = React.useRef(Boolean(deepLinkTaskId || deepLinkEntryIds))\n const [page, setPage] = React.useState(1)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'date', desc: true }])\n const [rows, setRows] = React.useState<TimeEntryListRow[]>([])\n const [total, setTotal] = React.useState(0)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [totalPages, setTotalPages] = React.useState(1)\n const [isLoading, setIsLoading] = React.useState(true)\n const [isRefreshing, setIsRefreshing] = React.useState(false)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [selectedIds, setSelectedIds] = React.useState<string[]>([])\n const [savingEntryId, setSavingEntryId] = React.useState<string | null>(null)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [dialogEntryId, setDialogEntryId] = React.useState<string | null>(null)\n const hasLoadedOnceRef = React.useRef(false)\n\n const labels = React.useMemo(\n () => ({\n title: t('staff.time_tracking.entries.title', 'Time entries'),\n add: t('staff.time_tracking.entries.actions.add', 'Add entry'),\n copyDay: t('staff.time_tracking.entries.actions.copyDay', 'Copy yesterday'),\n duplicate: t('staff.time_tracking.entries.actions.duplicate', 'Duplicate'),\n delete: t('staff.time_tracking.entries.actions.delete', 'Delete'),\n showReport: t('staff.time_tracking.entries.actions.showReport', 'Show the report'),\n refresh: t('staff.time_tracking.entries.actions.refresh', 'Refresh'),\n columns: {\n date: t('staff.time_tracking.entries.columns.date', 'Date'),\n task: t('staff.time_tracking.entries.columns.task', 'Task / description'),\n project: t('staff.time_tracking.entries.columns.project', 'Project'),\n clock: t('staff.time_tracking.entries.columns.clock', 'Hours'),\n duration: t('staff.time_tracking.entries.columns.duration', 'Time'),\n cost: t('staff.time_tracking.entries.columns.cost', 'Cost'),\n },\n selectAll: t('staff.time_tracking.entries.table.selectAll', 'Select all entries'),\n selectRow: t('staff.time_tracking.entries.table.selectRow', 'Select entry'),\n durationAria: t('staff.time_tracking.entries.table.durationAria', 'Entry duration'),\n noTask: t('staff.time_tracking.entries.table.noTask', 'No task'),\n empty: t('staff.time_tracking.entries.empty', 'No time entries in this period.'),\n badges: {\n locked: t('staff.time_tracking.entries.badges.locked', 'locked'),\n nonBillable: t('staff.time_tracking.entries.badges.nonBillable', 'non-billable'),\n },\n filters: {\n period: t('staff.time_tracking.entries.filters.period', 'Period'),\n project: t('staff.time_tracking.entries.filters.project', 'Project'),\n customer: t('staff.time_tracking.entries.filters.customer', 'Customer'),\n person: t('staff.time_tracking.entries.filters.person', 'Person'),\n tag: t('staff.time_tracking.entries.filters.tag', 'Tag'),\n billable: t('staff.time_tracking.entries.filters.billable', 'Billable'),\n billableYes: t('staff.time_tracking.entries.filters.billableYes', 'Billable only'),\n billableNo: t('staff.time_tracking.entries.filters.billableNo', 'Non-billable only'),\n locked: t('staff.time_tracking.entries.filters.locked', 'Report status'),\n lockedYes: t('staff.time_tracking.entries.filters.lockedYes', 'Locked in a report'),\n lockedNo: t('staff.time_tracking.entries.filters.lockedNo', 'Not yet reported'),\n text: t('staff.time_tracking.entries.filters.text', 'Description contains'),\n task: t('staff.time_tracking.entries.filters.task', 'Task'),\n entry: t('staff.time_tracking.entries.filters.entry', 'Entry'),\n entrySelection: t('staff.time_tracking.entries.filters.entrySelection', '{count} entries'),\n clearAll: t('staff.time_tracking.entries.filters.clearAll', 'Clear all'),\n remove: t('staff.time_tracking.entries.filters.remove', 'Remove filter {name}'),\n },\n presets: {\n mineWeek: t('staff.time_tracking.entries.presets.mineWeek', 'Mine, this week'),\n unbilled: t('staff.time_tracking.entries.presets.unbilled', 'Unbilled'),\n nonBillable: t('staff.time_tracking.entries.presets.nonBillable', 'Non-billable'),\n thisMonth: t('staff.time_tracking.entries.presets.thisMonth', 'This month'),\n },\n errors: {\n load: t('staff.time_tracking.entries.errors.load', 'Could not load the time entries.'),\n save: t('staff.time_tracking.entries.errors.save', 'Could not save the time entry.'),\n delete: t('staff.time_tracking.entries.errors.delete', 'Could not delete the time entry.'),\n duplicate: t('staff.time_tracking.entries.errors.duplicate', 'Could not duplicate the time entry.'),\n copyDay: t('staff.time_tracking.entries.errors.copyDay', 'Could not copy the day.'),\n locked: t(\n 'staff.time_tracking.entries.errors.entryLocked',\n 'This time entry is locked in a closed report and cannot be changed. Unlock the report first.',\n ),\n },\n }),\n [t],\n )\n\n const handleRefresh = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const reportFailure = React.useCallback(\n (error: unknown, fallbackMessage: string, logTag: string) => {\n if (surfaceRecordConflict(error, t)) return\n if (isEntryLockedError(error)) {\n flash(labels.errors.locked, 'error')\n return\n }\n logger.error(logTag, { err: error })\n flash(error instanceof Error && error.message ? error.message : fallbackMessage, 'error')\n },\n [labels.errors.locked, t],\n )\n\n const period = (filterValues.period ?? {}) as { from?: string; to?: string }\n const projectFilter = typeof filterValues.projectId === 'string' ? filterValues.projectId : ''\n\n // Filter vocabularies come from the API, not from the rows on screen: options\n // built from the loaded page can only narrow to what you can already see, which\n // makes the filter useless for finding anything that is not in front of you.\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n // `Promise.resolve` rather than `.catch` directly: a filter vocabulary that\n // fails to load must leave the page usable with a shorter list, and the\n // wrapper also tolerates a stub that returns nothing.\n const safeCall = async <T,>(url: string) => {\n try {\n return (await Promise.resolve(apiCall<T>(url))) ?? null\n } catch {\n return null\n }\n }\n const [selfRes, projectsRes, tagsRes, peopleRes] = await Promise.all([\n safeCall<{ member?: { id?: string } | null }>('/api/staff/team-members/self'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/time-projects?page=1&pageSize=100'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/tags?page=1&pageSize=100'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/team-members?page=1&pageSize=100'),\n ])\n if (cancelled) return\n\n const selfId = selfRes?.ok ? (selfRes.result?.member?.id ?? null) : null\n if (selfId) {\n setSelfStaffMemberId(selfId)\n if (!personSeededRef.current) {\n personSeededRef.current = true\n setFilterValues((prev) => ({ ...prev, staffMemberId: selfId }))\n }\n }\n\n const projects = projectsRes?.ok && Array.isArray(projectsRes.result?.items) ? projectsRes.result.items : []\n const customers = new Map<string, string>()\n setProjectOptions(\n projects\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n if (!id) return null\n const name = typeof row.name === 'string' ? row.name : id\n const code = typeof row.code === 'string' ? row.code : null\n const customerId = typeof row.customer_id === 'string' ? row.customer_id : null\n const snapshot = row.customer_snapshot as { name?: unknown } | null | undefined\n if (customerId && typeof snapshot?.name === 'string') customers.set(customerId, snapshot.name)\n return { value: id, label: code ? `${code} \u00B7 ${name}` : name }\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n setCustomerOptions([...customers].map(([value, label]) => ({ value, label })))\n\n const tags = tagsRes?.ok && Array.isArray(tagsRes.result?.items) ? tagsRes.result.items : []\n setTagOptions(\n tags\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.label === 'string' ? row.label : null\n return id && label ? { value: id, label } : null\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n\n const people = peopleRes?.ok && Array.isArray(peopleRes.result?.items) ? peopleRes.result.items : []\n setPeopleOptions(\n people\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.display_name === 'string'\n ? row.display_name\n : typeof row.displayName === 'string'\n ? row.displayName\n : null\n return id && label ? { value: id, label } : null\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n })()\n return () => { cancelled = true }\n }, [scopeVersion])\n\n const presets = React.useMemo<EntryFilterPreset[]>(\n () => [\n {\n id: 'mine-week',\n label: labels.presets.mineWeek,\n build: ({ staffMemberId }) => ({\n period: { from: defaultRange.from, to: defaultRange.to },\n ...(staffMemberId ? { staffMemberId } : {}),\n }),\n },\n {\n id: 'unbilled',\n label: labels.presets.unbilled,\n build: () => ({ locked: 'false', billable: 'true' }),\n },\n { id: 'non-billable', label: labels.presets.nonBillable, build: () => ({ billable: 'false' }) },\n {\n id: 'this-month',\n label: labels.presets.thisMonth,\n build: ({ today }) => {\n const start = `${today.slice(0, 7)}-01`\n return { period: { from: start, to: today } }\n },\n },\n ],\n [defaultRange.from, defaultRange.to, labels.presets],\n )\n\n const presetContext = React.useMemo(\n () => ({ staffMemberId: selfStaffMemberId, today: new Date().toISOString().slice(0, 10) }),\n [selfStaffMemberId],\n )\n const activePresetId = React.useMemo(\n () => matchActivePreset(filterValues, presets, presetContext),\n [filterValues, presetContext, presets],\n )\n\n const applyPreset = React.useCallback(\n (preset: EntryFilterPreset) => {\n // A preset is a starting point, not a mode: it replaces what it sets and\n // leaves the rest, so narrowing further does not mean rebuilding it.\n setFilterValues((current) => ({ ...current, ...preset.build(presetContext) }))\n setPage(1)\n },\n [presetContext],\n )\n\n /**\n * A deep-link filter lives in two places \u2014 the filter state and the address\n * bar \u2014 so dropping the chip has to drop the parameter too, otherwise the next\n * render (or a reload) puts the narrowing straight back.\n */\n const dropDeepLinkParams = React.useCallback(\n (ids: readonly string[]) => {\n if (!searchParams) return\n const next = new URLSearchParams(searchParams.toString())\n let changed = false\n for (const id of ids) {\n if (!next.has(id)) continue\n next.delete(id)\n changed = true\n }\n if (!changed) return\n const query = next.toString()\n router.replace(query ? `${pathname}?${query}` : pathname)\n },\n [pathname, router, searchParams],\n )\n\n const removeChip = React.useCallback(\n (id: string) => {\n setFilterValues((current) => {\n const next = { ...current }\n delete next[id]\n return next\n })\n setPage(1)\n dropDeepLinkParams([id])\n },\n [dropDeepLinkParams],\n )\n\n const clearAllFilters = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n dropDeepLinkParams(DEEP_LINK_PARAMS)\n }, [dropDeepLinkParams])\n\n const loadEntries = React.useCallback(async () => {\n if (hasLoadedOnceRef.current) setIsRefreshing(true)\n else setIsLoading(true)\n try {\n const params = new URLSearchParams({ page: String(page), pageSize: String(PAGE_SIZE) })\n const sort = sorting[0]\n params.set('sortField', sort?.id ?? 'date')\n params.set('sortDir', sort?.desc === false ? 'asc' : 'desc')\n if (period.from) params.set('from', period.from)\n if (period.to) params.set('to', period.to)\n if (projectFilter) params.set('projectId', projectFilter)\n const readFilter = (key: string): string =>\n typeof filterValues[key] === 'string' ? (filterValues[key] as string) : ''\n const person = readFilter('staffMemberId')\n if (person) params.set('staffMemberId', person)\n const billable = readFilter('billable')\n if (billable) params.set('billable', billable)\n const lockedFilter = readFilter('locked')\n if (lockedFilter) params.set('locked', lockedFilter)\n const customer = readFilter('customerId')\n if (customer) params.set('customerId', customer)\n const tag = readFilter('tagIds')\n if (tag) params.set('tagIds', tag)\n const text = readFilter('q')\n if (text.trim()) params.set('q', text.trim())\n const taskFilter = readFilter('taskId')\n if (taskFilter) params.set('taskId', taskFilter)\n const entryIdsFilter = readFilter('ids')\n if (entryIdsFilter) params.set('ids', entryIdsFilter)\n\n const listPayload = await readApiResultOrThrow<EntriesResponse>(\n `${ENTRIES_API}?${params.toString()}`,\n undefined,\n { errorMessage: labels.errors.load, fallback: { items: [], total: 0, totalPages: 1 } },\n )\n const items = Array.isArray(listPayload.items) ? listPayload.items : []\n const bare = items\n .map((item) => toTimeEntryListRow(item))\n .filter((row): row is TimeEntryListRow => row !== null)\n const { taskIds, projectIds } = collectDirectoryIds(bare)\n let directory: TimeEntryDirectory = { taskTitles: new Map(), projectLabels: new Map() }\n try {\n directory = await loadDirectory(taskIds, projectIds)\n } catch (error) {\n logger.warn('staff.time_tracking.entries directory load failed', { err: error })\n }\n setRows(\n items\n .map((item) => toTimeEntryListRow(item, directory))\n .filter((row): row is TimeEntryListRow => row !== null),\n )\n setTotal(typeof listPayload.total === 'number' ? listPayload.total : items.length)\n setTotalIsCapped(listPayload.totalIsCapped === true)\n setTotalPages(\n typeof listPayload.totalPages === 'number'\n ? listPayload.totalPages\n : Math.max(1, Math.ceil(items.length / PAGE_SIZE)),\n )\n } catch (error) {\n logger.error('staff.time_tracking.entries list failed', { err: error })\n flash(labels.errors.load, 'error')\n } finally {\n setIsLoading(false)\n setIsRefreshing(false)\n hasLoadedOnceRef.current = true\n }\n }, [labels.errors.load, page, period.from, period.to, projectFilter, sorting, filterValues])\n\n React.useEffect(() => {\n void loadEntries()\n }, [loadEntries, scopeVersion, reloadToken])\n\n React.useEffect(() => {\n setSelectedIds([])\n }, [page, period.from, period.to, projectFilter, scopeVersion])\n\n /**\n * Note 2's other half. The undo bar is AppShell's, so the click that restores\n * an entry never reaches this page \u2014 the operation store is what does. When a\n * token this page deleted shows up in the store's `undone` list the row is\n * back on the server, so the list is refetched rather than reconstructed.\n */\n const undoneTokens = useOperationStore((state) => state.undone.map((entry) => entry.undoToken).join('|'))\n const pendingUndoTokensRef = React.useRef<Set<string>>(new Set())\n React.useEffect(() => {\n if (pendingUndoTokensRef.current.size === 0) return\n const undone = new Set(undoneTokens.split('|').filter(Boolean))\n let restored = false\n for (const token of Array.from(pendingUndoTokensRef.current)) {\n if (!undone.has(token)) continue\n pendingUndoTokensRef.current.delete(token)\n restored = true\n }\n if (restored) handleRefresh()\n }, [handleRefresh, undoneTokens])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<MutationContext>({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: labels.errors.save,\n })\n\n const mutationContext = React.useCallback(\n (resourceId: string): MutationContext => ({\n formId: MUTATION_CONTEXT_ID,\n resourceKind: RESOURCE_KIND,\n resourceId,\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const handleDurationCommit = React.useCallback(\n async (entry: TimeEntryListRow, durationMinutes: number) => {\n setSavingEntryId(entry.id)\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n updateCrud(\n ENTRIES_API_PATH,\n { id: entry.id, durationMinutes },\n { errorMessage: labels.errors.save },\n ),\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id, durationMinutes },\n })\n handleRefresh()\n } catch (error) {\n reportFailure(error, labels.errors.save, 'staff.time_tracking.entries inline duration save failed')\n handleRefresh()\n } finally {\n setSavingEntryId(null)\n }\n },\n [handleRefresh, labels.errors.save, mutationContext, reportFailure, runMutation],\n )\n\n const handleDelete = React.useCallback(\n async (entry: TimeEntryListRow) => {\n const previousRows = rows\n setRows((current) => current.filter((row) => row.id !== entry.id))\n setTotal((current) => Math.max(0, current - 1))\n setSelectedIds((current) => current.filter((id) => id !== entry.id))\n try {\n const call = await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n deleteCrud(ENTRIES_API_PATH, entry.id, { errorMessage: labels.errors.delete }),\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id },\n })\n const undoToken = readUndoToken(call.response)\n if (undoToken) pendingUndoTokensRef.current.add(undoToken)\n flash(\n t('staff.time_tracking.entries.messages.deleted', 'Deleted \u201C{name}\u201D ({duration}). Use Undo to restore it.', {\n name: entry.taskTitle ?? entry.description ?? labels.noTask,\n duration: formatDuration(entry.durationMinutes, 'clock'),\n }),\n 'success',\n )\n } catch (error) {\n setRows(previousRows)\n setTotal((current) => current + 1)\n reportFailure(error, labels.errors.delete, 'staff.time_tracking.entries delete failed')\n }\n },\n [labels.errors.delete, labels.noTask, mutationContext, reportFailure, rows, runMutation, t],\n )\n\n const handleDuplicate = React.useCallback(\n async (entry: TimeEntryListRow) => {\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow<Record<string, unknown>>(\n `${ENTRIES_API}/${encodeURIComponent(entry.id)}/duplicate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tagIds: entry.tagIds }),\n },\n { errorMessage: labels.errors.duplicate },\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id },\n })\n flash(t('staff.time_tracking.entries.messages.duplicated', 'Entry duplicated.'), 'success')\n handleRefresh()\n } catch (error) {\n reportFailure(error, labels.errors.duplicate, 'staff.time_tracking.entries duplicate failed')\n }\n },\n [handleRefresh, labels.errors.duplicate, mutationContext, reportFailure, runMutation, t],\n )\n\n /**\n * \"Kopiuj wczorajszy dzie\u0144\" (screen 1 note 5). The copies are drafts to\n * review, so on success the period narrows to the target day: the user lands\n * on exactly the rows that were created instead of hunting for them.\n *\n * A non-empty target day answers `409 copy_day_target_not_empty` naming what\n * is already there. That is a question, not a dead end \u2014 the user is told the\n * count and offered the `allowDuplicates` path, which is the real case the\n * refusal would otherwise block (something already logged today, and\n * yesterday's set wanted on top).\n */\n const runCopyDay = React.useCallback(\n async (fromDate: string, toDate: string, allowDuplicates: boolean) =>\n runMutation({\n operation: () =>\n apiCallOrThrow<CopyDayResponse>(\n `${ENTRIES_API}/copy-day`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ fromDate, toDate, allowDuplicates }),\n },\n { errorMessage: labels.errors.copyDay },\n ),\n context: mutationContext(toDate),\n mutationPayload: { fromDate, toDate, allowDuplicates },\n }),\n [labels.errors.copyDay, mutationContext, runMutation],\n )\n\n const handleCopyDay = React.useCallback(async () => {\n const toDate = todayIsoDate()\n const fromDate = shiftIsoDate(toDate, -1)\n const announce = (result: CopyDayResponse | null) => {\n const copied = typeof result?.copied === 'number' ? result.copied : 0\n const skipped = Array.isArray(result?.skipped) ? result.skipped.length : 0\n if (copied === 0) {\n flash(t('staff.time_tracking.entries.copyDay.nothing', 'Yesterday has no entries to copy.'), 'info')\n } else {\n flash(\n t('staff.time_tracking.entries.copyDay.success', '{count} entries copied as drafts \u2014 review them.', {\n count: copied,\n }),\n 'success',\n )\n setFilterValues((current) => ({ ...current, period: { from: toDate, to: toDate } }))\n setPage(1)\n }\n if (skipped > 0) {\n flash(\n t('staff.time_tracking.entries.copyDay.skipped', '{count} entries were skipped because they are locked.', {\n count: skipped,\n }),\n 'warning',\n )\n }\n handleRefresh()\n }\n\n try {\n const call = await runCopyDay(fromDate, toDate, false)\n announce(call.result)\n } catch (error) {\n const conflict = readCopyDayTargetConflict(error)\n if (!conflict) {\n reportFailure(error, labels.errors.copyDay, 'staff.time_tracking.entries copy-day failed')\n return\n }\n const confirmed = await confirm({\n title: labels.copyDay,\n text: t(\n 'staff.time_tracking.entries.copyDay.conflict',\n 'Today already has {count} entries. Add yesterday\u2019s copies on top of them?',\n { count: conflict.existingEntryCount },\n ),\n confirmText: t('staff.time_tracking.entries.copyDay.confirmDuplicates', 'Add copies anyway'),\n })\n if (!confirmed) return\n try {\n const retry = await runCopyDay(fromDate, toDate, true)\n announce(retry.result)\n } catch (retryError) {\n reportFailure(retryError, labels.errors.copyDay, 'staff.time_tracking.entries copy-day retry failed')\n }\n }\n }, [confirm, handleRefresh, labels.copyDay, labels.errors.copyDay, reportFailure, runCopyDay, t])\n\n const openCreate = React.useCallback(() => {\n setDialogEntryId(null)\n setDialogOpen(true)\n }, [])\n\n const openEdit = React.useCallback((entryId: string) => {\n setDialogEntryId(entryId)\n setDialogOpen(true)\n }, [])\n\n const selectableRows = React.useMemo(() => rows.filter((row) => !row.isLocked), [rows])\n const allSelected = selectableRows.length > 0 && selectedIds.length === selectableRows.length\n const someSelected = selectedIds.length > 0 && !allSelected\n\n const toggleRowSelection = React.useCallback((id: string, checked: boolean) => {\n setSelectedIds((current) => (checked ? Array.from(new Set([...current, id])) : current.filter((value) => value !== id)))\n }, [])\n\n const toggleAllSelection = React.useCallback(\n (checked: boolean) => {\n setSelectedIds(checked ? selectableRows.map((row) => row.id) : [])\n },\n [selectableRows],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n { id: 'period', label: labels.filters.period, type: 'dateRange' },\n { id: 'projectId', label: labels.filters.project, type: 'select', options: projectOptions },\n { id: 'customerId', label: labels.filters.customer, type: 'select', options: customerOptions },\n // Only offered when the caller can actually see somebody else's time; for\n // everyone else the route already scopes to their own entries and the\n // control would be a lever attached to nothing.\n ...(canManageAll && peopleOptions.length > 1\n ? [{ id: 'staffMemberId', label: labels.filters.person, type: 'select' as const, options: peopleOptions }]\n : []),\n { id: 'tagIds', label: labels.filters.tag, type: 'select', options: tagOptions },\n {\n id: 'billable',\n label: labels.filters.billable,\n type: 'select',\n options: [\n { value: 'true', label: labels.filters.billableYes },\n { value: 'false', label: labels.filters.billableNo },\n ],\n },\n {\n id: 'locked',\n label: labels.filters.locked,\n type: 'select',\n options: [\n { value: 'true', label: labels.filters.lockedYes },\n { value: 'false', label: labels.filters.lockedNo },\n ],\n },\n { id: 'q', label: labels.filters.text, type: 'text' },\n ],\n [labels.filters, canManageAll, projectOptions, customerOptions, peopleOptions, tagOptions],\n )\n\n /**\n * Filters that can be applied but own no control in the overlay: the self\n * filter, which is seeded for everyone but only steerable by a `manage_all`\n * holder, and the two deep-link narrowings. Naming them here is what keeps\n * their chips reading as words instead of as a parameter name and a uuid.\n */\n const unlistedFilters = React.useMemo<FilterDef[]>(\n () => [\n { id: 'staffMemberId', label: labels.filters.person, type: 'select', options: peopleOptions },\n { id: 'taskId', label: labels.filters.task, type: 'text' },\n { id: 'ids', label: labels.filters.entry, type: 'text' },\n ],\n [labels.filters, peopleOptions],\n )\n\n /** Applied filters, as the reader would say them out loud. */\n const appliedChips = React.useMemo<AppliedFilterChip[]>(() => {\n const defOf = (id: string) =>\n filters.find((filter) => filter.id === id) ?? unlistedFilters.find((filter) => filter.id === id)\n const label = (id: string) => defOf(id)?.label ?? id\n const optionLabel = (id: string, value: string) =>\n defOf(id)?.options?.find((option) => option.value === value)?.label ?? value\n const chips: AppliedFilterChip[] = []\n for (const [id, raw] of Object.entries(filterValues)) {\n if (raw === undefined || raw === null || raw === '') continue\n if (id === 'period') {\n const range = raw as { from?: string; to?: string }\n if (!range?.from && !range?.to) continue\n chips.push({ id, label: label(id), value: `${range.from ?? '\u2026'} \u2192 ${range.to ?? '\u2026'}` })\n continue\n }\n if (typeof raw !== 'string') continue\n // A deep link arrives as an id, so its chip reads the loaded rows back for\n // the task or the entry it names \u2014 a uuid on a chip explains nothing.\n if (id === 'taskId') {\n const first = raw.split(',')[0] ?? raw\n const match = rows.find((row) => row.taskId === first)\n chips.push({ id, label: label(id), value: match?.taskTitle ?? first })\n continue\n }\n if (id === 'ids') {\n const list = raw.split(',').map((value) => value.trim()).filter(Boolean)\n if (list.length === 0) continue\n const single = list.length === 1 ? rows.find((row) => row.id === list[0]) : undefined\n chips.push({\n id,\n label: label(id),\n value: single\n ? (single.taskTitle ?? single.description ?? labels.noTask)\n : labels.filters.entrySelection.replace('{count}', String(list.length)),\n })\n continue\n }\n chips.push({ id, label: label(id), value: optionLabel(id, raw) })\n }\n return chips\n }, [filterValues, filters, labels.filters.entrySelection, labels.noTask, rows, unlistedFilters])\n\n const columns = React.useMemo<ColumnDef<TimeEntryListRow>[]>(() => {\n const rightHeader = (label: string) => () => <span className=\"block text-right\">{label}</span>\n const defs: ColumnDef<TimeEntryListRow>[] = []\n\n if (canManage) {\n defs.push({\n id: 'select',\n accessorKey: 'id',\n header: () => (\n <Checkbox\n checked={allSelected ? true : someSelected ? 'indeterminate' : false}\n onCheckedChange={(checked) => toggleAllSelection(Boolean(checked))}\n aria-label={labels.selectAll}\n />\n ),\n enableSorting: false,\n meta: { priority: 1 },\n cell: ({ row }) =>\n row.original.isLocked ? null : (\n <Checkbox\n checked={selectedIds.includes(row.original.id)}\n onCheckedChange={(checked) => toggleRowSelection(row.original.id, Boolean(checked))}\n onClick={(event) => event.stopPropagation()}\n aria-label={labels.selectRow}\n />\n ),\n })\n }\n\n defs.push({\n accessorKey: 'date',\n header: labels.columns.date,\n meta: { priority: 1 },\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap text-sm text-muted-foreground\">\n {formatEntryDay(row.original.date)}\n </span>\n ),\n })\n\n defs.push({\n id: 'task',\n accessorKey: 'taskTitle',\n header: labels.columns.task,\n enableSorting: false,\n // `taskTitle` matches none of DataTable's wide-column keys, so it fell to the\n // 150px default and truncated to \"Consulting / Worksh\u2026\" while the nowrap\n // numeric columns kept their slack. This is the row's primary content.\n meta: { priority: 1, maxWidth: '520px' },\n cell: ({ row }) => {\n const entry = row.original\n const rateOverrideLabel =\n canSeeMoney && entry.rateOverrideAmount !== null\n ? formatCurrency(entry.rateOverrideAmount, entry.currencyCode) ?? String(entry.rateOverrideAmount)\n : null\n return (\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">\n {entry.taskTitle ?? labels.noTask}\n </span>\n {!entry.isBillable ? (\n <StatusBadge variant=\"neutral\">{labels.badges.nonBillable}</StatusBadge>\n ) : null}\n {rateOverrideLabel ? (\n <span data-testid=\"entry-rate-override\">\n <StatusBadge variant=\"info\">\n {t('staff.time_tracking.entries.badges.rateOverride', '{amount}/h', { amount: rateOverrideLabel })}\n </StatusBadge>\n </span>\n ) : null}\n {entry.isLocked ? (\n <span data-testid=\"entry-locked-badge\">\n <StatusBadge variant=\"neutral\">\n <Lock className=\"h-3 w-3\" aria-hidden=\"true\" />\n {labels.badges.locked}\n </StatusBadge>\n </span>\n ) : null}\n </div>\n {entry.description ? (\n <span className=\"truncate text-xs text-muted-foreground\">{entry.description}</span>\n ) : null}\n </div>\n )\n },\n })\n\n defs.push({\n id: 'project',\n accessorKey: 'projectLabel',\n header: labels.columns.project,\n enableSorting: false,\n meta: { priority: 2, maxWidth: '240px' },\n cell: ({ row }) =>\n row.original.projectLabel ? (\n <span className=\"truncate text-sm text-muted-foreground\">{row.original.projectLabel}</span>\n ) : (\n <span className=\"text-xs text-muted-foreground/70\">\u2014</span>\n ),\n })\n\n defs.push({\n id: 'clock',\n accessorKey: 'startText',\n header: labels.columns.clock,\n enableSorting: false,\n meta: { priority: 3, maxWidth: '160px' },\n cell: ({ row }) => {\n const range = formatEntryClockRange(row.original)\n return range ? (\n <span className=\"whitespace-nowrap font-mono text-sm tabular-nums text-muted-foreground\">{range}</span>\n ) : (\n <span className=\"text-xs text-muted-foreground/70\">\u2014</span>\n )\n },\n })\n\n defs.push({\n id: 'durationMinutes',\n accessorKey: 'durationMinutes',\n header: rightHeader(labels.columns.duration),\n meta: { priority: 1, maxWidth: '120px' },\n cell: ({ row }) => (\n <TimeEntryDurationCell\n value={row.original.durationMinutes}\n readOnly={row.original.isLocked || !canManage}\n saving={savingEntryId === row.original.id}\n ariaLabel={labels.durationAria}\n lockedTitle={row.original.isLocked ? labels.errors.locked : undefined}\n onCommit={(minutes) => handleDurationCommit(row.original, minutes)}\n />\n ),\n })\n\n if (canSeeMoney) {\n defs.push({\n id: 'cost',\n accessorKey: 'cost',\n header: rightHeader(labels.columns.cost),\n enableSorting: false,\n meta: { priority: 4, maxWidth: '140px' },\n cell: ({ row }) => {\n const formatted =\n row.original.cost === null ? null : formatCurrency(row.original.cost, row.original.currencyCode)\n return formatted ? (\n <span\n className=\"block text-right font-mono text-sm tabular-nums text-foreground\"\n data-testid=\"entry-cost\"\n >\n {formatted}\n </span>\n ) : (\n <span className=\"block text-right text-xs text-muted-foreground/70\" data-testid=\"entry-cost-empty\">\n \u2014\n </span>\n )\n },\n })\n }\n\n return defs\n }, [\n allSelected,\n canManage,\n canSeeMoney,\n handleDurationCommit,\n labels,\n savingEntryId,\n selectedIds,\n someSelected,\n t,\n toggleAllSelection,\n toggleRowSelection,\n ])\n\n const summary = React.useMemo(() => summarizeTimeEntries(rows), [rows])\n\n return (\n <Page>\n <PageBody>\n <DataTable<TimeEntryListRow>\n extensionTableId={extensionPoints.hosts.timeEntriesTable.tableId}\n title={labels.title}\n titleHeadingLevel={1}\n data={rows}\n columns={columns}\n isLoading={isLoading}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n // The overlay does not carry the deep-link filters, so applying it\n // drops them \u2014 the address bar has to agree with what is on screen.\n dropDeepLinkParams(DEEP_LINK_PARAMS.filter((id) => !values[id]))\n }}\n onFiltersClear={clearAllFilters}\n activeFilterChips={\n <div className=\"flex flex-col gap-2\">\n <EntryFilterPresets\n presets={presets}\n activePresetId={activePresetId}\n onApplyPreset={applyPreset}\n />\n <EntryFilterChips\n chips={appliedChips}\n onRemove={removeChip}\n onClearAll={clearAllFilters}\n clearAllLabel={labels.filters.clearAll}\n removeLabel={(name) => labels.filters.remove.replace('{name}', name)}\n />\n </div>\n }\n filterAwareEmptyState={{\n active: appliedChips.length > 0,\n entityNamePlural: labels.title,\n canRemoveLast: appliedChips.length > 0,\n onClearAll: clearAllFilters,\n onRemoveLast: () => {\n const last = appliedChips[appliedChips.length - 1]\n if (last) removeChip(last.id)\n },\n }}\n emptyState={<div className=\"py-12 text-center text-sm text-muted-foreground\">{labels.empty}</div>}\n actions={\n canManage ? (\n <div className=\"flex items-center gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => void handleCopyDay()}>\n <Copy className=\"h-4 w-4\" aria-hidden=\"true\" />\n {labels.copyDay}\n </Button>\n <Button size=\"sm\" onClick={openCreate}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {labels.add}\n </Button>\n </div>\n ) : undefined\n }\n refreshButton={{\n label: labels.refresh,\n onRefresh: handleRefresh,\n isRefreshing: isLoading || isRefreshing,\n }}\n sortable\n manualSorting\n sorting={sorting}\n onSortingChange={setSorting}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalIsCapped,\n totalPages,\n onPageChange: setPage,\n }}\n stickyActionsColumn\n rowActions={(row) =>\n row.isLocked ? (\n <RowActions\n items={[\n {\n id: 'show-report',\n label: labels.showReport,\n href: row.lockedReportId\n ? `${REPORTS_PATH}?id=${encodeURIComponent(row.lockedReportId)}`\n : REPORTS_PATH,\n },\n ]}\n />\n ) : canManage ? (\n <RowActions\n items={[\n {\n id: 'duplicate',\n label: labels.duplicate,\n onSelect: () => {\n void handleDuplicate(row)\n },\n },\n {\n id: 'delete',\n label: labels.delete,\n destructive: true,\n onSelect: () => {\n void handleDelete(row)\n },\n },\n ]}\n />\n ) : null\n }\n onRowClick={(row) => {\n if (row.isLocked) return\n openEdit(row.id)\n }}\n />\n\n <TimeEntriesSummaryFooter summary={summary} totalCount={total} canSeeMoney={canSeeMoney} />\n\n {!canManage ? (\n <p className=\"mt-2 text-xs text-muted-foreground\">\n {t('staff.time_tracking.entries.readOnlyNotice', 'You can view these entries but not change them.')}\n </p>\n ) : null}\n </PageBody>\n\n <TimeEntryDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n entryId={dialogEntryId}\n onSaved={({ keptOpen }) => {\n handleRefresh()\n if (!keptOpen) setDialogOpen(false)\n }}\n onShowEntry={(entryId) => openEdit(entryId)}\n />\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\n/**\n * Screen 10 \u2014 the working list of time entries.\n *\n * The five notes on the mockup are the five decisions this page exists to make,\n * and none of them is decoration:\n *\n * 1. **The `Czas` cell edits in place** (US-D5). Correcting a duration is the\n * single most common thing anybody does to a logged entry, so it costs one\n * click, not a dialog round trip. The field is the shared `DurationInput`\n * in its compact variant, so `1h 40m` means here exactly what it means in\n * the entry dialog and the timesheet grid.\n * 2. **Delete is optimistic and reversible** (US-D5). The row leaves the table\n * immediately and the platform's undo bar \u2014 fed by the `x-om-operation`\n * header the command layer already attaches to the response \u2014 offers\n * \"Cofnij\" for a few seconds. The restore is the command's own `undo`,\n * never a row rebuilt from client state, which is why an entry restored\n * this way comes back with its tags, its rounding and its audit trail\n * intact. The page watches the operation store and refetches the moment its\n * token is undone, so the row reappears from the server.\n * 3. **A rate override is visible on the row** (US-F2). Without the badge two\n * rows with the same duration and different costs look like a bug.\n * 4. **A locked row is inert** (US-G3): no checkbox, no edit, no delete, a\n * lock badge, and exactly one action \u2014 the report that froze it. Unlocking\n * happens in the report (screen 15), not here.\n * 5. **`Koszt` is empty for a non-billable row, not `0,00`.** The list route\n * already answers `null`; a zero would read as free work rather than as\n * work that is out of scope.\n *\n * The page is also the landing spot for two deep links \u2014 the task drawer's\n * \"show all entries\" (`?taskId=`) and the entry dialog's jump to an overlapping\n * entry (`?ids=`). Either one replaces the default \"my entries, this week\"\n * seeding and shows up as a removable chip, because a list that quietly ignored\n * the link would answer a question nobody asked.\n *\n * Two things beyond the notes. Money columns exist only for a holder of\n * `staff.timesheets.rates.view` \u2014 the response has no `cost` for anybody else,\n * so the column is absent rather than blanked. And the footer never adds two\n * currencies together; see `summarizeTimeEntries`.\n */\n\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport * as React from 'react'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { Copy, Lock, Plus } from 'lucide-react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterOverlay'\nimport {\n EntryFilterChips,\n EntryFilterPresets,\n matchActivePreset,\n type AppliedFilterChip,\n type EntryFilterPreset,\n} from '../../../../lib/time-tracking-ui/EntryFilterBar'\nimport {\n apiCall,\n apiCallOrThrow,\n readApiResultOrThrow,\n withScopedApiRequestHeaders,\n} from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOperationStore } from '@open-mercato/ui/backend/operations/store'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { deserializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { TimeEntryDialog } from '../../../../lib/time-tracking-ui/TimeEntryDialog'\nimport { REPORTS_PATH, reportDetailHref } from '../../../../lib/time-tracking-ui/reportLinks'\nimport { TimeEntryDurationCell } from '../../../../lib/time-tracking-ui/TimeEntryDurationCell'\nimport { TimeEntriesSummaryFooter } from '../../../../lib/time-tracking-ui/TimeEntriesSummaryFooter'\nimport {\n collectDirectoryIds,\n currentWeekRange,\n formatEntryClockRange,\n formatEntryDay,\n isEntryLockedError,\n readCopyDayTargetConflict,\n summarizeTimeEntries,\n toTimeEntryListRow,\n type TimeEntryDirectory,\n type TimeEntryListRow,\n} from '../../../../lib/time-tracking-ui/timeEntryListData'\nimport {\n readRowItems,\n shiftIsoDate,\n todayIsoDate,\n toProjectOption,\n toTaskOption,\n} from '../../../../lib/time-tracking-ui/timeEntryDialogState'\nimport { formatDuration } from '../../../../lib/time-tracking/duration'\n\nconst logger = createLogger('staff').child({ component: 'time-tracking/entries' })\n\nconst PAGE_SIZE = 50\nconst DIRECTORY_PAGE_SIZE = 100\nconst ENTRIES_API_PATH = 'staff/timesheets/time-entries'\nconst ENTRIES_API = `/api/${ENTRIES_API_PATH}`\nconst RATES_FEATURE = 'staff.timesheets.rates.view'\nconst MANAGE_OWN_FEATURE = 'staff.timesheets.manage_own'\nconst MANAGE_ALL_FEATURE = 'staff.timesheets.manage_all'\n/**\n * Query parameters other screens use to hand this list a question: the task\n * drawer's \"show every entry for this task\" (`?taskId=`) and the entry dialog's\n * jump to a conflicting entry (`?ids=`). Both are read once on mount and become\n * ordinary filter values, so they carry a removable chip like anything else.\n */\nconst DEEP_LINK_PARAMS = ['taskId', 'ids'] as const\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.entriesList'\nconst RESOURCE_KIND = 'staff.timesheets.time_entry'\n\ntype EntriesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n totalIsCapped?: boolean\n totalPages?: number\n}\n\ntype CopyDayResponse = {\n copied?: number\n toDate?: string\n created?: Array<Record<string, unknown>>\n skipped?: Array<Record<string, unknown>>\n}\n\ntype MutationContext = {\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n}\n\nfunction readUndoToken(response: Response | null | undefined): string | null {\n const header = response?.headers?.get?.('x-om-operation') ?? null\n return deserializeOperationMetadata(header)?.undoToken ?? null\n}\n\nasync function loadDirectory(\n taskIds: readonly string[],\n projectIds: readonly string[],\n): Promise<TimeEntryDirectory> {\n const taskTitles = new Map<string, string>()\n const projectLabels = new Map<string, string>()\n const requests: Array<Promise<void>> = []\n\n if (taskIds.length > 0) {\n const params = new URLSearchParams({ page: '1', pageSize: String(DIRECTORY_PAGE_SIZE), ids: taskIds.join(',') })\n requests.push(\n readApiResultOrThrow<Record<string, unknown>>(`/api/staff/timesheets/tasks?${params.toString()}`, undefined, {\n allowNullResult: true,\n }).then((payload) => {\n for (const row of readRowItems(payload)) {\n const option = toTaskOption(row)\n if (option) taskTitles.set(option.id, option.title)\n }\n }),\n )\n }\n\n if (projectIds.length > 0) {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(DIRECTORY_PAGE_SIZE),\n ids: projectIds.join(','),\n })\n requests.push(\n readApiResultOrThrow<Record<string, unknown>>(\n `/api/staff/timesheets/time-projects?${params.toString()}`,\n undefined,\n { allowNullResult: true },\n ).then((payload) => {\n for (const row of readRowItems(payload)) {\n const option = toProjectOption(row)\n if (!option) continue\n projectLabels.set(option.id, option.customerName ? `${option.customerName} \u2014 ${option.name}` : option.name)\n }\n }),\n )\n }\n\n await Promise.all(requests)\n return { taskTitles, projectLabels }\n}\n\nexport default function TimeTrackingEntriesPage() {\n const t = useT()\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const scopeVersion = useOrganizationScopeVersion()\n const { payload } = useBackendChrome()\n const canSeeMoney = hasFeature(payload?.grantedFeatures, RATES_FEATURE)\n const canManage = hasFeature(payload?.grantedFeatures, MANAGE_OWN_FEATURE)\n // Reading somebody else's entries is `manage_all`, not `manage_own`: the list\n // route narrows every other caller back to their own rows, so offering them a\n // person filter promises a lookup the server will answer with nothing.\n const canManageAll = hasFeature(payload?.grantedFeatures, MANAGE_ALL_FEATURE)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const deepLinkTaskId = searchParams?.get('taskId')?.trim() ?? ''\n const deepLinkEntryIds = searchParams?.get('ids')?.trim() ?? ''\n\n const defaultRange = React.useMemo(() => currentWeekRange(), [])\n const [filterValues, setFilterValues] = React.useState<FilterValues>(() => {\n // A deep link is the entire question the reader arrived with, so it replaces\n // the default week instead of being intersected with it \u2014 an entry logged\n // last month would otherwise be filtered straight back out of the list the\n // link exists to show.\n if (deepLinkTaskId) return { taskId: deepLinkTaskId }\n if (deepLinkEntryIds) return { ids: deepLinkEntryIds }\n return { period: { from: defaultRange.from, to: defaultRange.to } }\n })\n // The list opens on your own timesheet. Someone holding `manage_all` sees every\n // entry in the organization otherwise, which is a page about other people.\n const [selfStaffMemberId, setSelfStaffMemberId] = React.useState<string | null>(null)\n const [projectOptions, setProjectOptions] = React.useState<{ value: string; label: string }[]>([])\n const [customerOptions, setCustomerOptions] = React.useState<{ value: string; label: string }[]>([])\n const [tagOptions, setTagOptions] = React.useState<{ value: string; label: string }[]>([])\n const [peopleOptions, setPeopleOptions] = React.useState<{ value: string; label: string }[]>([])\n // A deep link also outranks the \"my entries\" seeding the self lookup performs\n // once it resolves; the entry being pointed at is frequently someone else's.\n const personSeededRef = React.useRef(Boolean(deepLinkTaskId || deepLinkEntryIds))\n const [page, setPage] = React.useState(1)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'date', desc: true }])\n const [rows, setRows] = React.useState<TimeEntryListRow[]>([])\n const [total, setTotal] = React.useState(0)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [totalPages, setTotalPages] = React.useState(1)\n const [isLoading, setIsLoading] = React.useState(true)\n const [isRefreshing, setIsRefreshing] = React.useState(false)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [selectedIds, setSelectedIds] = React.useState<string[]>([])\n const [savingEntryId, setSavingEntryId] = React.useState<string | null>(null)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [dialogEntryId, setDialogEntryId] = React.useState<string | null>(null)\n const hasLoadedOnceRef = React.useRef(false)\n\n const labels = React.useMemo(\n () => ({\n title: t('staff.time_tracking.entries.title', 'Time entries'),\n add: t('staff.time_tracking.entries.actions.add', 'Add entry'),\n copyDay: t('staff.time_tracking.entries.actions.copyDay', 'Copy yesterday'),\n duplicate: t('staff.time_tracking.entries.actions.duplicate', 'Duplicate'),\n delete: t('staff.time_tracking.entries.actions.delete', 'Delete'),\n showReport: t('staff.time_tracking.entries.actions.showReport', 'Show the report'),\n refresh: t('staff.time_tracking.entries.actions.refresh', 'Refresh'),\n columns: {\n date: t('staff.time_tracking.entries.columns.date', 'Date'),\n task: t('staff.time_tracking.entries.columns.task', 'Task / description'),\n project: t('staff.time_tracking.entries.columns.project', 'Project'),\n clock: t('staff.time_tracking.entries.columns.clock', 'Hours'),\n duration: t('staff.time_tracking.entries.columns.duration', 'Time'),\n cost: t('staff.time_tracking.entries.columns.cost', 'Cost'),\n },\n selectAll: t('staff.time_tracking.entries.table.selectAll', 'Select all entries'),\n selectRow: t('staff.time_tracking.entries.table.selectRow', 'Select entry'),\n durationAria: t('staff.time_tracking.entries.table.durationAria', 'Entry duration'),\n noTask: t('staff.time_tracking.entries.table.noTask', 'No task'),\n empty: t('staff.time_tracking.entries.empty', 'No time entries in this period.'),\n badges: {\n locked: t('staff.time_tracking.entries.badges.locked', 'locked'),\n nonBillable: t('staff.time_tracking.entries.badges.nonBillable', 'non-billable'),\n },\n filters: {\n period: t('staff.time_tracking.entries.filters.period', 'Period'),\n project: t('staff.time_tracking.entries.filters.project', 'Project'),\n customer: t('staff.time_tracking.entries.filters.customer', 'Customer'),\n person: t('staff.time_tracking.entries.filters.person', 'Person'),\n tag: t('staff.time_tracking.entries.filters.tag', 'Tag'),\n billable: t('staff.time_tracking.entries.filters.billable', 'Billable'),\n billableYes: t('staff.time_tracking.entries.filters.billableYes', 'Billable only'),\n billableNo: t('staff.time_tracking.entries.filters.billableNo', 'Non-billable only'),\n locked: t('staff.time_tracking.entries.filters.locked', 'Report status'),\n lockedYes: t('staff.time_tracking.entries.filters.lockedYes', 'Locked in a report'),\n lockedNo: t('staff.time_tracking.entries.filters.lockedNo', 'Not yet reported'),\n text: t('staff.time_tracking.entries.filters.text', 'Description contains'),\n task: t('staff.time_tracking.entries.filters.task', 'Task'),\n entry: t('staff.time_tracking.entries.filters.entry', 'Entry'),\n entrySelection: t('staff.time_tracking.entries.filters.entrySelection', '{count} entries'),\n clearAll: t('staff.time_tracking.entries.filters.clearAll', 'Clear all'),\n remove: t('staff.time_tracking.entries.filters.remove', 'Remove filter {name}'),\n },\n presets: {\n mineWeek: t('staff.time_tracking.entries.presets.mineWeek', 'Mine, this week'),\n unbilled: t('staff.time_tracking.entries.presets.unbilled', 'Unbilled'),\n nonBillable: t('staff.time_tracking.entries.presets.nonBillable', 'Non-billable'),\n thisMonth: t('staff.time_tracking.entries.presets.thisMonth', 'This month'),\n },\n errors: {\n load: t('staff.time_tracking.entries.errors.load', 'Could not load the time entries.'),\n save: t('staff.time_tracking.entries.errors.save', 'Could not save the time entry.'),\n delete: t('staff.time_tracking.entries.errors.delete', 'Could not delete the time entry.'),\n duplicate: t('staff.time_tracking.entries.errors.duplicate', 'Could not duplicate the time entry.'),\n copyDay: t('staff.time_tracking.entries.errors.copyDay', 'Could not copy the day.'),\n locked: t(\n 'staff.time_tracking.entries.errors.entryLocked',\n 'This time entry is locked in a closed report and cannot be changed. Unlock the report first.',\n ),\n },\n }),\n [t],\n )\n\n const handleRefresh = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const reportFailure = React.useCallback(\n (error: unknown, fallbackMessage: string, logTag: string) => {\n if (surfaceRecordConflict(error, t)) return\n if (isEntryLockedError(error)) {\n flash(labels.errors.locked, 'error')\n return\n }\n logger.error(logTag, { err: error })\n flash(error instanceof Error && error.message ? error.message : fallbackMessage, 'error')\n },\n [labels.errors.locked, t],\n )\n\n const period = (filterValues.period ?? {}) as { from?: string; to?: string }\n const projectFilter = typeof filterValues.projectId === 'string' ? filterValues.projectId : ''\n\n // Filter vocabularies come from the API, not from the rows on screen: options\n // built from the loaded page can only narrow to what you can already see, which\n // makes the filter useless for finding anything that is not in front of you.\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n // `Promise.resolve` rather than `.catch` directly: a filter vocabulary that\n // fails to load must leave the page usable with a shorter list, and the\n // wrapper also tolerates a stub that returns nothing.\n const safeCall = async <T,>(url: string) => {\n try {\n return (await Promise.resolve(apiCall<T>(url))) ?? null\n } catch {\n return null\n }\n }\n const [selfRes, projectsRes, tagsRes, peopleRes] = await Promise.all([\n safeCall<{ member?: { id?: string } | null }>('/api/staff/team-members/self'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/time-projects?page=1&pageSize=100'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/tags?page=1&pageSize=100'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/team-members?page=1&pageSize=100'),\n ])\n if (cancelled) return\n\n const selfId = selfRes?.ok ? (selfRes.result?.member?.id ?? null) : null\n if (selfId) {\n setSelfStaffMemberId(selfId)\n if (!personSeededRef.current) {\n personSeededRef.current = true\n setFilterValues((prev) => ({ ...prev, staffMemberId: selfId }))\n }\n }\n\n const projects = projectsRes?.ok && Array.isArray(projectsRes.result?.items) ? projectsRes.result.items : []\n const customers = new Map<string, string>()\n setProjectOptions(\n projects\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n if (!id) return null\n const name = typeof row.name === 'string' ? row.name : id\n const code = typeof row.code === 'string' ? row.code : null\n const customerId = typeof row.customer_id === 'string' ? row.customer_id : null\n const snapshot = row.customer_snapshot as { name?: unknown } | null | undefined\n if (customerId && typeof snapshot?.name === 'string') customers.set(customerId, snapshot.name)\n return { value: id, label: code ? `${code} \u00B7 ${name}` : name }\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n setCustomerOptions([...customers].map(([value, label]) => ({ value, label })))\n\n const tags = tagsRes?.ok && Array.isArray(tagsRes.result?.items) ? tagsRes.result.items : []\n setTagOptions(\n tags\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.label === 'string' ? row.label : null\n return id && label ? { value: id, label } : null\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n\n const people = peopleRes?.ok && Array.isArray(peopleRes.result?.items) ? peopleRes.result.items : []\n setPeopleOptions(\n people\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.display_name === 'string'\n ? row.display_name\n : typeof row.displayName === 'string'\n ? row.displayName\n : null\n return id && label ? { value: id, label } : null\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n })()\n return () => { cancelled = true }\n }, [scopeVersion])\n\n const presets = React.useMemo<EntryFilterPreset[]>(\n () => [\n {\n id: 'mine-week',\n label: labels.presets.mineWeek,\n build: ({ staffMemberId }) => ({\n period: { from: defaultRange.from, to: defaultRange.to },\n ...(staffMemberId ? { staffMemberId } : {}),\n }),\n },\n {\n id: 'unbilled',\n label: labels.presets.unbilled,\n build: () => ({ locked: 'false', billable: 'true' }),\n },\n { id: 'non-billable', label: labels.presets.nonBillable, build: () => ({ billable: 'false' }) },\n {\n id: 'this-month',\n label: labels.presets.thisMonth,\n build: ({ today }) => {\n const start = `${today.slice(0, 7)}-01`\n return { period: { from: start, to: today } }\n },\n },\n ],\n [defaultRange.from, defaultRange.to, labels.presets],\n )\n\n const presetContext = React.useMemo(\n () => ({ staffMemberId: selfStaffMemberId, today: new Date().toISOString().slice(0, 10) }),\n [selfStaffMemberId],\n )\n const activePresetId = React.useMemo(\n () => matchActivePreset(filterValues, presets, presetContext),\n [filterValues, presetContext, presets],\n )\n\n const applyPreset = React.useCallback(\n (preset: EntryFilterPreset) => {\n // A preset is a starting point, not a mode: it replaces what it sets and\n // leaves the rest, so narrowing further does not mean rebuilding it.\n setFilterValues((current) => ({ ...current, ...preset.build(presetContext) }))\n setPage(1)\n },\n [presetContext],\n )\n\n /**\n * A deep-link filter lives in two places \u2014 the filter state and the address\n * bar \u2014 so dropping the chip has to drop the parameter too, otherwise the next\n * render (or a reload) puts the narrowing straight back.\n */\n const dropDeepLinkParams = React.useCallback(\n (ids: readonly string[]) => {\n if (!searchParams) return\n const next = new URLSearchParams(searchParams.toString())\n let changed = false\n for (const id of ids) {\n if (!next.has(id)) continue\n next.delete(id)\n changed = true\n }\n if (!changed) return\n const query = next.toString()\n router.replace(query ? `${pathname}?${query}` : pathname)\n },\n [pathname, router, searchParams],\n )\n\n const removeChip = React.useCallback(\n (id: string) => {\n setFilterValues((current) => {\n const next = { ...current }\n delete next[id]\n return next\n })\n setPage(1)\n dropDeepLinkParams([id])\n },\n [dropDeepLinkParams],\n )\n\n const clearAllFilters = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n dropDeepLinkParams(DEEP_LINK_PARAMS)\n }, [dropDeepLinkParams])\n\n const loadEntries = React.useCallback(async () => {\n if (hasLoadedOnceRef.current) setIsRefreshing(true)\n else setIsLoading(true)\n try {\n const params = new URLSearchParams({ page: String(page), pageSize: String(PAGE_SIZE) })\n const sort = sorting[0]\n params.set('sortField', sort?.id ?? 'date')\n params.set('sortDir', sort?.desc === false ? 'asc' : 'desc')\n if (period.from) params.set('from', period.from)\n if (period.to) params.set('to', period.to)\n if (projectFilter) params.set('projectId', projectFilter)\n const readFilter = (key: string): string =>\n typeof filterValues[key] === 'string' ? (filterValues[key] as string) : ''\n const person = readFilter('staffMemberId')\n if (person) params.set('staffMemberId', person)\n const billable = readFilter('billable')\n if (billable) params.set('billable', billable)\n const lockedFilter = readFilter('locked')\n if (lockedFilter) params.set('locked', lockedFilter)\n const customer = readFilter('customerId')\n if (customer) params.set('customerId', customer)\n const tag = readFilter('tagIds')\n if (tag) params.set('tagIds', tag)\n const text = readFilter('q')\n if (text.trim()) params.set('q', text.trim())\n const taskFilter = readFilter('taskId')\n if (taskFilter) params.set('taskId', taskFilter)\n const entryIdsFilter = readFilter('ids')\n if (entryIdsFilter) params.set('ids', entryIdsFilter)\n\n const listPayload = await readApiResultOrThrow<EntriesResponse>(\n `${ENTRIES_API}?${params.toString()}`,\n undefined,\n { errorMessage: labels.errors.load, fallback: { items: [], total: 0, totalPages: 1 } },\n )\n const items = Array.isArray(listPayload.items) ? listPayload.items : []\n const bare = items\n .map((item) => toTimeEntryListRow(item))\n .filter((row): row is TimeEntryListRow => row !== null)\n const { taskIds, projectIds } = collectDirectoryIds(bare)\n let directory: TimeEntryDirectory = { taskTitles: new Map(), projectLabels: new Map() }\n try {\n directory = await loadDirectory(taskIds, projectIds)\n } catch (error) {\n logger.warn('staff.time_tracking.entries directory load failed', { err: error })\n }\n setRows(\n items\n .map((item) => toTimeEntryListRow(item, directory))\n .filter((row): row is TimeEntryListRow => row !== null),\n )\n setTotal(typeof listPayload.total === 'number' ? listPayload.total : items.length)\n setTotalIsCapped(listPayload.totalIsCapped === true)\n setTotalPages(\n typeof listPayload.totalPages === 'number'\n ? listPayload.totalPages\n : Math.max(1, Math.ceil(items.length / PAGE_SIZE)),\n )\n } catch (error) {\n logger.error('staff.time_tracking.entries list failed', { err: error })\n flash(labels.errors.load, 'error')\n } finally {\n setIsLoading(false)\n setIsRefreshing(false)\n hasLoadedOnceRef.current = true\n }\n }, [labels.errors.load, page, period.from, period.to, projectFilter, sorting, filterValues])\n\n React.useEffect(() => {\n void loadEntries()\n }, [loadEntries, scopeVersion, reloadToken])\n\n React.useEffect(() => {\n setSelectedIds([])\n }, [page, period.from, period.to, projectFilter, scopeVersion])\n\n /**\n * Note 2's other half. The undo bar is AppShell's, so the click that restores\n * an entry never reaches this page \u2014 the operation store is what does. When a\n * token this page deleted shows up in the store's `undone` list the row is\n * back on the server, so the list is refetched rather than reconstructed.\n */\n const undoneTokens = useOperationStore((state) => state.undone.map((entry) => entry.undoToken).join('|'))\n const pendingUndoTokensRef = React.useRef<Set<string>>(new Set())\n React.useEffect(() => {\n if (pendingUndoTokensRef.current.size === 0) return\n const undone = new Set(undoneTokens.split('|').filter(Boolean))\n let restored = false\n for (const token of Array.from(pendingUndoTokensRef.current)) {\n if (!undone.has(token)) continue\n pendingUndoTokensRef.current.delete(token)\n restored = true\n }\n if (restored) handleRefresh()\n }, [handleRefresh, undoneTokens])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<MutationContext>({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: labels.errors.save,\n })\n\n const mutationContext = React.useCallback(\n (resourceId: string): MutationContext => ({\n formId: MUTATION_CONTEXT_ID,\n resourceKind: RESOURCE_KIND,\n resourceId,\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const handleDurationCommit = React.useCallback(\n async (entry: TimeEntryListRow, durationMinutes: number) => {\n setSavingEntryId(entry.id)\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n updateCrud(\n ENTRIES_API_PATH,\n { id: entry.id, durationMinutes },\n { errorMessage: labels.errors.save },\n ),\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id, durationMinutes },\n })\n handleRefresh()\n } catch (error) {\n reportFailure(error, labels.errors.save, 'staff.time_tracking.entries inline duration save failed')\n handleRefresh()\n } finally {\n setSavingEntryId(null)\n }\n },\n [handleRefresh, labels.errors.save, mutationContext, reportFailure, runMutation],\n )\n\n const handleDelete = React.useCallback(\n async (entry: TimeEntryListRow) => {\n const previousRows = rows\n setRows((current) => current.filter((row) => row.id !== entry.id))\n setTotal((current) => Math.max(0, current - 1))\n setSelectedIds((current) => current.filter((id) => id !== entry.id))\n try {\n const call = await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n deleteCrud(ENTRIES_API_PATH, entry.id, { errorMessage: labels.errors.delete }),\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id },\n })\n const undoToken = readUndoToken(call.response)\n if (undoToken) pendingUndoTokensRef.current.add(undoToken)\n flash(\n t('staff.time_tracking.entries.messages.deleted', 'Deleted \u201C{name}\u201D ({duration}). Use Undo to restore it.', {\n name: entry.taskTitle ?? entry.description ?? labels.noTask,\n duration: formatDuration(entry.durationMinutes, 'clock'),\n }),\n 'success',\n )\n } catch (error) {\n setRows(previousRows)\n setTotal((current) => current + 1)\n reportFailure(error, labels.errors.delete, 'staff.time_tracking.entries delete failed')\n }\n },\n [labels.errors.delete, labels.noTask, mutationContext, reportFailure, rows, runMutation, t],\n )\n\n const handleDuplicate = React.useCallback(\n async (entry: TimeEntryListRow) => {\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow<Record<string, unknown>>(\n `${ENTRIES_API}/${encodeURIComponent(entry.id)}/duplicate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tagIds: entry.tagIds }),\n },\n { errorMessage: labels.errors.duplicate },\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id },\n })\n flash(t('staff.time_tracking.entries.messages.duplicated', 'Entry duplicated.'), 'success')\n handleRefresh()\n } catch (error) {\n reportFailure(error, labels.errors.duplicate, 'staff.time_tracking.entries duplicate failed')\n }\n },\n [handleRefresh, labels.errors.duplicate, mutationContext, reportFailure, runMutation, t],\n )\n\n /**\n * \"Kopiuj wczorajszy dzie\u0144\" (screen 1 note 5). The copies are drafts to\n * review, so on success the period narrows to the target day: the user lands\n * on exactly the rows that were created instead of hunting for them.\n *\n * A non-empty target day answers `409 copy_day_target_not_empty` naming what\n * is already there. That is a question, not a dead end \u2014 the user is told the\n * count and offered the `allowDuplicates` path, which is the real case the\n * refusal would otherwise block (something already logged today, and\n * yesterday's set wanted on top).\n */\n const runCopyDay = React.useCallback(\n async (fromDate: string, toDate: string, allowDuplicates: boolean) =>\n runMutation({\n operation: () =>\n apiCallOrThrow<CopyDayResponse>(\n `${ENTRIES_API}/copy-day`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ fromDate, toDate, allowDuplicates }),\n },\n { errorMessage: labels.errors.copyDay },\n ),\n context: mutationContext(toDate),\n mutationPayload: { fromDate, toDate, allowDuplicates },\n }),\n [labels.errors.copyDay, mutationContext, runMutation],\n )\n\n const handleCopyDay = React.useCallback(async () => {\n const toDate = todayIsoDate()\n const fromDate = shiftIsoDate(toDate, -1)\n const announce = (result: CopyDayResponse | null) => {\n const copied = typeof result?.copied === 'number' ? result.copied : 0\n const skipped = Array.isArray(result?.skipped) ? result.skipped.length : 0\n if (copied === 0) {\n flash(t('staff.time_tracking.entries.copyDay.nothing', 'Yesterday has no entries to copy.'), 'info')\n } else {\n flash(\n t('staff.time_tracking.entries.copyDay.success', '{count} entries copied as drafts \u2014 review them.', {\n count: copied,\n }),\n 'success',\n )\n setFilterValues((current) => ({ ...current, period: { from: toDate, to: toDate } }))\n setPage(1)\n }\n if (skipped > 0) {\n flash(\n t('staff.time_tracking.entries.copyDay.skipped', '{count} entries were skipped because they are locked.', {\n count: skipped,\n }),\n 'warning',\n )\n }\n handleRefresh()\n }\n\n try {\n const call = await runCopyDay(fromDate, toDate, false)\n announce(call.result)\n } catch (error) {\n const conflict = readCopyDayTargetConflict(error)\n if (!conflict) {\n reportFailure(error, labels.errors.copyDay, 'staff.time_tracking.entries copy-day failed')\n return\n }\n const confirmed = await confirm({\n title: labels.copyDay,\n text: t(\n 'staff.time_tracking.entries.copyDay.conflict',\n 'Today already has {count} entries. Add yesterday\u2019s copies on top of them?',\n { count: conflict.existingEntryCount },\n ),\n confirmText: t('staff.time_tracking.entries.copyDay.confirmDuplicates', 'Add copies anyway'),\n })\n if (!confirmed) return\n try {\n const retry = await runCopyDay(fromDate, toDate, true)\n announce(retry.result)\n } catch (retryError) {\n reportFailure(retryError, labels.errors.copyDay, 'staff.time_tracking.entries copy-day retry failed')\n }\n }\n }, [confirm, handleRefresh, labels.copyDay, labels.errors.copyDay, reportFailure, runCopyDay, t])\n\n const openCreate = React.useCallback(() => {\n setDialogEntryId(null)\n setDialogOpen(true)\n }, [])\n\n const openEdit = React.useCallback((entryId: string) => {\n setDialogEntryId(entryId)\n setDialogOpen(true)\n }, [])\n\n const selectableRows = React.useMemo(() => rows.filter((row) => !row.isLocked), [rows])\n const allSelected = selectableRows.length > 0 && selectedIds.length === selectableRows.length\n const someSelected = selectedIds.length > 0 && !allSelected\n\n const toggleRowSelection = React.useCallback((id: string, checked: boolean) => {\n setSelectedIds((current) => (checked ? Array.from(new Set([...current, id])) : current.filter((value) => value !== id)))\n }, [])\n\n const toggleAllSelection = React.useCallback(\n (checked: boolean) => {\n setSelectedIds(checked ? selectableRows.map((row) => row.id) : [])\n },\n [selectableRows],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n { id: 'period', label: labels.filters.period, type: 'dateRange' },\n { id: 'projectId', label: labels.filters.project, type: 'select', options: projectOptions },\n { id: 'customerId', label: labels.filters.customer, type: 'select', options: customerOptions },\n // Only offered when the caller can actually see somebody else's time; for\n // everyone else the route already scopes to their own entries and the\n // control would be a lever attached to nothing.\n ...(canManageAll && peopleOptions.length > 1\n ? [{ id: 'staffMemberId', label: labels.filters.person, type: 'select' as const, options: peopleOptions }]\n : []),\n { id: 'tagIds', label: labels.filters.tag, type: 'select', options: tagOptions },\n {\n id: 'billable',\n label: labels.filters.billable,\n type: 'select',\n options: [\n { value: 'true', label: labels.filters.billableYes },\n { value: 'false', label: labels.filters.billableNo },\n ],\n },\n {\n id: 'locked',\n label: labels.filters.locked,\n type: 'select',\n options: [\n { value: 'true', label: labels.filters.lockedYes },\n { value: 'false', label: labels.filters.lockedNo },\n ],\n },\n { id: 'q', label: labels.filters.text, type: 'text' },\n ],\n [labels.filters, canManageAll, projectOptions, customerOptions, peopleOptions, tagOptions],\n )\n\n /**\n * Filters that can be applied but own no control in the overlay: the self\n * filter, which is seeded for everyone but only steerable by a `manage_all`\n * holder, and the two deep-link narrowings. Naming them here is what keeps\n * their chips reading as words instead of as a parameter name and a uuid.\n */\n const unlistedFilters = React.useMemo<FilterDef[]>(\n () => [\n { id: 'staffMemberId', label: labels.filters.person, type: 'select', options: peopleOptions },\n { id: 'taskId', label: labels.filters.task, type: 'text' },\n { id: 'ids', label: labels.filters.entry, type: 'text' },\n ],\n [labels.filters, peopleOptions],\n )\n\n /** Applied filters, as the reader would say them out loud. */\n const appliedChips = React.useMemo<AppliedFilterChip[]>(() => {\n const defOf = (id: string) =>\n filters.find((filter) => filter.id === id) ?? unlistedFilters.find((filter) => filter.id === id)\n const label = (id: string) => defOf(id)?.label ?? id\n const optionLabel = (id: string, value: string) =>\n defOf(id)?.options?.find((option) => option.value === value)?.label ?? value\n const chips: AppliedFilterChip[] = []\n for (const [id, raw] of Object.entries(filterValues)) {\n if (raw === undefined || raw === null || raw === '') continue\n if (id === 'period') {\n const range = raw as { from?: string; to?: string }\n if (!range?.from && !range?.to) continue\n chips.push({ id, label: label(id), value: `${range.from ?? '\u2026'} \u2192 ${range.to ?? '\u2026'}` })\n continue\n }\n if (typeof raw !== 'string') continue\n // A deep link arrives as an id, so its chip reads the loaded rows back for\n // the task or the entry it names \u2014 a uuid on a chip explains nothing.\n if (id === 'taskId') {\n const first = raw.split(',')[0] ?? raw\n const match = rows.find((row) => row.taskId === first)\n chips.push({ id, label: label(id), value: match?.taskTitle ?? first })\n continue\n }\n if (id === 'ids') {\n const list = raw.split(',').map((value) => value.trim()).filter(Boolean)\n if (list.length === 0) continue\n const single = list.length === 1 ? rows.find((row) => row.id === list[0]) : undefined\n chips.push({\n id,\n label: label(id),\n value: single\n ? (single.taskTitle ?? single.description ?? labels.noTask)\n : labels.filters.entrySelection.replace('{count}', String(list.length)),\n })\n continue\n }\n chips.push({ id, label: label(id), value: optionLabel(id, raw) })\n }\n return chips\n }, [filterValues, filters, labels.filters.entrySelection, labels.noTask, rows, unlistedFilters])\n\n const columns = React.useMemo<ColumnDef<TimeEntryListRow>[]>(() => {\n const rightHeader = (label: string) => () => <span className=\"block text-right\">{label}</span>\n const defs: ColumnDef<TimeEntryListRow>[] = []\n\n if (canManage) {\n defs.push({\n id: 'select',\n accessorKey: 'id',\n header: () => (\n <Checkbox\n checked={allSelected ? true : someSelected ? 'indeterminate' : false}\n onCheckedChange={(checked) => toggleAllSelection(Boolean(checked))}\n aria-label={labels.selectAll}\n />\n ),\n enableSorting: false,\n meta: { priority: 1 },\n cell: ({ row }) =>\n row.original.isLocked ? null : (\n <Checkbox\n checked={selectedIds.includes(row.original.id)}\n onCheckedChange={(checked) => toggleRowSelection(row.original.id, Boolean(checked))}\n onClick={(event) => event.stopPropagation()}\n aria-label={labels.selectRow}\n />\n ),\n })\n }\n\n defs.push({\n accessorKey: 'date',\n header: labels.columns.date,\n meta: { priority: 1 },\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap text-sm text-muted-foreground\">\n {formatEntryDay(row.original.date)}\n </span>\n ),\n })\n\n defs.push({\n id: 'task',\n accessorKey: 'taskTitle',\n header: labels.columns.task,\n enableSorting: false,\n // `taskTitle` matches none of DataTable's wide-column keys, so it fell to the\n // 150px default and truncated to \"Consulting / Worksh\u2026\" while the nowrap\n // numeric columns kept their slack. This is the row's primary content.\n meta: { priority: 1, maxWidth: '520px' },\n cell: ({ row }) => {\n const entry = row.original\n const rateOverrideLabel =\n canSeeMoney && entry.rateOverrideAmount !== null\n ? formatCurrency(entry.rateOverrideAmount, entry.currencyCode) ?? String(entry.rateOverrideAmount)\n : null\n return (\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">\n {entry.taskTitle ?? labels.noTask}\n </span>\n {!entry.isBillable ? (\n <StatusBadge variant=\"neutral\">{labels.badges.nonBillable}</StatusBadge>\n ) : null}\n {rateOverrideLabel ? (\n <span data-testid=\"entry-rate-override\">\n <StatusBadge variant=\"info\">\n {t('staff.time_tracking.entries.badges.rateOverride', '{amount}/h', { amount: rateOverrideLabel })}\n </StatusBadge>\n </span>\n ) : null}\n {entry.isLocked ? (\n <span data-testid=\"entry-locked-badge\">\n <StatusBadge variant=\"neutral\">\n <Lock className=\"h-3 w-3\" aria-hidden=\"true\" />\n {labels.badges.locked}\n </StatusBadge>\n </span>\n ) : null}\n </div>\n {entry.description ? (\n <span className=\"truncate text-xs text-muted-foreground\">{entry.description}</span>\n ) : null}\n </div>\n )\n },\n })\n\n defs.push({\n id: 'project',\n accessorKey: 'projectLabel',\n header: labels.columns.project,\n enableSorting: false,\n meta: { priority: 2, maxWidth: '240px' },\n cell: ({ row }) =>\n row.original.projectLabel ? (\n <span className=\"truncate text-sm text-muted-foreground\">{row.original.projectLabel}</span>\n ) : (\n <span className=\"text-xs text-muted-foreground/70\">\u2014</span>\n ),\n })\n\n defs.push({\n id: 'clock',\n accessorKey: 'startText',\n header: labels.columns.clock,\n enableSorting: false,\n meta: { priority: 3, maxWidth: '160px' },\n cell: ({ row }) => {\n const range = formatEntryClockRange(row.original)\n return range ? (\n <span className=\"whitespace-nowrap font-mono text-sm tabular-nums text-muted-foreground\">{range}</span>\n ) : (\n <span className=\"text-xs text-muted-foreground/70\">\u2014</span>\n )\n },\n })\n\n defs.push({\n id: 'durationMinutes',\n accessorKey: 'durationMinutes',\n header: rightHeader(labels.columns.duration),\n meta: { priority: 1, maxWidth: '120px' },\n cell: ({ row }) => (\n <TimeEntryDurationCell\n value={row.original.durationMinutes}\n readOnly={row.original.isLocked || !canManage}\n saving={savingEntryId === row.original.id}\n ariaLabel={labels.durationAria}\n lockedTitle={row.original.isLocked ? labels.errors.locked : undefined}\n onCommit={(minutes) => handleDurationCommit(row.original, minutes)}\n />\n ),\n })\n\n if (canSeeMoney) {\n defs.push({\n id: 'cost',\n accessorKey: 'cost',\n header: rightHeader(labels.columns.cost),\n enableSorting: false,\n meta: { priority: 4, maxWidth: '140px' },\n cell: ({ row }) => {\n const formatted =\n row.original.cost === null ? null : formatCurrency(row.original.cost, row.original.currencyCode)\n return formatted ? (\n <span\n className=\"block text-right font-mono text-sm tabular-nums text-foreground\"\n data-testid=\"entry-cost\"\n >\n {formatted}\n </span>\n ) : (\n <span className=\"block text-right text-xs text-muted-foreground/70\" data-testid=\"entry-cost-empty\">\n \u2014\n </span>\n )\n },\n })\n }\n\n return defs\n }, [\n allSelected,\n canManage,\n canSeeMoney,\n handleDurationCommit,\n labels,\n savingEntryId,\n selectedIds,\n someSelected,\n t,\n toggleAllSelection,\n toggleRowSelection,\n ])\n\n const summary = React.useMemo(() => summarizeTimeEntries(rows), [rows])\n\n return (\n <Page>\n <PageBody>\n <DataTable<TimeEntryListRow>\n extensionTableId={extensionPoints.hosts.timeEntriesTable.tableId}\n title={labels.title}\n titleHeadingLevel={1}\n data={rows}\n columns={columns}\n isLoading={isLoading}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n // The overlay does not carry the deep-link filters, so applying it\n // drops them \u2014 the address bar has to agree with what is on screen.\n dropDeepLinkParams(DEEP_LINK_PARAMS.filter((id) => !values[id]))\n }}\n onFiltersClear={clearAllFilters}\n activeFilterChips={\n <div className=\"flex flex-col gap-2\">\n <EntryFilterPresets\n presets={presets}\n activePresetId={activePresetId}\n onApplyPreset={applyPreset}\n />\n <EntryFilterChips\n chips={appliedChips}\n onRemove={removeChip}\n onClearAll={clearAllFilters}\n clearAllLabel={labels.filters.clearAll}\n removeLabel={(name) => labels.filters.remove.replace('{name}', name)}\n />\n </div>\n }\n filterAwareEmptyState={{\n active: appliedChips.length > 0,\n entityNamePlural: labels.title,\n canRemoveLast: appliedChips.length > 0,\n onClearAll: clearAllFilters,\n onRemoveLast: () => {\n const last = appliedChips[appliedChips.length - 1]\n if (last) removeChip(last.id)\n },\n }}\n emptyState={<div className=\"py-12 text-center text-sm text-muted-foreground\">{labels.empty}</div>}\n actions={\n canManage ? (\n <div className=\"flex items-center gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => void handleCopyDay()}>\n <Copy className=\"h-4 w-4\" aria-hidden=\"true\" />\n {labels.copyDay}\n </Button>\n <Button size=\"sm\" onClick={openCreate}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {labels.add}\n </Button>\n </div>\n ) : undefined\n }\n refreshButton={{\n label: labels.refresh,\n onRefresh: handleRefresh,\n isRefreshing: isLoading || isRefreshing,\n }}\n sortable\n manualSorting\n sorting={sorting}\n onSortingChange={setSorting}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalIsCapped,\n totalPages,\n onPageChange: setPage,\n }}\n stickyActionsColumn\n rowActions={(row) =>\n row.isLocked ? (\n <RowActions\n items={[\n {\n id: 'show-report',\n label: labels.showReport,\n href: row.lockedReportId\n ? reportDetailHref(row.lockedReportId)\n : REPORTS_PATH,\n },\n ]}\n />\n ) : canManage ? (\n <RowActions\n items={[\n {\n id: 'duplicate',\n label: labels.duplicate,\n onSelect: () => {\n void handleDuplicate(row)\n },\n },\n {\n id: 'delete',\n label: labels.delete,\n destructive: true,\n onSelect: () => {\n void handleDelete(row)\n },\n },\n ]}\n />\n ) : null\n }\n onRowClick={(row) => {\n if (row.isLocked) return\n openEdit(row.id)\n }}\n />\n\n <TimeEntriesSummaryFooter summary={summary} totalCount={total} canSeeMoney={canSeeMoney} />\n\n {!canManage ? (\n <p className=\"mt-2 text-xs text-muted-foreground\">\n {t('staff.time_tracking.entries.readOnlyNotice', 'You can view these entries but not change them.')}\n </p>\n ) : null}\n </PageBody>\n\n <TimeEntryDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n entryId={dialogEntryId}\n onSaved={({ keptOpen }) => {\n handleRefresh()\n if (!keptOpen) setDialogOpen(false)\n }}\n onShowEntry={(entryId) => openEdit(entryId)}\n />\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -49,6 +49,7 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
49
49
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
50
50
  import { createLogger } from "@open-mercato/shared/lib/logger";
51
51
  import { DurationInput } from "./DurationInput.js";
52
+ import { reportDetailHref } from "./reportLinks.js";
52
53
  import { formatDuration } from "../time-tracking/duration.js";
53
54
  import { parseClock } from "../time-tracking/interval.js";
54
55
  import { applicableRate, entryAmount } from "../time-tracking/cost.js";
@@ -75,7 +76,7 @@ function fieldValueSnapshot(value) {
75
76
  }
76
77
  const RATES_FEATURE = "staff.timesheets.rates.view";
77
78
  const ENTRIES_LIST_PATH = "/backend/staff/time-tracking/entries";
78
- const REPORTS_PATH = "/backend/staff/time-tracking/reports";
79
+ import { REPORTS_PATH } from "./reportLinks.js";
79
80
  const DIALOG_QUERY_ROOT = ["staff", "time-tracking", "entry-dialog"];
80
81
  const DIRECTORY_PAGE_SIZE = 100;
81
82
  const TASK_SEARCH_PAGE_SIZE = 50;
@@ -1017,7 +1018,7 @@ function DefaultTimeEntryDialog({
1017
1018
  size: "sm",
1018
1019
  className: "mt-2",
1019
1020
  "data-testid": "entry-dialog-locked-report",
1020
- onClick: () => window.open(`${REPORTS_PATH}?id=${encodeURIComponent(lockedReportId)}`, "_blank", "noopener"),
1021
+ onClick: () => window.open(reportDetailHref(lockedReportId), "_blank", "noopener"),
1021
1022
  children: t("staff.time_tracking.entryDialog.locked.showReport", "Show the report")
1022
1023
  }
1023
1024
  ) : null
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { Lock, X } from 'lucide-react'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Separator } from '@open-mercato/ui/primitives/separator'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { TagPicker, tagChipStyle } from './TagPicker'\nimport { TaskPicker, type TaskPickerItem, type TaskPickerStatus } from './TaskPicker'\nimport { slugifyProjectName } from '../time-tracking/projectCode'\nimport { autoColorFromName } from '../timesheets-ui/colors'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { normalizeCrudServerError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { InjectionSpot, useInjectionSpotEvents } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { registerComponent } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'\nimport {\n callbackProp,\n opaqueProp,\n optionalCallbackProp,\n} from '../time-tracking/componentContracts'\nimport { extensionSpotChildId } from '@open-mercato/shared/modules/widgets/extension-points'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { DurationInput } from './DurationInput'\nimport { formatDuration } from '../time-tracking/duration'\nimport { parseClock } from '../time-tracking/interval'\nimport { applicableRate, entryAmount } from '../time-tracking/cost'\nimport { roundMinutes, DEFAULT_ROUNDING_SETTINGS, type RoundingSettings } from '../time-tracking/rounding'\nimport {\n combineDateAndClock,\n createIntervalState,\n reduceIntervalState,\n resetIntervalState,\n readRowItems,\n readRowString,\n shiftIsoDate,\n todayIsoDate,\n toOverlapEntry,\n toProjectOption,\n toTaskOption,\n toTimeEntryRecord,\n type OverlapEntry,\n type ProjectOption,\n type TagOption,\n type TaskOption,\n type TimeEntryRecord,\n type TimeIntervalState,\n} from './timeEntryDialogState'\n\nconst logger = createLogger('staff').child({ component: 'TimeEntryDialog' })\n\nexport const TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID = 'staff.time_tracking.entryDialog'\nconst TIME_ENTRY_ENTITY_ID = 'staff:staff_time_entry'\n\ntype TimeEntryFormInjectionContext = {\n formId: string\n entityId: string\n recordId: string | null\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update'\n locked: boolean\n retryLastMutation: () => Promise<boolean>\n}\n\nfunction fieldValueSnapshot(value: unknown): string {\n return JSON.stringify(value ?? null)\n}\nexport const RATES_FEATURE = 'staff.timesheets.rates.view'\nexport const ENTRIES_LIST_PATH = '/backend/staff/time-tracking/entries'\nexport const REPORTS_PATH = '/backend/staff/time-tracking/reports'\n\nconst DIALOG_QUERY_ROOT = ['staff', 'time-tracking', 'entry-dialog'] as const\nconst DIRECTORY_PAGE_SIZE = 100\nconst TASK_SEARCH_PAGE_SIZE = 50\nconst TASK_SEARCH_DEBOUNCE_MS = 250\n\nexport type TimeEntryDialogDefaults = {\n date?: string\n taskId?: string | null\n timeProjectId?: string | null\n durationMinutes?: number | null\n startClock?: string | null\n endClock?: string | null\n description?: string | null\n isBillable?: boolean | null\n tagIds?: string[]\n}\n\nexport type TimeEntryDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Present \u2192 edit that entry; absent \u2192 a brand new one. */\n entryId?: string | null\n /** Seed values for a new entry: a stopped timer, a grid cell, a board card. */\n defaults?: TimeEntryDialogDefaults\n /** Sub-title line, e.g. \"Created from a stopped timer \u00B7 01:14:02\". */\n headerHint?: string | null\n /** Fired after every successful write, including \"save and add another\". */\n onSaved?: (result: { id: string | null; keptOpen: boolean }) => void\n /** \"Show that entry\" on the overlap warning; falls back to the entries list. */\n onShowEntry?: (entryId: string) => void\n}\n\ntype SettingsPayload = {\n rounding: RoundingSettings\n defaults: { billable: boolean; chainStartFromPreviousEnd: boolean }\n warnings: { overlap: boolean }\n}\n\nconst FALLBACK_SETTINGS: SettingsPayload = {\n rounding: { ...DEFAULT_ROUNDING_SETTINGS },\n defaults: { billable: true, chainStartFromPreviousEnd: true },\n warnings: { overlap: true },\n}\n\nfunction readSettings(payload: unknown): SettingsPayload {\n const source = (payload ?? {}) as Record<string, unknown>\n const rounding = (source.rounding ?? {}) as Record<string, unknown>\n const defaults = (source.defaults ?? {}) as Record<string, unknown>\n const warnings = (source.warnings ?? {}) as Record<string, unknown>\n return {\n rounding: {\n unitMinutes:\n rounding.unitMinutes === 5 || rounding.unitMinutes === 10 || rounding.unitMinutes === 15\n ? rounding.unitMinutes\n : 0,\n direction: rounding.direction === 'nearest' ? 'nearest' : 'up',\n },\n defaults: {\n billable: typeof defaults.billable === 'boolean' ? defaults.billable : true,\n chainStartFromPreviousEnd:\n typeof defaults.chainStartFromPreviousEnd === 'boolean' ? defaults.chainStartFromPreviousEnd : true,\n },\n warnings: { overlap: typeof warnings.overlap === 'boolean' ? warnings.overlap : true },\n }\n}\n\nfunction parseRateText(value: string): number | null {\n const trimmed = value.trim().replace(',', '.')\n if (!trimmed) return null\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : null\n}\n\n/**\n * Everything a person can type into the form, in the one shape both the seed and\n * the live state can be expressed in. Comparing two of these is how the dialog\n * knows whether closing it would throw work away.\n */\ntype FormValues = {\n taskId: string | null\n description: string\n date: string\n startText: string\n endText: string\n durationMinutes: number | null\n isBillable: boolean\n rateOverrideEnabled: boolean\n rateOverrideAmount: number | null\n tagIds: string[]\n}\n\ntype SaveMode = 'close' | 'again'\n\n/** The two controls a failed save can point at, empty when nothing is wrong. */\ntype FieldIssues = {\n task: string | null\n duration: string | null\n}\n\nconst NO_FIELD_ISSUES: FieldIssues = { task: null, duration: null }\n\n/**\n * A server complaint that names a field belongs under that field, not in a\n * toast. Only the two the form can point at are claimed; anything else stays\n * global and is flashed.\n */\nfunction readFieldIssues(error: unknown): FieldIssues | null {\n const { fieldErrors } = normalizeCrudServerError(error)\n if (!fieldErrors) return null\n const task = fieldErrors.taskId ?? fieldErrors.task ?? null\n const duration = fieldErrors.durationMinutes ?? fieldErrors.duration ?? null\n if (!task && !duration) return null\n return { task, duration }\n}\n\nfunction formSnapshot(values: FormValues): string {\n return JSON.stringify([\n values.taskId,\n values.description.trim(),\n values.date,\n values.startText,\n values.endText,\n values.durationMinutes,\n values.isBillable,\n values.rateOverrideEnabled,\n values.rateOverrideAmount,\n [...values.tagIds].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),\n ])\n}\n\n/**\n * Two requests, merged.\n *\n * A single term cannot be routed to one field by shape \u2014 \"Booking\" is a title\n * word and \"AWR\" is a code, and both are one alphanumeric token \u2014 and the query\n * engine has no OR across fields, so the picker asks both questions and unions\n * the answers. This is what makes task 400 of 900 findable: the directory the\n * picker opens on is only ever its first page.\n */\nasync function fetchTaskOptions(term: string): Promise<TaskOption[]> {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(TASK_SEARCH_PAGE_SIZE),\n sortField: 'title',\n sortDir: 'asc',\n })\n const responses = await Promise.all(\n [\n `/api/staff/timesheets/tasks?${params.toString()}&q=${encodeURIComponent(term)}`,\n `/api/staff/timesheets/tasks?${params.toString()}&reference=${encodeURIComponent(term)}`,\n ].map((url) => apiCall<Record<string, unknown>>(url).catch(() => null)),\n )\n const merged = new Map<string, TaskOption>()\n for (const response of responses) {\n if (!response?.ok) continue\n for (const row of readRowItems(response.result)) {\n const option = toTaskOption(row)\n if (option && !merged.has(option.id)) merged.set(option.id, option)\n }\n }\n return [...merged.values()]\n}\n\n/** Resolves the entry's own task when it sorts outside the directory's first page. */\nasync function fetchTaskById(id: string): Promise<TaskOption | null> {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/tasks?ids=${encodeURIComponent(id)}&pageSize=1`,\n )\n if (!call.ok) return null\n const row = readRowItems(call.result)[0]\n return row ? toTaskOption(row) : null\n}\n\nfunction readErrorCode(error: unknown): string | null {\n const body = (error as { body?: { code?: unknown; error?: unknown } } | null)?.body\n if (body && typeof body.code === 'string') return body.code\n if (body && typeof body.error === 'string') return body.error\n return null\n}\n\n/**\n * The full time entry form of screen 8, plus the two edge states of screen 9.\n * Every surface of the module funnels through it \u2014 the entries list, the\n * timesheet, the board and the task drawer \u2014 so it takes an entry id (edit) or a\n * set of defaults (create) and owns nothing else about its host.\n *\n * Four decisions carry the design intent:\n *\n * 1. **The 2-of-3 arithmetic lives in one reducer, not in the render path.**\n * `reduceIntervalState` calls `deriveInterval` with exactly the two anchor\n * fields and stores which one came back computed, so the `wyliczone` badge is\n * read off the state rather than recomputed. `start` is the preferred anchor,\n * which is what makes editing the end restate the duration (note 1).\n * 2. **The duration field is never rewritten while it is being typed.**\n * `DurationInput` keeps unparseable text on its own; the dialog only remounts\n * it \u2014 via `durationEpoch` \u2014 when the *derived* duration changed, i.e. when\n * the user was editing some other field.\n * 3. **Overlap is advisory.** The probe is a plain `apiCall`; a non-200, a\n * throw or an empty list all mean \"no warning\", and Save never observes it.\n * Only an unparseable duration disables Save (screen 9 notes 2 and 4).\n * 4. **A locked entry opens read-only.** Every write path answers\n * `409 entry_locked`, so the form shows the lock and a way to the report that\n * closed it instead of letting someone compose a request that cannot succeed.\n */\nfunction DefaultTimeEntryDialog({\n open,\n onOpenChange,\n entryId,\n defaults,\n headerHint,\n onSaved,\n onShowEntry,\n}: TimeEntryDialogProps): React.ReactElement {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { payload } = useBackendChrome()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const canSeeMoney = hasFeature(payload?.grantedFeatures, RATES_FEATURE)\n const isEdit = typeof entryId === 'string' && entryId.length > 0\n\n const [taskId, setTaskId] = React.useState<string | null>(null)\n const [description, setDescription] = React.useState('')\n const [date, setDate] = React.useState(() => todayIsoDate())\n const [interval, setIntervalState] = React.useState<TimeIntervalState>(() => createIntervalState({}))\n const [isBillable, setIsBillable] = React.useState(true)\n const [rateOverrideEnabled, setRateOverrideEnabled] = React.useState(false)\n const [rateText, setRateText] = React.useState('')\n const [tagIds, setTagIds] = React.useState<string[]>([])\n const [overlaps, setOverlaps] = React.useState<OverlapEntry[]>([])\n /** Which button is in flight, so the spinner lands on the one that was pressed. */\n const [savingMode, setSavingMode] = React.useState<SaveMode | null>(null)\n /**\n * What is wrong with a single control, said under that control. A disabled\n * Save cannot explain which of the two required fields it is waiting for, and\n * a server complaint about one field is not a global failure either.\n */\n const [fieldIssues, setFieldIssues] = React.useState<FieldIssues>(NO_FIELD_ISSUES)\n const saving = savingMode !== null\n const [taskSearch, setTaskSearch] = React.useState('')\n const [debouncedTaskSearch, setDebouncedTaskSearch] = React.useState('')\n /**\n * The task the form is on, kept out of the two lists that come and go.\n * The directory holds the first page only and the search results are replaced\n * on every term, so without this the chosen task would vanish from under the\n * selection the moment the search box was cleared.\n */\n const [pinnedTask, setPinnedTask] = React.useState<TaskOption | null>(null)\n /** What the form was seeded with; `null` until it has been seeded at all. */\n const [baseline, setBaseline] = React.useState<FormValues | null>(null)\n /**\n * T7.4 \u2014 the entry loop must be completable without a mouse, so the first field\n * of the loop is also the focus target twice over: when the dialog opens (Radix\n * would otherwise land on the \u00D7 close button, which renders before the body) and\n * after \"save and add another\", where focus would otherwise be stranded on the\n * button and the next entry would start with a handful of Shift+Tabs.\n */\n const taskTriggerRef = React.useRef<HTMLDivElement | null>(null)\n /**\n * The picker is a search box inside a container, not a single focusable\n * control, so the keyboard loop focuses the input within it. Without this,\n * \"save and add another\" left focus stranded and the loop stopped being a loop.\n */\n const focusTaskPicker = React.useCallback(() => {\n const host = taskTriggerRef.current\n if (!host) return false\n const focusable = host.querySelector('input, select, button') as HTMLElement | null\n if (!focusable) return false\n focusable.focus()\n return true\n }, [])\n\n const seedKeyRef = React.useRef<string | null>(null)\n const billableDefaultRef = React.useRef<string | null>(null)\n const versionRef = React.useRef<string | null>(null)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,\n blockedMessage: t('staff.time_tracking.entryDialog.errors.save', 'Could not save the time entry.'),\n })\n\n const settingsQuery = useQuery<SettingsPayload>({\n queryKey: [...DIALOG_QUERY_ROOT, 'settings', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>('/api/staff/timesheets/settings')\n return call.ok ? readSettings(call.result) : FALLBACK_SETTINGS\n },\n })\n const settings = settingsQuery.data ?? FALLBACK_SETTINGS\n\n const selfQuery = useQuery<{ id: string; displayName: string } | null>({\n queryKey: [...DIALOG_QUERY_ROOT, 'self', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<{ member?: { id?: string; displayName?: string } | null }>(\n '/api/staff/team-members/self',\n )\n const member = call.ok ? call.result?.member ?? null : null\n if (!member?.id) return null\n return { id: member.id, displayName: member.displayName ?? '' }\n },\n })\n\n /**\n * The first page of tasks, which is what the picker opens on. It is a head\n * start, not the corpus \u2014 anything past it is reached through the search below.\n */\n const tasksQuery = useQuery<TaskOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'tasks', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 60_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/tasks?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=title&sortDir=asc`,\n )\n if (!call.ok) return []\n return readRowItems(call.result)\n .map(toTaskOption)\n .filter((task): task is TaskOption => task !== null)\n },\n })\n\n // Debounced so a typed word is one pair of requests rather than one per letter.\n React.useEffect(() => {\n if (!open) {\n setDebouncedTaskSearch('')\n return\n }\n const handle = setTimeout(() => setDebouncedTaskSearch(taskSearch.trim()), TASK_SEARCH_DEBOUNCE_MS)\n return () => clearTimeout(handle)\n }, [open, taskSearch])\n\n const taskSearchQuery = useQuery<TaskOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'task-search', `scope:${scopeVersion}`, debouncedTaskSearch],\n enabled: open && debouncedTaskSearch.length > 0,\n staleTime: 60_000,\n queryFn: () => fetchTaskOptions(debouncedTaskSearch),\n })\n\n const projectsQuery = useQuery<ProjectOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'projects', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 60_000,\n queryFn: async () => {\n // Best effort: a consultant without `projects.view` still gets the task\n // picker, only without the customer and project suffix on each option.\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/time-projects?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=name&sortDir=asc`,\n )\n if (!call.ok) return []\n return readRowItems(call.result)\n .map(toProjectOption)\n .filter((project): project is ProjectOption => project !== null)\n },\n })\n\n const staffMemberId = selfQuery.data?.id ?? null\n\n const statusesQuery = useQuery<Record<string, TaskPickerStatus>>({\n queryKey: [...DIALOG_QUERY_ROOT, 'task-statuses', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/task-statuses?page=1&pageSize=${DIRECTORY_PAGE_SIZE}`,\n )\n if (!call.ok) return {}\n const map: Record<string, TaskPickerStatus> = {}\n for (const row of readRowItems(call.result)) {\n const id = readRowString(row, 'id')\n if (!id) continue\n map[id] = {\n id,\n name: readRowString(row, 'name') ?? '',\n color: readRowString(row, 'color'),\n isDone: row.is_done === true || row.isDone === true,\n }\n }\n return map\n },\n })\n\n /**\n * The tasks this person logged to recently. Repetition is the norm in time\n * logging, so opening the picker on these turns the common case into one\n * keystroke. No new endpoint \u2014 it is the entries list asked a narrower question.\n */\n const recentQuery = useQuery<string[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'recent-tasks', `scope:${scopeVersion}`, staffMemberId ?? 'none'],\n enabled: open && !!staffMemberId,\n staleTime: 60_000,\n queryFn: async () => {\n const since = shiftIsoDate(todayIsoDate(), -21)\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/time-entries?page=1&pageSize=100&sortField=date&sortDir=desc`\n + `&staffMemberId=${encodeURIComponent(staffMemberId as string)}&from=${since}`,\n )\n if (!call.ok) return []\n const seen: string[] = []\n for (const row of readRowItems(call.result)) {\n const taskId = readRowString(row, 'task_id', 'taskId')\n if (taskId && !seen.includes(taskId)) seen.push(taskId)\n if (seen.length >= 6) break\n }\n return seen\n },\n })\n\n const tagsQuery = useQuery<TagOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'tags', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/tags?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=label&sortDir=asc`,\n )\n if (!call.ok) return []\n return readRowItems(call.result)\n .map((row) => {\n const id = readRowString(row, 'id')\n // The colour has always been stored and never rendered \u2014 the chips were\n // monochrome while `staff_time_tags.color` held a DS palette key.\n return id\n ? { id, label: readRowString(row, 'label') ?? id, color: readRowString(row, 'color') }\n : null\n })\n .filter((tag): tag is TagOption => tag !== null)\n },\n })\n\n const entryQuery = useQuery<TimeEntryRecord | null>({\n queryKey: [...DIALOG_QUERY_ROOT, 'entry', `scope:${scopeVersion}`, entryId ?? 'new'],\n enabled: open && isEdit,\n staleTime: 0,\n queryFn: async () => {\n // `ids`, not `id`: the list schema declares the plural and is `.passthrough()`,\n // so `?id=` was accepted, never read, and the filter silently disappeared \u2014\n // leaving `pageSize=1` to return the first entry of the whole table.\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/time-entries?ids=${encodeURIComponent(entryId as string)}&pageSize=1`,\n )\n if (!call.ok) throw new Error('[internal] time entry request failed')\n const row = readRowItems(call.result)[0]\n if (!row) return null\n const record = toTimeEntryRecord(row)\n if (!record) return null\n // A dialog that edits a record other than the one it was asked for writes\n // one entry's values onto another, so a mismatch fails loudly rather than\n // populating the form. Nothing but a dropped filter can produce it.\n if (record.id !== entryId) {\n throw new Error('[internal] time entry response did not match the requested id')\n }\n return record\n },\n })\n\n const entry = entryQuery.data ?? null\n const locked = !!entry?.isLocked\n const lockedReportId = entry?.lockedReportId ?? null\n const directoryTasks = React.useMemo(() => tasksQuery.data ?? [], [tasksQuery.data])\n const searchedTasks = React.useMemo(() => taskSearchQuery.data ?? [], [taskSearchQuery.data])\n\n React.useEffect(() => {\n if (!taskId) {\n setPinnedTask(null)\n return\n }\n const found =\n directoryTasks.find((task) => task.id === taskId) ??\n searchedTasks.find((task) => task.id === taskId) ??\n null\n if (found) setPinnedTask(found)\n }, [directoryTasks, searchedTasks, taskId])\n\n /**\n * An entry can point at a task that neither the first directory page nor the\n * current search contains \u2014 editing one written months ago, for instance \u2014 and\n * a form that cannot name the task it is on cannot resolve its project or rate\n * either, so that one row is fetched by id.\n */\n const unresolvedTaskId =\n taskId && !pinnedTask && !tasksQuery.isPending && !taskSearchQuery.isFetching ? taskId : null\n\n const unresolvedTaskQuery = useQuery<TaskOption | null>({\n queryKey: [...DIALOG_QUERY_ROOT, 'task', `scope:${scopeVersion}`, unresolvedTaskId ?? 'none'],\n enabled: open && !!unresolvedTaskId,\n staleTime: 60_000,\n queryFn: () => fetchTaskById(unresolvedTaskId as string),\n })\n\n const tasks = React.useMemo(() => {\n const merged = new Map<string, TaskOption>()\n for (const task of directoryTasks) merged.set(task.id, task)\n for (const task of searchedTasks) if (!merged.has(task.id)) merged.set(task.id, task)\n for (const task of [pinnedTask, unresolvedTaskQuery.data ?? null]) {\n if (task && !merged.has(task.id)) merged.set(task.id, task)\n }\n return [...merged.values()]\n }, [directoryTasks, pinnedTask, searchedTasks, unresolvedTaskQuery.data])\n\n const projects = React.useMemo(() => projectsQuery.data ?? [], [projectsQuery.data])\n const tagOptions = React.useMemo(() => tagsQuery.data ?? [], [tagsQuery.data])\n\n const projectById = React.useMemo(() => {\n const map = new Map<string, ProjectOption>()\n for (const project of projects) map.set(project.id, project)\n return map\n }, [projects])\n\n const selectedTask = React.useMemo(\n () => tasks.find((task) => task.id === taskId) ?? null,\n [taskId, tasks],\n )\n\n const projectId = selectedTask?.timeProjectId ?? entry?.timeProjectId ?? defaults?.timeProjectId ?? null\n const project = projectId ? projectById.get(projectId) ?? null : null\n\n const resetForm = React.useCallback(\n (seed: {\n taskId: string | null\n description: string\n date: string\n start: string | null\n end: string | null\n durationMinutes: number | null\n isBillable: boolean\n rateOverrideAmount: number | null\n tagIds: string[]\n }) => {\n setTaskId(seed.taskId)\n setDescription(seed.description)\n setDate(seed.date || todayIsoDate())\n setIntervalState((current) =>\n resetIntervalState(current, {\n start: seed.start,\n end: seed.end,\n durationMinutes: seed.durationMinutes,\n }),\n )\n setIsBillable(seed.isBillable)\n setRateOverrideEnabled(seed.rateOverrideAmount !== null)\n setRateText(seed.rateOverrideAmount !== null ? String(seed.rateOverrideAmount) : '')\n setTagIds(seed.tagIds)\n setOverlaps([])\n setFieldIssues(NO_FIELD_ISSUES)\n // The seed is run through the same derivation the state uses, so the\n // baseline is what the form will actually show rather than what was asked\n // for \u2014 otherwise a seeded start plus duration would read as an edit.\n const derived = createIntervalState({\n start: seed.start,\n end: seed.end,\n durationMinutes: seed.durationMinutes,\n })\n setBaseline({\n taskId: seed.taskId,\n description: seed.description,\n date: seed.date || todayIsoDate(),\n startText: derived.startText,\n endText: derived.endText,\n durationMinutes: derived.durationMinutes,\n isBillable: seed.isBillable,\n rateOverrideEnabled: seed.rateOverrideAmount !== null,\n rateOverrideAmount: seed.rateOverrideAmount,\n tagIds: seed.tagIds,\n })\n },\n [],\n )\n\n React.useEffect(() => {\n if (!open) {\n seedKeyRef.current = null\n billableDefaultRef.current = null\n setBaseline(null)\n setTaskSearch('')\n setPinnedTask(null)\n return\n }\n if (isEdit && !entry) return\n const key = isEdit ? `edit:${entry?.id ?? entryId}:${entry?.updatedAt ?? ''}` : 'create'\n if (seedKeyRef.current === key) return\n seedKeyRef.current = key\n versionRef.current = entry?.updatedAt ?? null\n if (entry) {\n resetForm({\n taskId: entry.taskId,\n description: entry.description,\n date: entry.date,\n start: entry.startText,\n end: entry.endText,\n durationMinutes: entry.durationMinutes,\n isBillable: entry.isBillable,\n rateOverrideAmount: entry.rateOverrideAmount,\n tagIds: entry.tagIds,\n })\n return\n }\n resetForm({\n taskId: defaults?.taskId ?? null,\n description: defaults?.description ?? '',\n date: defaults?.date ?? todayIsoDate(),\n start: defaults?.startClock ?? null,\n end: defaults?.endClock ?? null,\n durationMinutes: defaults?.durationMinutes ?? null,\n isBillable: defaults?.isBillable ?? true,\n rateOverrideAmount: null,\n tagIds: defaults?.tagIds ?? [],\n })\n }, [defaults, entry, entryId, isEdit, open, resetForm])\n\n /**\n * The tenant's billable default lands separately, and only on the one field it\n * owns. Seeding the whole form once the settings request answers would race the\n * first keystrokes and wipe them, which is exactly what this split avoids.\n */\n React.useEffect(() => {\n if (!open || isEdit || settingsQuery.isPending) return\n if (defaults?.isBillable !== undefined && defaults?.isBillable !== null) return\n if (billableDefaultRef.current === seedKeyRef.current) return\n billableDefaultRef.current = seedKeyRef.current\n setIsBillable(settings.defaults.billable)\n // The tenant default is part of the seed, just a late-arriving part, so the\n // baseline moves with it \u2014 otherwise every new entry would open \"dirty\".\n setBaseline((current) =>\n current ? { ...current, isBillable: settings.defaults.billable } : current,\n )\n }, [defaults?.isBillable, isEdit, open, settings.defaults.billable, settingsQuery.isPending])\n\n const durationMinutes = interval.durationMinutes\n const startClock = interval.startText\n const endClock = interval.endText\n\n /**\n * Advisory overlap probe. Every failure mode \u2014 a non-200, a throw, a partial\n * span \u2014 lands on \"no warning\", because the endpoint may not answer and the\n * form must still save.\n */\n React.useEffect(() => {\n if (!open || locked || !settings.warnings.overlap) {\n setOverlaps([])\n return\n }\n if (!date || parseClock(startClock) === null || !durationMinutes) {\n setOverlaps([])\n return\n }\n let cancelled = false\n const params = new URLSearchParams({\n date,\n startedAt: startClock,\n durationMinutes: String(durationMinutes),\n })\n if (parseClock(endClock) !== null) params.set('endedAt', endClock)\n if (isEdit && entryId) params.set('excludeId', entryId)\n void apiCall<Record<string, unknown>>(`/api/staff/timesheets/time-entries/overlaps?${params.toString()}`)\n .then((call) => {\n if (cancelled) return\n if (!call.ok) {\n setOverlaps([])\n return\n }\n setOverlaps(\n readRowItems(call.result)\n .map(toOverlapEntry)\n .filter((item): item is OverlapEntry => item !== null),\n )\n })\n .catch(() => {\n if (!cancelled) setOverlaps([])\n })\n return () => {\n cancelled = true\n }\n }, [date, durationMinutes, endClock, entryId, isEdit, locked, open, settings.warnings.overlap, startClock])\n\n const rateOverrideAmount = rateOverrideEnabled ? parseRateText(rateText) : null\n const currencyCode = project?.currencyCode ?? entry?.currencyCode ?? null\n\n /**\n * The dialog is hand-rolled, so it plays the `CrudForm` host contract by hand:\n * the four render spots below, plus the `onFieldChange` / `onBeforeSave` /\n * `onAfterSave` lifecycle, dispatched with the same semantics `CrudForm` uses \u2014\n * a widget returning `ok: false` from `onBeforeSave` blocks the write and its\n * message is surfaced, and a value returned from `onFieldChange` is written back\n * into the form.\n */\n const entryFormValues = React.useMemo<FormValues>(\n () => ({\n taskId,\n description,\n date,\n startText: startClock,\n endText: endClock,\n durationMinutes,\n isBillable,\n rateOverrideEnabled,\n rateOverrideAmount,\n tagIds,\n }),\n [\n date,\n description,\n durationMinutes,\n endClock,\n isBillable,\n rateOverrideAmount,\n rateOverrideEnabled,\n startClock,\n tagIds,\n taskId,\n ],\n )\n\n const entryInjectionContext = React.useMemo<TimeEntryFormInjectionContext>(\n () => ({\n formId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,\n entityId: TIME_ENTRY_ENTITY_ID,\n recordId: entryId ?? null,\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: entryId ?? 'new',\n operation: isEdit ? 'update' : 'create',\n locked,\n retryLastMutation,\n }),\n [entryId, isEdit, locked, retryLastMutation],\n )\n\n const { triggerEvent: triggerEntryFormEvent } = useInjectionSpotEvents<\n TimeEntryFormInjectionContext,\n FormValues\n >(extensionPoints.hosts.timeEntryForm.spotId)\n\n /**\n * Fields the last write to came from a widget, not from the person. Without it\n * an `onFieldChange` handler that rewrites its own field would see the rewrite\n * as a fresh edit and be dispatched again on every apply.\n */\n const injectedFieldWritesRef = React.useRef(new Set<string>())\n\n const applyInjectedFieldValue = React.useCallback((fieldId: string, value: unknown) => {\n switch (fieldId) {\n case 'taskId':\n injectedFieldWritesRef.current.add(fieldId)\n setTaskId(typeof value === 'string' && value.length > 0 ? value : null)\n return\n case 'description':\n injectedFieldWritesRef.current.add(fieldId)\n setDescription(typeof value === 'string' ? value : '')\n return\n case 'date':\n if (typeof value !== 'string') return\n injectedFieldWritesRef.current.add(fieldId)\n setDate(value)\n return\n case 'isBillable':\n injectedFieldWritesRef.current.add(fieldId)\n setIsBillable(value === true)\n return\n case 'rateOverrideEnabled':\n injectedFieldWritesRef.current.add(fieldId)\n setRateOverrideEnabled(value === true)\n return\n case 'rateOverrideAmount':\n injectedFieldWritesRef.current.add(fieldId)\n setRateText(typeof value === 'number' || typeof value === 'string' ? String(value) : '')\n return\n case 'tagIds':\n if (!Array.isArray(value)) return\n injectedFieldWritesRef.current.add(fieldId)\n setTagIds(value.filter((id): id is string => typeof id === 'string'))\n return\n case 'startText':\n if (typeof value !== 'string') return\n injectedFieldWritesRef.current.add(fieldId)\n setIntervalState((current) => reduceIntervalState(current, { field: 'start', value }))\n return\n case 'endText':\n if (typeof value !== 'string') return\n injectedFieldWritesRef.current.add(fieldId)\n setIntervalState((current) => reduceIntervalState(current, { field: 'end', value }))\n return\n case 'durationMinutes':\n injectedFieldWritesRef.current.add(fieldId)\n setIntervalState((current) =>\n reduceIntervalState(current, {\n field: 'duration',\n minutes: typeof value === 'number' ? value : null,\n invalid: false,\n }),\n )\n return\n default:\n }\n }, [])\n\n const previousEntryFormValuesRef = React.useRef<FormValues | null>(null)\n\n React.useEffect(() => {\n if (!open || locked) {\n previousEntryFormValuesRef.current = null\n injectedFieldWritesRef.current.clear()\n return\n }\n const previous = previousEntryFormValuesRef.current\n previousEntryFormValuesRef.current = entryFormValues\n if (!previous) return\n const injectedWrites = injectedFieldWritesRef.current\n const changedFieldIds = (Object.keys(entryFormValues) as Array<keyof FormValues>).filter(\n (fieldId) => fieldValueSnapshot(previous[fieldId]) !== fieldValueSnapshot(entryFormValues[fieldId]),\n )\n for (const fieldId of changedFieldIds) {\n if (injectedWrites.delete(fieldId)) continue\n void triggerEntryFormEvent('onFieldChange', entryFormValues, entryInjectionContext, {\n fieldId,\n fieldValue: entryFormValues[fieldId],\n })\n .then((result) => {\n if (!result.ok || !result.fieldChange) return\n const change = result.fieldChange\n if (change.value !== undefined) applyInjectedFieldValue(fieldId, change.value)\n for (const [sideFieldId, sideValue] of Object.entries(change.sideEffects ?? {})) {\n applyInjectedFieldValue(sideFieldId, sideValue)\n }\n for (const message of change.messages ?? []) flash(message.text, message.severity)\n })\n .catch((err) => {\n logger.error('staff.time_tracking entry dialog onFieldChange failed', { err })\n })\n }\n }, [\n applyInjectedFieldValue,\n entryFormValues,\n entryInjectionContext,\n locked,\n open,\n triggerEntryFormEvent,\n ])\n const roundedMinutes = roundMinutes(durationMinutes ?? 0, settings.rounding)\n const rate = applicableRate({ rateOverrideAmount }, { hourlyRate: project?.hourlyRate ?? null })\n const cost = entryAmount(\n { isBillable, roundedMinutes, rateOverrideAmount },\n { hourlyRate: project?.hourlyRate ?? null },\n )\n\n const computedBadge = (field: 'start' | 'end' | 'duration' | 'cost') => (\n <Badge variant=\"neutral\" data-testid={`entry-dialog-computed-${field}`}>\n {t('staff.time_tracking.entryDialog.computed', 'computed')}\n </Badge>\n )\n\n const midnightEndDate = interval.crossesMidnight ? shiftIsoDate(date, 1) : null\n /**\n * Unparseable duration text keeps Save disabled \u2014 the field already explains\n * itself and there is nothing to send. A *missing* required value does not:\n * pressing Save is how the form is asked what it is waiting for, and it\n * answers under the control rather than by staying greyed out and silent.\n */\n const canSubmit = !locked && !interval.durationInvalid && !saving\n\n // A field stops complaining the moment it is filled in.\n React.useEffect(() => {\n if (!taskId) return\n setFieldIssues((current) => (current.task ? { ...current, task: null } : current))\n }, [taskId])\n\n React.useEffect(() => {\n if (!durationMinutes) return\n setFieldIssues((current) => (current.duration ? { ...current, duration: null } : current))\n }, [durationMinutes])\n\n const validateRequired = React.useCallback(() => {\n const next: FieldIssues = {\n task: taskId\n ? null\n : t('staff.time_tracking.entryDialog.errors.taskRequired', 'Pick the task this time belongs to.'),\n duration: durationMinutes\n ? null\n : t('staff.time_tracking.entryDialog.errors.durationRequired', 'Enter how long the work took.'),\n }\n setFieldIssues(next)\n return !next.task && !next.duration\n }, [durationMinutes, t, taskId])\n\n /**\n * A locked entry has nothing editable in it, and a form that has not been\n * seeded yet has nothing to compare against; everywhere else the seed is the\n * baseline and any difference is work that closing would throw away.\n */\n const isDirty = React.useMemo(() => {\n if (locked || !baseline) return false\n return (\n formSnapshot(baseline) !==\n formSnapshot({\n taskId,\n description,\n date,\n startText: startClock,\n endText: endClock,\n durationMinutes,\n isBillable,\n rateOverrideEnabled,\n rateOverrideAmount,\n tagIds,\n })\n )\n }, [\n baseline,\n date,\n description,\n durationMinutes,\n endClock,\n isBillable,\n locked,\n rateOverrideAmount,\n rateOverrideEnabled,\n startClock,\n tagIds,\n taskId,\n ])\n\n /**\n * Every way out of the dialog \u2014 Escape, the \u00D7, the overlay, Cancel \u2014 lands\n * here, because a half-entered entry is lost the same way whichever one is\n * used. A clean form closes silently; only real work is worth an interruption.\n */\n const requestClose = React.useCallback(async () => {\n if (saving) return\n if (!isDirty) {\n onOpenChange(false)\n return\n }\n const confirmed = await confirm({\n title: t('staff.time_tracking.entryDialog.discard.title', 'Discard this time entry?'),\n text: t(\n 'staff.time_tracking.entryDialog.discard.text',\n 'It has not been saved yet, so closing now loses what you entered.',\n ),\n confirmText: t('staff.time_tracking.entryDialog.discard.confirm', 'Discard'),\n cancelText: t('staff.time_tracking.entryDialog.discard.cancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (confirmed) onOpenChange(false)\n }, [confirm, isDirty, onOpenChange, saving, t])\n\n const buildPayload = React.useCallback(() => {\n const startedAt = parseClock(startClock) !== null ? combineDateAndClock(date, startClock) : null\n const endedAt =\n parseClock(endClock) !== null ? combineDateAndClock(date, endClock, interval.crossesMidnight ? 1 : 0) : null\n return {\n date,\n durationMinutes: durationMinutes ?? 0,\n startedAt,\n endedAt,\n taskId,\n timeProjectId: projectId,\n description: description.trim() ? description.trim() : null,\n isBillable,\n tagIds,\n rateOverrideAmount: canSeeMoney ? rateOverrideAmount : undefined,\n }\n }, [\n canSeeMoney,\n date,\n description,\n durationMinutes,\n endClock,\n interval.crossesMidnight,\n isBillable,\n projectId,\n rateOverrideAmount,\n startClock,\n tagIds,\n taskId,\n ])\n\n const reportFailure = React.useCallback(\n (error: unknown) => {\n if (surfaceRecordConflict(error, t)) return\n if (readErrorCode(error) === 'entry_locked') {\n flash(\n t(\n 'staff.time_tracking.entryDialog.locked.notice',\n 'This entry is closed in a report, so it can no longer be edited.',\n ),\n 'error',\n )\n return\n }\n const issues = readFieldIssues(error)\n if (issues) {\n setFieldIssues(issues)\n return\n }\n logger.error('staff.time_tracking entry dialog save failed', { err: error })\n flash(\n error instanceof Error && error.message\n ? error.message\n : t('staff.time_tracking.entryDialog.errors.save', 'Could not save the time entry.'),\n 'error',\n )\n },\n [t],\n )\n\n const submit = React.useCallback(\n async (mode: SaveMode) => {\n if (!canSubmit) return\n if (!validateRequired()) return\n if (!isEdit && !staffMemberId) {\n flash(\n t(\n 'staff.time_tracking.entryDialog.errors.noStaffMember',\n 'Your account is not linked to a staff member, so time cannot be logged.',\n ),\n 'error',\n )\n return\n }\n const body = buildPayload()\n\n const beforeSave = await triggerEntryFormEvent('onBeforeSave', entryFormValues, entryInjectionContext)\n if (!beforeSave.ok) {\n if (beforeSave.fieldErrors) {\n const taskIssue = beforeSave.fieldErrors.taskId ?? beforeSave.fieldErrors.task ?? null\n const durationIssue =\n beforeSave.fieldErrors.durationMinutes ?? beforeSave.fieldErrors.duration ?? null\n if (taskIssue || durationIssue) setFieldIssues({ task: taskIssue, duration: durationIssue })\n }\n flash(\n beforeSave.message || t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n 'error',\n )\n return\n }\n const injectedRequestHeaders = beforeSave.requestHeaders ?? {}\n\n setSavingMode(mode)\n try {\n const saved = await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(\n {\n ...injectedRequestHeaders,\n ...(isEdit ? buildOptimisticLockHeader(versionRef.current) : {}),\n },\n () =>\n apiCallOrThrow<Record<string, unknown>>(\n '/api/staff/timesheets/time-entries',\n {\n method: isEdit ? 'PUT' : 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(\n isEdit ? { id: entryId, ...body } : { staffMemberId, source: 'manual', ...body },\n ),\n },\n {\n errorMessage: t(\n 'staff.time_tracking.entryDialog.errors.save',\n 'Could not save the time entry.',\n ),\n },\n ),\n ),\n context: {\n formId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: entryId ?? 'new',\n retryLastMutation,\n },\n })\n const savedId = typeof saved?.result?.id === 'string' ? saved.result.id : entryId ?? null\n try {\n await triggerEntryFormEvent('onAfterSave', entryFormValues, entryInjectionContext)\n } catch (err) {\n logger.error('staff.time_tracking entry dialog onAfterSave failed', { err })\n }\n flash(t('staff.time_tracking.entryDialog.saved', 'Time entry saved.'), 'success')\n if (mode === 'again') {\n // Note 3: the dialog stays open, the description and the times clear,\n // and the next entry starts where this one ended.\n const chain = settings.defaults.chainStartFromPreviousEnd\n const nextDate = interval.crossesMidnight && midnightEndDate ? midnightEndDate : date\n const nextStart = chain ? endClock || null : null\n seedKeyRef.current = 'create'\n setDescription('')\n setDate(nextDate)\n setIntervalState((current) => resetIntervalState(current, { start: nextStart }))\n setOverlaps([])\n // The next entry starts clean, so this is its baseline \u2014 without it the\n // dialog would still be holding the one that was just written.\n setBaseline({\n taskId,\n description: '',\n date: nextDate,\n startText: nextStart ?? '',\n endText: '',\n durationMinutes: null,\n isBillable,\n rateOverrideEnabled,\n rateOverrideAmount,\n tagIds,\n })\n onSaved?.({ id: savedId, keptOpen: true })\n // Back to the top of the loop: pick task \u2192 duration \u2192 save \u2192 next.\n focusTaskPicker()\n return\n }\n onSaved?.({ id: savedId, keptOpen: false })\n onOpenChange(false)\n } catch (error) {\n reportFailure(error)\n } finally {\n setSavingMode(null)\n }\n },\n [\n buildPayload,\n canSubmit,\n date,\n endClock,\n entryFormValues,\n entryId,\n entryInjectionContext,\n interval.crossesMidnight,\n isBillable,\n isEdit,\n midnightEndDate,\n onOpenChange,\n onSaved,\n rateOverrideAmount,\n rateOverrideEnabled,\n reportFailure,\n retryLastMutation,\n runMutation,\n settings.defaults.chainStartFromPreviousEnd,\n staffMemberId,\n t,\n tagIds,\n taskId,\n triggerEntryFormEvent,\n validateRequired,\n ],\n )\n\n const handleShowOverlap = React.useCallback(\n (overlap: OverlapEntry) => {\n if (onShowEntry) {\n onShowEntry(overlap.id)\n return\n }\n window.open(`${ENTRIES_LIST_PATH}?ids=${encodeURIComponent(overlap.id)}`, '_blank', 'noopener')\n },\n [onShowEntry],\n )\n\n /**\n * The picker needs the project and its customer per row so it can group by\n * them; both already arrive with the projects query the dialog runs for the\n * rate, so this is a join in memory rather than another request.\n */\n const pickerItems = React.useMemo<TaskPickerItem[]>(\n () =>\n tasks.map((task) => {\n const project = task.timeProjectId ? projectById.get(task.timeProjectId) : undefined\n return {\n id: task.id,\n reference: task.reference,\n title: task.title,\n projectId: task.timeProjectId,\n projectName: project?.name ?? null,\n customerName: project?.customerName ?? null,\n statusId: task.taskStatusId ?? null,\n assigneeInitials: null,\n assigneeName: null,\n loggedMinutes: task.loggedMinutes ?? null,\n }\n }),\n [projectById, tasks],\n )\n\n const queryClient = useQueryClient()\n /**\n * Creates the tag, then hands it back so the caller can assign it.\n *\n * The colour is decided here rather than left to the server, which stores\n * whatever it is sent and defaults to null. The picker already tints its dot\n * with the name-derived hue, so sending that same hue is what makes the chip\n * the entry ends up wearing the colour the row promised \u2014 instead of the grey\n * one an unset column renders as.\n */\n const createTag = React.useCallback(\n async (label: string): Promise<TagOption | null> => {\n const color = autoColorFromName(label).key\n try {\n const created = await apiCallOrThrow<Record<string, unknown>>(\n '/api/staff/timesheets/tags',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ label, slug: slugifyProjectName(label).toLowerCase(), color }),\n },\n { errorMessage: t('staff.time_tracking.entryDialog.errors.createTag', 'Could not create the tag.') },\n )\n const id = typeof created?.result?.id === 'string' ? created.result.id : null\n if (!id) return null\n const option: TagOption = { id, label, color }\n // Seeded before the refetch so the chip is never briefly the grey one the\n // fix is about; the invalidation right after replaces it with the row the\n // server actually stored.\n queryClient.setQueryData<TagOption[]>(\n [...DIALOG_QUERY_ROOT, 'tags', `scope:${scopeVersion}`],\n (current) => [...(current ?? []), option],\n )\n await queryClient.invalidateQueries({ queryKey: [...DIALOG_QUERY_ROOT, 'tags'] })\n return option\n } catch (error) {\n logger.warn('staff.time_tracking.entryDialog tag create failed', { err: error })\n flash(t('staff.time_tracking.entryDialog.errors.createTag', 'Could not create the tag.'), 'error')\n return null\n }\n },\n [queryClient, scopeVersion, t],\n )\n\n const overlap = overlaps[0] ?? null\n const availableTags = React.useMemo(\n () => tagOptions.filter((tag) => !tagIds.includes(tag.id)),\n [tagIds, tagOptions],\n )\n const assignedTags = React.useMemo(() => {\n const byId = new Map(tagOptions.map((tag) => [tag.id, tag]))\n return tagIds.map((id) => byId.get(id) ?? { id, label: id, color: null })\n }, [tagIds, tagOptions])\n\n const rateLabel = rate !== null ? formatCurrency(rate, currencyCode) ?? String(rate) : null\n const projectRateLabel =\n project?.hourlyRate !== null && project?.hourlyRate !== undefined\n ? formatCurrency(project.hourlyRate, currencyCode) ?? String(project.hourlyRate)\n : null\n\n const body = (() => {\n if (isEdit && entryQuery.isLoading) {\n return <LoadingMessage label={t('staff.time_tracking.entryDialog.loading', 'Loading the time entry\u2026')} />\n }\n if (isEdit && entryQuery.isError) {\n return <ErrorMessage label={t('staff.time_tracking.entryDialog.errors.load', 'Could not load the time entry.')} />\n }\n if (isEdit && !entry) {\n return <ErrorMessage label={t('staff.time_tracking.entryDialog.errors.notFound', 'Time entry not found or not accessible.')} />\n }\n\n return (\n <div className=\"flex flex-col gap-4\">\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'before-fields')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n\n {locked ? (\n <Alert status=\"warning\" data-testid=\"entry-dialog-locked\">\n <AlertTitle className=\"flex items-center gap-2\">\n <Lock className=\"size-3.5\" aria-hidden=\"true\" />\n {t('staff.time_tracking.entryDialog.locked.badge', 'Locked')}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'staff.time_tracking.entryDialog.locked.notice',\n 'This entry is closed in a report, so it can no longer be edited.',\n )}\n </AlertDescription>\n {lockedReportId ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"mt-2\"\n data-testid=\"entry-dialog-locked-report\"\n onClick={() =>\n window.open(`${REPORTS_PATH}?id=${encodeURIComponent(lockedReportId)}`, '_blank', 'noopener')\n }\n >\n {t('staff.time_tracking.entryDialog.locked.showReport', 'Show the report')}\n </Button>\n ) : null}\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-task\">\n {t('staff.time_tracking.entryDialog.task', 'Task')}\n <span aria-hidden=\"true\"> *</span>\n </Label>\n <div\n data-testid=\"entry-dialog-task\"\n ref={taskTriggerRef}\n aria-invalid={fieldIssues.task ? true : undefined}\n aria-describedby={fieldIssues.task ? 'entry-dialog-task-message' : undefined}\n >\n <TaskPicker\n value={taskId}\n onChange={(next) => setTaskId(next || null)}\n items={pickerItems}\n recentTaskIds={recentQuery.data ?? []}\n statuses={statusesQuery.data ?? {}}\n onQueryChange={setTaskSearch}\n searching={taskSearchQuery.isFetching}\n loading={tasksQuery.isLoading}\n disabled={locked}\n />\n </div>\n {fieldIssues.task ? (\n <p\n id=\"entry-dialog-task-message\"\n className=\"text-xs text-status-error-text\"\n role=\"alert\"\n data-testid=\"entry-dialog-task-error\"\n >\n {fieldIssues.task}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.taskHint',\n 'The project and the customer follow from the task \u2014 you do not pick them separately.',\n )}\n </p>\n )}\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-description\">\n {t('staff.time_tracking.entryDialog.description', 'Description')}\n </Label>\n <Input\n id=\"entry-dialog-description\"\n value={description}\n disabled={locked}\n onChange={(event) => setDescription(event.target.value)}\n data-testid=\"entry-dialog-description\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-date\">{t('staff.time_tracking.entryDialog.date', 'Date')}</Label>\n <Input\n id=\"entry-dialog-date\"\n type=\"date\"\n value={date}\n disabled={locked}\n onChange={(event) => setDate(event.target.value)}\n data-testid=\"entry-dialog-date\"\n />\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-duration\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.duration', 'Duration')}\n {interval.computed === 'duration' ? computedBadge('duration') : null}\n </Label>\n <DurationInput\n key={`entry-dialog-duration-${interval.durationEpoch}`}\n id=\"entry-dialog-duration\"\n value={durationMinutes}\n disabled={locked}\n error={fieldIssues.duration ?? undefined}\n ariaLabel={t('staff.time_tracking.entryDialog.duration', 'Duration')}\n onChange={(minutes, state) =>\n setIntervalState((current) =>\n reduceIntervalState(current, {\n field: 'duration',\n minutes,\n invalid: state.status === 'invalid',\n }),\n )\n }\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-start\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.start', 'Start')}\n {interval.computed === 'start' ? computedBadge('start') : null}\n </Label>\n <Input\n id=\"entry-dialog-start\"\n value={startClock}\n disabled={locked}\n inputClassName=\"font-mono tabular-nums\"\n placeholder=\"15:10\"\n onChange={(event) =>\n setIntervalState((current) =>\n reduceIntervalState(current, { field: 'start', value: event.target.value }),\n )\n }\n data-testid=\"entry-dialog-start\"\n />\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-end\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.end', 'End')}\n {interval.computed === 'end' ? computedBadge('end') : null}\n </Label>\n <Input\n id=\"entry-dialog-end\"\n value={endClock}\n disabled={locked}\n inputClassName=\"font-mono tabular-nums\"\n placeholder=\"16:25\"\n onChange={(event) =>\n setIntervalState((current) =>\n reduceIntervalState(current, { field: 'end', value: event.target.value }),\n )\n }\n data-testid=\"entry-dialog-end\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.endHint',\n 'Change this field and the duration recalculates itself.',\n )}\n </p>\n </div>\n </div>\n\n {midnightEndDate ? (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"entry-dialog-midnight\">\n {t('staff.time_tracking.entryDialog.midnight', 'This entry ends the next day, {date}.', {\n date: midnightEndDate,\n })}\n </p>\n ) : null}\n\n {overlap ? (\n <Alert status=\"warning\" data-testid=\"entry-dialog-overlap\">\n <AlertTitle>\n {t('staff.time_tracking.entryDialog.overlap.title', 'This time overlaps another entry')}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'staff.time_tracking.entryDialog.overlap.body',\n '{start} \u2013 {end} \u00B7 \u201E{task}\u201D \u00B7 {project} ({duration}). Overlapping is allowed \u2014 if it is intentional, just save.',\n {\n start: overlap.startText,\n end: overlap.endText,\n task:\n overlap.taskTitle ??\n overlap.description ??\n t('staff.time_tracking.entryDialog.overlap.noTask', 'no task'),\n project:\n overlap.projectName ??\n t('staff.time_tracking.entryDialog.overlap.noProject', 'no project'),\n duration: formatDuration(overlap.durationMinutes, 'clock'),\n },\n )}\n </AlertDescription>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleShowOverlap(overlap)}\n data-testid=\"entry-dialog-overlap-show\"\n >\n {t('staff.time_tracking.entryDialog.overlap.show', 'Show that entry')}\n </Button>\n {overlap.endText ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={locked}\n onClick={() =>\n setIntervalState((current) =>\n reduceIntervalState(current, { field: 'snapStart', value: overlap.endText }),\n )\n }\n data-testid=\"entry-dialog-overlap-snap\"\n >\n {t('staff.time_tracking.entryDialog.overlap.snap', 'Snap start to {time}', {\n time: overlap.endText,\n })}\n </Button>\n ) : null}\n </div>\n </Alert>\n ) : null}\n\n <Separator />\n\n <div className=\"flex items-center gap-3\">\n <Switch\n checked={isBillable}\n disabled={locked}\n onCheckedChange={(next) => setIsBillable(next)}\n aria-label={t('staff.time_tracking.entryDialog.billable', 'Billable')}\n data-testid=\"entry-dialog-billable\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"text-sm font-medium text-foreground\">\n {t('staff.time_tracking.entryDialog.billable', 'Billable')}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.entryDialog.billableHint', 'On by default for this project.')}\n </span>\n </div>\n {canSeeMoney && rateLabel ? (\n <Badge variant=\"success\" data-testid=\"entry-dialog-rate-badge\">\n {t('staff.time_tracking.entryDialog.ratePerHour', '{amount}/h', { amount: rateLabel })}\n </Badge>\n ) : null}\n </div>\n\n {canSeeMoney ? (\n <>\n <div className=\"flex items-center gap-3\">\n <Switch\n checked={rateOverrideEnabled}\n disabled={locked}\n onCheckedChange={(next) => setRateOverrideEnabled(next)}\n aria-label={t('staff.time_tracking.entryDialog.rateOverride', 'Custom rate for this entry')}\n data-testid=\"entry-dialog-rate-override\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"text-sm font-medium text-foreground\">\n {t('staff.time_tracking.entryDialog.rateOverride', 'Custom rate for this entry')}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.rateOverrideHint',\n 'Overrides the project rate here only.',\n )}\n </span>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {rateOverrideEnabled ? (\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-rate\">\n {t('staff.time_tracking.entryDialog.rate', 'Rate')}\n </Label>\n <Input\n id=\"entry-dialog-rate\"\n value={rateText}\n disabled={locked}\n inputMode=\"decimal\"\n onChange={(event) => setRateText(event.target.value)}\n data-testid=\"entry-dialog-rate\"\n />\n {projectRateLabel ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.rateHint',\n 'Defaults to {amount} (the project rate).',\n { amount: projectRateLabel },\n )}\n </p>\n ) : null}\n </div>\n ) : null}\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-cost\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.cost', 'Cost')}\n {computedBadge('cost')}\n </Label>\n <Input\n id=\"entry-dialog-cost\"\n readOnly\n disabled\n value={cost === null ? '' : formatCurrency(cost, currencyCode) ?? String(cost)}\n inputClassName=\"font-mono tabular-nums\"\n data-testid=\"entry-dialog-cost\"\n />\n {rateLabel && durationMinutes ? (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"entry-dialog-cost-hint\">\n {t(\n 'staff.time_tracking.entryDialog.costHint',\n '{raw} rounded to {rounded} \u00D7 {rate}',\n {\n raw: formatDuration(durationMinutes, 'clock'),\n rounded: formatDuration(roundedMinutes, 'clock'),\n rate: rateLabel,\n },\n )}\n </p>\n ) : null}\n </div>\n </div>\n </>\n ) : (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"entry-dialog-money-hidden\">\n {t(\n 'staff.time_tracking.entryDialog.moneyHidden',\n 'Rates and cost are hidden \u2014 your permissions do not cover money on time entries.',\n )}\n </p>\n )}\n\n <div className=\"flex flex-col gap-1.5\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {t('staff.time_tracking.entryDialog.tags', 'Tags')}\n </span>\n <div className=\"flex flex-wrap items-center gap-2\">\n {assignedTags.map((tag) => (\n <Badge\n key={tag.id}\n variant=\"neutral\"\n className=\"gap-1\"\n style={tagChipStyle(tag)}\n >\n {tag.label}\n {locked ? null : (\n <button\n type=\"button\"\n aria-label={t('staff.time_tracking.entryDialog.removeTag', 'Remove tag {label}', {\n label: tag.label,\n })}\n onClick={() => setTagIds((current) => current.filter((id) => id !== tag.id))}\n >\n <X className=\"size-3\" aria-hidden=\"true\" />\n </button>\n )}\n </Badge>\n ))}\n {!locked ? (\n <TagPicker\n available={availableTags}\n onAssign={(id) => setTagIds((current) => (current.includes(id) ? current : [...current, id]))}\n onCreate={createTag}\n labels={{\n add: t('staff.time_tracking.entryDialog.addTag', 'Add tag'),\n create: (name) =>\n t('staff.time_tracking.entryDialog.createTag', 'Create \"{name}\"', { name }),\n empty: t('staff.time_tracking.entryDialog.noTags', 'No matching tag'),\n }}\n />\n ) : null}\n </div>\n </div>\n\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'fields')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n\n <Alert status=\"information\" data-testid=\"entry-dialog-creator\">\n <AlertDescription>\n {selfQuery.data?.displayName\n ? t(\n 'staff.time_tracking.entryDialog.creator',\n 'Entry author: {name} \u2014 assigned automatically and not editable.',\n { name: selfQuery.data.displayName },\n )\n : t(\n 'staff.time_tracking.entryDialog.creatorUnknown',\n 'The entry author is assigned automatically and is not editable.',\n )}\n </AlertDescription>\n </Alert>\n\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'after-fields')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n </div>\n )\n })()\n\n return (\n <Dialog\n open={open}\n // Escape, the \u00D7, and a click on the overlay all arrive here, which is why\n // the unsaved-work guard sits on this one callback rather than on each of\n // them. Radix keeps the dialog mounted until `open` actually flips, so\n // declining the prompt simply leaves the form where it was.\n onOpenChange={(next) => {\n if (next) {\n onOpenChange(true)\n return\n }\n void requestClose()\n }}\n >\n <DialogContent\n size=\"lg\"\n data-testid=\"entry-dialog\"\n onOpenAutoFocus={(event) => {\n // A locked entry has nothing to type into, so the default focus (the\n // close button) is the right one there.\n if (locked || !taskTriggerRef.current) return\n event.preventDefault()\n focusTaskPicker()\n }}\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n // Shift keeps the dialog open for the next entry, so a run of entries\n // never needs the pointer.\n void submit(event.shiftKey ? 'again' : 'close')\n }\n // Escape is deliberately not handled here. Radix's `DismissableLayer`\n // already dismisses only the topmost layer, so a picker popover open\n // inside the form consumes the key on its own; a handler here closed\n // the whole dialog regardless of what was open on top of it.\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {isEdit\n ? t('staff.time_tracking.entryDialog.titleEdit', 'Time entry')\n : t('staff.time_tracking.entryDialog.titleCreate', 'New time entry')}\n </DialogTitle>\n {headerHint ? <span className=\"text-xs text-muted-foreground\">{headerHint}</span> : null}\n </DialogHeader>\n\n {body}\n\n <DialogFooter>\n {/*\n * The labels are flex items, so without `whitespace-nowrap` \"save and\n * add another\" shrinks into a four-line column rather than staying on\n * the line. Hidden below `sm` as well: on a narrow dialog the hint\n * competes with the buttons for room it does not deserve, and a\n * keyboard hint is useless on a touch device anyway.\n */}\n <span className=\"mr-auto hidden items-center gap-1 whitespace-nowrap text-xs text-muted-foreground sm:flex\">\n <Kbd>\u2318</Kbd>\n <Kbd>\u21B5</Kbd>\n {t('staff.time_tracking.entryDialog.shortcutSave', 'save')}\n {locked ? null : (\n <>\n <span aria-hidden=\"true\">\u00B7</span>\n <Kbd>\u2318</Kbd>\n <Kbd>\u21E7</Kbd>\n <Kbd>\u21B5</Kbd>\n {t('staff.time_tracking.entryDialog.shortcutSaveAgain', 'save and add another')}\n </>\n )}\n <span aria-hidden=\"true\">\u00B7</span>\n <Kbd>esc</Kbd>\n {t('staff.time_tracking.entryDialog.shortcutCancel', 'cancel')}\n </span>\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'footer')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n <Button type=\"button\" variant=\"ghost\" onClick={() => { void requestClose() }}>\n {locked\n ? t('staff.time_tracking.entryDialog.close', 'Close')\n : t('staff.time_tracking.entryDialog.cancel', 'Cancel')}\n </Button>\n {locked ? null : (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={!canSubmit}\n onClick={() => void submit('again')}\n data-testid=\"entry-dialog-save-again\"\n >\n {savingMode === 'again' ? <Spinner className=\"size-4\" /> : null}\n {t('staff.time_tracking.entryDialog.saveAndNew', 'Save and add another')}\n </Button>\n <Button\n type=\"button\"\n disabled={!canSubmit}\n onClick={() => void submit('close')}\n data-testid=\"entry-dialog-save\"\n >\n {savingMode === 'close' ? <Spinner className=\"size-4\" /> : null}\n {t('staff.time_tracking.entryDialog.save', 'Save')}\n </Button>\n </>\n )}\n </DialogFooter>\n {ConfirmDialogElement}\n </DialogContent>\n </Dialog>\n )\n}\n\nconst timeEntryDialogPropsSchema: z.ZodType<TimeEntryDialogProps> = z.object({\n open: z.boolean(),\n onOpenChange: callbackProp<(open: boolean) => void>(),\n entryId: z.string().nullable().optional(),\n defaults: opaqueProp<TimeEntryDialogDefaults>().optional(),\n headerHint: z.string().nullable().optional(),\n onSaved: optionalCallbackProp<(result: { id: string | null; keptOpen: boolean }) => void>(),\n onShowEntry: optionalCallbackProp<(entryId: string) => void>(),\n})\n\nregisterComponent<TimeEntryDialogProps>({\n id: extensionPoints.hosts.timeEntryDialogComponent.componentId,\n component: DefaultTimeEntryDialog,\n metadata: {\n module: 'staff',\n description: 'Create/edit dialog for a single time entry.',\n propsSchema: timeEntryDialogPropsSchema,\n },\n})\n\nexport function TimeEntryDialog(props: TimeEntryDialogProps) {\n const Resolved = useRegisteredComponent<TimeEntryDialogProps>(\n extensionPoints.hosts.timeEntryDialogComponent.componentId,\n DefaultTimeEntryDialog,\n )\n return <Resolved {...props} />\n}\n\nexport default TimeEntryDialog\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport { Lock, X } from 'lucide-react'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Separator } from '@open-mercato/ui/primitives/separator'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall, apiCallOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { TagPicker, tagChipStyle } from './TagPicker'\nimport { TaskPicker, type TaskPickerItem, type TaskPickerStatus } from './TaskPicker'\nimport { slugifyProjectName } from '../time-tracking/projectCode'\nimport { autoColorFromName } from '../timesheets-ui/colors'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { normalizeCrudServerError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { InjectionSpot, useInjectionSpotEvents } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { registerComponent } from '@open-mercato/shared/modules/widgets/component-registry'\nimport { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'\nimport {\n callbackProp,\n opaqueProp,\n optionalCallbackProp,\n} from '../time-tracking/componentContracts'\nimport { extensionSpotChildId } from '@open-mercato/shared/modules/widgets/extension-points'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { DurationInput } from './DurationInput'\nimport { reportDetailHref } from './reportLinks'\nimport { formatDuration } from '../time-tracking/duration'\nimport { parseClock } from '../time-tracking/interval'\nimport { applicableRate, entryAmount } from '../time-tracking/cost'\nimport { roundMinutes, DEFAULT_ROUNDING_SETTINGS, type RoundingSettings } from '../time-tracking/rounding'\nimport {\n combineDateAndClock,\n createIntervalState,\n reduceIntervalState,\n resetIntervalState,\n readRowItems,\n readRowString,\n shiftIsoDate,\n todayIsoDate,\n toOverlapEntry,\n toProjectOption,\n toTaskOption,\n toTimeEntryRecord,\n type OverlapEntry,\n type ProjectOption,\n type TagOption,\n type TaskOption,\n type TimeEntryRecord,\n type TimeIntervalState,\n} from './timeEntryDialogState'\n\nconst logger = createLogger('staff').child({ component: 'TimeEntryDialog' })\n\nexport const TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID = 'staff.time_tracking.entryDialog'\nconst TIME_ENTRY_ENTITY_ID = 'staff:staff_time_entry'\n\ntype TimeEntryFormInjectionContext = {\n formId: string\n entityId: string\n recordId: string | null\n resourceKind: string\n resourceId: string\n operation: 'create' | 'update'\n locked: boolean\n retryLastMutation: () => Promise<boolean>\n}\n\nfunction fieldValueSnapshot(value: unknown): string {\n return JSON.stringify(value ?? null)\n}\nexport const RATES_FEATURE = 'staff.timesheets.rates.view'\nexport const ENTRIES_LIST_PATH = '/backend/staff/time-tracking/entries'\nexport { REPORTS_PATH } from './reportLinks'\n\nconst DIALOG_QUERY_ROOT = ['staff', 'time-tracking', 'entry-dialog'] as const\nconst DIRECTORY_PAGE_SIZE = 100\nconst TASK_SEARCH_PAGE_SIZE = 50\nconst TASK_SEARCH_DEBOUNCE_MS = 250\n\nexport type TimeEntryDialogDefaults = {\n date?: string\n taskId?: string | null\n timeProjectId?: string | null\n durationMinutes?: number | null\n startClock?: string | null\n endClock?: string | null\n description?: string | null\n isBillable?: boolean | null\n tagIds?: string[]\n}\n\nexport type TimeEntryDialogProps = {\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Present \u2192 edit that entry; absent \u2192 a brand new one. */\n entryId?: string | null\n /** Seed values for a new entry: a stopped timer, a grid cell, a board card. */\n defaults?: TimeEntryDialogDefaults\n /** Sub-title line, e.g. \"Created from a stopped timer \u00B7 01:14:02\". */\n headerHint?: string | null\n /** Fired after every successful write, including \"save and add another\". */\n onSaved?: (result: { id: string | null; keptOpen: boolean }) => void\n /** \"Show that entry\" on the overlap warning; falls back to the entries list. */\n onShowEntry?: (entryId: string) => void\n}\n\ntype SettingsPayload = {\n rounding: RoundingSettings\n defaults: { billable: boolean; chainStartFromPreviousEnd: boolean }\n warnings: { overlap: boolean }\n}\n\nconst FALLBACK_SETTINGS: SettingsPayload = {\n rounding: { ...DEFAULT_ROUNDING_SETTINGS },\n defaults: { billable: true, chainStartFromPreviousEnd: true },\n warnings: { overlap: true },\n}\n\nfunction readSettings(payload: unknown): SettingsPayload {\n const source = (payload ?? {}) as Record<string, unknown>\n const rounding = (source.rounding ?? {}) as Record<string, unknown>\n const defaults = (source.defaults ?? {}) as Record<string, unknown>\n const warnings = (source.warnings ?? {}) as Record<string, unknown>\n return {\n rounding: {\n unitMinutes:\n rounding.unitMinutes === 5 || rounding.unitMinutes === 10 || rounding.unitMinutes === 15\n ? rounding.unitMinutes\n : 0,\n direction: rounding.direction === 'nearest' ? 'nearest' : 'up',\n },\n defaults: {\n billable: typeof defaults.billable === 'boolean' ? defaults.billable : true,\n chainStartFromPreviousEnd:\n typeof defaults.chainStartFromPreviousEnd === 'boolean' ? defaults.chainStartFromPreviousEnd : true,\n },\n warnings: { overlap: typeof warnings.overlap === 'boolean' ? warnings.overlap : true },\n }\n}\n\nfunction parseRateText(value: string): number | null {\n const trimmed = value.trim().replace(',', '.')\n if (!trimmed) return null\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : null\n}\n\n/**\n * Everything a person can type into the form, in the one shape both the seed and\n * the live state can be expressed in. Comparing two of these is how the dialog\n * knows whether closing it would throw work away.\n */\ntype FormValues = {\n taskId: string | null\n description: string\n date: string\n startText: string\n endText: string\n durationMinutes: number | null\n isBillable: boolean\n rateOverrideEnabled: boolean\n rateOverrideAmount: number | null\n tagIds: string[]\n}\n\ntype SaveMode = 'close' | 'again'\n\n/** The two controls a failed save can point at, empty when nothing is wrong. */\ntype FieldIssues = {\n task: string | null\n duration: string | null\n}\n\nconst NO_FIELD_ISSUES: FieldIssues = { task: null, duration: null }\n\n/**\n * A server complaint that names a field belongs under that field, not in a\n * toast. Only the two the form can point at are claimed; anything else stays\n * global and is flashed.\n */\nfunction readFieldIssues(error: unknown): FieldIssues | null {\n const { fieldErrors } = normalizeCrudServerError(error)\n if (!fieldErrors) return null\n const task = fieldErrors.taskId ?? fieldErrors.task ?? null\n const duration = fieldErrors.durationMinutes ?? fieldErrors.duration ?? null\n if (!task && !duration) return null\n return { task, duration }\n}\n\nfunction formSnapshot(values: FormValues): string {\n return JSON.stringify([\n values.taskId,\n values.description.trim(),\n values.date,\n values.startText,\n values.endText,\n values.durationMinutes,\n values.isBillable,\n values.rateOverrideEnabled,\n values.rateOverrideAmount,\n [...values.tagIds].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)),\n ])\n}\n\n/**\n * Two requests, merged.\n *\n * A single term cannot be routed to one field by shape \u2014 \"Booking\" is a title\n * word and \"AWR\" is a code, and both are one alphanumeric token \u2014 and the query\n * engine has no OR across fields, so the picker asks both questions and unions\n * the answers. This is what makes task 400 of 900 findable: the directory the\n * picker opens on is only ever its first page.\n */\nasync function fetchTaskOptions(term: string): Promise<TaskOption[]> {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(TASK_SEARCH_PAGE_SIZE),\n sortField: 'title',\n sortDir: 'asc',\n })\n const responses = await Promise.all(\n [\n `/api/staff/timesheets/tasks?${params.toString()}&q=${encodeURIComponent(term)}`,\n `/api/staff/timesheets/tasks?${params.toString()}&reference=${encodeURIComponent(term)}`,\n ].map((url) => apiCall<Record<string, unknown>>(url).catch(() => null)),\n )\n const merged = new Map<string, TaskOption>()\n for (const response of responses) {\n if (!response?.ok) continue\n for (const row of readRowItems(response.result)) {\n const option = toTaskOption(row)\n if (option && !merged.has(option.id)) merged.set(option.id, option)\n }\n }\n return [...merged.values()]\n}\n\n/** Resolves the entry's own task when it sorts outside the directory's first page. */\nasync function fetchTaskById(id: string): Promise<TaskOption | null> {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/tasks?ids=${encodeURIComponent(id)}&pageSize=1`,\n )\n if (!call.ok) return null\n const row = readRowItems(call.result)[0]\n return row ? toTaskOption(row) : null\n}\n\nfunction readErrorCode(error: unknown): string | null {\n const body = (error as { body?: { code?: unknown; error?: unknown } } | null)?.body\n if (body && typeof body.code === 'string') return body.code\n if (body && typeof body.error === 'string') return body.error\n return null\n}\n\n/**\n * The full time entry form of screen 8, plus the two edge states of screen 9.\n * Every surface of the module funnels through it \u2014 the entries list, the\n * timesheet, the board and the task drawer \u2014 so it takes an entry id (edit) or a\n * set of defaults (create) and owns nothing else about its host.\n *\n * Four decisions carry the design intent:\n *\n * 1. **The 2-of-3 arithmetic lives in one reducer, not in the render path.**\n * `reduceIntervalState` calls `deriveInterval` with exactly the two anchor\n * fields and stores which one came back computed, so the `wyliczone` badge is\n * read off the state rather than recomputed. `start` is the preferred anchor,\n * which is what makes editing the end restate the duration (note 1).\n * 2. **The duration field is never rewritten while it is being typed.**\n * `DurationInput` keeps unparseable text on its own; the dialog only remounts\n * it \u2014 via `durationEpoch` \u2014 when the *derived* duration changed, i.e. when\n * the user was editing some other field.\n * 3. **Overlap is advisory.** The probe is a plain `apiCall`; a non-200, a\n * throw or an empty list all mean \"no warning\", and Save never observes it.\n * Only an unparseable duration disables Save (screen 9 notes 2 and 4).\n * 4. **A locked entry opens read-only.** Every write path answers\n * `409 entry_locked`, so the form shows the lock and a way to the report that\n * closed it instead of letting someone compose a request that cannot succeed.\n */\nfunction DefaultTimeEntryDialog({\n open,\n onOpenChange,\n entryId,\n defaults,\n headerHint,\n onSaved,\n onShowEntry,\n}: TimeEntryDialogProps): React.ReactElement {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { payload } = useBackendChrome()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const canSeeMoney = hasFeature(payload?.grantedFeatures, RATES_FEATURE)\n const isEdit = typeof entryId === 'string' && entryId.length > 0\n\n const [taskId, setTaskId] = React.useState<string | null>(null)\n const [description, setDescription] = React.useState('')\n const [date, setDate] = React.useState(() => todayIsoDate())\n const [interval, setIntervalState] = React.useState<TimeIntervalState>(() => createIntervalState({}))\n const [isBillable, setIsBillable] = React.useState(true)\n const [rateOverrideEnabled, setRateOverrideEnabled] = React.useState(false)\n const [rateText, setRateText] = React.useState('')\n const [tagIds, setTagIds] = React.useState<string[]>([])\n const [overlaps, setOverlaps] = React.useState<OverlapEntry[]>([])\n /** Which button is in flight, so the spinner lands on the one that was pressed. */\n const [savingMode, setSavingMode] = React.useState<SaveMode | null>(null)\n /**\n * What is wrong with a single control, said under that control. A disabled\n * Save cannot explain which of the two required fields it is waiting for, and\n * a server complaint about one field is not a global failure either.\n */\n const [fieldIssues, setFieldIssues] = React.useState<FieldIssues>(NO_FIELD_ISSUES)\n const saving = savingMode !== null\n const [taskSearch, setTaskSearch] = React.useState('')\n const [debouncedTaskSearch, setDebouncedTaskSearch] = React.useState('')\n /**\n * The task the form is on, kept out of the two lists that come and go.\n * The directory holds the first page only and the search results are replaced\n * on every term, so without this the chosen task would vanish from under the\n * selection the moment the search box was cleared.\n */\n const [pinnedTask, setPinnedTask] = React.useState<TaskOption | null>(null)\n /** What the form was seeded with; `null` until it has been seeded at all. */\n const [baseline, setBaseline] = React.useState<FormValues | null>(null)\n /**\n * T7.4 \u2014 the entry loop must be completable without a mouse, so the first field\n * of the loop is also the focus target twice over: when the dialog opens (Radix\n * would otherwise land on the \u00D7 close button, which renders before the body) and\n * after \"save and add another\", where focus would otherwise be stranded on the\n * button and the next entry would start with a handful of Shift+Tabs.\n */\n const taskTriggerRef = React.useRef<HTMLDivElement | null>(null)\n /**\n * The picker is a search box inside a container, not a single focusable\n * control, so the keyboard loop focuses the input within it. Without this,\n * \"save and add another\" left focus stranded and the loop stopped being a loop.\n */\n const focusTaskPicker = React.useCallback(() => {\n const host = taskTriggerRef.current\n if (!host) return false\n const focusable = host.querySelector('input, select, button') as HTMLElement | null\n if (!focusable) return false\n focusable.focus()\n return true\n }, [])\n\n const seedKeyRef = React.useRef<string | null>(null)\n const billableDefaultRef = React.useRef<string | null>(null)\n const versionRef = React.useRef<string | null>(null)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,\n blockedMessage: t('staff.time_tracking.entryDialog.errors.save', 'Could not save the time entry.'),\n })\n\n const settingsQuery = useQuery<SettingsPayload>({\n queryKey: [...DIALOG_QUERY_ROOT, 'settings', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>('/api/staff/timesheets/settings')\n return call.ok ? readSettings(call.result) : FALLBACK_SETTINGS\n },\n })\n const settings = settingsQuery.data ?? FALLBACK_SETTINGS\n\n const selfQuery = useQuery<{ id: string; displayName: string } | null>({\n queryKey: [...DIALOG_QUERY_ROOT, 'self', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<{ member?: { id?: string; displayName?: string } | null }>(\n '/api/staff/team-members/self',\n )\n const member = call.ok ? call.result?.member ?? null : null\n if (!member?.id) return null\n return { id: member.id, displayName: member.displayName ?? '' }\n },\n })\n\n /**\n * The first page of tasks, which is what the picker opens on. It is a head\n * start, not the corpus \u2014 anything past it is reached through the search below.\n */\n const tasksQuery = useQuery<TaskOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'tasks', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 60_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/tasks?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=title&sortDir=asc`,\n )\n if (!call.ok) return []\n return readRowItems(call.result)\n .map(toTaskOption)\n .filter((task): task is TaskOption => task !== null)\n },\n })\n\n // Debounced so a typed word is one pair of requests rather than one per letter.\n React.useEffect(() => {\n if (!open) {\n setDebouncedTaskSearch('')\n return\n }\n const handle = setTimeout(() => setDebouncedTaskSearch(taskSearch.trim()), TASK_SEARCH_DEBOUNCE_MS)\n return () => clearTimeout(handle)\n }, [open, taskSearch])\n\n const taskSearchQuery = useQuery<TaskOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'task-search', `scope:${scopeVersion}`, debouncedTaskSearch],\n enabled: open && debouncedTaskSearch.length > 0,\n staleTime: 60_000,\n queryFn: () => fetchTaskOptions(debouncedTaskSearch),\n })\n\n const projectsQuery = useQuery<ProjectOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'projects', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 60_000,\n queryFn: async () => {\n // Best effort: a consultant without `projects.view` still gets the task\n // picker, only without the customer and project suffix on each option.\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/time-projects?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=name&sortDir=asc`,\n )\n if (!call.ok) return []\n return readRowItems(call.result)\n .map(toProjectOption)\n .filter((project): project is ProjectOption => project !== null)\n },\n })\n\n const staffMemberId = selfQuery.data?.id ?? null\n\n const statusesQuery = useQuery<Record<string, TaskPickerStatus>>({\n queryKey: [...DIALOG_QUERY_ROOT, 'task-statuses', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/task-statuses?page=1&pageSize=${DIRECTORY_PAGE_SIZE}`,\n )\n if (!call.ok) return {}\n const map: Record<string, TaskPickerStatus> = {}\n for (const row of readRowItems(call.result)) {\n const id = readRowString(row, 'id')\n if (!id) continue\n map[id] = {\n id,\n name: readRowString(row, 'name') ?? '',\n color: readRowString(row, 'color'),\n isDone: row.is_done === true || row.isDone === true,\n }\n }\n return map\n },\n })\n\n /**\n * The tasks this person logged to recently. Repetition is the norm in time\n * logging, so opening the picker on these turns the common case into one\n * keystroke. No new endpoint \u2014 it is the entries list asked a narrower question.\n */\n const recentQuery = useQuery<string[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'recent-tasks', `scope:${scopeVersion}`, staffMemberId ?? 'none'],\n enabled: open && !!staffMemberId,\n staleTime: 60_000,\n queryFn: async () => {\n const since = shiftIsoDate(todayIsoDate(), -21)\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/time-entries?page=1&pageSize=100&sortField=date&sortDir=desc`\n + `&staffMemberId=${encodeURIComponent(staffMemberId as string)}&from=${since}`,\n )\n if (!call.ok) return []\n const seen: string[] = []\n for (const row of readRowItems(call.result)) {\n const taskId = readRowString(row, 'task_id', 'taskId')\n if (taskId && !seen.includes(taskId)) seen.push(taskId)\n if (seen.length >= 6) break\n }\n return seen\n },\n })\n\n const tagsQuery = useQuery<TagOption[]>({\n queryKey: [...DIALOG_QUERY_ROOT, 'tags', `scope:${scopeVersion}`],\n enabled: open,\n staleTime: 300_000,\n queryFn: async () => {\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/tags?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&sortField=label&sortDir=asc`,\n )\n if (!call.ok) return []\n return readRowItems(call.result)\n .map((row) => {\n const id = readRowString(row, 'id')\n // The colour has always been stored and never rendered \u2014 the chips were\n // monochrome while `staff_time_tags.color` held a DS palette key.\n return id\n ? { id, label: readRowString(row, 'label') ?? id, color: readRowString(row, 'color') }\n : null\n })\n .filter((tag): tag is TagOption => tag !== null)\n },\n })\n\n const entryQuery = useQuery<TimeEntryRecord | null>({\n queryKey: [...DIALOG_QUERY_ROOT, 'entry', `scope:${scopeVersion}`, entryId ?? 'new'],\n enabled: open && isEdit,\n staleTime: 0,\n queryFn: async () => {\n // `ids`, not `id`: the list schema declares the plural and is `.passthrough()`,\n // so `?id=` was accepted, never read, and the filter silently disappeared \u2014\n // leaving `pageSize=1` to return the first entry of the whole table.\n const call = await apiCall<Record<string, unknown>>(\n `/api/staff/timesheets/time-entries?ids=${encodeURIComponent(entryId as string)}&pageSize=1`,\n )\n if (!call.ok) throw new Error('[internal] time entry request failed')\n const row = readRowItems(call.result)[0]\n if (!row) return null\n const record = toTimeEntryRecord(row)\n if (!record) return null\n // A dialog that edits a record other than the one it was asked for writes\n // one entry's values onto another, so a mismatch fails loudly rather than\n // populating the form. Nothing but a dropped filter can produce it.\n if (record.id !== entryId) {\n throw new Error('[internal] time entry response did not match the requested id')\n }\n return record\n },\n })\n\n const entry = entryQuery.data ?? null\n const locked = !!entry?.isLocked\n const lockedReportId = entry?.lockedReportId ?? null\n const directoryTasks = React.useMemo(() => tasksQuery.data ?? [], [tasksQuery.data])\n const searchedTasks = React.useMemo(() => taskSearchQuery.data ?? [], [taskSearchQuery.data])\n\n React.useEffect(() => {\n if (!taskId) {\n setPinnedTask(null)\n return\n }\n const found =\n directoryTasks.find((task) => task.id === taskId) ??\n searchedTasks.find((task) => task.id === taskId) ??\n null\n if (found) setPinnedTask(found)\n }, [directoryTasks, searchedTasks, taskId])\n\n /**\n * An entry can point at a task that neither the first directory page nor the\n * current search contains \u2014 editing one written months ago, for instance \u2014 and\n * a form that cannot name the task it is on cannot resolve its project or rate\n * either, so that one row is fetched by id.\n */\n const unresolvedTaskId =\n taskId && !pinnedTask && !tasksQuery.isPending && !taskSearchQuery.isFetching ? taskId : null\n\n const unresolvedTaskQuery = useQuery<TaskOption | null>({\n queryKey: [...DIALOG_QUERY_ROOT, 'task', `scope:${scopeVersion}`, unresolvedTaskId ?? 'none'],\n enabled: open && !!unresolvedTaskId,\n staleTime: 60_000,\n queryFn: () => fetchTaskById(unresolvedTaskId as string),\n })\n\n const tasks = React.useMemo(() => {\n const merged = new Map<string, TaskOption>()\n for (const task of directoryTasks) merged.set(task.id, task)\n for (const task of searchedTasks) if (!merged.has(task.id)) merged.set(task.id, task)\n for (const task of [pinnedTask, unresolvedTaskQuery.data ?? null]) {\n if (task && !merged.has(task.id)) merged.set(task.id, task)\n }\n return [...merged.values()]\n }, [directoryTasks, pinnedTask, searchedTasks, unresolvedTaskQuery.data])\n\n const projects = React.useMemo(() => projectsQuery.data ?? [], [projectsQuery.data])\n const tagOptions = React.useMemo(() => tagsQuery.data ?? [], [tagsQuery.data])\n\n const projectById = React.useMemo(() => {\n const map = new Map<string, ProjectOption>()\n for (const project of projects) map.set(project.id, project)\n return map\n }, [projects])\n\n const selectedTask = React.useMemo(\n () => tasks.find((task) => task.id === taskId) ?? null,\n [taskId, tasks],\n )\n\n const projectId = selectedTask?.timeProjectId ?? entry?.timeProjectId ?? defaults?.timeProjectId ?? null\n const project = projectId ? projectById.get(projectId) ?? null : null\n\n const resetForm = React.useCallback(\n (seed: {\n taskId: string | null\n description: string\n date: string\n start: string | null\n end: string | null\n durationMinutes: number | null\n isBillable: boolean\n rateOverrideAmount: number | null\n tagIds: string[]\n }) => {\n setTaskId(seed.taskId)\n setDescription(seed.description)\n setDate(seed.date || todayIsoDate())\n setIntervalState((current) =>\n resetIntervalState(current, {\n start: seed.start,\n end: seed.end,\n durationMinutes: seed.durationMinutes,\n }),\n )\n setIsBillable(seed.isBillable)\n setRateOverrideEnabled(seed.rateOverrideAmount !== null)\n setRateText(seed.rateOverrideAmount !== null ? String(seed.rateOverrideAmount) : '')\n setTagIds(seed.tagIds)\n setOverlaps([])\n setFieldIssues(NO_FIELD_ISSUES)\n // The seed is run through the same derivation the state uses, so the\n // baseline is what the form will actually show rather than what was asked\n // for \u2014 otherwise a seeded start plus duration would read as an edit.\n const derived = createIntervalState({\n start: seed.start,\n end: seed.end,\n durationMinutes: seed.durationMinutes,\n })\n setBaseline({\n taskId: seed.taskId,\n description: seed.description,\n date: seed.date || todayIsoDate(),\n startText: derived.startText,\n endText: derived.endText,\n durationMinutes: derived.durationMinutes,\n isBillable: seed.isBillable,\n rateOverrideEnabled: seed.rateOverrideAmount !== null,\n rateOverrideAmount: seed.rateOverrideAmount,\n tagIds: seed.tagIds,\n })\n },\n [],\n )\n\n React.useEffect(() => {\n if (!open) {\n seedKeyRef.current = null\n billableDefaultRef.current = null\n setBaseline(null)\n setTaskSearch('')\n setPinnedTask(null)\n return\n }\n if (isEdit && !entry) return\n const key = isEdit ? `edit:${entry?.id ?? entryId}:${entry?.updatedAt ?? ''}` : 'create'\n if (seedKeyRef.current === key) return\n seedKeyRef.current = key\n versionRef.current = entry?.updatedAt ?? null\n if (entry) {\n resetForm({\n taskId: entry.taskId,\n description: entry.description,\n date: entry.date,\n start: entry.startText,\n end: entry.endText,\n durationMinutes: entry.durationMinutes,\n isBillable: entry.isBillable,\n rateOverrideAmount: entry.rateOverrideAmount,\n tagIds: entry.tagIds,\n })\n return\n }\n resetForm({\n taskId: defaults?.taskId ?? null,\n description: defaults?.description ?? '',\n date: defaults?.date ?? todayIsoDate(),\n start: defaults?.startClock ?? null,\n end: defaults?.endClock ?? null,\n durationMinutes: defaults?.durationMinutes ?? null,\n isBillable: defaults?.isBillable ?? true,\n rateOverrideAmount: null,\n tagIds: defaults?.tagIds ?? [],\n })\n }, [defaults, entry, entryId, isEdit, open, resetForm])\n\n /**\n * The tenant's billable default lands separately, and only on the one field it\n * owns. Seeding the whole form once the settings request answers would race the\n * first keystrokes and wipe them, which is exactly what this split avoids.\n */\n React.useEffect(() => {\n if (!open || isEdit || settingsQuery.isPending) return\n if (defaults?.isBillable !== undefined && defaults?.isBillable !== null) return\n if (billableDefaultRef.current === seedKeyRef.current) return\n billableDefaultRef.current = seedKeyRef.current\n setIsBillable(settings.defaults.billable)\n // The tenant default is part of the seed, just a late-arriving part, so the\n // baseline moves with it \u2014 otherwise every new entry would open \"dirty\".\n setBaseline((current) =>\n current ? { ...current, isBillable: settings.defaults.billable } : current,\n )\n }, [defaults?.isBillable, isEdit, open, settings.defaults.billable, settingsQuery.isPending])\n\n const durationMinutes = interval.durationMinutes\n const startClock = interval.startText\n const endClock = interval.endText\n\n /**\n * Advisory overlap probe. Every failure mode \u2014 a non-200, a throw, a partial\n * span \u2014 lands on \"no warning\", because the endpoint may not answer and the\n * form must still save.\n */\n React.useEffect(() => {\n if (!open || locked || !settings.warnings.overlap) {\n setOverlaps([])\n return\n }\n if (!date || parseClock(startClock) === null || !durationMinutes) {\n setOverlaps([])\n return\n }\n let cancelled = false\n const params = new URLSearchParams({\n date,\n startedAt: startClock,\n durationMinutes: String(durationMinutes),\n })\n if (parseClock(endClock) !== null) params.set('endedAt', endClock)\n if (isEdit && entryId) params.set('excludeId', entryId)\n void apiCall<Record<string, unknown>>(`/api/staff/timesheets/time-entries/overlaps?${params.toString()}`)\n .then((call) => {\n if (cancelled) return\n if (!call.ok) {\n setOverlaps([])\n return\n }\n setOverlaps(\n readRowItems(call.result)\n .map(toOverlapEntry)\n .filter((item): item is OverlapEntry => item !== null),\n )\n })\n .catch(() => {\n if (!cancelled) setOverlaps([])\n })\n return () => {\n cancelled = true\n }\n }, [date, durationMinutes, endClock, entryId, isEdit, locked, open, settings.warnings.overlap, startClock])\n\n const rateOverrideAmount = rateOverrideEnabled ? parseRateText(rateText) : null\n const currencyCode = project?.currencyCode ?? entry?.currencyCode ?? null\n\n /**\n * The dialog is hand-rolled, so it plays the `CrudForm` host contract by hand:\n * the four render spots below, plus the `onFieldChange` / `onBeforeSave` /\n * `onAfterSave` lifecycle, dispatched with the same semantics `CrudForm` uses \u2014\n * a widget returning `ok: false` from `onBeforeSave` blocks the write and its\n * message is surfaced, and a value returned from `onFieldChange` is written back\n * into the form.\n */\n const entryFormValues = React.useMemo<FormValues>(\n () => ({\n taskId,\n description,\n date,\n startText: startClock,\n endText: endClock,\n durationMinutes,\n isBillable,\n rateOverrideEnabled,\n rateOverrideAmount,\n tagIds,\n }),\n [\n date,\n description,\n durationMinutes,\n endClock,\n isBillable,\n rateOverrideAmount,\n rateOverrideEnabled,\n startClock,\n tagIds,\n taskId,\n ],\n )\n\n const entryInjectionContext = React.useMemo<TimeEntryFormInjectionContext>(\n () => ({\n formId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,\n entityId: TIME_ENTRY_ENTITY_ID,\n recordId: entryId ?? null,\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: entryId ?? 'new',\n operation: isEdit ? 'update' : 'create',\n locked,\n retryLastMutation,\n }),\n [entryId, isEdit, locked, retryLastMutation],\n )\n\n const { triggerEvent: triggerEntryFormEvent } = useInjectionSpotEvents<\n TimeEntryFormInjectionContext,\n FormValues\n >(extensionPoints.hosts.timeEntryForm.spotId)\n\n /**\n * Fields the last write to came from a widget, not from the person. Without it\n * an `onFieldChange` handler that rewrites its own field would see the rewrite\n * as a fresh edit and be dispatched again on every apply.\n */\n const injectedFieldWritesRef = React.useRef(new Set<string>())\n\n const applyInjectedFieldValue = React.useCallback((fieldId: string, value: unknown) => {\n switch (fieldId) {\n case 'taskId':\n injectedFieldWritesRef.current.add(fieldId)\n setTaskId(typeof value === 'string' && value.length > 0 ? value : null)\n return\n case 'description':\n injectedFieldWritesRef.current.add(fieldId)\n setDescription(typeof value === 'string' ? value : '')\n return\n case 'date':\n if (typeof value !== 'string') return\n injectedFieldWritesRef.current.add(fieldId)\n setDate(value)\n return\n case 'isBillable':\n injectedFieldWritesRef.current.add(fieldId)\n setIsBillable(value === true)\n return\n case 'rateOverrideEnabled':\n injectedFieldWritesRef.current.add(fieldId)\n setRateOverrideEnabled(value === true)\n return\n case 'rateOverrideAmount':\n injectedFieldWritesRef.current.add(fieldId)\n setRateText(typeof value === 'number' || typeof value === 'string' ? String(value) : '')\n return\n case 'tagIds':\n if (!Array.isArray(value)) return\n injectedFieldWritesRef.current.add(fieldId)\n setTagIds(value.filter((id): id is string => typeof id === 'string'))\n return\n case 'startText':\n if (typeof value !== 'string') return\n injectedFieldWritesRef.current.add(fieldId)\n setIntervalState((current) => reduceIntervalState(current, { field: 'start', value }))\n return\n case 'endText':\n if (typeof value !== 'string') return\n injectedFieldWritesRef.current.add(fieldId)\n setIntervalState((current) => reduceIntervalState(current, { field: 'end', value }))\n return\n case 'durationMinutes':\n injectedFieldWritesRef.current.add(fieldId)\n setIntervalState((current) =>\n reduceIntervalState(current, {\n field: 'duration',\n minutes: typeof value === 'number' ? value : null,\n invalid: false,\n }),\n )\n return\n default:\n }\n }, [])\n\n const previousEntryFormValuesRef = React.useRef<FormValues | null>(null)\n\n React.useEffect(() => {\n if (!open || locked) {\n previousEntryFormValuesRef.current = null\n injectedFieldWritesRef.current.clear()\n return\n }\n const previous = previousEntryFormValuesRef.current\n previousEntryFormValuesRef.current = entryFormValues\n if (!previous) return\n const injectedWrites = injectedFieldWritesRef.current\n const changedFieldIds = (Object.keys(entryFormValues) as Array<keyof FormValues>).filter(\n (fieldId) => fieldValueSnapshot(previous[fieldId]) !== fieldValueSnapshot(entryFormValues[fieldId]),\n )\n for (const fieldId of changedFieldIds) {\n if (injectedWrites.delete(fieldId)) continue\n void triggerEntryFormEvent('onFieldChange', entryFormValues, entryInjectionContext, {\n fieldId,\n fieldValue: entryFormValues[fieldId],\n })\n .then((result) => {\n if (!result.ok || !result.fieldChange) return\n const change = result.fieldChange\n if (change.value !== undefined) applyInjectedFieldValue(fieldId, change.value)\n for (const [sideFieldId, sideValue] of Object.entries(change.sideEffects ?? {})) {\n applyInjectedFieldValue(sideFieldId, sideValue)\n }\n for (const message of change.messages ?? []) flash(message.text, message.severity)\n })\n .catch((err) => {\n logger.error('staff.time_tracking entry dialog onFieldChange failed', { err })\n })\n }\n }, [\n applyInjectedFieldValue,\n entryFormValues,\n entryInjectionContext,\n locked,\n open,\n triggerEntryFormEvent,\n ])\n const roundedMinutes = roundMinutes(durationMinutes ?? 0, settings.rounding)\n const rate = applicableRate({ rateOverrideAmount }, { hourlyRate: project?.hourlyRate ?? null })\n const cost = entryAmount(\n { isBillable, roundedMinutes, rateOverrideAmount },\n { hourlyRate: project?.hourlyRate ?? null },\n )\n\n const computedBadge = (field: 'start' | 'end' | 'duration' | 'cost') => (\n <Badge variant=\"neutral\" data-testid={`entry-dialog-computed-${field}`}>\n {t('staff.time_tracking.entryDialog.computed', 'computed')}\n </Badge>\n )\n\n const midnightEndDate = interval.crossesMidnight ? shiftIsoDate(date, 1) : null\n /**\n * Unparseable duration text keeps Save disabled \u2014 the field already explains\n * itself and there is nothing to send. A *missing* required value does not:\n * pressing Save is how the form is asked what it is waiting for, and it\n * answers under the control rather than by staying greyed out and silent.\n */\n const canSubmit = !locked && !interval.durationInvalid && !saving\n\n // A field stops complaining the moment it is filled in.\n React.useEffect(() => {\n if (!taskId) return\n setFieldIssues((current) => (current.task ? { ...current, task: null } : current))\n }, [taskId])\n\n React.useEffect(() => {\n if (!durationMinutes) return\n setFieldIssues((current) => (current.duration ? { ...current, duration: null } : current))\n }, [durationMinutes])\n\n const validateRequired = React.useCallback(() => {\n const next: FieldIssues = {\n task: taskId\n ? null\n : t('staff.time_tracking.entryDialog.errors.taskRequired', 'Pick the task this time belongs to.'),\n duration: durationMinutes\n ? null\n : t('staff.time_tracking.entryDialog.errors.durationRequired', 'Enter how long the work took.'),\n }\n setFieldIssues(next)\n return !next.task && !next.duration\n }, [durationMinutes, t, taskId])\n\n /**\n * A locked entry has nothing editable in it, and a form that has not been\n * seeded yet has nothing to compare against; everywhere else the seed is the\n * baseline and any difference is work that closing would throw away.\n */\n const isDirty = React.useMemo(() => {\n if (locked || !baseline) return false\n return (\n formSnapshot(baseline) !==\n formSnapshot({\n taskId,\n description,\n date,\n startText: startClock,\n endText: endClock,\n durationMinutes,\n isBillable,\n rateOverrideEnabled,\n rateOverrideAmount,\n tagIds,\n })\n )\n }, [\n baseline,\n date,\n description,\n durationMinutes,\n endClock,\n isBillable,\n locked,\n rateOverrideAmount,\n rateOverrideEnabled,\n startClock,\n tagIds,\n taskId,\n ])\n\n /**\n * Every way out of the dialog \u2014 Escape, the \u00D7, the overlay, Cancel \u2014 lands\n * here, because a half-entered entry is lost the same way whichever one is\n * used. A clean form closes silently; only real work is worth an interruption.\n */\n const requestClose = React.useCallback(async () => {\n if (saving) return\n if (!isDirty) {\n onOpenChange(false)\n return\n }\n const confirmed = await confirm({\n title: t('staff.time_tracking.entryDialog.discard.title', 'Discard this time entry?'),\n text: t(\n 'staff.time_tracking.entryDialog.discard.text',\n 'It has not been saved yet, so closing now loses what you entered.',\n ),\n confirmText: t('staff.time_tracking.entryDialog.discard.confirm', 'Discard'),\n cancelText: t('staff.time_tracking.entryDialog.discard.cancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (confirmed) onOpenChange(false)\n }, [confirm, isDirty, onOpenChange, saving, t])\n\n const buildPayload = React.useCallback(() => {\n const startedAt = parseClock(startClock) !== null ? combineDateAndClock(date, startClock) : null\n const endedAt =\n parseClock(endClock) !== null ? combineDateAndClock(date, endClock, interval.crossesMidnight ? 1 : 0) : null\n return {\n date,\n durationMinutes: durationMinutes ?? 0,\n startedAt,\n endedAt,\n taskId,\n timeProjectId: projectId,\n description: description.trim() ? description.trim() : null,\n isBillable,\n tagIds,\n rateOverrideAmount: canSeeMoney ? rateOverrideAmount : undefined,\n }\n }, [\n canSeeMoney,\n date,\n description,\n durationMinutes,\n endClock,\n interval.crossesMidnight,\n isBillable,\n projectId,\n rateOverrideAmount,\n startClock,\n tagIds,\n taskId,\n ])\n\n const reportFailure = React.useCallback(\n (error: unknown) => {\n if (surfaceRecordConflict(error, t)) return\n if (readErrorCode(error) === 'entry_locked') {\n flash(\n t(\n 'staff.time_tracking.entryDialog.locked.notice',\n 'This entry is closed in a report, so it can no longer be edited.',\n ),\n 'error',\n )\n return\n }\n const issues = readFieldIssues(error)\n if (issues) {\n setFieldIssues(issues)\n return\n }\n logger.error('staff.time_tracking entry dialog save failed', { err: error })\n flash(\n error instanceof Error && error.message\n ? error.message\n : t('staff.time_tracking.entryDialog.errors.save', 'Could not save the time entry.'),\n 'error',\n )\n },\n [t],\n )\n\n const submit = React.useCallback(\n async (mode: SaveMode) => {\n if (!canSubmit) return\n if (!validateRequired()) return\n if (!isEdit && !staffMemberId) {\n flash(\n t(\n 'staff.time_tracking.entryDialog.errors.noStaffMember',\n 'Your account is not linked to a staff member, so time cannot be logged.',\n ),\n 'error',\n )\n return\n }\n const body = buildPayload()\n\n const beforeSave = await triggerEntryFormEvent('onBeforeSave', entryFormValues, entryInjectionContext)\n if (!beforeSave.ok) {\n if (beforeSave.fieldErrors) {\n const taskIssue = beforeSave.fieldErrors.taskId ?? beforeSave.fieldErrors.task ?? null\n const durationIssue =\n beforeSave.fieldErrors.durationMinutes ?? beforeSave.fieldErrors.duration ?? null\n if (taskIssue || durationIssue) setFieldIssues({ task: taskIssue, duration: durationIssue })\n }\n flash(\n beforeSave.message || t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n 'error',\n )\n return\n }\n const injectedRequestHeaders = beforeSave.requestHeaders ?? {}\n\n setSavingMode(mode)\n try {\n const saved = await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(\n {\n ...injectedRequestHeaders,\n ...(isEdit ? buildOptimisticLockHeader(versionRef.current) : {}),\n },\n () =>\n apiCallOrThrow<Record<string, unknown>>(\n '/api/staff/timesheets/time-entries',\n {\n method: isEdit ? 'PUT' : 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(\n isEdit ? { id: entryId, ...body } : { staffMemberId, source: 'manual', ...body },\n ),\n },\n {\n errorMessage: t(\n 'staff.time_tracking.entryDialog.errors.save',\n 'Could not save the time entry.',\n ),\n },\n ),\n ),\n context: {\n formId: TIME_ENTRY_DIALOG_MUTATION_CONTEXT_ID,\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: entryId ?? 'new',\n retryLastMutation,\n },\n })\n const savedId = typeof saved?.result?.id === 'string' ? saved.result.id : entryId ?? null\n try {\n await triggerEntryFormEvent('onAfterSave', entryFormValues, entryInjectionContext)\n } catch (err) {\n logger.error('staff.time_tracking entry dialog onAfterSave failed', { err })\n }\n flash(t('staff.time_tracking.entryDialog.saved', 'Time entry saved.'), 'success')\n if (mode === 'again') {\n // Note 3: the dialog stays open, the description and the times clear,\n // and the next entry starts where this one ended.\n const chain = settings.defaults.chainStartFromPreviousEnd\n const nextDate = interval.crossesMidnight && midnightEndDate ? midnightEndDate : date\n const nextStart = chain ? endClock || null : null\n seedKeyRef.current = 'create'\n setDescription('')\n setDate(nextDate)\n setIntervalState((current) => resetIntervalState(current, { start: nextStart }))\n setOverlaps([])\n // The next entry starts clean, so this is its baseline \u2014 without it the\n // dialog would still be holding the one that was just written.\n setBaseline({\n taskId,\n description: '',\n date: nextDate,\n startText: nextStart ?? '',\n endText: '',\n durationMinutes: null,\n isBillable,\n rateOverrideEnabled,\n rateOverrideAmount,\n tagIds,\n })\n onSaved?.({ id: savedId, keptOpen: true })\n // Back to the top of the loop: pick task \u2192 duration \u2192 save \u2192 next.\n focusTaskPicker()\n return\n }\n onSaved?.({ id: savedId, keptOpen: false })\n onOpenChange(false)\n } catch (error) {\n reportFailure(error)\n } finally {\n setSavingMode(null)\n }\n },\n [\n buildPayload,\n canSubmit,\n date,\n endClock,\n entryFormValues,\n entryId,\n entryInjectionContext,\n interval.crossesMidnight,\n isBillable,\n isEdit,\n midnightEndDate,\n onOpenChange,\n onSaved,\n rateOverrideAmount,\n rateOverrideEnabled,\n reportFailure,\n retryLastMutation,\n runMutation,\n settings.defaults.chainStartFromPreviousEnd,\n staffMemberId,\n t,\n tagIds,\n taskId,\n triggerEntryFormEvent,\n validateRequired,\n ],\n )\n\n const handleShowOverlap = React.useCallback(\n (overlap: OverlapEntry) => {\n if (onShowEntry) {\n onShowEntry(overlap.id)\n return\n }\n window.open(`${ENTRIES_LIST_PATH}?ids=${encodeURIComponent(overlap.id)}`, '_blank', 'noopener')\n },\n [onShowEntry],\n )\n\n /**\n * The picker needs the project and its customer per row so it can group by\n * them; both already arrive with the projects query the dialog runs for the\n * rate, so this is a join in memory rather than another request.\n */\n const pickerItems = React.useMemo<TaskPickerItem[]>(\n () =>\n tasks.map((task) => {\n const project = task.timeProjectId ? projectById.get(task.timeProjectId) : undefined\n return {\n id: task.id,\n reference: task.reference,\n title: task.title,\n projectId: task.timeProjectId,\n projectName: project?.name ?? null,\n customerName: project?.customerName ?? null,\n statusId: task.taskStatusId ?? null,\n assigneeInitials: null,\n assigneeName: null,\n loggedMinutes: task.loggedMinutes ?? null,\n }\n }),\n [projectById, tasks],\n )\n\n const queryClient = useQueryClient()\n /**\n * Creates the tag, then hands it back so the caller can assign it.\n *\n * The colour is decided here rather than left to the server, which stores\n * whatever it is sent and defaults to null. The picker already tints its dot\n * with the name-derived hue, so sending that same hue is what makes the chip\n * the entry ends up wearing the colour the row promised \u2014 instead of the grey\n * one an unset column renders as.\n */\n const createTag = React.useCallback(\n async (label: string): Promise<TagOption | null> => {\n const color = autoColorFromName(label).key\n try {\n const created = await apiCallOrThrow<Record<string, unknown>>(\n '/api/staff/timesheets/tags',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ label, slug: slugifyProjectName(label).toLowerCase(), color }),\n },\n { errorMessage: t('staff.time_tracking.entryDialog.errors.createTag', 'Could not create the tag.') },\n )\n const id = typeof created?.result?.id === 'string' ? created.result.id : null\n if (!id) return null\n const option: TagOption = { id, label, color }\n // Seeded before the refetch so the chip is never briefly the grey one the\n // fix is about; the invalidation right after replaces it with the row the\n // server actually stored.\n queryClient.setQueryData<TagOption[]>(\n [...DIALOG_QUERY_ROOT, 'tags', `scope:${scopeVersion}`],\n (current) => [...(current ?? []), option],\n )\n await queryClient.invalidateQueries({ queryKey: [...DIALOG_QUERY_ROOT, 'tags'] })\n return option\n } catch (error) {\n logger.warn('staff.time_tracking.entryDialog tag create failed', { err: error })\n flash(t('staff.time_tracking.entryDialog.errors.createTag', 'Could not create the tag.'), 'error')\n return null\n }\n },\n [queryClient, scopeVersion, t],\n )\n\n const overlap = overlaps[0] ?? null\n const availableTags = React.useMemo(\n () => tagOptions.filter((tag) => !tagIds.includes(tag.id)),\n [tagIds, tagOptions],\n )\n const assignedTags = React.useMemo(() => {\n const byId = new Map(tagOptions.map((tag) => [tag.id, tag]))\n return tagIds.map((id) => byId.get(id) ?? { id, label: id, color: null })\n }, [tagIds, tagOptions])\n\n const rateLabel = rate !== null ? formatCurrency(rate, currencyCode) ?? String(rate) : null\n const projectRateLabel =\n project?.hourlyRate !== null && project?.hourlyRate !== undefined\n ? formatCurrency(project.hourlyRate, currencyCode) ?? String(project.hourlyRate)\n : null\n\n const body = (() => {\n if (isEdit && entryQuery.isLoading) {\n return <LoadingMessage label={t('staff.time_tracking.entryDialog.loading', 'Loading the time entry\u2026')} />\n }\n if (isEdit && entryQuery.isError) {\n return <ErrorMessage label={t('staff.time_tracking.entryDialog.errors.load', 'Could not load the time entry.')} />\n }\n if (isEdit && !entry) {\n return <ErrorMessage label={t('staff.time_tracking.entryDialog.errors.notFound', 'Time entry not found or not accessible.')} />\n }\n\n return (\n <div className=\"flex flex-col gap-4\">\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'before-fields')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n\n {locked ? (\n <Alert status=\"warning\" data-testid=\"entry-dialog-locked\">\n <AlertTitle className=\"flex items-center gap-2\">\n <Lock className=\"size-3.5\" aria-hidden=\"true\" />\n {t('staff.time_tracking.entryDialog.locked.badge', 'Locked')}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'staff.time_tracking.entryDialog.locked.notice',\n 'This entry is closed in a report, so it can no longer be edited.',\n )}\n </AlertDescription>\n {lockedReportId ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"mt-2\"\n data-testid=\"entry-dialog-locked-report\"\n onClick={() =>\n window.open(reportDetailHref(lockedReportId), '_blank', 'noopener')\n }\n >\n {t('staff.time_tracking.entryDialog.locked.showReport', 'Show the report')}\n </Button>\n ) : null}\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-task\">\n {t('staff.time_tracking.entryDialog.task', 'Task')}\n <span aria-hidden=\"true\"> *</span>\n </Label>\n <div\n data-testid=\"entry-dialog-task\"\n ref={taskTriggerRef}\n aria-invalid={fieldIssues.task ? true : undefined}\n aria-describedby={fieldIssues.task ? 'entry-dialog-task-message' : undefined}\n >\n <TaskPicker\n value={taskId}\n onChange={(next) => setTaskId(next || null)}\n items={pickerItems}\n recentTaskIds={recentQuery.data ?? []}\n statuses={statusesQuery.data ?? {}}\n onQueryChange={setTaskSearch}\n searching={taskSearchQuery.isFetching}\n loading={tasksQuery.isLoading}\n disabled={locked}\n />\n </div>\n {fieldIssues.task ? (\n <p\n id=\"entry-dialog-task-message\"\n className=\"text-xs text-status-error-text\"\n role=\"alert\"\n data-testid=\"entry-dialog-task-error\"\n >\n {fieldIssues.task}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.taskHint',\n 'The project and the customer follow from the task \u2014 you do not pick them separately.',\n )}\n </p>\n )}\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-description\">\n {t('staff.time_tracking.entryDialog.description', 'Description')}\n </Label>\n <Input\n id=\"entry-dialog-description\"\n value={description}\n disabled={locked}\n onChange={(event) => setDescription(event.target.value)}\n data-testid=\"entry-dialog-description\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-date\">{t('staff.time_tracking.entryDialog.date', 'Date')}</Label>\n <Input\n id=\"entry-dialog-date\"\n type=\"date\"\n value={date}\n disabled={locked}\n onChange={(event) => setDate(event.target.value)}\n data-testid=\"entry-dialog-date\"\n />\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-duration\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.duration', 'Duration')}\n {interval.computed === 'duration' ? computedBadge('duration') : null}\n </Label>\n <DurationInput\n key={`entry-dialog-duration-${interval.durationEpoch}`}\n id=\"entry-dialog-duration\"\n value={durationMinutes}\n disabled={locked}\n error={fieldIssues.duration ?? undefined}\n ariaLabel={t('staff.time_tracking.entryDialog.duration', 'Duration')}\n onChange={(minutes, state) =>\n setIntervalState((current) =>\n reduceIntervalState(current, {\n field: 'duration',\n minutes,\n invalid: state.status === 'invalid',\n }),\n )\n }\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-start\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.start', 'Start')}\n {interval.computed === 'start' ? computedBadge('start') : null}\n </Label>\n <Input\n id=\"entry-dialog-start\"\n value={startClock}\n disabled={locked}\n inputClassName=\"font-mono tabular-nums\"\n placeholder=\"15:10\"\n onChange={(event) =>\n setIntervalState((current) =>\n reduceIntervalState(current, { field: 'start', value: event.target.value }),\n )\n }\n data-testid=\"entry-dialog-start\"\n />\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-end\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.end', 'End')}\n {interval.computed === 'end' ? computedBadge('end') : null}\n </Label>\n <Input\n id=\"entry-dialog-end\"\n value={endClock}\n disabled={locked}\n inputClassName=\"font-mono tabular-nums\"\n placeholder=\"16:25\"\n onChange={(event) =>\n setIntervalState((current) =>\n reduceIntervalState(current, { field: 'end', value: event.target.value }),\n )\n }\n data-testid=\"entry-dialog-end\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.endHint',\n 'Change this field and the duration recalculates itself.',\n )}\n </p>\n </div>\n </div>\n\n {midnightEndDate ? (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"entry-dialog-midnight\">\n {t('staff.time_tracking.entryDialog.midnight', 'This entry ends the next day, {date}.', {\n date: midnightEndDate,\n })}\n </p>\n ) : null}\n\n {overlap ? (\n <Alert status=\"warning\" data-testid=\"entry-dialog-overlap\">\n <AlertTitle>\n {t('staff.time_tracking.entryDialog.overlap.title', 'This time overlaps another entry')}\n </AlertTitle>\n <AlertDescription>\n {t(\n 'staff.time_tracking.entryDialog.overlap.body',\n '{start} \u2013 {end} \u00B7 \u201E{task}\u201D \u00B7 {project} ({duration}). Overlapping is allowed \u2014 if it is intentional, just save.',\n {\n start: overlap.startText,\n end: overlap.endText,\n task:\n overlap.taskTitle ??\n overlap.description ??\n t('staff.time_tracking.entryDialog.overlap.noTask', 'no task'),\n project:\n overlap.projectName ??\n t('staff.time_tracking.entryDialog.overlap.noProject', 'no project'),\n duration: formatDuration(overlap.durationMinutes, 'clock'),\n },\n )}\n </AlertDescription>\n <div className=\"mt-2 flex flex-wrap gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleShowOverlap(overlap)}\n data-testid=\"entry-dialog-overlap-show\"\n >\n {t('staff.time_tracking.entryDialog.overlap.show', 'Show that entry')}\n </Button>\n {overlap.endText ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={locked}\n onClick={() =>\n setIntervalState((current) =>\n reduceIntervalState(current, { field: 'snapStart', value: overlap.endText }),\n )\n }\n data-testid=\"entry-dialog-overlap-snap\"\n >\n {t('staff.time_tracking.entryDialog.overlap.snap', 'Snap start to {time}', {\n time: overlap.endText,\n })}\n </Button>\n ) : null}\n </div>\n </Alert>\n ) : null}\n\n <Separator />\n\n <div className=\"flex items-center gap-3\">\n <Switch\n checked={isBillable}\n disabled={locked}\n onCheckedChange={(next) => setIsBillable(next)}\n aria-label={t('staff.time_tracking.entryDialog.billable', 'Billable')}\n data-testid=\"entry-dialog-billable\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"text-sm font-medium text-foreground\">\n {t('staff.time_tracking.entryDialog.billable', 'Billable')}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.entryDialog.billableHint', 'On by default for this project.')}\n </span>\n </div>\n {canSeeMoney && rateLabel ? (\n <Badge variant=\"success\" data-testid=\"entry-dialog-rate-badge\">\n {t('staff.time_tracking.entryDialog.ratePerHour', '{amount}/h', { amount: rateLabel })}\n </Badge>\n ) : null}\n </div>\n\n {canSeeMoney ? (\n <>\n <div className=\"flex items-center gap-3\">\n <Switch\n checked={rateOverrideEnabled}\n disabled={locked}\n onCheckedChange={(next) => setRateOverrideEnabled(next)}\n aria-label={t('staff.time_tracking.entryDialog.rateOverride', 'Custom rate for this entry')}\n data-testid=\"entry-dialog-rate-override\"\n />\n <div className=\"flex min-w-0 flex-1 flex-col\">\n <span className=\"text-sm font-medium text-foreground\">\n {t('staff.time_tracking.entryDialog.rateOverride', 'Custom rate for this entry')}\n </span>\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.rateOverrideHint',\n 'Overrides the project rate here only.',\n )}\n </span>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n {rateOverrideEnabled ? (\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-rate\">\n {t('staff.time_tracking.entryDialog.rate', 'Rate')}\n </Label>\n <Input\n id=\"entry-dialog-rate\"\n value={rateText}\n disabled={locked}\n inputMode=\"decimal\"\n onChange={(event) => setRateText(event.target.value)}\n data-testid=\"entry-dialog-rate\"\n />\n {projectRateLabel ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.entryDialog.rateHint',\n 'Defaults to {amount} (the project rate).',\n { amount: projectRateLabel },\n )}\n </p>\n ) : null}\n </div>\n ) : null}\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"entry-dialog-cost\" className=\"flex items-center gap-2\">\n {t('staff.time_tracking.entryDialog.cost', 'Cost')}\n {computedBadge('cost')}\n </Label>\n <Input\n id=\"entry-dialog-cost\"\n readOnly\n disabled\n value={cost === null ? '' : formatCurrency(cost, currencyCode) ?? String(cost)}\n inputClassName=\"font-mono tabular-nums\"\n data-testid=\"entry-dialog-cost\"\n />\n {rateLabel && durationMinutes ? (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"entry-dialog-cost-hint\">\n {t(\n 'staff.time_tracking.entryDialog.costHint',\n '{raw} rounded to {rounded} \u00D7 {rate}',\n {\n raw: formatDuration(durationMinutes, 'clock'),\n rounded: formatDuration(roundedMinutes, 'clock'),\n rate: rateLabel,\n },\n )}\n </p>\n ) : null}\n </div>\n </div>\n </>\n ) : (\n <p className=\"text-xs text-muted-foreground\" data-testid=\"entry-dialog-money-hidden\">\n {t(\n 'staff.time_tracking.entryDialog.moneyHidden',\n 'Rates and cost are hidden \u2014 your permissions do not cover money on time entries.',\n )}\n </p>\n )}\n\n <div className=\"flex flex-col gap-1.5\">\n <span className=\"text-xs font-medium text-muted-foreground\">\n {t('staff.time_tracking.entryDialog.tags', 'Tags')}\n </span>\n <div className=\"flex flex-wrap items-center gap-2\">\n {assignedTags.map((tag) => (\n <Badge\n key={tag.id}\n variant=\"neutral\"\n className=\"gap-1\"\n style={tagChipStyle(tag)}\n >\n {tag.label}\n {locked ? null : (\n <button\n type=\"button\"\n aria-label={t('staff.time_tracking.entryDialog.removeTag', 'Remove tag {label}', {\n label: tag.label,\n })}\n onClick={() => setTagIds((current) => current.filter((id) => id !== tag.id))}\n >\n <X className=\"size-3\" aria-hidden=\"true\" />\n </button>\n )}\n </Badge>\n ))}\n {!locked ? (\n <TagPicker\n available={availableTags}\n onAssign={(id) => setTagIds((current) => (current.includes(id) ? current : [...current, id]))}\n onCreate={createTag}\n labels={{\n add: t('staff.time_tracking.entryDialog.addTag', 'Add tag'),\n create: (name) =>\n t('staff.time_tracking.entryDialog.createTag', 'Create \"{name}\"', { name }),\n empty: t('staff.time_tracking.entryDialog.noTags', 'No matching tag'),\n }}\n />\n ) : null}\n </div>\n </div>\n\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'fields')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n\n <Alert status=\"information\" data-testid=\"entry-dialog-creator\">\n <AlertDescription>\n {selfQuery.data?.displayName\n ? t(\n 'staff.time_tracking.entryDialog.creator',\n 'Entry author: {name} \u2014 assigned automatically and not editable.',\n { name: selfQuery.data.displayName },\n )\n : t(\n 'staff.time_tracking.entryDialog.creatorUnknown',\n 'The entry author is assigned automatically and is not editable.',\n )}\n </AlertDescription>\n </Alert>\n\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'after-fields')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n </div>\n )\n })()\n\n return (\n <Dialog\n open={open}\n // Escape, the \u00D7, and a click on the overlay all arrive here, which is why\n // the unsaved-work guard sits on this one callback rather than on each of\n // them. Radix keeps the dialog mounted until `open` actually flips, so\n // declining the prompt simply leaves the form where it was.\n onOpenChange={(next) => {\n if (next) {\n onOpenChange(true)\n return\n }\n void requestClose()\n }}\n >\n <DialogContent\n size=\"lg\"\n data-testid=\"entry-dialog\"\n onOpenAutoFocus={(event) => {\n // A locked entry has nothing to type into, so the default focus (the\n // close button) is the right one there.\n if (locked || !taskTriggerRef.current) return\n event.preventDefault()\n focusTaskPicker()\n }}\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n // Shift keeps the dialog open for the next entry, so a run of entries\n // never needs the pointer.\n void submit(event.shiftKey ? 'again' : 'close')\n }\n // Escape is deliberately not handled here. Radix's `DismissableLayer`\n // already dismisses only the topmost layer, so a picker popover open\n // inside the form consumes the key on its own; a handler here closed\n // the whole dialog regardless of what was open on top of it.\n }}\n >\n <DialogHeader>\n <DialogTitle>\n {isEdit\n ? t('staff.time_tracking.entryDialog.titleEdit', 'Time entry')\n : t('staff.time_tracking.entryDialog.titleCreate', 'New time entry')}\n </DialogTitle>\n {headerHint ? <span className=\"text-xs text-muted-foreground\">{headerHint}</span> : null}\n </DialogHeader>\n\n {body}\n\n <DialogFooter>\n {/*\n * The labels are flex items, so without `whitespace-nowrap` \"save and\n * add another\" shrinks into a four-line column rather than staying on\n * the line. Hidden below `sm` as well: on a narrow dialog the hint\n * competes with the buttons for room it does not deserve, and a\n * keyboard hint is useless on a touch device anyway.\n */}\n <span className=\"mr-auto hidden items-center gap-1 whitespace-nowrap text-xs text-muted-foreground sm:flex\">\n <Kbd>\u2318</Kbd>\n <Kbd>\u21B5</Kbd>\n {t('staff.time_tracking.entryDialog.shortcutSave', 'save')}\n {locked ? null : (\n <>\n <span aria-hidden=\"true\">\u00B7</span>\n <Kbd>\u2318</Kbd>\n <Kbd>\u21E7</Kbd>\n <Kbd>\u21B5</Kbd>\n {t('staff.time_tracking.entryDialog.shortcutSaveAgain', 'save and add another')}\n </>\n )}\n <span aria-hidden=\"true\">\u00B7</span>\n <Kbd>esc</Kbd>\n {t('staff.time_tracking.entryDialog.shortcutCancel', 'cancel')}\n </span>\n <InjectionSpot\n spotId={extensionSpotChildId(extensionPoints.hosts.timeEntryForm.spotId, 'footer')}\n context={entryInjectionContext}\n data={entryFormValues}\n />\n <Button type=\"button\" variant=\"ghost\" onClick={() => { void requestClose() }}>\n {locked\n ? t('staff.time_tracking.entryDialog.close', 'Close')\n : t('staff.time_tracking.entryDialog.cancel', 'Cancel')}\n </Button>\n {locked ? null : (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={!canSubmit}\n onClick={() => void submit('again')}\n data-testid=\"entry-dialog-save-again\"\n >\n {savingMode === 'again' ? <Spinner className=\"size-4\" /> : null}\n {t('staff.time_tracking.entryDialog.saveAndNew', 'Save and add another')}\n </Button>\n <Button\n type=\"button\"\n disabled={!canSubmit}\n onClick={() => void submit('close')}\n data-testid=\"entry-dialog-save\"\n >\n {savingMode === 'close' ? <Spinner className=\"size-4\" /> : null}\n {t('staff.time_tracking.entryDialog.save', 'Save')}\n </Button>\n </>\n )}\n </DialogFooter>\n {ConfirmDialogElement}\n </DialogContent>\n </Dialog>\n )\n}\n\nconst timeEntryDialogPropsSchema: z.ZodType<TimeEntryDialogProps> = z.object({\n open: z.boolean(),\n onOpenChange: callbackProp<(open: boolean) => void>(),\n entryId: z.string().nullable().optional(),\n defaults: opaqueProp<TimeEntryDialogDefaults>().optional(),\n headerHint: z.string().nullable().optional(),\n onSaved: optionalCallbackProp<(result: { id: string | null; keptOpen: boolean }) => void>(),\n onShowEntry: optionalCallbackProp<(entryId: string) => void>(),\n})\n\nregisterComponent<TimeEntryDialogProps>({\n id: extensionPoints.hosts.timeEntryDialogComponent.componentId,\n component: DefaultTimeEntryDialog,\n metadata: {\n module: 'staff',\n description: 'Create/edit dialog for a single time entry.',\n propsSchema: timeEntryDialogPropsSchema,\n },\n})\n\nexport function TimeEntryDialog(props: TimeEntryDialogProps) {\n const Resolved = useRegisteredComponent<TimeEntryDialogProps>(\n extensionPoints.hosts.timeEntryDialogComponent.componentId,\n DefaultTimeEntryDialog,\n )\n return <Resolved {...props} />\n}\n\nexport default TimeEntryDialog\n"],
5
+ "mappings": "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",
6
6
  "names": ["project", "taskId", "body", "overlap"]
7
7
  }
@@ -0,0 +1,9 @@
1
+ const REPORTS_PATH = "/backend/staff/time-tracking/reports";
2
+ function reportDetailHref(reportId) {
3
+ return `${REPORTS_PATH}/${encodeURIComponent(reportId)}`;
4
+ }
5
+ export {
6
+ REPORTS_PATH,
7
+ reportDetailHref
8
+ };
9
+ //# sourceMappingURL=reportLinks.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/time-tracking-ui/reportLinks.ts"],
4
+ "sourcesContent": ["export const REPORTS_PATH = '/backend/staff/time-tracking/reports'\n\nexport function reportDetailHref(reportId: string): string {\n return `${REPORTS_PATH}/${encodeURIComponent(reportId)}`\n}\n"],
5
+ "mappings": "AAAO,MAAM,eAAe;AAErB,SAAS,iBAAiB,UAA0B;AACzD,SAAO,GAAG,YAAY,IAAI,mBAAmB,QAAQ,CAAC;AACxD;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.9.1-develop.7464.1.9a392fca41",
3
+ "version": "0.9.1-develop.7467.1.3b2bb92d47",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -264,16 +264,16 @@
264
264
  "zod": "^4.4.3"
265
265
  },
266
266
  "peerDependencies": {
267
- "@open-mercato/ai-assistant": "0.9.1-develop.7464.1.9a392fca41",
268
- "@open-mercato/shared": "0.9.1-develop.7464.1.9a392fca41",
269
- "@open-mercato/ui": "0.9.1-develop.7464.1.9a392fca41",
267
+ "@open-mercato/ai-assistant": "0.9.1-develop.7467.1.3b2bb92d47",
268
+ "@open-mercato/shared": "0.9.1-develop.7467.1.3b2bb92d47",
269
+ "@open-mercato/ui": "0.9.1-develop.7467.1.3b2bb92d47",
270
270
  "react": "^19.0.0",
271
271
  "react-dom": "^19.0.0"
272
272
  },
273
273
  "devDependencies": {
274
- "@open-mercato/ai-assistant": "0.9.1-develop.7464.1.9a392fca41",
275
- "@open-mercato/shared": "0.9.1-develop.7464.1.9a392fca41",
276
- "@open-mercato/ui": "0.9.1-develop.7464.1.9a392fca41",
274
+ "@open-mercato/ai-assistant": "0.9.1-develop.7467.1.3b2bb92d47",
275
+ "@open-mercato/shared": "0.9.1-develop.7467.1.3b2bb92d47",
276
+ "@open-mercato/ui": "0.9.1-develop.7467.1.3b2bb92d47",
277
277
  "@testing-library/dom": "^10.4.1",
278
278
  "@testing-library/jest-dom": "^7.0.1",
279
279
  "@testing-library/react": "^16.3.3",
@@ -81,6 +81,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
81
81
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
82
82
  import { createLogger } from '@open-mercato/shared/lib/logger'
83
83
  import { TimeEntryDialog } from '../../../../lib/time-tracking-ui/TimeEntryDialog'
84
+ import { REPORTS_PATH, reportDetailHref } from '../../../../lib/time-tracking-ui/reportLinks'
84
85
  import { TimeEntryDurationCell } from '../../../../lib/time-tracking-ui/TimeEntryDurationCell'
85
86
  import { TimeEntriesSummaryFooter } from '../../../../lib/time-tracking-ui/TimeEntriesSummaryFooter'
86
87
  import {
@@ -120,7 +121,6 @@ const MANAGE_ALL_FEATURE = 'staff.timesheets.manage_all'
120
121
  * ordinary filter values, so they carry a removable chip like anything else.
121
122
  */
122
123
  const DEEP_LINK_PARAMS = ['taskId', 'ids'] as const
123
- const REPORTS_PATH = '/backend/staff/time-tracking/reports'
124
124
  const MUTATION_CONTEXT_ID = 'staff.time_tracking.entriesList'
125
125
  const RESOURCE_KIND = 'staff.timesheets.time_entry'
126
126
 
@@ -1171,7 +1171,7 @@ export default function TimeTrackingEntriesPage() {
1171
1171
  id: 'show-report',
1172
1172
  label: labels.showReport,
1173
1173
  href: row.lockedReportId
1174
- ? `${REPORTS_PATH}?id=${encodeURIComponent(row.lockedReportId)}`
1174
+ ? reportDetailHref(row.lockedReportId)
1175
1175
  : REPORTS_PATH,
1176
1176
  },
1177
1177
  ]}
@@ -56,6 +56,7 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
56
56
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
57
57
  import { createLogger } from '@open-mercato/shared/lib/logger'
58
58
  import { DurationInput } from './DurationInput'
59
+ import { reportDetailHref } from './reportLinks'
59
60
  import { formatDuration } from '../time-tracking/duration'
60
61
  import { parseClock } from '../time-tracking/interval'
61
62
  import { applicableRate, entryAmount } from '../time-tracking/cost'
@@ -102,7 +103,7 @@ function fieldValueSnapshot(value: unknown): string {
102
103
  }
103
104
  export const RATES_FEATURE = 'staff.timesheets.rates.view'
104
105
  export const ENTRIES_LIST_PATH = '/backend/staff/time-tracking/entries'
105
- export const REPORTS_PATH = '/backend/staff/time-tracking/reports'
106
+ export { REPORTS_PATH } from './reportLinks'
106
107
 
107
108
  const DIALOG_QUERY_ROOT = ['staff', 'time-tracking', 'entry-dialog'] as const
108
109
  const DIRECTORY_PAGE_SIZE = 100
@@ -1390,7 +1391,7 @@ function DefaultTimeEntryDialog({
1390
1391
  className="mt-2"
1391
1392
  data-testid="entry-dialog-locked-report"
1392
1393
  onClick={() =>
1393
- window.open(`${REPORTS_PATH}?id=${encodeURIComponent(lockedReportId)}`, '_blank', 'noopener')
1394
+ window.open(reportDetailHref(lockedReportId), '_blank', 'noopener')
1394
1395
  }
1395
1396
  >
1396
1397
  {t('staff.time_tracking.entryDialog.locked.showReport', 'Show the report')}
@@ -0,0 +1,5 @@
1
+ export const REPORTS_PATH = '/backend/staff/time-tracking/reports'
2
+
3
+ export function reportDetailHref(reportId: string): string {
4
+ return `${REPORTS_PATH}/${encodeURIComponent(reportId)}`
5
+ }