@open-mercato/core 0.9.1-develop.7497.1.543ea44c40 → 0.9.1-develop.7513.1.ae2715553f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/audit_logs/components/AuditLogsActions.js +1 -1
- package/dist/modules/audit_logs/components/AuditLogsActions.js.map +2 -2
- package/dist/modules/auth/frontend/reset.js +7 -3
- package/dist/modules/auth/frontend/reset.js.map +2 -2
- package/dist/modules/business_rules/api/rules/route.js +12 -1
- package/dist/modules/business_rules/api/rules/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +17 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +3 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +1 -0
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/components/products/productFormUtils.js +9 -0
- package/dist/modules/catalog/components/products/productFormUtils.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +32 -32
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +22 -22
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +34 -34
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/ecommerce/search.js +1 -2
- package/dist/modules/ecommerce/search.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js +10 -7
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js.map +2 -2
- package/dist/modules/wms/api/interceptors.js +3 -0
- package/dist/modules/wms/api/interceptors.js.map +2 -2
- package/dist/modules/wms/commands/configuration.js +16 -16
- package/dist/modules/wms/commands/configuration.js.map +2 -2
- package/dist/modules/wms/events.js +1 -0
- package/dist/modules/wms/events.js.map +2 -2
- package/dist/modules/wms/lib/catalogInventoryProfileIntent.js +9 -0
- package/dist/modules/wms/lib/catalogInventoryProfileIntent.js.map +2 -2
- package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js +11 -3
- package/dist/modules/wms/widgets/injection/catalog-inventory-profile/widget.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/audit_logs/components/AuditLogsActions.tsx +1 -1
- package/src/modules/auth/frontend/reset.tsx +8 -3
- package/src/modules/business_rules/api/rules/route.ts +11 -0
- package/src/modules/business_rules/i18n/de.json +1 -0
- package/src/modules/business_rules/i18n/en.json +1 -0
- package/src/modules/business_rules/i18n/es.json +1 -0
- package/src/modules/business_rules/i18n/ko.json +1 -0
- package/src/modules/business_rules/i18n/pl.json +1 -0
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +20 -1
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +2 -1
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +1 -0
- package/src/modules/catalog/components/products/productFormUtils.ts +9 -0
- package/src/modules/customers/backend/customers/companies/page.tsx +32 -32
- package/src/modules/customers/backend/customers/deals/page.tsx +22 -22
- package/src/modules/customers/backend/customers/people/page.tsx +34 -34
- package/src/modules/customers/i18n/de.json +12 -0
- package/src/modules/customers/i18n/en.json +12 -0
- package/src/modules/customers/i18n/es.json +12 -0
- package/src/modules/customers/i18n/ko.json +12 -0
- package/src/modules/customers/i18n/pl.json +12 -0
- package/src/modules/ecommerce/i18n/de.json +2 -1
- package/src/modules/ecommerce/i18n/en.json +2 -1
- package/src/modules/ecommerce/i18n/es.json +2 -1
- package/src/modules/ecommerce/i18n/ko.json +2 -1
- package/src/modules/ecommerce/i18n/pl.json +2 -1
- package/src/modules/ecommerce/search.ts +0 -1
- package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +20 -14
- package/src/modules/wms/api/interceptors.ts +9 -0
- package/src/modules/wms/commands/configuration.ts +28 -16
- package/src/modules/wms/events.ts +1 -0
- package/src/modules/wms/lib/catalogInventoryProfileIntent.ts +9 -0
- package/src/modules/wms/widgets/injection/catalog-inventory-profile/widget.ts +13 -4
|
@@ -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 { 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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",
|
|
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; displayName?: 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 const personOptions = 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 // The team list is closed to a caller without team-member access, yet the\n // self filter is seeded for everyone \u2014 name it from the self lookup so its\n // chip never falls back to the raw member id.\n const selfName = selfRes?.ok ? selfRes.result?.member?.displayName : undefined\n if (selfId && selfName && !personOptions.some((option) => option.value === selfId)) {\n personOptions.push({ value: selfId, label: selfName })\n }\n setPeopleOptions(personOptions)\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
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { E } from "../../../generated/entities.ids.generated.js";
|
|
1
2
|
import {
|
|
2
3
|
decodeCatalogInventoryProfileIntent,
|
|
3
4
|
WMS_CATALOG_PROFILE_HEADER
|
|
@@ -26,6 +27,7 @@ const interceptors = [
|
|
|
26
27
|
const rawHeader = request.headers[WMS_CATALOG_PROFILE_HEADER];
|
|
27
28
|
if (!rawHeader) return { ok: true };
|
|
28
29
|
const intent = decodeCatalogInventoryProfileIntent(rawHeader);
|
|
30
|
+
if (intent?.sourceEntityId !== E.catalog.catalog_product) return { ok: true };
|
|
29
31
|
return {
|
|
30
32
|
ok: true,
|
|
31
33
|
metadata: { intent, target: "product" }
|
|
@@ -58,6 +60,7 @@ const interceptors = [
|
|
|
58
60
|
const rawHeader = request.headers[WMS_CATALOG_PROFILE_HEADER];
|
|
59
61
|
if (!rawHeader) return { ok: true };
|
|
60
62
|
const intent = decodeCatalogInventoryProfileIntent(rawHeader);
|
|
63
|
+
if (intent?.sourceEntityId !== E.catalog.catalog_product_variant) return { ok: true };
|
|
61
64
|
return {
|
|
62
65
|
ok: true,
|
|
63
66
|
metadata: { intent, target: "variant" }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/wms/api/interceptors.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ApiInterceptor } from '@open-mercato/shared/lib/crud/api-interceptor'\nimport {\n decodeCatalogInventoryProfileIntent,\n WMS_CATALOG_PROFILE_HEADER,\n} from '../lib/catalogInventoryProfileIntent'\nimport {\n syncCatalogInventoryProfile,\n} from '../lib/catalogInventoryProfileSync'\n\nfunction resolveRecordId(\n requestBody: Record<string, unknown> | undefined,\n responseBody: Record<string, unknown> | undefined,\n): string | null {\n const responseId = responseBody?.id\n if (typeof responseId === 'string' && responseId.trim().length > 0) {\n return responseId\n }\n\n const requestId = requestBody?.id\n if (typeof requestId === 'string' && requestId.trim().length > 0) {\n return requestId\n }\n\n return null\n}\n\nexport const interceptors: ApiInterceptor[] = [\n {\n id: 'wms.catalog-products.inventory-profile-sync',\n targetRoute: 'catalog/products',\n methods: ['POST', 'PUT'],\n priority: 120,\n async before(request) {\n const rawHeader = request.headers[WMS_CATALOG_PROFILE_HEADER]\n if (!rawHeader) return { ok: true }\n\n const intent = decodeCatalogInventoryProfileIntent(rawHeader)\n return {\n ok: true,\n metadata: { intent, target: 'product' },\n }\n },\n async after(request, response, context) {\n if (response.statusCode >= 400) return {}\n\n const metadata = context.metadata as\n | { intent?: ReturnType<typeof decodeCatalogInventoryProfileIntent>; target?: 'product' | 'variant' }\n | undefined\n if (!metadata?.intent) return {}\n\n const recordId = resolveRecordId(request.body, response.body)\n if (!recordId) return {}\n\n await syncCatalogInventoryProfile({\n intent: metadata.intent,\n target: 'product',\n recordId,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n userId: context.userId,\n container: context.container,\n })\n\n return {}\n },\n },\n {\n id: 'wms.catalog-variants.inventory-profile-sync',\n targetRoute: 'catalog/variants',\n methods: ['POST', 'PUT'],\n priority: 120,\n async before(request) {\n const rawHeader = request.headers[WMS_CATALOG_PROFILE_HEADER]\n if (!rawHeader) return { ok: true }\n\n const intent = decodeCatalogInventoryProfileIntent(rawHeader)\n return {\n ok: true,\n metadata: { intent, target: 'variant' },\n }\n },\n async after(request, response, context) {\n if (response.statusCode >= 400) return {}\n\n const metadata = context.metadata as\n | { intent?: ReturnType<typeof decodeCatalogInventoryProfileIntent>; target?: 'product' | 'variant' }\n | undefined\n if (!metadata?.intent) return {}\n\n const recordId = resolveRecordId(request.body, response.body)\n if (!recordId) return {}\n\n await syncCatalogInventoryProfile({\n intent: metadata.intent,\n target: 'variant',\n recordId,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n userId: context.userId,\n container: context.container,\n })\n\n return {}\n },\n },\n]\n\nexport default interceptors\n"],
|
|
5
|
-
"mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OACK;AAEP,SAAS,gBACP,aACA,cACe;AACf,QAAM,aAAa,cAAc;AACjC,MAAI,OAAO,eAAe,YAAY,WAAW,KAAK,EAAE,SAAS,GAAG;AAClE,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,aAAa;AAC/B,MAAI,OAAO,cAAc,YAAY,UAAU,KAAK,EAAE,SAAS,GAAG;AAChE,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,MAAM,eAAiC;AAAA,EAC5C;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,SAAS,CAAC,QAAQ,KAAK;AAAA,IACvB,UAAU;AAAA,IACV,MAAM,OAAO,SAAS;AACpB,YAAM,YAAY,QAAQ,QAAQ,0BAA0B;AAC5D,UAAI,CAAC,UAAW,QAAO,EAAE,IAAI,KAAK;AAElC,YAAM,SAAS,oCAAoC,SAAS;
|
|
4
|
+
"sourcesContent": ["import type { ApiInterceptor } from '@open-mercato/shared/lib/crud/api-interceptor'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n decodeCatalogInventoryProfileIntent,\n WMS_CATALOG_PROFILE_HEADER,\n} from '../lib/catalogInventoryProfileIntent'\nimport {\n syncCatalogInventoryProfile,\n} from '../lib/catalogInventoryProfileSync'\n\nfunction resolveRecordId(\n requestBody: Record<string, unknown> | undefined,\n responseBody: Record<string, unknown> | undefined,\n): string | null {\n const responseId = responseBody?.id\n if (typeof responseId === 'string' && responseId.trim().length > 0) {\n return responseId\n }\n\n const requestId = requestBody?.id\n if (typeof requestId === 'string' && requestId.trim().length > 0) {\n return requestId\n }\n\n return null\n}\n\nexport const interceptors: ApiInterceptor[] = [\n {\n id: 'wms.catalog-products.inventory-profile-sync',\n targetRoute: 'catalog/products',\n methods: ['POST', 'PUT'],\n priority: 120,\n async before(request) {\n const rawHeader = request.headers[WMS_CATALOG_PROFILE_HEADER]\n if (!rawHeader) return { ok: true }\n\n const intent = decodeCatalogInventoryProfileIntent(rawHeader)\n // CrudForm scopes `onBeforeSave` request headers around the whole\n // `onSubmit` callback, so a page whose submit handler also creates\n // other records (e.g. the product-create wizard implicitly creating\n // its default variant) can leak this header onto those unrelated\n // requests. Only honor an intent the product form itself computed.\n if (intent?.sourceEntityId !== E.catalog.catalog_product) return { ok: true }\n return {\n ok: true,\n metadata: { intent, target: 'product' },\n }\n },\n async after(request, response, context) {\n if (response.statusCode >= 400) return {}\n\n const metadata = context.metadata as\n | { intent?: ReturnType<typeof decodeCatalogInventoryProfileIntent>; target?: 'product' | 'variant' }\n | undefined\n if (!metadata?.intent) return {}\n\n const recordId = resolveRecordId(request.body, response.body)\n if (!recordId) return {}\n\n await syncCatalogInventoryProfile({\n intent: metadata.intent,\n target: 'product',\n recordId,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n userId: context.userId,\n container: context.container,\n })\n\n return {}\n },\n },\n {\n id: 'wms.catalog-variants.inventory-profile-sync',\n targetRoute: 'catalog/variants',\n methods: ['POST', 'PUT'],\n priority: 120,\n async before(request) {\n const rawHeader = request.headers[WMS_CATALOG_PROFILE_HEADER]\n if (!rawHeader) return { ok: true }\n\n const intent = decodeCatalogInventoryProfileIntent(rawHeader)\n // See the product interceptor's `before` for why this check exists.\n if (intent?.sourceEntityId !== E.catalog.catalog_product_variant) return { ok: true }\n return {\n ok: true,\n metadata: { intent, target: 'variant' },\n }\n },\n async after(request, response, context) {\n if (response.statusCode >= 400) return {}\n\n const metadata = context.metadata as\n | { intent?: ReturnType<typeof decodeCatalogInventoryProfileIntent>; target?: 'product' | 'variant' }\n | undefined\n if (!metadata?.intent) return {}\n\n const recordId = resolveRecordId(request.body, response.body)\n if (!recordId) return {}\n\n await syncCatalogInventoryProfile({\n intent: metadata.intent,\n target: 'variant',\n recordId,\n organizationId: context.organizationId,\n tenantId: context.tenantId,\n userId: context.userId,\n container: context.container,\n })\n\n return {}\n },\n },\n]\n\nexport default interceptors\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,SAAS;AAClB;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OACK;AAEP,SAAS,gBACP,aACA,cACe;AACf,QAAM,aAAa,cAAc;AACjC,MAAI,OAAO,eAAe,YAAY,WAAW,KAAK,EAAE,SAAS,GAAG;AAClE,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,aAAa;AAC/B,MAAI,OAAO,cAAc,YAAY,UAAU,KAAK,EAAE,SAAS,GAAG;AAChE,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAEO,MAAM,eAAiC;AAAA,EAC5C;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,SAAS,CAAC,QAAQ,KAAK;AAAA,IACvB,UAAU;AAAA,IACV,MAAM,OAAO,SAAS;AACpB,YAAM,YAAY,QAAQ,QAAQ,0BAA0B;AAC5D,UAAI,CAAC,UAAW,QAAO,EAAE,IAAI,KAAK;AAElC,YAAM,SAAS,oCAAoC,SAAS;AAM5D,UAAI,QAAQ,mBAAmB,EAAE,QAAQ,gBAAiB,QAAO,EAAE,IAAI,KAAK;AAC5E,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,UAAU,EAAE,QAAQ,QAAQ,UAAU;AAAA,MACxC;AAAA,IACF;AAAA,IACA,MAAM,MAAM,SAAS,UAAU,SAAS;AACtC,UAAI,SAAS,cAAc,IAAK,QAAO,CAAC;AAExC,YAAM,WAAW,QAAQ;AAGzB,UAAI,CAAC,UAAU,OAAQ,QAAO,CAAC;AAE/B,YAAM,WAAW,gBAAgB,QAAQ,MAAM,SAAS,IAAI;AAC5D,UAAI,CAAC,SAAU,QAAO,CAAC;AAEvB,YAAM,4BAA4B;AAAA,QAChC,QAAQ,SAAS;AAAA,QACjB,QAAQ;AAAA,QACR;AAAA,QACA,gBAAgB,QAAQ;AAAA,QACxB,UAAU,QAAQ;AAAA,QAClB,QAAQ,QAAQ;AAAA,QAChB,WAAW,QAAQ;AAAA,MACrB,CAAC;AAED,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,aAAa;AAAA,IACb,SAAS,CAAC,QAAQ,KAAK;AAAA,IACvB,UAAU;AAAA,IACV,MAAM,OAAO,SAAS;AACpB,YAAM,YAAY,QAAQ,QAAQ,0BAA0B;AAC5D,UAAI,CAAC,UAAW,QAAO,EAAE,IAAI,KAAK;AAElC,YAAM,SAAS,oCAAoC,SAAS;AAE5D,UAAI,QAAQ,mBAAmB,EAAE,QAAQ,wBAAyB,QAAO,EAAE,IAAI,KAAK;AACpF,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,UAAU,EAAE,QAAQ,QAAQ,UAAU;AAAA,MACxC;AAAA,IACF;AAAA,IACA,MAAM,MAAM,SAAS,UAAU,SAAS;AACtC,UAAI,SAAS,cAAc,IAAK,QAAO,CAAC;AAExC,YAAM,WAAW,QAAQ;AAGzB,UAAI,CAAC,UAAU,OAAQ,QAAO,CAAC;AAE/B,YAAM,WAAW,gBAAgB,QAAQ,MAAM,SAAS,IAAI;AAC5D,UAAI,CAAC,SAAU,QAAO,CAAC;AAEvB,YAAM,4BAA4B;AAAA,QAChC,QAAQ,SAAS;AAAA,QACjB,QAAQ;AAAA,QACR;AAAA,QACA,gBAAgB,QAAQ;AAAA,QACxB,UAAU,QAAQ;AAAA,QAClB,QAAQ,QAAQ;AAAA,QAChB,WAAW,QAAQ;AAAA,MACrB,CAAC;AAED,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;AAEA,IAAO,uBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1185,6 +1185,16 @@ function restoreInventoryProfileFromSnapshot(em, before, record) {
|
|
|
1185
1185
|
record.metadata = before.metadata;
|
|
1186
1186
|
record.deletedAt = null;
|
|
1187
1187
|
}
|
|
1188
|
+
function emitInventoryProfileEvent(eventId, profile) {
|
|
1189
|
+
void emitWmsEvent(eventId, {
|
|
1190
|
+
id: profile.id,
|
|
1191
|
+
profileId: profile.id,
|
|
1192
|
+
catalogProductId: profile.catalogProductId,
|
|
1193
|
+
catalogVariantId: profile.catalogVariantId ?? null,
|
|
1194
|
+
tenantId: profile.tenantId,
|
|
1195
|
+
organizationId: profile.organizationId
|
|
1196
|
+
}).catch(() => void 0);
|
|
1197
|
+
}
|
|
1188
1198
|
const createProductInventoryProfileCommand = {
|
|
1189
1199
|
id: "wms.inventoryProfiles.create",
|
|
1190
1200
|
async execute(rawInput, ctx) {
|
|
@@ -1208,14 +1218,7 @@ const createProductInventoryProfileCommand = {
|
|
|
1208
1218
|
metadata: toJsonValue(parsed.metadata)
|
|
1209
1219
|
});
|
|
1210
1220
|
await em.persist(profile).flush();
|
|
1211
|
-
|
|
1212
|
-
id: profile.id,
|
|
1213
|
-
profileId: profile.id,
|
|
1214
|
-
catalogProductId: profile.catalogProductId,
|
|
1215
|
-
catalogVariantId: profile.catalogVariantId ?? null,
|
|
1216
|
-
tenantId: profile.tenantId,
|
|
1217
|
-
organizationId: profile.organizationId
|
|
1218
|
-
}).catch(() => void 0);
|
|
1221
|
+
emitInventoryProfileEvent("wms.inventory_profile.created", profile);
|
|
1219
1222
|
return { profileId: profile.id };
|
|
1220
1223
|
},
|
|
1221
1224
|
captureAfter: async (_input, result, ctx) => {
|
|
@@ -1244,6 +1247,7 @@ const createProductInventoryProfileCommand = {
|
|
|
1244
1247
|
ensureOrganizationScope(ctx, record.organizationId);
|
|
1245
1248
|
record.deletedAt = /* @__PURE__ */ new Date();
|
|
1246
1249
|
await em.flush();
|
|
1250
|
+
emitInventoryProfileEvent("wms.inventory_profile.deleted", record);
|
|
1247
1251
|
}
|
|
1248
1252
|
};
|
|
1249
1253
|
const updateProductInventoryProfileCommand = {
|
|
@@ -1278,14 +1282,7 @@ const updateProductInventoryProfileCommand = {
|
|
|
1278
1282
|
if (parsed.safetyStock !== void 0) profile.safetyStock = toNumericString(parsed.safetyStock);
|
|
1279
1283
|
if (parsed.metadata !== void 0) profile.metadata = toJsonValue(parsed.metadata);
|
|
1280
1284
|
await em.flush();
|
|
1281
|
-
|
|
1282
|
-
id: profile.id,
|
|
1283
|
-
profileId: profile.id,
|
|
1284
|
-
catalogProductId: profile.catalogProductId,
|
|
1285
|
-
catalogVariantId: profile.catalogVariantId ?? null,
|
|
1286
|
-
tenantId: profile.tenantId,
|
|
1287
|
-
organizationId: profile.organizationId
|
|
1288
|
-
}).catch(() => void 0);
|
|
1285
|
+
emitInventoryProfileEvent("wms.inventory_profile.updated", profile);
|
|
1289
1286
|
return { profileId: profile.id };
|
|
1290
1287
|
},
|
|
1291
1288
|
captureAfter: async (_input, result, ctx) => {
|
|
@@ -1335,6 +1332,7 @@ const updateProductInventoryProfileCommand = {
|
|
|
1335
1332
|
restoreInventoryProfileFromSnapshot(em, before, record);
|
|
1336
1333
|
}
|
|
1337
1334
|
await em.flush();
|
|
1335
|
+
emitInventoryProfileEvent("wms.inventory_profile.updated", before);
|
|
1338
1336
|
}
|
|
1339
1337
|
};
|
|
1340
1338
|
const deleteProductInventoryProfileCommand = {
|
|
@@ -1351,6 +1349,7 @@ const deleteProductInventoryProfileCommand = {
|
|
|
1351
1349
|
const profile = await loadProfile(em, ctx, profileId);
|
|
1352
1350
|
profile.deletedAt = /* @__PURE__ */ new Date();
|
|
1353
1351
|
await em.flush();
|
|
1352
|
+
emitInventoryProfileEvent("wms.inventory_profile.deleted", profile);
|
|
1354
1353
|
return { profileId: profile.id };
|
|
1355
1354
|
},
|
|
1356
1355
|
buildLog: async ({ input, result, ctx, snapshots }) => {
|
|
@@ -1395,6 +1394,7 @@ const deleteProductInventoryProfileCommand = {
|
|
|
1395
1394
|
restoreInventoryProfileFromSnapshot(em, before, record);
|
|
1396
1395
|
}
|
|
1397
1396
|
await em.flush();
|
|
1397
|
+
emitInventoryProfileEvent("wms.inventory_profile.created", before);
|
|
1398
1398
|
}
|
|
1399
1399
|
};
|
|
1400
1400
|
function restoreInventoryLotFromSnapshot(_em, before, record) {
|