@open-mercato/core 0.9.1-develop.7462.1.94c09f158f → 0.9.1-develop.7466.1.2b0cea0016
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/customers/components/detail/ActivitiesSection.js +8 -1
- package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/entries/page.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js +3 -2
- package/dist/modules/staff/lib/time-tracking-ui/TimeEntryDialog.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/reportLinks.js +9 -0
- package/dist/modules/staff/lib/time-tracking-ui/reportLinks.js.map +7 -0
- package/package.json +7 -7
- package/src/modules/customers/components/detail/ActivitiesSection.tsx +8 -1
- package/src/modules/staff/backend/staff/time-tracking/entries/page.tsx +2 -2
- package/src/modules/staff/lib/time-tracking-ui/TimeEntryDialog.tsx +3 -2
- package/src/modules/staff/lib/time-tracking-ui/reportLinks.ts +5 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -74,6 +74,7 @@ function ActivitiesSection({
|
|
|
74
74
|
useCanonicalInteractions = false,
|
|
75
75
|
onActionChange,
|
|
76
76
|
onLoadingChange,
|
|
77
|
+
onDataRefresh,
|
|
77
78
|
refreshKey = 0,
|
|
78
79
|
onEditActivity,
|
|
79
80
|
runGuardedMutation,
|
|
@@ -232,8 +233,14 @@ function ActivitiesSection({
|
|
|
232
233
|
} catch (err) {
|
|
233
234
|
logger.warn("Mark done failed", { component: "ActivitiesSection", activityId, err });
|
|
234
235
|
flash(t("customers.activities.actions.markDoneError", "Could not mark activity as done"), "error");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
await Promise.resolve(onDataRefresh?.());
|
|
240
|
+
} catch (err) {
|
|
241
|
+
logger.warn("Parent refresh after mark done failed", { component: "ActivitiesSection", activityId, err });
|
|
235
242
|
}
|
|
236
|
-
}, [loadActivities, runGuardedMutation, t]);
|
|
243
|
+
}, [loadActivities, onDataRefresh, runGuardedMutation, t]);
|
|
237
244
|
const resolvedUserIdsRef = React.useRef(/* @__PURE__ */ new Set());
|
|
238
245
|
React.useEffect(() => {
|
|
239
246
|
loadActivities().then(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/ActivitiesSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Clock, Search } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport type { SectionAction, TabEmptyStateConfig } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { ActivityTimelineFilters } from './ActivityTimelineFilters'\nimport { ActivityTimeline } from './ActivityTimeline'\nimport { resolveAuthorUserNames } from './authorUserLookup'\nimport type { ActivitySummary, InteractionSummary } from './types'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\nexport type ActivitiesSectionProps = {\n entityId: string | null\n entityName?: string | null\n dealId?: string | null\n useCanonicalInteractions?: boolean\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n dealOptions?: Array<{ id: string; label: string }>\n entityOptions?: Array<{ id: string; label: string }>\n defaultEntityId?: string | null\n runGuardedMutation?: GuardedMutationRunner\n refreshKey?: number\n onEditActivity?: (activity: InteractionSummary) => void\n /** Interaction type hidden from the timeline by default ('task' unless overridden); pass null to show every type. */\n excludeInteractionType?: string | null\n}\n\nfunction toDateOnly(value: string | null | undefined): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)\n}\n\nfunction normalizeLegacyActivity(activity: ActivitySummary): InteractionSummary {\n return {\n id: activity.id,\n interactionType: activity.activityType,\n title: activity.subject ?? null,\n body: activity.body ?? null,\n status: 'done',\n scheduledAt: null,\n occurredAt: activity.occurredAt ?? null,\n priority: null,\n authorUserId: activity.authorUserId ?? null,\n ownerUserId: null,\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n source: 'legacy-activity',\n entityId: activity.entityId ?? null,\n dealId: activity.dealId ?? null,\n organizationId: null,\n tenantId: null,\n authorName: activity.authorName ?? null,\n authorEmail: activity.authorEmail ?? null,\n dealTitle: activity.dealTitle ?? null,\n customValues: activity.customValues ?? null,\n createdAt: activity.createdAt,\n updatedAt: activity.createdAt,\n }\n}\n\nfunction sortTimelineActivities(items: InteractionSummary[]): InteractionSummary[] {\n const now = Date.now()\n return [...items].sort((left, right) => {\n const leftScheduled = left.scheduledAt ? new Date(left.scheduledAt).getTime() : Number.NaN\n const rightScheduled = right.scheduledAt ? new Date(right.scheduledAt).getTime() : Number.NaN\n const leftIsPlanned = left.status === 'planned' && Number.isFinite(leftScheduled)\n const rightIsPlanned = right.status === 'planned' && Number.isFinite(rightScheduled)\n const leftIsUpcoming = leftIsPlanned && leftScheduled >= now\n const rightIsUpcoming = rightIsPlanned && rightScheduled >= now\n\n if (leftIsUpcoming !== rightIsUpcoming) {\n return leftIsUpcoming ? -1 : 1\n }\n\n if (leftIsUpcoming && rightIsUpcoming) {\n if (leftScheduled === rightScheduled) return left.id.localeCompare(right.id)\n return leftScheduled - rightScheduled\n }\n\n const leftTime = left.occurredAt ?? left.createdAt\n const rightTime = right.occurredAt ?? right.createdAt\n const compare = rightTime.localeCompare(leftTime)\n if (compare !== 0) return compare\n return right.id.localeCompare(left.id)\n })\n}\n\nexport function ActivitiesSection({\n entityId,\n entityName,\n dealId,\n useCanonicalInteractions = false,\n onActionChange,\n onLoadingChange,\n refreshKey = 0,\n onEditActivity,\n runGuardedMutation,\n excludeInteractionType = 'task',\n}: ActivitiesSectionProps) {\n const t = useT()\n const [filterTypes, setFilterTypes] = React.useState<string[]>([])\n const [filterDateFrom, setFilterDateFrom] = React.useState('')\n const [filterDateTo, setFilterDateTo] = React.useState('')\n const [searchTerm, setSearchTerm] = React.useState('')\n const [activities, setActivities] = React.useState<InteractionSummary[]>([])\n const [loading, setLoading] = React.useState(false)\n const [hasMore, setHasMore] = React.useState(false)\n const [loadedPages, setLoadedPages] = React.useState(1)\n const searchInputRef = React.useRef<HTMLInputElement>(null)\n\n React.useEffect(() => {\n if (!entityId) return\n function handleShortcut(event: KeyboardEvent) {\n if ((event.metaKey || event.ctrlKey) && event.key === '1') {\n event.preventDefault()\n searchInputRef.current?.focus()\n }\n }\n window.addEventListener('keydown', handleShortcut)\n return () => window.removeEventListener('keydown', handleShortcut)\n }, [entityId])\n\n const visibleActivities = React.useMemo(() => {\n const term = searchTerm.trim().toLowerCase()\n if (!term) return activities\n return activities.filter((activity) => {\n const haystack = [\n activity.title,\n activity.body,\n activity.authorName,\n activity.dealTitle,\n activity.interactionType,\n ]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(term)\n })\n }, [activities, searchTerm])\n\n React.useEffect(() => {\n onActionChange?.(null)\n return () => onActionChange?.(null)\n }, [onActionChange])\n\n React.useEffect(() => {\n onLoadingChange?.(loading)\n }, [loading, onLoadingChange])\n\n const loadActivities = React.useCallback(async () => {\n if (!entityId) {\n setActivities([])\n return\n }\n\n setLoading(true)\n try {\n // Always fetch canonical interactions (new activities are always created here)\n const excludedFilterActive = excludeInteractionType ? filterTypes.includes(excludeInteractionType) : true\n const canonicalParams = new URLSearchParams({\n entityId,\n limit: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n // Hide the configured type (tasks by default \u2014 they have their own tab)\n // from the activity timeline, but lift the exclusion when the user\n // explicitly toggled that type's chip on (mirrors\n // `ActivityHistorySection.tsx` after the #1805 fix).\n if (excludeInteractionType && !excludedFilterActive) canonicalParams.set('excludeInteractionType', excludeInteractionType)\n if (dealId) canonicalParams.set('dealId', dealId)\n if (filterTypes.length > 0) canonicalParams.set('type', filterTypes.join(','))\n if (filterDateFrom) canonicalParams.set('from', filterDateFrom)\n if (filterDateTo) canonicalParams.set('to', filterDateTo)\n\n const canonicalItems: InteractionSummary[] = []\n let canonicalCursor: string | undefined\n let canonicalHasMore = false\n let pageIndex = 0\n do {\n const params = new URLSearchParams(canonicalParams)\n if (canonicalCursor) params.set('cursor', canonicalCursor)\n const canonicalPayload = await readApiResultOrThrow<{ items?: InteractionSummary[]; nextCursor?: string }>(\n `/api/customers/interactions?${params.toString()}`,\n ).catch(() => ({ items: [] as InteractionSummary[], nextCursor: undefined }))\n canonicalItems.push(...(Array.isArray(canonicalPayload?.items) ? canonicalPayload.items : []))\n canonicalCursor = typeof canonicalPayload?.nextCursor === 'string' ? canonicalPayload.nextCursor : undefined\n canonicalHasMore = Boolean(canonicalCursor)\n pageIndex += 1\n } while (canonicalCursor && pageIndex < loadedPages)\n\n if (useCanonicalInteractions) {\n setActivities(sortTimelineActivities(canonicalItems))\n setHasMore(canonicalHasMore)\n return\n }\n\n // In legacy mode, also fetch legacy activities and merge with canonical.\n // Legacy fallback uses known page numbers, so request every page up front\n // and resolve them together instead of awaiting each one sequentially.\n const legacyPageNumbers = Array.from({ length: loadedPages }, (_, index) => index + 1)\n const legacyPayloads = await Promise.all(\n legacyPageNumbers.map((legacyPage) => {\n const legacyParams = new URLSearchParams({\n entityId,\n page: String(legacyPage),\n pageSize: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n if (dealId) legacyParams.set('dealId', dealId)\n return readApiResultOrThrow<{ items?: ActivitySummary[]; totalPages?: number }>(\n `/api/customers/activities?${legacyParams.toString()}`,\n ).catch(() => ({ items: [] as ActivitySummary[], totalPages: 1 }))\n }),\n )\n // Merge in page order so timeline ordering stays stable regardless of\n // which request settles first.\n const legacyItems: InteractionSummary[] = []\n let legacyTotalPages = 1\n for (const legacyPayload of legacyPayloads) {\n legacyItems.push(...(Array.isArray(legacyPayload?.items) ? legacyPayload.items.map(normalizeLegacyActivity) : []))\n legacyTotalPages = typeof legacyPayload?.totalPages === 'number' ? legacyPayload.totalPages : legacyTotalPages\n }\n const legacyFiltered = legacyItems.filter((entry) => {\n if (filterTypes.length > 0 && !filterTypes.includes(entry.interactionType)) return false\n const dateOnly = toDateOnly(entry.occurredAt ?? entry.createdAt)\n if (filterDateFrom && dateOnly < filterDateFrom) return false\n if (filterDateTo && dateOnly > filterDateTo) return false\n return true\n })\n\n // Merge and deduplicate by id, sort newest first\n const seen = new Set<string>()\n const merged: InteractionSummary[] = []\n for (const item of [...canonicalItems, ...legacyFiltered]) {\n if (!seen.has(item.id)) {\n seen.add(item.id)\n merged.push(item)\n }\n }\n setActivities(sortTimelineActivities(merged))\n setHasMore(canonicalHasMore || legacyTotalPages > loadedPages)\n } catch (error) {\n logger.error('customers.activities.history failed', { err: error })\n flash(t('customers.activities.loadFailed', 'Failed to load activities.'), 'error')\n setActivities([])\n setHasMore(false)\n } finally {\n setLoading(false)\n }\n }, [dealId, entityId, excludeInteractionType, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t])\n\n React.useEffect(() => {\n setLoadedPages(1)\n }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, useCanonicalInteractions])\n\n const handleMarkDone = React.useCallback(async (activityId: string) => {\n try {\n const operation = () =>\n apiCallOrThrow('/api/customers/interactions/complete', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: activityId, occurredAt: new Date().toISOString() }),\n })\n if (runGuardedMutation) {\n await runGuardedMutation(operation, {\n id: activityId,\n status: 'done',\n operation: 'completeActivity',\n })\n } else {\n await operation()\n }\n flash(t('customers.activities.actions.markDoneSuccess', 'Activity marked done'), 'success')\n await loadActivities()\n } catch (err) {\n logger.warn('Mark done failed', { component: 'ActivitiesSection', activityId, err })\n flash(t('customers.activities.actions.markDoneError', 'Could not mark activity as done'), 'error')\n }\n }, [loadActivities, runGuardedMutation, t])\n\n const resolvedUserIdsRef = React.useRef(new Set<string>())\n\n // Resolve missing author names from user IDs\n React.useEffect(() => {\n loadActivities()\n .then(() => { resolvedUserIdsRef.current = new Set() })\n .catch((err) => logger.warn('loadActivities failed', { component: 'ActivitiesSection', err }))\n }, [loadActivities])\n\n React.useEffect(() => {\n const unresolvedIds = new Set<string>()\n for (const a of activities) {\n if (a.authorUserId && !a.authorName && !resolvedUserIdsRef.current.has(a.authorUserId)) {\n unresolvedIds.add(a.authorUserId)\n }\n }\n if (unresolvedIds.size === 0) return\n\n const controller = new AbortController()\n resolveAuthorUserNames([...unresolvedIds], controller.signal)\n .then(({ names: nameMap, resolvedIds }) => {\n if (controller.signal.aborted) return\n // Only ids the server actually answered for are remembered. Marking an id whose request\n // failed \u2014 or one the server silently dropped past its `?ids=` cap \u2014 would leave that\n // author blank for the life of the component, even though the next attempt would work.\n for (const uid of resolvedIds) resolvedUserIdsRef.current.add(uid)\n if (nameMap.size > 0) {\n setActivities((prev) =>\n prev.map((a) => {\n if (a.authorUserId && !a.authorName && nameMap.has(a.authorUserId)) {\n return { ...a, authorName: nameMap.get(a.authorUserId) ?? null }\n }\n return a\n }),\n )\n }\n })\n .catch((err) => logger.warn('resolve author names failed', { component: 'ActivitiesSection', err }))\n return () => controller.abort()\n }, [activities])\n\n const totalCount = activities.length\n const visibleCount = visibleActivities.length\n\n return (\n <div className=\"flex flex-col gap-3.5 rounded-[10px] border border-border bg-card pt-4 pb-[18px] px-[18px]\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-[15px] text-muted-foreground\" />\n <h3 className=\"text-[13px] font-semibold text-foreground\">\n {entityName\n ? t('customers.timeline.history.title', 'Interaction history with {{name}}', { name: entityName })\n : t('customers.timeline.history.titleGeneric', 'Interaction history')}\n </h3>\n </div>\n\n <label className=\"relative flex items-center\">\n <Search className=\"pointer-events-none absolute left-2.5 size-5 text-muted-foreground\" aria-hidden />\n <input\n ref={searchInputRef}\n type=\"search\"\n value={searchTerm}\n onChange={(event) => setSearchTerm(event.target.value)}\n placeholder={t('customers.timeline.history.searchPlaceholder', 'Search...')}\n aria-label={t('customers.timeline.history.searchAriaLabel', 'Search interaction history')}\n className=\"h-9 w-full rounded-[10px] border border-border bg-card pl-9 pr-14 text-sm text-foreground shadow-xs placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden\"\n />\n <Kbd className=\"pointer-events-none absolute right-2 hidden text-[11px] uppercase tracking-[0.48px] sm:inline-flex\">\n \u23181\n </Kbd>\n </label>\n\n <ActivityTimelineFilters\n entityId={entityId}\n activeTypes={filterTypes}\n dateFrom={filterDateFrom}\n dateTo={filterDateTo}\n onTypesChange={setFilterTypes}\n onDateFromChange={setFilterDateFrom}\n onDateToChange={setFilterDateTo}\n onReset={() => {\n setFilterTypes([])\n setFilterDateFrom('')\n setFilterDateTo('')\n }}\n />\n\n {loading && totalCount === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/70 px-4 py-8 text-sm text-muted-foreground\">\n {t('customers.people.detail.activities.loading', 'Loading activities\u2026')}\n </div>\n ) : (\n <>\n <ActivityTimeline\n activities={visibleActivities}\n onEdit={onEditActivity}\n onMarkDone={handleMarkDone}\n />\n {totalCount > 0 ? (\n <div className=\"flex items-center justify-between gap-3 border-t border-border/60 pt-3\">\n <span className=\"text-xs text-muted-foreground\">\n {searchTerm.trim()\n ? t('customers.activities.seeMatching', 'Showing {visible} of {total} activities', {\n visible: visibleCount,\n total: totalCount,\n })\n : t('customers.activities.seeAll', 'See all {count} activities', { count: totalCount })}\n </span>\n {hasMore ? (\n <Button type=\"button\" variant=\"link\" size=\"sm\" onClick={() => setLoadedPages((value) => value + 1)}>\n {t('customers.activities.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n ) : null}\n </>\n )}\n </div>\n )\n}\n\nexport default ActivitiesSection\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Clock, Search } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport type { SectionAction, TabEmptyStateConfig } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { ActivityTimelineFilters } from './ActivityTimelineFilters'\nimport { ActivityTimeline } from './ActivityTimeline'\nimport { resolveAuthorUserNames } from './authorUserLookup'\nimport type { ActivitySummary, InteractionSummary } from './types'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\nexport type ActivitiesSectionProps = {\n entityId: string | null\n entityName?: string | null\n dealId?: string | null\n useCanonicalInteractions?: boolean\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n dealOptions?: Array<{ id: string; label: string }>\n entityOptions?: Array<{ id: string; label: string }>\n defaultEntityId?: string | null\n runGuardedMutation?: GuardedMutationRunner\n refreshKey?: number\n onEditActivity?: (activity: InteractionSummary) => void\n /** Interaction type hidden from the timeline by default ('task' unless overridden); pass null to show every type. */\n excludeInteractionType?: string | null\n}\n\nfunction toDateOnly(value: string | null | undefined): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)\n}\n\nfunction normalizeLegacyActivity(activity: ActivitySummary): InteractionSummary {\n return {\n id: activity.id,\n interactionType: activity.activityType,\n title: activity.subject ?? null,\n body: activity.body ?? null,\n status: 'done',\n scheduledAt: null,\n occurredAt: activity.occurredAt ?? null,\n priority: null,\n authorUserId: activity.authorUserId ?? null,\n ownerUserId: null,\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n source: 'legacy-activity',\n entityId: activity.entityId ?? null,\n dealId: activity.dealId ?? null,\n organizationId: null,\n tenantId: null,\n authorName: activity.authorName ?? null,\n authorEmail: activity.authorEmail ?? null,\n dealTitle: activity.dealTitle ?? null,\n customValues: activity.customValues ?? null,\n createdAt: activity.createdAt,\n updatedAt: activity.createdAt,\n }\n}\n\nfunction sortTimelineActivities(items: InteractionSummary[]): InteractionSummary[] {\n const now = Date.now()\n return [...items].sort((left, right) => {\n const leftScheduled = left.scheduledAt ? new Date(left.scheduledAt).getTime() : Number.NaN\n const rightScheduled = right.scheduledAt ? new Date(right.scheduledAt).getTime() : Number.NaN\n const leftIsPlanned = left.status === 'planned' && Number.isFinite(leftScheduled)\n const rightIsPlanned = right.status === 'planned' && Number.isFinite(rightScheduled)\n const leftIsUpcoming = leftIsPlanned && leftScheduled >= now\n const rightIsUpcoming = rightIsPlanned && rightScheduled >= now\n\n if (leftIsUpcoming !== rightIsUpcoming) {\n return leftIsUpcoming ? -1 : 1\n }\n\n if (leftIsUpcoming && rightIsUpcoming) {\n if (leftScheduled === rightScheduled) return left.id.localeCompare(right.id)\n return leftScheduled - rightScheduled\n }\n\n const leftTime = left.occurredAt ?? left.createdAt\n const rightTime = right.occurredAt ?? right.createdAt\n const compare = rightTime.localeCompare(leftTime)\n if (compare !== 0) return compare\n return right.id.localeCompare(left.id)\n })\n}\n\nexport function ActivitiesSection({\n entityId,\n entityName,\n dealId,\n useCanonicalInteractions = false,\n onActionChange,\n onLoadingChange,\n onDataRefresh,\n refreshKey = 0,\n onEditActivity,\n runGuardedMutation,\n excludeInteractionType = 'task',\n}: ActivitiesSectionProps) {\n const t = useT()\n const [filterTypes, setFilterTypes] = React.useState<string[]>([])\n const [filterDateFrom, setFilterDateFrom] = React.useState('')\n const [filterDateTo, setFilterDateTo] = React.useState('')\n const [searchTerm, setSearchTerm] = React.useState('')\n const [activities, setActivities] = React.useState<InteractionSummary[]>([])\n const [loading, setLoading] = React.useState(false)\n const [hasMore, setHasMore] = React.useState(false)\n const [loadedPages, setLoadedPages] = React.useState(1)\n const searchInputRef = React.useRef<HTMLInputElement>(null)\n\n React.useEffect(() => {\n if (!entityId) return\n function handleShortcut(event: KeyboardEvent) {\n if ((event.metaKey || event.ctrlKey) && event.key === '1') {\n event.preventDefault()\n searchInputRef.current?.focus()\n }\n }\n window.addEventListener('keydown', handleShortcut)\n return () => window.removeEventListener('keydown', handleShortcut)\n }, [entityId])\n\n const visibleActivities = React.useMemo(() => {\n const term = searchTerm.trim().toLowerCase()\n if (!term) return activities\n return activities.filter((activity) => {\n const haystack = [\n activity.title,\n activity.body,\n activity.authorName,\n activity.dealTitle,\n activity.interactionType,\n ]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(term)\n })\n }, [activities, searchTerm])\n\n React.useEffect(() => {\n onActionChange?.(null)\n return () => onActionChange?.(null)\n }, [onActionChange])\n\n React.useEffect(() => {\n onLoadingChange?.(loading)\n }, [loading, onLoadingChange])\n\n const loadActivities = React.useCallback(async () => {\n if (!entityId) {\n setActivities([])\n return\n }\n\n setLoading(true)\n try {\n // Always fetch canonical interactions (new activities are always created here)\n const excludedFilterActive = excludeInteractionType ? filterTypes.includes(excludeInteractionType) : true\n const canonicalParams = new URLSearchParams({\n entityId,\n limit: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n // Hide the configured type (tasks by default \u2014 they have their own tab)\n // from the activity timeline, but lift the exclusion when the user\n // explicitly toggled that type's chip on (mirrors\n // `ActivityHistorySection.tsx` after the #1805 fix).\n if (excludeInteractionType && !excludedFilterActive) canonicalParams.set('excludeInteractionType', excludeInteractionType)\n if (dealId) canonicalParams.set('dealId', dealId)\n if (filterTypes.length > 0) canonicalParams.set('type', filterTypes.join(','))\n if (filterDateFrom) canonicalParams.set('from', filterDateFrom)\n if (filterDateTo) canonicalParams.set('to', filterDateTo)\n\n const canonicalItems: InteractionSummary[] = []\n let canonicalCursor: string | undefined\n let canonicalHasMore = false\n let pageIndex = 0\n do {\n const params = new URLSearchParams(canonicalParams)\n if (canonicalCursor) params.set('cursor', canonicalCursor)\n const canonicalPayload = await readApiResultOrThrow<{ items?: InteractionSummary[]; nextCursor?: string }>(\n `/api/customers/interactions?${params.toString()}`,\n ).catch(() => ({ items: [] as InteractionSummary[], nextCursor: undefined }))\n canonicalItems.push(...(Array.isArray(canonicalPayload?.items) ? canonicalPayload.items : []))\n canonicalCursor = typeof canonicalPayload?.nextCursor === 'string' ? canonicalPayload.nextCursor : undefined\n canonicalHasMore = Boolean(canonicalCursor)\n pageIndex += 1\n } while (canonicalCursor && pageIndex < loadedPages)\n\n if (useCanonicalInteractions) {\n setActivities(sortTimelineActivities(canonicalItems))\n setHasMore(canonicalHasMore)\n return\n }\n\n // In legacy mode, also fetch legacy activities and merge with canonical.\n // Legacy fallback uses known page numbers, so request every page up front\n // and resolve them together instead of awaiting each one sequentially.\n const legacyPageNumbers = Array.from({ length: loadedPages }, (_, index) => index + 1)\n const legacyPayloads = await Promise.all(\n legacyPageNumbers.map((legacyPage) => {\n const legacyParams = new URLSearchParams({\n entityId,\n page: String(legacyPage),\n pageSize: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n if (dealId) legacyParams.set('dealId', dealId)\n return readApiResultOrThrow<{ items?: ActivitySummary[]; totalPages?: number }>(\n `/api/customers/activities?${legacyParams.toString()}`,\n ).catch(() => ({ items: [] as ActivitySummary[], totalPages: 1 }))\n }),\n )\n // Merge in page order so timeline ordering stays stable regardless of\n // which request settles first.\n const legacyItems: InteractionSummary[] = []\n let legacyTotalPages = 1\n for (const legacyPayload of legacyPayloads) {\n legacyItems.push(...(Array.isArray(legacyPayload?.items) ? legacyPayload.items.map(normalizeLegacyActivity) : []))\n legacyTotalPages = typeof legacyPayload?.totalPages === 'number' ? legacyPayload.totalPages : legacyTotalPages\n }\n const legacyFiltered = legacyItems.filter((entry) => {\n if (filterTypes.length > 0 && !filterTypes.includes(entry.interactionType)) return false\n const dateOnly = toDateOnly(entry.occurredAt ?? entry.createdAt)\n if (filterDateFrom && dateOnly < filterDateFrom) return false\n if (filterDateTo && dateOnly > filterDateTo) return false\n return true\n })\n\n // Merge and deduplicate by id, sort newest first\n const seen = new Set<string>()\n const merged: InteractionSummary[] = []\n for (const item of [...canonicalItems, ...legacyFiltered]) {\n if (!seen.has(item.id)) {\n seen.add(item.id)\n merged.push(item)\n }\n }\n setActivities(sortTimelineActivities(merged))\n setHasMore(canonicalHasMore || legacyTotalPages > loadedPages)\n } catch (error) {\n logger.error('customers.activities.history failed', { err: error })\n flash(t('customers.activities.loadFailed', 'Failed to load activities.'), 'error')\n setActivities([])\n setHasMore(false)\n } finally {\n setLoading(false)\n }\n }, [dealId, entityId, excludeInteractionType, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t])\n\n React.useEffect(() => {\n setLoadedPages(1)\n }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, useCanonicalInteractions])\n\n const handleMarkDone = React.useCallback(async (activityId: string) => {\n try {\n const operation = () =>\n apiCallOrThrow('/api/customers/interactions/complete', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: activityId, occurredAt: new Date().toISOString() }),\n })\n if (runGuardedMutation) {\n await runGuardedMutation(operation, {\n id: activityId,\n status: 'done',\n operation: 'completeActivity',\n })\n } else {\n await operation()\n }\n flash(t('customers.activities.actions.markDoneSuccess', 'Activity marked done'), 'success')\n await loadActivities()\n } catch (err) {\n logger.warn('Mark done failed', { component: 'ActivitiesSection', activityId, err })\n flash(t('customers.activities.actions.markDoneError', 'Could not mark activity as done'), 'error')\n return\n }\n try {\n await Promise.resolve(onDataRefresh?.())\n } catch (err) {\n logger.warn('Parent refresh after mark done failed', { component: 'ActivitiesSection', activityId, err })\n }\n }, [loadActivities, onDataRefresh, runGuardedMutation, t])\n\n const resolvedUserIdsRef = React.useRef(new Set<string>())\n\n // Resolve missing author names from user IDs\n React.useEffect(() => {\n loadActivities()\n .then(() => { resolvedUserIdsRef.current = new Set() })\n .catch((err) => logger.warn('loadActivities failed', { component: 'ActivitiesSection', err }))\n }, [loadActivities])\n\n React.useEffect(() => {\n const unresolvedIds = new Set<string>()\n for (const a of activities) {\n if (a.authorUserId && !a.authorName && !resolvedUserIdsRef.current.has(a.authorUserId)) {\n unresolvedIds.add(a.authorUserId)\n }\n }\n if (unresolvedIds.size === 0) return\n\n const controller = new AbortController()\n resolveAuthorUserNames([...unresolvedIds], controller.signal)\n .then(({ names: nameMap, resolvedIds }) => {\n if (controller.signal.aborted) return\n // Only ids the server actually answered for are remembered. Marking an id whose request\n // failed \u2014 or one the server silently dropped past its `?ids=` cap \u2014 would leave that\n // author blank for the life of the component, even though the next attempt would work.\n for (const uid of resolvedIds) resolvedUserIdsRef.current.add(uid)\n if (nameMap.size > 0) {\n setActivities((prev) =>\n prev.map((a) => {\n if (a.authorUserId && !a.authorName && nameMap.has(a.authorUserId)) {\n return { ...a, authorName: nameMap.get(a.authorUserId) ?? null }\n }\n return a\n }),\n )\n }\n })\n .catch((err) => logger.warn('resolve author names failed', { component: 'ActivitiesSection', err }))\n return () => controller.abort()\n }, [activities])\n\n const totalCount = activities.length\n const visibleCount = visibleActivities.length\n\n return (\n <div className=\"flex flex-col gap-3.5 rounded-[10px] border border-border bg-card pt-4 pb-[18px] px-[18px]\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-[15px] text-muted-foreground\" />\n <h3 className=\"text-[13px] font-semibold text-foreground\">\n {entityName\n ? t('customers.timeline.history.title', 'Interaction history with {{name}}', { name: entityName })\n : t('customers.timeline.history.titleGeneric', 'Interaction history')}\n </h3>\n </div>\n\n <label className=\"relative flex items-center\">\n <Search className=\"pointer-events-none absolute left-2.5 size-5 text-muted-foreground\" aria-hidden />\n <input\n ref={searchInputRef}\n type=\"search\"\n value={searchTerm}\n onChange={(event) => setSearchTerm(event.target.value)}\n placeholder={t('customers.timeline.history.searchPlaceholder', 'Search...')}\n aria-label={t('customers.timeline.history.searchAriaLabel', 'Search interaction history')}\n className=\"h-9 w-full rounded-[10px] border border-border bg-card pl-9 pr-14 text-sm text-foreground shadow-xs placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden\"\n />\n <Kbd className=\"pointer-events-none absolute right-2 hidden text-[11px] uppercase tracking-[0.48px] sm:inline-flex\">\n \u23181\n </Kbd>\n </label>\n\n <ActivityTimelineFilters\n entityId={entityId}\n activeTypes={filterTypes}\n dateFrom={filterDateFrom}\n dateTo={filterDateTo}\n onTypesChange={setFilterTypes}\n onDateFromChange={setFilterDateFrom}\n onDateToChange={setFilterDateTo}\n onReset={() => {\n setFilterTypes([])\n setFilterDateFrom('')\n setFilterDateTo('')\n }}\n />\n\n {loading && totalCount === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/70 px-4 py-8 text-sm text-muted-foreground\">\n {t('customers.people.detail.activities.loading', 'Loading activities\u2026')}\n </div>\n ) : (\n <>\n <ActivityTimeline\n activities={visibleActivities}\n onEdit={onEditActivity}\n onMarkDone={handleMarkDone}\n />\n {totalCount > 0 ? (\n <div className=\"flex items-center justify-between gap-3 border-t border-border/60 pt-3\">\n <span className=\"text-xs text-muted-foreground\">\n {searchTerm.trim()\n ? t('customers.activities.seeMatching', 'Showing {visible} of {total} activities', {\n visible: visibleCount,\n total: totalCount,\n })\n : t('customers.activities.seeAll', 'See all {count} activities', { count: totalCount })}\n </span>\n {hasMore ? (\n <Button type=\"button\" variant=\"link\" size=\"sm\" onClick={() => setLoadedPages((value) => value + 1)}>\n {t('customers.activities.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n ) : null}\n </>\n )}\n </div>\n )\n}\n\nexport default ActivitiesSection\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -36,6 +36,7 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
|
36
36
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
37
37
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
38
38
|
import { TimeEntryDialog } from "../../../../lib/time-tracking-ui/TimeEntryDialog.js";
|
|
39
|
+
import { REPORTS_PATH, reportDetailHref } from "../../../../lib/time-tracking-ui/reportLinks.js";
|
|
39
40
|
import { TimeEntryDurationCell } from "../../../../lib/time-tracking-ui/TimeEntryDurationCell.js";
|
|
40
41
|
import { TimeEntriesSummaryFooter } from "../../../../lib/time-tracking-ui/TimeEntriesSummaryFooter.js";
|
|
41
42
|
import {
|
|
@@ -65,7 +66,6 @@ const RATES_FEATURE = "staff.timesheets.rates.view";
|
|
|
65
66
|
const MANAGE_OWN_FEATURE = "staff.timesheets.manage_own";
|
|
66
67
|
const MANAGE_ALL_FEATURE = "staff.timesheets.manage_all";
|
|
67
68
|
const DEEP_LINK_PARAMS = ["taskId", "ids"];
|
|
68
|
-
const REPORTS_PATH = "/backend/staff/time-tracking/reports";
|
|
69
69
|
const MUTATION_CONTEXT_ID = "staff.time_tracking.entriesList";
|
|
70
70
|
const RESOURCE_KIND = "staff.timesheets.time_entry";
|
|
71
71
|
function readUndoToken(response) {
|
|
@@ -924,7 +924,7 @@ function TimeTrackingEntriesPage() {
|
|
|
924
924
|
{
|
|
925
925
|
id: "show-report",
|
|
926
926
|
label: labels.showReport,
|
|
927
|
-
href: row.lockedReportId ?
|
|
927
|
+
href: row.lockedReportId ? reportDetailHref(row.lockedReportId) : REPORTS_PATH
|
|
928
928
|
}
|
|
929
929
|
]
|
|
930
930
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/staff/backend/staff/time-tracking/entries/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\n/**\n * Screen 10 \u2014 the working list of time entries.\n *\n * The five notes on the mockup are the five decisions this page exists to make,\n * and none of them is decoration:\n *\n * 1. **The `Czas` cell edits in place** (US-D5). Correcting a duration is the\n * single most common thing anybody does to a logged entry, so it costs one\n * click, not a dialog round trip. The field is the shared `DurationInput`\n * in its compact variant, so `1h 40m` means here exactly what it means in\n * the entry dialog and the timesheet grid.\n * 2. **Delete is optimistic and reversible** (US-D5). The row leaves the table\n * immediately and the platform's undo bar \u2014 fed by the `x-om-operation`\n * header the command layer already attaches to the response \u2014 offers\n * \"Cofnij\" for a few seconds. The restore is the command's own `undo`,\n * never a row rebuilt from client state, which is why an entry restored\n * this way comes back with its tags, its rounding and its audit trail\n * intact. The page watches the operation store and refetches the moment its\n * token is undone, so the row reappears from the server.\n * 3. **A rate override is visible on the row** (US-F2). Without the badge two\n * rows with the same duration and different costs look like a bug.\n * 4. **A locked row is inert** (US-G3): no checkbox, no edit, no delete, a\n * lock badge, and exactly one action \u2014 the report that froze it. Unlocking\n * happens in the report (screen 15), not here.\n * 5. **`Koszt` is empty for a non-billable row, not `0,00`.** The list route\n * already answers `null`; a zero would read as free work rather than as\n * work that is out of scope.\n *\n * The page is also the landing spot for two deep links \u2014 the task drawer's\n * \"show all entries\" (`?taskId=`) and the entry dialog's jump to an overlapping\n * entry (`?ids=`). Either one replaces the default \"my entries, this week\"\n * seeding and shows up as a removable chip, because a list that quietly ignored\n * the link would answer a question nobody asked.\n *\n * Two things beyond the notes. Money columns exist only for a holder of\n * `staff.timesheets.rates.view` \u2014 the response has no `cost` for anybody else,\n * so the column is absent rather than blanked. And the footer never adds two\n * currencies together; see `summarizeTimeEntries`.\n */\n\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport * as React from 'react'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { Copy, Lock, Plus } from 'lucide-react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterOverlay'\nimport {\n EntryFilterChips,\n EntryFilterPresets,\n matchActivePreset,\n type AppliedFilterChip,\n type EntryFilterPreset,\n} from '../../../../lib/time-tracking-ui/EntryFilterBar'\nimport {\n apiCall,\n apiCallOrThrow,\n readApiResultOrThrow,\n withScopedApiRequestHeaders,\n} from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOperationStore } from '@open-mercato/ui/backend/operations/store'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { deserializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { TimeEntryDialog } from '../../../../lib/time-tracking-ui/TimeEntryDialog'\nimport { TimeEntryDurationCell } from '../../../../lib/time-tracking-ui/TimeEntryDurationCell'\nimport { TimeEntriesSummaryFooter } from '../../../../lib/time-tracking-ui/TimeEntriesSummaryFooter'\nimport {\n collectDirectoryIds,\n currentWeekRange,\n formatEntryClockRange,\n formatEntryDay,\n isEntryLockedError,\n readCopyDayTargetConflict,\n summarizeTimeEntries,\n toTimeEntryListRow,\n type TimeEntryDirectory,\n type TimeEntryListRow,\n} from '../../../../lib/time-tracking-ui/timeEntryListData'\nimport {\n readRowItems,\n shiftIsoDate,\n todayIsoDate,\n toProjectOption,\n toTaskOption,\n} from '../../../../lib/time-tracking-ui/timeEntryDialogState'\nimport { formatDuration } from '../../../../lib/time-tracking/duration'\n\nconst logger = createLogger('staff').child({ component: 'time-tracking/entries' })\n\nconst PAGE_SIZE = 50\nconst DIRECTORY_PAGE_SIZE = 100\nconst ENTRIES_API_PATH = 'staff/timesheets/time-entries'\nconst ENTRIES_API = `/api/${ENTRIES_API_PATH}`\nconst RATES_FEATURE = 'staff.timesheets.rates.view'\nconst MANAGE_OWN_FEATURE = 'staff.timesheets.manage_own'\nconst MANAGE_ALL_FEATURE = 'staff.timesheets.manage_all'\n/**\n * Query parameters other screens use to hand this list a question: the task\n * drawer's \"show every entry for this task\" (`?taskId=`) and the entry dialog's\n * jump to a conflicting entry (`?ids=`). Both are read once on mount and become\n * ordinary filter values, so they carry a removable chip like anything else.\n */\nconst DEEP_LINK_PARAMS = ['taskId', 'ids'] as const\nconst REPORTS_PATH = '/backend/staff/time-tracking/reports'\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.entriesList'\nconst RESOURCE_KIND = 'staff.timesheets.time_entry'\n\ntype EntriesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n totalIsCapped?: boolean\n totalPages?: number\n}\n\ntype CopyDayResponse = {\n copied?: number\n toDate?: string\n created?: Array<Record<string, unknown>>\n skipped?: Array<Record<string, unknown>>\n}\n\ntype MutationContext = {\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n}\n\nfunction readUndoToken(response: Response | null | undefined): string | null {\n const header = response?.headers?.get?.('x-om-operation') ?? null\n return deserializeOperationMetadata(header)?.undoToken ?? null\n}\n\nasync function loadDirectory(\n taskIds: readonly string[],\n projectIds: readonly string[],\n): Promise<TimeEntryDirectory> {\n const taskTitles = new Map<string, string>()\n const projectLabels = new Map<string, string>()\n const requests: Array<Promise<void>> = []\n\n if (taskIds.length > 0) {\n const params = new URLSearchParams({ page: '1', pageSize: String(DIRECTORY_PAGE_SIZE), ids: taskIds.join(',') })\n requests.push(\n readApiResultOrThrow<Record<string, unknown>>(`/api/staff/timesheets/tasks?${params.toString()}`, undefined, {\n allowNullResult: true,\n }).then((payload) => {\n for (const row of readRowItems(payload)) {\n const option = toTaskOption(row)\n if (option) taskTitles.set(option.id, option.title)\n }\n }),\n )\n }\n\n if (projectIds.length > 0) {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(DIRECTORY_PAGE_SIZE),\n ids: projectIds.join(','),\n })\n requests.push(\n readApiResultOrThrow<Record<string, unknown>>(\n `/api/staff/timesheets/time-projects?${params.toString()}`,\n undefined,\n { allowNullResult: true },\n ).then((payload) => {\n for (const row of readRowItems(payload)) {\n const option = toProjectOption(row)\n if (!option) continue\n projectLabels.set(option.id, option.customerName ? `${option.customerName} \u2014 ${option.name}` : option.name)\n }\n }),\n )\n }\n\n await Promise.all(requests)\n return { taskTitles, projectLabels }\n}\n\nexport default function TimeTrackingEntriesPage() {\n const t = useT()\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const scopeVersion = useOrganizationScopeVersion()\n const { payload } = useBackendChrome()\n const canSeeMoney = hasFeature(payload?.grantedFeatures, RATES_FEATURE)\n const canManage = hasFeature(payload?.grantedFeatures, MANAGE_OWN_FEATURE)\n // Reading somebody else's entries is `manage_all`, not `manage_own`: the list\n // route narrows every other caller back to their own rows, so offering them a\n // person filter promises a lookup the server will answer with nothing.\n const canManageAll = hasFeature(payload?.grantedFeatures, MANAGE_ALL_FEATURE)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const deepLinkTaskId = searchParams?.get('taskId')?.trim() ?? ''\n const deepLinkEntryIds = searchParams?.get('ids')?.trim() ?? ''\n\n const defaultRange = React.useMemo(() => currentWeekRange(), [])\n const [filterValues, setFilterValues] = React.useState<FilterValues>(() => {\n // A deep link is the entire question the reader arrived with, so it replaces\n // the default week instead of being intersected with it \u2014 an entry logged\n // last month would otherwise be filtered straight back out of the list the\n // link exists to show.\n if (deepLinkTaskId) return { taskId: deepLinkTaskId }\n if (deepLinkEntryIds) return { ids: deepLinkEntryIds }\n return { period: { from: defaultRange.from, to: defaultRange.to } }\n })\n // The list opens on your own timesheet. Someone holding `manage_all` sees every\n // entry in the organization otherwise, which is a page about other people.\n const [selfStaffMemberId, setSelfStaffMemberId] = React.useState<string | null>(null)\n const [projectOptions, setProjectOptions] = React.useState<{ value: string; label: string }[]>([])\n const [customerOptions, setCustomerOptions] = React.useState<{ value: string; label: string }[]>([])\n const [tagOptions, setTagOptions] = React.useState<{ value: string; label: string }[]>([])\n const [peopleOptions, setPeopleOptions] = React.useState<{ value: string; label: string }[]>([])\n // A deep link also outranks the \"my entries\" seeding the self lookup performs\n // once it resolves; the entry being pointed at is frequently someone else's.\n const personSeededRef = React.useRef(Boolean(deepLinkTaskId || deepLinkEntryIds))\n const [page, setPage] = React.useState(1)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'date', desc: true }])\n const [rows, setRows] = React.useState<TimeEntryListRow[]>([])\n const [total, setTotal] = React.useState(0)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [totalPages, setTotalPages] = React.useState(1)\n const [isLoading, setIsLoading] = React.useState(true)\n const [isRefreshing, setIsRefreshing] = React.useState(false)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [selectedIds, setSelectedIds] = React.useState<string[]>([])\n const [savingEntryId, setSavingEntryId] = React.useState<string | null>(null)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [dialogEntryId, setDialogEntryId] = React.useState<string | null>(null)\n const hasLoadedOnceRef = React.useRef(false)\n\n const labels = React.useMemo(\n () => ({\n title: t('staff.time_tracking.entries.title', 'Time entries'),\n add: t('staff.time_tracking.entries.actions.add', 'Add entry'),\n copyDay: t('staff.time_tracking.entries.actions.copyDay', 'Copy yesterday'),\n duplicate: t('staff.time_tracking.entries.actions.duplicate', 'Duplicate'),\n delete: t('staff.time_tracking.entries.actions.delete', 'Delete'),\n showReport: t('staff.time_tracking.entries.actions.showReport', 'Show the report'),\n refresh: t('staff.time_tracking.entries.actions.refresh', 'Refresh'),\n columns: {\n date: t('staff.time_tracking.entries.columns.date', 'Date'),\n task: t('staff.time_tracking.entries.columns.task', 'Task / description'),\n project: t('staff.time_tracking.entries.columns.project', 'Project'),\n clock: t('staff.time_tracking.entries.columns.clock', 'Hours'),\n duration: t('staff.time_tracking.entries.columns.duration', 'Time'),\n cost: t('staff.time_tracking.entries.columns.cost', 'Cost'),\n },\n selectAll: t('staff.time_tracking.entries.table.selectAll', 'Select all entries'),\n selectRow: t('staff.time_tracking.entries.table.selectRow', 'Select entry'),\n durationAria: t('staff.time_tracking.entries.table.durationAria', 'Entry duration'),\n noTask: t('staff.time_tracking.entries.table.noTask', 'No task'),\n empty: t('staff.time_tracking.entries.empty', 'No time entries in this period.'),\n badges: {\n locked: t('staff.time_tracking.entries.badges.locked', 'locked'),\n nonBillable: t('staff.time_tracking.entries.badges.nonBillable', 'non-billable'),\n },\n filters: {\n period: t('staff.time_tracking.entries.filters.period', 'Period'),\n project: t('staff.time_tracking.entries.filters.project', 'Project'),\n customer: t('staff.time_tracking.entries.filters.customer', 'Customer'),\n person: t('staff.time_tracking.entries.filters.person', 'Person'),\n tag: t('staff.time_tracking.entries.filters.tag', 'Tag'),\n billable: t('staff.time_tracking.entries.filters.billable', 'Billable'),\n billableYes: t('staff.time_tracking.entries.filters.billableYes', 'Billable only'),\n billableNo: t('staff.time_tracking.entries.filters.billableNo', 'Non-billable only'),\n locked: t('staff.time_tracking.entries.filters.locked', 'Report status'),\n lockedYes: t('staff.time_tracking.entries.filters.lockedYes', 'Locked in a report'),\n lockedNo: t('staff.time_tracking.entries.filters.lockedNo', 'Not yet reported'),\n text: t('staff.time_tracking.entries.filters.text', 'Description contains'),\n task: t('staff.time_tracking.entries.filters.task', 'Task'),\n entry: t('staff.time_tracking.entries.filters.entry', 'Entry'),\n entrySelection: t('staff.time_tracking.entries.filters.entrySelection', '{count} entries'),\n clearAll: t('staff.time_tracking.entries.filters.clearAll', 'Clear all'),\n remove: t('staff.time_tracking.entries.filters.remove', 'Remove filter {name}'),\n },\n presets: {\n mineWeek: t('staff.time_tracking.entries.presets.mineWeek', 'Mine, this week'),\n unbilled: t('staff.time_tracking.entries.presets.unbilled', 'Unbilled'),\n nonBillable: t('staff.time_tracking.entries.presets.nonBillable', 'Non-billable'),\n thisMonth: t('staff.time_tracking.entries.presets.thisMonth', 'This month'),\n },\n errors: {\n load: t('staff.time_tracking.entries.errors.load', 'Could not load the time entries.'),\n save: t('staff.time_tracking.entries.errors.save', 'Could not save the time entry.'),\n delete: t('staff.time_tracking.entries.errors.delete', 'Could not delete the time entry.'),\n duplicate: t('staff.time_tracking.entries.errors.duplicate', 'Could not duplicate the time entry.'),\n copyDay: t('staff.time_tracking.entries.errors.copyDay', 'Could not copy the day.'),\n locked: t(\n 'staff.time_tracking.entries.errors.entryLocked',\n 'This time entry is locked in a closed report and cannot be changed. Unlock the report first.',\n ),\n },\n }),\n [t],\n )\n\n const handleRefresh = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const reportFailure = React.useCallback(\n (error: unknown, fallbackMessage: string, logTag: string) => {\n if (surfaceRecordConflict(error, t)) return\n if (isEntryLockedError(error)) {\n flash(labels.errors.locked, 'error')\n return\n }\n logger.error(logTag, { err: error })\n flash(error instanceof Error && error.message ? error.message : fallbackMessage, 'error')\n },\n [labels.errors.locked, t],\n )\n\n const period = (filterValues.period ?? {}) as { from?: string; to?: string }\n const projectFilter = typeof filterValues.projectId === 'string' ? filterValues.projectId : ''\n\n // Filter vocabularies come from the API, not from the rows on screen: options\n // built from the loaded page can only narrow to what you can already see, which\n // makes the filter useless for finding anything that is not in front of you.\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n // `Promise.resolve` rather than `.catch` directly: a filter vocabulary that\n // fails to load must leave the page usable with a shorter list, and the\n // wrapper also tolerates a stub that returns nothing.\n const safeCall = async <T,>(url: string) => {\n try {\n return (await Promise.resolve(apiCall<T>(url))) ?? null\n } catch {\n return null\n }\n }\n const [selfRes, projectsRes, tagsRes, peopleRes] = await Promise.all([\n safeCall<{ member?: { id?: string } | null }>('/api/staff/team-members/self'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/time-projects?page=1&pageSize=100'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/tags?page=1&pageSize=100'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/team-members?page=1&pageSize=100'),\n ])\n if (cancelled) return\n\n const selfId = selfRes?.ok ? (selfRes.result?.member?.id ?? null) : null\n if (selfId) {\n setSelfStaffMemberId(selfId)\n if (!personSeededRef.current) {\n personSeededRef.current = true\n setFilterValues((prev) => ({ ...prev, staffMemberId: selfId }))\n }\n }\n\n const projects = projectsRes?.ok && Array.isArray(projectsRes.result?.items) ? projectsRes.result.items : []\n const customers = new Map<string, string>()\n setProjectOptions(\n projects\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n if (!id) return null\n const name = typeof row.name === 'string' ? row.name : id\n const code = typeof row.code === 'string' ? row.code : null\n const customerId = typeof row.customer_id === 'string' ? row.customer_id : null\n const snapshot = row.customer_snapshot as { name?: unknown } | null | undefined\n if (customerId && typeof snapshot?.name === 'string') customers.set(customerId, snapshot.name)\n return { value: id, label: code ? `${code} \u00B7 ${name}` : name }\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n setCustomerOptions([...customers].map(([value, label]) => ({ value, label })))\n\n const tags = tagsRes?.ok && Array.isArray(tagsRes.result?.items) ? tagsRes.result.items : []\n setTagOptions(\n tags\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.label === 'string' ? row.label : null\n return id && label ? { value: id, label } : null\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n\n const people = peopleRes?.ok && Array.isArray(peopleRes.result?.items) ? peopleRes.result.items : []\n setPeopleOptions(\n people\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.display_name === 'string'\n ? row.display_name\n : typeof row.displayName === 'string'\n ? row.displayName\n : null\n return id && label ? { value: id, label } : null\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n })()\n return () => { cancelled = true }\n }, [scopeVersion])\n\n const presets = React.useMemo<EntryFilterPreset[]>(\n () => [\n {\n id: 'mine-week',\n label: labels.presets.mineWeek,\n build: ({ staffMemberId }) => ({\n period: { from: defaultRange.from, to: defaultRange.to },\n ...(staffMemberId ? { staffMemberId } : {}),\n }),\n },\n {\n id: 'unbilled',\n label: labels.presets.unbilled,\n build: () => ({ locked: 'false', billable: 'true' }),\n },\n { id: 'non-billable', label: labels.presets.nonBillable, build: () => ({ billable: 'false' }) },\n {\n id: 'this-month',\n label: labels.presets.thisMonth,\n build: ({ today }) => {\n const start = `${today.slice(0, 7)}-01`\n return { period: { from: start, to: today } }\n },\n },\n ],\n [defaultRange.from, defaultRange.to, labels.presets],\n )\n\n const presetContext = React.useMemo(\n () => ({ staffMemberId: selfStaffMemberId, today: new Date().toISOString().slice(0, 10) }),\n [selfStaffMemberId],\n )\n const activePresetId = React.useMemo(\n () => matchActivePreset(filterValues, presets, presetContext),\n [filterValues, presetContext, presets],\n )\n\n const applyPreset = React.useCallback(\n (preset: EntryFilterPreset) => {\n // A preset is a starting point, not a mode: it replaces what it sets and\n // leaves the rest, so narrowing further does not mean rebuilding it.\n setFilterValues((current) => ({ ...current, ...preset.build(presetContext) }))\n setPage(1)\n },\n [presetContext],\n )\n\n /**\n * A deep-link filter lives in two places \u2014 the filter state and the address\n * bar \u2014 so dropping the chip has to drop the parameter too, otherwise the next\n * render (or a reload) puts the narrowing straight back.\n */\n const dropDeepLinkParams = React.useCallback(\n (ids: readonly string[]) => {\n if (!searchParams) return\n const next = new URLSearchParams(searchParams.toString())\n let changed = false\n for (const id of ids) {\n if (!next.has(id)) continue\n next.delete(id)\n changed = true\n }\n if (!changed) return\n const query = next.toString()\n router.replace(query ? `${pathname}?${query}` : pathname)\n },\n [pathname, router, searchParams],\n )\n\n const removeChip = React.useCallback(\n (id: string) => {\n setFilterValues((current) => {\n const next = { ...current }\n delete next[id]\n return next\n })\n setPage(1)\n dropDeepLinkParams([id])\n },\n [dropDeepLinkParams],\n )\n\n const clearAllFilters = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n dropDeepLinkParams(DEEP_LINK_PARAMS)\n }, [dropDeepLinkParams])\n\n const loadEntries = React.useCallback(async () => {\n if (hasLoadedOnceRef.current) setIsRefreshing(true)\n else setIsLoading(true)\n try {\n const params = new URLSearchParams({ page: String(page), pageSize: String(PAGE_SIZE) })\n const sort = sorting[0]\n params.set('sortField', sort?.id ?? 'date')\n params.set('sortDir', sort?.desc === false ? 'asc' : 'desc')\n if (period.from) params.set('from', period.from)\n if (period.to) params.set('to', period.to)\n if (projectFilter) params.set('projectId', projectFilter)\n const readFilter = (key: string): string =>\n typeof filterValues[key] === 'string' ? (filterValues[key] as string) : ''\n const person = readFilter('staffMemberId')\n if (person) params.set('staffMemberId', person)\n const billable = readFilter('billable')\n if (billable) params.set('billable', billable)\n const lockedFilter = readFilter('locked')\n if (lockedFilter) params.set('locked', lockedFilter)\n const customer = readFilter('customerId')\n if (customer) params.set('customerId', customer)\n const tag = readFilter('tagIds')\n if (tag) params.set('tagIds', tag)\n const text = readFilter('q')\n if (text.trim()) params.set('q', text.trim())\n const taskFilter = readFilter('taskId')\n if (taskFilter) params.set('taskId', taskFilter)\n const entryIdsFilter = readFilter('ids')\n if (entryIdsFilter) params.set('ids', entryIdsFilter)\n\n const listPayload = await readApiResultOrThrow<EntriesResponse>(\n `${ENTRIES_API}?${params.toString()}`,\n undefined,\n { errorMessage: labels.errors.load, fallback: { items: [], total: 0, totalPages: 1 } },\n )\n const items = Array.isArray(listPayload.items) ? listPayload.items : []\n const bare = items\n .map((item) => toTimeEntryListRow(item))\n .filter((row): row is TimeEntryListRow => row !== null)\n const { taskIds, projectIds } = collectDirectoryIds(bare)\n let directory: TimeEntryDirectory = { taskTitles: new Map(), projectLabels: new Map() }\n try {\n directory = await loadDirectory(taskIds, projectIds)\n } catch (error) {\n logger.warn('staff.time_tracking.entries directory load failed', { err: error })\n }\n setRows(\n items\n .map((item) => toTimeEntryListRow(item, directory))\n .filter((row): row is TimeEntryListRow => row !== null),\n )\n setTotal(typeof listPayload.total === 'number' ? listPayload.total : items.length)\n setTotalIsCapped(listPayload.totalIsCapped === true)\n setTotalPages(\n typeof listPayload.totalPages === 'number'\n ? listPayload.totalPages\n : Math.max(1, Math.ceil(items.length / PAGE_SIZE)),\n )\n } catch (error) {\n logger.error('staff.time_tracking.entries list failed', { err: error })\n flash(labels.errors.load, 'error')\n } finally {\n setIsLoading(false)\n setIsRefreshing(false)\n hasLoadedOnceRef.current = true\n }\n }, [labels.errors.load, page, period.from, period.to, projectFilter, sorting, filterValues])\n\n React.useEffect(() => {\n void loadEntries()\n }, [loadEntries, scopeVersion, reloadToken])\n\n React.useEffect(() => {\n setSelectedIds([])\n }, [page, period.from, period.to, projectFilter, scopeVersion])\n\n /**\n * Note 2's other half. The undo bar is AppShell's, so the click that restores\n * an entry never reaches this page \u2014 the operation store is what does. When a\n * token this page deleted shows up in the store's `undone` list the row is\n * back on the server, so the list is refetched rather than reconstructed.\n */\n const undoneTokens = useOperationStore((state) => state.undone.map((entry) => entry.undoToken).join('|'))\n const pendingUndoTokensRef = React.useRef<Set<string>>(new Set())\n React.useEffect(() => {\n if (pendingUndoTokensRef.current.size === 0) return\n const undone = new Set(undoneTokens.split('|').filter(Boolean))\n let restored = false\n for (const token of Array.from(pendingUndoTokensRef.current)) {\n if (!undone.has(token)) continue\n pendingUndoTokensRef.current.delete(token)\n restored = true\n }\n if (restored) handleRefresh()\n }, [handleRefresh, undoneTokens])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<MutationContext>({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: labels.errors.save,\n })\n\n const mutationContext = React.useCallback(\n (resourceId: string): MutationContext => ({\n formId: MUTATION_CONTEXT_ID,\n resourceKind: RESOURCE_KIND,\n resourceId,\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const handleDurationCommit = React.useCallback(\n async (entry: TimeEntryListRow, durationMinutes: number) => {\n setSavingEntryId(entry.id)\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n updateCrud(\n ENTRIES_API_PATH,\n { id: entry.id, durationMinutes },\n { errorMessage: labels.errors.save },\n ),\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id, durationMinutes },\n })\n handleRefresh()\n } catch (error) {\n reportFailure(error, labels.errors.save, 'staff.time_tracking.entries inline duration save failed')\n handleRefresh()\n } finally {\n setSavingEntryId(null)\n }\n },\n [handleRefresh, labels.errors.save, mutationContext, reportFailure, runMutation],\n )\n\n const handleDelete = React.useCallback(\n async (entry: TimeEntryListRow) => {\n const previousRows = rows\n setRows((current) => current.filter((row) => row.id !== entry.id))\n setTotal((current) => Math.max(0, current - 1))\n setSelectedIds((current) => current.filter((id) => id !== entry.id))\n try {\n const call = await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n deleteCrud(ENTRIES_API_PATH, entry.id, { errorMessage: labels.errors.delete }),\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id },\n })\n const undoToken = readUndoToken(call.response)\n if (undoToken) pendingUndoTokensRef.current.add(undoToken)\n flash(\n t('staff.time_tracking.entries.messages.deleted', 'Deleted \u201C{name}\u201D ({duration}). Use Undo to restore it.', {\n name: entry.taskTitle ?? entry.description ?? labels.noTask,\n duration: formatDuration(entry.durationMinutes, 'clock'),\n }),\n 'success',\n )\n } catch (error) {\n setRows(previousRows)\n setTotal((current) => current + 1)\n reportFailure(error, labels.errors.delete, 'staff.time_tracking.entries delete failed')\n }\n },\n [labels.errors.delete, labels.noTask, mutationContext, reportFailure, rows, runMutation, t],\n )\n\n const handleDuplicate = React.useCallback(\n async (entry: TimeEntryListRow) => {\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow<Record<string, unknown>>(\n `${ENTRIES_API}/${encodeURIComponent(entry.id)}/duplicate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tagIds: entry.tagIds }),\n },\n { errorMessage: labels.errors.duplicate },\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id },\n })\n flash(t('staff.time_tracking.entries.messages.duplicated', 'Entry duplicated.'), 'success')\n handleRefresh()\n } catch (error) {\n reportFailure(error, labels.errors.duplicate, 'staff.time_tracking.entries duplicate failed')\n }\n },\n [handleRefresh, labels.errors.duplicate, mutationContext, reportFailure, runMutation, t],\n )\n\n /**\n * \"Kopiuj wczorajszy dzie\u0144\" (screen 1 note 5). The copies are drafts to\n * review, so on success the period narrows to the target day: the user lands\n * on exactly the rows that were created instead of hunting for them.\n *\n * A non-empty target day answers `409 copy_day_target_not_empty` naming what\n * is already there. That is a question, not a dead end \u2014 the user is told the\n * count and offered the `allowDuplicates` path, which is the real case the\n * refusal would otherwise block (something already logged today, and\n * yesterday's set wanted on top).\n */\n const runCopyDay = React.useCallback(\n async (fromDate: string, toDate: string, allowDuplicates: boolean) =>\n runMutation({\n operation: () =>\n apiCallOrThrow<CopyDayResponse>(\n `${ENTRIES_API}/copy-day`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ fromDate, toDate, allowDuplicates }),\n },\n { errorMessage: labels.errors.copyDay },\n ),\n context: mutationContext(toDate),\n mutationPayload: { fromDate, toDate, allowDuplicates },\n }),\n [labels.errors.copyDay, mutationContext, runMutation],\n )\n\n const handleCopyDay = React.useCallback(async () => {\n const toDate = todayIsoDate()\n const fromDate = shiftIsoDate(toDate, -1)\n const announce = (result: CopyDayResponse | null) => {\n const copied = typeof result?.copied === 'number' ? result.copied : 0\n const skipped = Array.isArray(result?.skipped) ? result.skipped.length : 0\n if (copied === 0) {\n flash(t('staff.time_tracking.entries.copyDay.nothing', 'Yesterday has no entries to copy.'), 'info')\n } else {\n flash(\n t('staff.time_tracking.entries.copyDay.success', '{count} entries copied as drafts \u2014 review them.', {\n count: copied,\n }),\n 'success',\n )\n setFilterValues((current) => ({ ...current, period: { from: toDate, to: toDate } }))\n setPage(1)\n }\n if (skipped > 0) {\n flash(\n t('staff.time_tracking.entries.copyDay.skipped', '{count} entries were skipped because they are locked.', {\n count: skipped,\n }),\n 'warning',\n )\n }\n handleRefresh()\n }\n\n try {\n const call = await runCopyDay(fromDate, toDate, false)\n announce(call.result)\n } catch (error) {\n const conflict = readCopyDayTargetConflict(error)\n if (!conflict) {\n reportFailure(error, labels.errors.copyDay, 'staff.time_tracking.entries copy-day failed')\n return\n }\n const confirmed = await confirm({\n title: labels.copyDay,\n text: t(\n 'staff.time_tracking.entries.copyDay.conflict',\n 'Today already has {count} entries. Add yesterday\u2019s copies on top of them?',\n { count: conflict.existingEntryCount },\n ),\n confirmText: t('staff.time_tracking.entries.copyDay.confirmDuplicates', 'Add copies anyway'),\n })\n if (!confirmed) return\n try {\n const retry = await runCopyDay(fromDate, toDate, true)\n announce(retry.result)\n } catch (retryError) {\n reportFailure(retryError, labels.errors.copyDay, 'staff.time_tracking.entries copy-day retry failed')\n }\n }\n }, [confirm, handleRefresh, labels.copyDay, labels.errors.copyDay, reportFailure, runCopyDay, t])\n\n const openCreate = React.useCallback(() => {\n setDialogEntryId(null)\n setDialogOpen(true)\n }, [])\n\n const openEdit = React.useCallback((entryId: string) => {\n setDialogEntryId(entryId)\n setDialogOpen(true)\n }, [])\n\n const selectableRows = React.useMemo(() => rows.filter((row) => !row.isLocked), [rows])\n const allSelected = selectableRows.length > 0 && selectedIds.length === selectableRows.length\n const someSelected = selectedIds.length > 0 && !allSelected\n\n const toggleRowSelection = React.useCallback((id: string, checked: boolean) => {\n setSelectedIds((current) => (checked ? Array.from(new Set([...current, id])) : current.filter((value) => value !== id)))\n }, [])\n\n const toggleAllSelection = React.useCallback(\n (checked: boolean) => {\n setSelectedIds(checked ? selectableRows.map((row) => row.id) : [])\n },\n [selectableRows],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n { id: 'period', label: labels.filters.period, type: 'dateRange' },\n { id: 'projectId', label: labels.filters.project, type: 'select', options: projectOptions },\n { id: 'customerId', label: labels.filters.customer, type: 'select', options: customerOptions },\n // Only offered when the caller can actually see somebody else's time; for\n // everyone else the route already scopes to their own entries and the\n // control would be a lever attached to nothing.\n ...(canManageAll && peopleOptions.length > 1\n ? [{ id: 'staffMemberId', label: labels.filters.person, type: 'select' as const, options: peopleOptions }]\n : []),\n { id: 'tagIds', label: labels.filters.tag, type: 'select', options: tagOptions },\n {\n id: 'billable',\n label: labels.filters.billable,\n type: 'select',\n options: [\n { value: 'true', label: labels.filters.billableYes },\n { value: 'false', label: labels.filters.billableNo },\n ],\n },\n {\n id: 'locked',\n label: labels.filters.locked,\n type: 'select',\n options: [\n { value: 'true', label: labels.filters.lockedYes },\n { value: 'false', label: labels.filters.lockedNo },\n ],\n },\n { id: 'q', label: labels.filters.text, type: 'text' },\n ],\n [labels.filters, canManageAll, projectOptions, customerOptions, peopleOptions, tagOptions],\n )\n\n /**\n * Filters that can be applied but own no control in the overlay: the self\n * filter, which is seeded for everyone but only steerable by a `manage_all`\n * holder, and the two deep-link narrowings. Naming them here is what keeps\n * their chips reading as words instead of as a parameter name and a uuid.\n */\n const unlistedFilters = React.useMemo<FilterDef[]>(\n () => [\n { id: 'staffMemberId', label: labels.filters.person, type: 'select', options: peopleOptions },\n { id: 'taskId', label: labels.filters.task, type: 'text' },\n { id: 'ids', label: labels.filters.entry, type: 'text' },\n ],\n [labels.filters, peopleOptions],\n )\n\n /** Applied filters, as the reader would say them out loud. */\n const appliedChips = React.useMemo<AppliedFilterChip[]>(() => {\n const defOf = (id: string) =>\n filters.find((filter) => filter.id === id) ?? unlistedFilters.find((filter) => filter.id === id)\n const label = (id: string) => defOf(id)?.label ?? id\n const optionLabel = (id: string, value: string) =>\n defOf(id)?.options?.find((option) => option.value === value)?.label ?? value\n const chips: AppliedFilterChip[] = []\n for (const [id, raw] of Object.entries(filterValues)) {\n if (raw === undefined || raw === null || raw === '') continue\n if (id === 'period') {\n const range = raw as { from?: string; to?: string }\n if (!range?.from && !range?.to) continue\n chips.push({ id, label: label(id), value: `${range.from ?? '\u2026'} \u2192 ${range.to ?? '\u2026'}` })\n continue\n }\n if (typeof raw !== 'string') continue\n // A deep link arrives as an id, so its chip reads the loaded rows back for\n // the task or the entry it names \u2014 a uuid on a chip explains nothing.\n if (id === 'taskId') {\n const first = raw.split(',')[0] ?? raw\n const match = rows.find((row) => row.taskId === first)\n chips.push({ id, label: label(id), value: match?.taskTitle ?? first })\n continue\n }\n if (id === 'ids') {\n const list = raw.split(',').map((value) => value.trim()).filter(Boolean)\n if (list.length === 0) continue\n const single = list.length === 1 ? rows.find((row) => row.id === list[0]) : undefined\n chips.push({\n id,\n label: label(id),\n value: single\n ? (single.taskTitle ?? single.description ?? labels.noTask)\n : labels.filters.entrySelection.replace('{count}', String(list.length)),\n })\n continue\n }\n chips.push({ id, label: label(id), value: optionLabel(id, raw) })\n }\n return chips\n }, [filterValues, filters, labels.filters.entrySelection, labels.noTask, rows, unlistedFilters])\n\n const columns = React.useMemo<ColumnDef<TimeEntryListRow>[]>(() => {\n const rightHeader = (label: string) => () => <span className=\"block text-right\">{label}</span>\n const defs: ColumnDef<TimeEntryListRow>[] = []\n\n if (canManage) {\n defs.push({\n id: 'select',\n accessorKey: 'id',\n header: () => (\n <Checkbox\n checked={allSelected ? true : someSelected ? 'indeterminate' : false}\n onCheckedChange={(checked) => toggleAllSelection(Boolean(checked))}\n aria-label={labels.selectAll}\n />\n ),\n enableSorting: false,\n meta: { priority: 1 },\n cell: ({ row }) =>\n row.original.isLocked ? null : (\n <Checkbox\n checked={selectedIds.includes(row.original.id)}\n onCheckedChange={(checked) => toggleRowSelection(row.original.id, Boolean(checked))}\n onClick={(event) => event.stopPropagation()}\n aria-label={labels.selectRow}\n />\n ),\n })\n }\n\n defs.push({\n accessorKey: 'date',\n header: labels.columns.date,\n meta: { priority: 1 },\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap text-sm text-muted-foreground\">\n {formatEntryDay(row.original.date)}\n </span>\n ),\n })\n\n defs.push({\n id: 'task',\n accessorKey: 'taskTitle',\n header: labels.columns.task,\n enableSorting: false,\n // `taskTitle` matches none of DataTable's wide-column keys, so it fell to the\n // 150px default and truncated to \"Consulting / Worksh\u2026\" while the nowrap\n // numeric columns kept their slack. This is the row's primary content.\n meta: { priority: 1, maxWidth: '520px' },\n cell: ({ row }) => {\n const entry = row.original\n const rateOverrideLabel =\n canSeeMoney && entry.rateOverrideAmount !== null\n ? formatCurrency(entry.rateOverrideAmount, entry.currencyCode) ?? String(entry.rateOverrideAmount)\n : null\n return (\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">\n {entry.taskTitle ?? labels.noTask}\n </span>\n {!entry.isBillable ? (\n <StatusBadge variant=\"neutral\">{labels.badges.nonBillable}</StatusBadge>\n ) : null}\n {rateOverrideLabel ? (\n <span data-testid=\"entry-rate-override\">\n <StatusBadge variant=\"info\">\n {t('staff.time_tracking.entries.badges.rateOverride', '{amount}/h', { amount: rateOverrideLabel })}\n </StatusBadge>\n </span>\n ) : null}\n {entry.isLocked ? (\n <span data-testid=\"entry-locked-badge\">\n <StatusBadge variant=\"neutral\">\n <Lock className=\"h-3 w-3\" aria-hidden=\"true\" />\n {labels.badges.locked}\n </StatusBadge>\n </span>\n ) : null}\n </div>\n {entry.description ? (\n <span className=\"truncate text-xs text-muted-foreground\">{entry.description}</span>\n ) : null}\n </div>\n )\n },\n })\n\n defs.push({\n id: 'project',\n accessorKey: 'projectLabel',\n header: labels.columns.project,\n enableSorting: false,\n meta: { priority: 2, maxWidth: '240px' },\n cell: ({ row }) =>\n row.original.projectLabel ? (\n <span className=\"truncate text-sm text-muted-foreground\">{row.original.projectLabel}</span>\n ) : (\n <span className=\"text-xs text-muted-foreground/70\">\u2014</span>\n ),\n })\n\n defs.push({\n id: 'clock',\n accessorKey: 'startText',\n header: labels.columns.clock,\n enableSorting: false,\n meta: { priority: 3, maxWidth: '160px' },\n cell: ({ row }) => {\n const range = formatEntryClockRange(row.original)\n return range ? (\n <span className=\"whitespace-nowrap font-mono text-sm tabular-nums text-muted-foreground\">{range}</span>\n ) : (\n <span className=\"text-xs text-muted-foreground/70\">\u2014</span>\n )\n },\n })\n\n defs.push({\n id: 'durationMinutes',\n accessorKey: 'durationMinutes',\n header: rightHeader(labels.columns.duration),\n meta: { priority: 1, maxWidth: '120px' },\n cell: ({ row }) => (\n <TimeEntryDurationCell\n value={row.original.durationMinutes}\n readOnly={row.original.isLocked || !canManage}\n saving={savingEntryId === row.original.id}\n ariaLabel={labels.durationAria}\n lockedTitle={row.original.isLocked ? labels.errors.locked : undefined}\n onCommit={(minutes) => handleDurationCommit(row.original, minutes)}\n />\n ),\n })\n\n if (canSeeMoney) {\n defs.push({\n id: 'cost',\n accessorKey: 'cost',\n header: rightHeader(labels.columns.cost),\n enableSorting: false,\n meta: { priority: 4, maxWidth: '140px' },\n cell: ({ row }) => {\n const formatted =\n row.original.cost === null ? null : formatCurrency(row.original.cost, row.original.currencyCode)\n return formatted ? (\n <span\n className=\"block text-right font-mono text-sm tabular-nums text-foreground\"\n data-testid=\"entry-cost\"\n >\n {formatted}\n </span>\n ) : (\n <span className=\"block text-right text-xs text-muted-foreground/70\" data-testid=\"entry-cost-empty\">\n \u2014\n </span>\n )\n },\n })\n }\n\n return defs\n }, [\n allSelected,\n canManage,\n canSeeMoney,\n handleDurationCommit,\n labels,\n savingEntryId,\n selectedIds,\n someSelected,\n t,\n toggleAllSelection,\n toggleRowSelection,\n ])\n\n const summary = React.useMemo(() => summarizeTimeEntries(rows), [rows])\n\n return (\n <Page>\n <PageBody>\n <DataTable<TimeEntryListRow>\n extensionTableId={extensionPoints.hosts.timeEntriesTable.tableId}\n title={labels.title}\n titleHeadingLevel={1}\n data={rows}\n columns={columns}\n isLoading={isLoading}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n // The overlay does not carry the deep-link filters, so applying it\n // drops them \u2014 the address bar has to agree with what is on screen.\n dropDeepLinkParams(DEEP_LINK_PARAMS.filter((id) => !values[id]))\n }}\n onFiltersClear={clearAllFilters}\n activeFilterChips={\n <div className=\"flex flex-col gap-2\">\n <EntryFilterPresets\n presets={presets}\n activePresetId={activePresetId}\n onApplyPreset={applyPreset}\n />\n <EntryFilterChips\n chips={appliedChips}\n onRemove={removeChip}\n onClearAll={clearAllFilters}\n clearAllLabel={labels.filters.clearAll}\n removeLabel={(name) => labels.filters.remove.replace('{name}', name)}\n />\n </div>\n }\n filterAwareEmptyState={{\n active: appliedChips.length > 0,\n entityNamePlural: labels.title,\n canRemoveLast: appliedChips.length > 0,\n onClearAll: clearAllFilters,\n onRemoveLast: () => {\n const last = appliedChips[appliedChips.length - 1]\n if (last) removeChip(last.id)\n },\n }}\n emptyState={<div className=\"py-12 text-center text-sm text-muted-foreground\">{labels.empty}</div>}\n actions={\n canManage ? (\n <div className=\"flex items-center gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => void handleCopyDay()}>\n <Copy className=\"h-4 w-4\" aria-hidden=\"true\" />\n {labels.copyDay}\n </Button>\n <Button size=\"sm\" onClick={openCreate}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {labels.add}\n </Button>\n </div>\n ) : undefined\n }\n refreshButton={{\n label: labels.refresh,\n onRefresh: handleRefresh,\n isRefreshing: isLoading || isRefreshing,\n }}\n sortable\n manualSorting\n sorting={sorting}\n onSortingChange={setSorting}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalIsCapped,\n totalPages,\n onPageChange: setPage,\n }}\n stickyActionsColumn\n rowActions={(row) =>\n row.isLocked ? (\n <RowActions\n items={[\n {\n id: 'show-report',\n label: labels.showReport,\n href: row.lockedReportId\n ? `${REPORTS_PATH}?id=${encodeURIComponent(row.lockedReportId)}`\n : REPORTS_PATH,\n },\n ]}\n />\n ) : canManage ? (\n <RowActions\n items={[\n {\n id: 'duplicate',\n label: labels.duplicate,\n onSelect: () => {\n void handleDuplicate(row)\n },\n },\n {\n id: 'delete',\n label: labels.delete,\n destructive: true,\n onSelect: () => {\n void handleDelete(row)\n },\n },\n ]}\n />\n ) : null\n }\n onRowClick={(row) => {\n if (row.isLocked) return\n openEdit(row.id)\n }}\n />\n\n <TimeEntriesSummaryFooter summary={summary} totalCount={total} canSeeMoney={canSeeMoney} />\n\n {!canManage ? (\n <p className=\"mt-2 text-xs text-muted-foreground\">\n {t('staff.time_tracking.entries.readOnlyNotice', 'You can view these entries but not change them.')}\n </p>\n ) : null}\n </PageBody>\n\n <TimeEntryDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n entryId={dialogEntryId}\n onSaved={({ keptOpen }) => {\n handleRefresh()\n if (!keptOpen) setDialogOpen(false)\n }}\n onShowEntry={(entryId) => openEdit(entryId)}\n />\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\n/**\n * Screen 10 \u2014 the working list of time entries.\n *\n * The five notes on the mockup are the five decisions this page exists to make,\n * and none of them is decoration:\n *\n * 1. **The `Czas` cell edits in place** (US-D5). Correcting a duration is the\n * single most common thing anybody does to a logged entry, so it costs one\n * click, not a dialog round trip. The field is the shared `DurationInput`\n * in its compact variant, so `1h 40m` means here exactly what it means in\n * the entry dialog and the timesheet grid.\n * 2. **Delete is optimistic and reversible** (US-D5). The row leaves the table\n * immediately and the platform's undo bar \u2014 fed by the `x-om-operation`\n * header the command layer already attaches to the response \u2014 offers\n * \"Cofnij\" for a few seconds. The restore is the command's own `undo`,\n * never a row rebuilt from client state, which is why an entry restored\n * this way comes back with its tags, its rounding and its audit trail\n * intact. The page watches the operation store and refetches the moment its\n * token is undone, so the row reappears from the server.\n * 3. **A rate override is visible on the row** (US-F2). Without the badge two\n * rows with the same duration and different costs look like a bug.\n * 4. **A locked row is inert** (US-G3): no checkbox, no edit, no delete, a\n * lock badge, and exactly one action \u2014 the report that froze it. Unlocking\n * happens in the report (screen 15), not here.\n * 5. **`Koszt` is empty for a non-billable row, not `0,00`.** The list route\n * already answers `null`; a zero would read as free work rather than as\n * work that is out of scope.\n *\n * The page is also the landing spot for two deep links \u2014 the task drawer's\n * \"show all entries\" (`?taskId=`) and the entry dialog's jump to an overlapping\n * entry (`?ids=`). Either one replaces the default \"my entries, this week\"\n * seeding and shows up as a removable chip, because a list that quietly ignored\n * the link would answer a question nobody asked.\n *\n * Two things beyond the notes. Money columns exist only for a holder of\n * `staff.timesheets.rates.view` \u2014 the response has no `cost` for anybody else,\n * so the column is absent rather than blanked. And the footer never adds two\n * currencies together; see `summarizeTimeEntries`.\n */\n\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport * as React from 'react'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { Copy, Lock, Plus } from 'lucide-react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterOverlay'\nimport {\n EntryFilterChips,\n EntryFilterPresets,\n matchActivePreset,\n type AppliedFilterChip,\n type EntryFilterPreset,\n} from '../../../../lib/time-tracking-ui/EntryFilterBar'\nimport {\n apiCall,\n apiCallOrThrow,\n readApiResultOrThrow,\n withScopedApiRequestHeaders,\n} from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOperationStore } from '@open-mercato/ui/backend/operations/store'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { deserializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { TimeEntryDialog } from '../../../../lib/time-tracking-ui/TimeEntryDialog'\nimport { REPORTS_PATH, reportDetailHref } from '../../../../lib/time-tracking-ui/reportLinks'\nimport { TimeEntryDurationCell } from '../../../../lib/time-tracking-ui/TimeEntryDurationCell'\nimport { TimeEntriesSummaryFooter } from '../../../../lib/time-tracking-ui/TimeEntriesSummaryFooter'\nimport {\n collectDirectoryIds,\n currentWeekRange,\n formatEntryClockRange,\n formatEntryDay,\n isEntryLockedError,\n readCopyDayTargetConflict,\n summarizeTimeEntries,\n toTimeEntryListRow,\n type TimeEntryDirectory,\n type TimeEntryListRow,\n} from '../../../../lib/time-tracking-ui/timeEntryListData'\nimport {\n readRowItems,\n shiftIsoDate,\n todayIsoDate,\n toProjectOption,\n toTaskOption,\n} from '../../../../lib/time-tracking-ui/timeEntryDialogState'\nimport { formatDuration } from '../../../../lib/time-tracking/duration'\n\nconst logger = createLogger('staff').child({ component: 'time-tracking/entries' })\n\nconst PAGE_SIZE = 50\nconst DIRECTORY_PAGE_SIZE = 100\nconst ENTRIES_API_PATH = 'staff/timesheets/time-entries'\nconst ENTRIES_API = `/api/${ENTRIES_API_PATH}`\nconst RATES_FEATURE = 'staff.timesheets.rates.view'\nconst MANAGE_OWN_FEATURE = 'staff.timesheets.manage_own'\nconst MANAGE_ALL_FEATURE = 'staff.timesheets.manage_all'\n/**\n * Query parameters other screens use to hand this list a question: the task\n * drawer's \"show every entry for this task\" (`?taskId=`) and the entry dialog's\n * jump to a conflicting entry (`?ids=`). Both are read once on mount and become\n * ordinary filter values, so they carry a removable chip like anything else.\n */\nconst DEEP_LINK_PARAMS = ['taskId', 'ids'] as const\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.entriesList'\nconst RESOURCE_KIND = 'staff.timesheets.time_entry'\n\ntype EntriesResponse = {\n items?: Array<Record<string, unknown>>\n total?: number\n totalIsCapped?: boolean\n totalPages?: number\n}\n\ntype CopyDayResponse = {\n copied?: number\n toDate?: string\n created?: Array<Record<string, unknown>>\n skipped?: Array<Record<string, unknown>>\n}\n\ntype MutationContext = {\n formId: string\n resourceKind: string\n resourceId: string\n retryLastMutation: () => Promise<boolean>\n}\n\nfunction readUndoToken(response: Response | null | undefined): string | null {\n const header = response?.headers?.get?.('x-om-operation') ?? null\n return deserializeOperationMetadata(header)?.undoToken ?? null\n}\n\nasync function loadDirectory(\n taskIds: readonly string[],\n projectIds: readonly string[],\n): Promise<TimeEntryDirectory> {\n const taskTitles = new Map<string, string>()\n const projectLabels = new Map<string, string>()\n const requests: Array<Promise<void>> = []\n\n if (taskIds.length > 0) {\n const params = new URLSearchParams({ page: '1', pageSize: String(DIRECTORY_PAGE_SIZE), ids: taskIds.join(',') })\n requests.push(\n readApiResultOrThrow<Record<string, unknown>>(`/api/staff/timesheets/tasks?${params.toString()}`, undefined, {\n allowNullResult: true,\n }).then((payload) => {\n for (const row of readRowItems(payload)) {\n const option = toTaskOption(row)\n if (option) taskTitles.set(option.id, option.title)\n }\n }),\n )\n }\n\n if (projectIds.length > 0) {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(DIRECTORY_PAGE_SIZE),\n ids: projectIds.join(','),\n })\n requests.push(\n readApiResultOrThrow<Record<string, unknown>>(\n `/api/staff/timesheets/time-projects?${params.toString()}`,\n undefined,\n { allowNullResult: true },\n ).then((payload) => {\n for (const row of readRowItems(payload)) {\n const option = toProjectOption(row)\n if (!option) continue\n projectLabels.set(option.id, option.customerName ? `${option.customerName} \u2014 ${option.name}` : option.name)\n }\n }),\n )\n }\n\n await Promise.all(requests)\n return { taskTitles, projectLabels }\n}\n\nexport default function TimeTrackingEntriesPage() {\n const t = useT()\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const scopeVersion = useOrganizationScopeVersion()\n const { payload } = useBackendChrome()\n const canSeeMoney = hasFeature(payload?.grantedFeatures, RATES_FEATURE)\n const canManage = hasFeature(payload?.grantedFeatures, MANAGE_OWN_FEATURE)\n // Reading somebody else's entries is `manage_all`, not `manage_own`: the list\n // route narrows every other caller back to their own rows, so offering them a\n // person filter promises a lookup the server will answer with nothing.\n const canManageAll = hasFeature(payload?.grantedFeatures, MANAGE_ALL_FEATURE)\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const deepLinkTaskId = searchParams?.get('taskId')?.trim() ?? ''\n const deepLinkEntryIds = searchParams?.get('ids')?.trim() ?? ''\n\n const defaultRange = React.useMemo(() => currentWeekRange(), [])\n const [filterValues, setFilterValues] = React.useState<FilterValues>(() => {\n // A deep link is the entire question the reader arrived with, so it replaces\n // the default week instead of being intersected with it \u2014 an entry logged\n // last month would otherwise be filtered straight back out of the list the\n // link exists to show.\n if (deepLinkTaskId) return { taskId: deepLinkTaskId }\n if (deepLinkEntryIds) return { ids: deepLinkEntryIds }\n return { period: { from: defaultRange.from, to: defaultRange.to } }\n })\n // The list opens on your own timesheet. Someone holding `manage_all` sees every\n // entry in the organization otherwise, which is a page about other people.\n const [selfStaffMemberId, setSelfStaffMemberId] = React.useState<string | null>(null)\n const [projectOptions, setProjectOptions] = React.useState<{ value: string; label: string }[]>([])\n const [customerOptions, setCustomerOptions] = React.useState<{ value: string; label: string }[]>([])\n const [tagOptions, setTagOptions] = React.useState<{ value: string; label: string }[]>([])\n const [peopleOptions, setPeopleOptions] = React.useState<{ value: string; label: string }[]>([])\n // A deep link also outranks the \"my entries\" seeding the self lookup performs\n // once it resolves; the entry being pointed at is frequently someone else's.\n const personSeededRef = React.useRef(Boolean(deepLinkTaskId || deepLinkEntryIds))\n const [page, setPage] = React.useState(1)\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'date', desc: true }])\n const [rows, setRows] = React.useState<TimeEntryListRow[]>([])\n const [total, setTotal] = React.useState(0)\n const [totalIsCapped, setTotalIsCapped] = React.useState(false)\n const [totalPages, setTotalPages] = React.useState(1)\n const [isLoading, setIsLoading] = React.useState(true)\n const [isRefreshing, setIsRefreshing] = React.useState(false)\n const [reloadToken, setReloadToken] = React.useState(0)\n const [selectedIds, setSelectedIds] = React.useState<string[]>([])\n const [savingEntryId, setSavingEntryId] = React.useState<string | null>(null)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n const [dialogEntryId, setDialogEntryId] = React.useState<string | null>(null)\n const hasLoadedOnceRef = React.useRef(false)\n\n const labels = React.useMemo(\n () => ({\n title: t('staff.time_tracking.entries.title', 'Time entries'),\n add: t('staff.time_tracking.entries.actions.add', 'Add entry'),\n copyDay: t('staff.time_tracking.entries.actions.copyDay', 'Copy yesterday'),\n duplicate: t('staff.time_tracking.entries.actions.duplicate', 'Duplicate'),\n delete: t('staff.time_tracking.entries.actions.delete', 'Delete'),\n showReport: t('staff.time_tracking.entries.actions.showReport', 'Show the report'),\n refresh: t('staff.time_tracking.entries.actions.refresh', 'Refresh'),\n columns: {\n date: t('staff.time_tracking.entries.columns.date', 'Date'),\n task: t('staff.time_tracking.entries.columns.task', 'Task / description'),\n project: t('staff.time_tracking.entries.columns.project', 'Project'),\n clock: t('staff.time_tracking.entries.columns.clock', 'Hours'),\n duration: t('staff.time_tracking.entries.columns.duration', 'Time'),\n cost: t('staff.time_tracking.entries.columns.cost', 'Cost'),\n },\n selectAll: t('staff.time_tracking.entries.table.selectAll', 'Select all entries'),\n selectRow: t('staff.time_tracking.entries.table.selectRow', 'Select entry'),\n durationAria: t('staff.time_tracking.entries.table.durationAria', 'Entry duration'),\n noTask: t('staff.time_tracking.entries.table.noTask', 'No task'),\n empty: t('staff.time_tracking.entries.empty', 'No time entries in this period.'),\n badges: {\n locked: t('staff.time_tracking.entries.badges.locked', 'locked'),\n nonBillable: t('staff.time_tracking.entries.badges.nonBillable', 'non-billable'),\n },\n filters: {\n period: t('staff.time_tracking.entries.filters.period', 'Period'),\n project: t('staff.time_tracking.entries.filters.project', 'Project'),\n customer: t('staff.time_tracking.entries.filters.customer', 'Customer'),\n person: t('staff.time_tracking.entries.filters.person', 'Person'),\n tag: t('staff.time_tracking.entries.filters.tag', 'Tag'),\n billable: t('staff.time_tracking.entries.filters.billable', 'Billable'),\n billableYes: t('staff.time_tracking.entries.filters.billableYes', 'Billable only'),\n billableNo: t('staff.time_tracking.entries.filters.billableNo', 'Non-billable only'),\n locked: t('staff.time_tracking.entries.filters.locked', 'Report status'),\n lockedYes: t('staff.time_tracking.entries.filters.lockedYes', 'Locked in a report'),\n lockedNo: t('staff.time_tracking.entries.filters.lockedNo', 'Not yet reported'),\n text: t('staff.time_tracking.entries.filters.text', 'Description contains'),\n task: t('staff.time_tracking.entries.filters.task', 'Task'),\n entry: t('staff.time_tracking.entries.filters.entry', 'Entry'),\n entrySelection: t('staff.time_tracking.entries.filters.entrySelection', '{count} entries'),\n clearAll: t('staff.time_tracking.entries.filters.clearAll', 'Clear all'),\n remove: t('staff.time_tracking.entries.filters.remove', 'Remove filter {name}'),\n },\n presets: {\n mineWeek: t('staff.time_tracking.entries.presets.mineWeek', 'Mine, this week'),\n unbilled: t('staff.time_tracking.entries.presets.unbilled', 'Unbilled'),\n nonBillable: t('staff.time_tracking.entries.presets.nonBillable', 'Non-billable'),\n thisMonth: t('staff.time_tracking.entries.presets.thisMonth', 'This month'),\n },\n errors: {\n load: t('staff.time_tracking.entries.errors.load', 'Could not load the time entries.'),\n save: t('staff.time_tracking.entries.errors.save', 'Could not save the time entry.'),\n delete: t('staff.time_tracking.entries.errors.delete', 'Could not delete the time entry.'),\n duplicate: t('staff.time_tracking.entries.errors.duplicate', 'Could not duplicate the time entry.'),\n copyDay: t('staff.time_tracking.entries.errors.copyDay', 'Could not copy the day.'),\n locked: t(\n 'staff.time_tracking.entries.errors.entryLocked',\n 'This time entry is locked in a closed report and cannot be changed. Unlock the report first.',\n ),\n },\n }),\n [t],\n )\n\n const handleRefresh = React.useCallback(() => {\n setReloadToken((token) => token + 1)\n }, [])\n\n const reportFailure = React.useCallback(\n (error: unknown, fallbackMessage: string, logTag: string) => {\n if (surfaceRecordConflict(error, t)) return\n if (isEntryLockedError(error)) {\n flash(labels.errors.locked, 'error')\n return\n }\n logger.error(logTag, { err: error })\n flash(error instanceof Error && error.message ? error.message : fallbackMessage, 'error')\n },\n [labels.errors.locked, t],\n )\n\n const period = (filterValues.period ?? {}) as { from?: string; to?: string }\n const projectFilter = typeof filterValues.projectId === 'string' ? filterValues.projectId : ''\n\n // Filter vocabularies come from the API, not from the rows on screen: options\n // built from the loaded page can only narrow to what you can already see, which\n // makes the filter useless for finding anything that is not in front of you.\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n // `Promise.resolve` rather than `.catch` directly: a filter vocabulary that\n // fails to load must leave the page usable with a shorter list, and the\n // wrapper also tolerates a stub that returns nothing.\n const safeCall = async <T,>(url: string) => {\n try {\n return (await Promise.resolve(apiCall<T>(url))) ?? null\n } catch {\n return null\n }\n }\n const [selfRes, projectsRes, tagsRes, peopleRes] = await Promise.all([\n safeCall<{ member?: { id?: string } | null }>('/api/staff/team-members/self'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/time-projects?page=1&pageSize=100'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/tags?page=1&pageSize=100'),\n safeCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/team-members?page=1&pageSize=100'),\n ])\n if (cancelled) return\n\n const selfId = selfRes?.ok ? (selfRes.result?.member?.id ?? null) : null\n if (selfId) {\n setSelfStaffMemberId(selfId)\n if (!personSeededRef.current) {\n personSeededRef.current = true\n setFilterValues((prev) => ({ ...prev, staffMemberId: selfId }))\n }\n }\n\n const projects = projectsRes?.ok && Array.isArray(projectsRes.result?.items) ? projectsRes.result.items : []\n const customers = new Map<string, string>()\n setProjectOptions(\n projects\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n if (!id) return null\n const name = typeof row.name === 'string' ? row.name : id\n const code = typeof row.code === 'string' ? row.code : null\n const customerId = typeof row.customer_id === 'string' ? row.customer_id : null\n const snapshot = row.customer_snapshot as { name?: unknown } | null | undefined\n if (customerId && typeof snapshot?.name === 'string') customers.set(customerId, snapshot.name)\n return { value: id, label: code ? `${code} \u00B7 ${name}` : name }\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n setCustomerOptions([...customers].map(([value, label]) => ({ value, label })))\n\n const tags = tagsRes?.ok && Array.isArray(tagsRes.result?.items) ? tagsRes.result.items : []\n setTagOptions(\n tags\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.label === 'string' ? row.label : null\n return id && label ? { value: id, label } : null\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n\n const people = peopleRes?.ok && Array.isArray(peopleRes.result?.items) ? peopleRes.result.items : []\n setPeopleOptions(\n people\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.display_name === 'string'\n ? row.display_name\n : typeof row.displayName === 'string'\n ? row.displayName\n : null\n return id && label ? { value: id, label } : null\n })\n .filter((option): option is { value: string; label: string } => option !== null),\n )\n })()\n return () => { cancelled = true }\n }, [scopeVersion])\n\n const presets = React.useMemo<EntryFilterPreset[]>(\n () => [\n {\n id: 'mine-week',\n label: labels.presets.mineWeek,\n build: ({ staffMemberId }) => ({\n period: { from: defaultRange.from, to: defaultRange.to },\n ...(staffMemberId ? { staffMemberId } : {}),\n }),\n },\n {\n id: 'unbilled',\n label: labels.presets.unbilled,\n build: () => ({ locked: 'false', billable: 'true' }),\n },\n { id: 'non-billable', label: labels.presets.nonBillable, build: () => ({ billable: 'false' }) },\n {\n id: 'this-month',\n label: labels.presets.thisMonth,\n build: ({ today }) => {\n const start = `${today.slice(0, 7)}-01`\n return { period: { from: start, to: today } }\n },\n },\n ],\n [defaultRange.from, defaultRange.to, labels.presets],\n )\n\n const presetContext = React.useMemo(\n () => ({ staffMemberId: selfStaffMemberId, today: new Date().toISOString().slice(0, 10) }),\n [selfStaffMemberId],\n )\n const activePresetId = React.useMemo(\n () => matchActivePreset(filterValues, presets, presetContext),\n [filterValues, presetContext, presets],\n )\n\n const applyPreset = React.useCallback(\n (preset: EntryFilterPreset) => {\n // A preset is a starting point, not a mode: it replaces what it sets and\n // leaves the rest, so narrowing further does not mean rebuilding it.\n setFilterValues((current) => ({ ...current, ...preset.build(presetContext) }))\n setPage(1)\n },\n [presetContext],\n )\n\n /**\n * A deep-link filter lives in two places \u2014 the filter state and the address\n * bar \u2014 so dropping the chip has to drop the parameter too, otherwise the next\n * render (or a reload) puts the narrowing straight back.\n */\n const dropDeepLinkParams = React.useCallback(\n (ids: readonly string[]) => {\n if (!searchParams) return\n const next = new URLSearchParams(searchParams.toString())\n let changed = false\n for (const id of ids) {\n if (!next.has(id)) continue\n next.delete(id)\n changed = true\n }\n if (!changed) return\n const query = next.toString()\n router.replace(query ? `${pathname}?${query}` : pathname)\n },\n [pathname, router, searchParams],\n )\n\n const removeChip = React.useCallback(\n (id: string) => {\n setFilterValues((current) => {\n const next = { ...current }\n delete next[id]\n return next\n })\n setPage(1)\n dropDeepLinkParams([id])\n },\n [dropDeepLinkParams],\n )\n\n const clearAllFilters = React.useCallback(() => {\n setFilterValues({})\n setPage(1)\n dropDeepLinkParams(DEEP_LINK_PARAMS)\n }, [dropDeepLinkParams])\n\n const loadEntries = React.useCallback(async () => {\n if (hasLoadedOnceRef.current) setIsRefreshing(true)\n else setIsLoading(true)\n try {\n const params = new URLSearchParams({ page: String(page), pageSize: String(PAGE_SIZE) })\n const sort = sorting[0]\n params.set('sortField', sort?.id ?? 'date')\n params.set('sortDir', sort?.desc === false ? 'asc' : 'desc')\n if (period.from) params.set('from', period.from)\n if (period.to) params.set('to', period.to)\n if (projectFilter) params.set('projectId', projectFilter)\n const readFilter = (key: string): string =>\n typeof filterValues[key] === 'string' ? (filterValues[key] as string) : ''\n const person = readFilter('staffMemberId')\n if (person) params.set('staffMemberId', person)\n const billable = readFilter('billable')\n if (billable) params.set('billable', billable)\n const lockedFilter = readFilter('locked')\n if (lockedFilter) params.set('locked', lockedFilter)\n const customer = readFilter('customerId')\n if (customer) params.set('customerId', customer)\n const tag = readFilter('tagIds')\n if (tag) params.set('tagIds', tag)\n const text = readFilter('q')\n if (text.trim()) params.set('q', text.trim())\n const taskFilter = readFilter('taskId')\n if (taskFilter) params.set('taskId', taskFilter)\n const entryIdsFilter = readFilter('ids')\n if (entryIdsFilter) params.set('ids', entryIdsFilter)\n\n const listPayload = await readApiResultOrThrow<EntriesResponse>(\n `${ENTRIES_API}?${params.toString()}`,\n undefined,\n { errorMessage: labels.errors.load, fallback: { items: [], total: 0, totalPages: 1 } },\n )\n const items = Array.isArray(listPayload.items) ? listPayload.items : []\n const bare = items\n .map((item) => toTimeEntryListRow(item))\n .filter((row): row is TimeEntryListRow => row !== null)\n const { taskIds, projectIds } = collectDirectoryIds(bare)\n let directory: TimeEntryDirectory = { taskTitles: new Map(), projectLabels: new Map() }\n try {\n directory = await loadDirectory(taskIds, projectIds)\n } catch (error) {\n logger.warn('staff.time_tracking.entries directory load failed', { err: error })\n }\n setRows(\n items\n .map((item) => toTimeEntryListRow(item, directory))\n .filter((row): row is TimeEntryListRow => row !== null),\n )\n setTotal(typeof listPayload.total === 'number' ? listPayload.total : items.length)\n setTotalIsCapped(listPayload.totalIsCapped === true)\n setTotalPages(\n typeof listPayload.totalPages === 'number'\n ? listPayload.totalPages\n : Math.max(1, Math.ceil(items.length / PAGE_SIZE)),\n )\n } catch (error) {\n logger.error('staff.time_tracking.entries list failed', { err: error })\n flash(labels.errors.load, 'error')\n } finally {\n setIsLoading(false)\n setIsRefreshing(false)\n hasLoadedOnceRef.current = true\n }\n }, [labels.errors.load, page, period.from, period.to, projectFilter, sorting, filterValues])\n\n React.useEffect(() => {\n void loadEntries()\n }, [loadEntries, scopeVersion, reloadToken])\n\n React.useEffect(() => {\n setSelectedIds([])\n }, [page, period.from, period.to, projectFilter, scopeVersion])\n\n /**\n * Note 2's other half. The undo bar is AppShell's, so the click that restores\n * an entry never reaches this page \u2014 the operation store is what does. When a\n * token this page deleted shows up in the store's `undone` list the row is\n * back on the server, so the list is refetched rather than reconstructed.\n */\n const undoneTokens = useOperationStore((state) => state.undone.map((entry) => entry.undoToken).join('|'))\n const pendingUndoTokensRef = React.useRef<Set<string>>(new Set())\n React.useEffect(() => {\n if (pendingUndoTokensRef.current.size === 0) return\n const undone = new Set(undoneTokens.split('|').filter(Boolean))\n let restored = false\n for (const token of Array.from(pendingUndoTokensRef.current)) {\n if (!undone.has(token)) continue\n pendingUndoTokensRef.current.delete(token)\n restored = true\n }\n if (restored) handleRefresh()\n }, [handleRefresh, undoneTokens])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<MutationContext>({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: labels.errors.save,\n })\n\n const mutationContext = React.useCallback(\n (resourceId: string): MutationContext => ({\n formId: MUTATION_CONTEXT_ID,\n resourceKind: RESOURCE_KIND,\n resourceId,\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const handleDurationCommit = React.useCallback(\n async (entry: TimeEntryListRow, durationMinutes: number) => {\n setSavingEntryId(entry.id)\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n updateCrud(\n ENTRIES_API_PATH,\n { id: entry.id, durationMinutes },\n { errorMessage: labels.errors.save },\n ),\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id, durationMinutes },\n })\n handleRefresh()\n } catch (error) {\n reportFailure(error, labels.errors.save, 'staff.time_tracking.entries inline duration save failed')\n handleRefresh()\n } finally {\n setSavingEntryId(null)\n }\n },\n [handleRefresh, labels.errors.save, mutationContext, reportFailure, runMutation],\n )\n\n const handleDelete = React.useCallback(\n async (entry: TimeEntryListRow) => {\n const previousRows = rows\n setRows((current) => current.filter((row) => row.id !== entry.id))\n setTotal((current) => Math.max(0, current - 1))\n setSelectedIds((current) => current.filter((id) => id !== entry.id))\n try {\n const call = await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n deleteCrud(ENTRIES_API_PATH, entry.id, { errorMessage: labels.errors.delete }),\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id },\n })\n const undoToken = readUndoToken(call.response)\n if (undoToken) pendingUndoTokensRef.current.add(undoToken)\n flash(\n t('staff.time_tracking.entries.messages.deleted', 'Deleted \u201C{name}\u201D ({duration}). Use Undo to restore it.', {\n name: entry.taskTitle ?? entry.description ?? labels.noTask,\n duration: formatDuration(entry.durationMinutes, 'clock'),\n }),\n 'success',\n )\n } catch (error) {\n setRows(previousRows)\n setTotal((current) => current + 1)\n reportFailure(error, labels.errors.delete, 'staff.time_tracking.entries delete failed')\n }\n },\n [labels.errors.delete, labels.noTask, mutationContext, reportFailure, rows, runMutation, t],\n )\n\n const handleDuplicate = React.useCallback(\n async (entry: TimeEntryListRow) => {\n try {\n await runMutation({\n operation: () =>\n apiCallOrThrow<Record<string, unknown>>(\n `${ENTRIES_API}/${encodeURIComponent(entry.id)}/duplicate`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ tagIds: entry.tagIds }),\n },\n { errorMessage: labels.errors.duplicate },\n ),\n context: mutationContext(entry.id),\n mutationPayload: { id: entry.id },\n })\n flash(t('staff.time_tracking.entries.messages.duplicated', 'Entry duplicated.'), 'success')\n handleRefresh()\n } catch (error) {\n reportFailure(error, labels.errors.duplicate, 'staff.time_tracking.entries duplicate failed')\n }\n },\n [handleRefresh, labels.errors.duplicate, mutationContext, reportFailure, runMutation, t],\n )\n\n /**\n * \"Kopiuj wczorajszy dzie\u0144\" (screen 1 note 5). The copies are drafts to\n * review, so on success the period narrows to the target day: the user lands\n * on exactly the rows that were created instead of hunting for them.\n *\n * A non-empty target day answers `409 copy_day_target_not_empty` naming what\n * is already there. That is a question, not a dead end \u2014 the user is told the\n * count and offered the `allowDuplicates` path, which is the real case the\n * refusal would otherwise block (something already logged today, and\n * yesterday's set wanted on top).\n */\n const runCopyDay = React.useCallback(\n async (fromDate: string, toDate: string, allowDuplicates: boolean) =>\n runMutation({\n operation: () =>\n apiCallOrThrow<CopyDayResponse>(\n `${ENTRIES_API}/copy-day`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ fromDate, toDate, allowDuplicates }),\n },\n { errorMessage: labels.errors.copyDay },\n ),\n context: mutationContext(toDate),\n mutationPayload: { fromDate, toDate, allowDuplicates },\n }),\n [labels.errors.copyDay, mutationContext, runMutation],\n )\n\n const handleCopyDay = React.useCallback(async () => {\n const toDate = todayIsoDate()\n const fromDate = shiftIsoDate(toDate, -1)\n const announce = (result: CopyDayResponse | null) => {\n const copied = typeof result?.copied === 'number' ? result.copied : 0\n const skipped = Array.isArray(result?.skipped) ? result.skipped.length : 0\n if (copied === 0) {\n flash(t('staff.time_tracking.entries.copyDay.nothing', 'Yesterday has no entries to copy.'), 'info')\n } else {\n flash(\n t('staff.time_tracking.entries.copyDay.success', '{count} entries copied as drafts \u2014 review them.', {\n count: copied,\n }),\n 'success',\n )\n setFilterValues((current) => ({ ...current, period: { from: toDate, to: toDate } }))\n setPage(1)\n }\n if (skipped > 0) {\n flash(\n t('staff.time_tracking.entries.copyDay.skipped', '{count} entries were skipped because they are locked.', {\n count: skipped,\n }),\n 'warning',\n )\n }\n handleRefresh()\n }\n\n try {\n const call = await runCopyDay(fromDate, toDate, false)\n announce(call.result)\n } catch (error) {\n const conflict = readCopyDayTargetConflict(error)\n if (!conflict) {\n reportFailure(error, labels.errors.copyDay, 'staff.time_tracking.entries copy-day failed')\n return\n }\n const confirmed = await confirm({\n title: labels.copyDay,\n text: t(\n 'staff.time_tracking.entries.copyDay.conflict',\n 'Today already has {count} entries. Add yesterday\u2019s copies on top of them?',\n { count: conflict.existingEntryCount },\n ),\n confirmText: t('staff.time_tracking.entries.copyDay.confirmDuplicates', 'Add copies anyway'),\n })\n if (!confirmed) return\n try {\n const retry = await runCopyDay(fromDate, toDate, true)\n announce(retry.result)\n } catch (retryError) {\n reportFailure(retryError, labels.errors.copyDay, 'staff.time_tracking.entries copy-day retry failed')\n }\n }\n }, [confirm, handleRefresh, labels.copyDay, labels.errors.copyDay, reportFailure, runCopyDay, t])\n\n const openCreate = React.useCallback(() => {\n setDialogEntryId(null)\n setDialogOpen(true)\n }, [])\n\n const openEdit = React.useCallback((entryId: string) => {\n setDialogEntryId(entryId)\n setDialogOpen(true)\n }, [])\n\n const selectableRows = React.useMemo(() => rows.filter((row) => !row.isLocked), [rows])\n const allSelected = selectableRows.length > 0 && selectedIds.length === selectableRows.length\n const someSelected = selectedIds.length > 0 && !allSelected\n\n const toggleRowSelection = React.useCallback((id: string, checked: boolean) => {\n setSelectedIds((current) => (checked ? Array.from(new Set([...current, id])) : current.filter((value) => value !== id)))\n }, [])\n\n const toggleAllSelection = React.useCallback(\n (checked: boolean) => {\n setSelectedIds(checked ? selectableRows.map((row) => row.id) : [])\n },\n [selectableRows],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n { id: 'period', label: labels.filters.period, type: 'dateRange' },\n { id: 'projectId', label: labels.filters.project, type: 'select', options: projectOptions },\n { id: 'customerId', label: labels.filters.customer, type: 'select', options: customerOptions },\n // Only offered when the caller can actually see somebody else's time; for\n // everyone else the route already scopes to their own entries and the\n // control would be a lever attached to nothing.\n ...(canManageAll && peopleOptions.length > 1\n ? [{ id: 'staffMemberId', label: labels.filters.person, type: 'select' as const, options: peopleOptions }]\n : []),\n { id: 'tagIds', label: labels.filters.tag, type: 'select', options: tagOptions },\n {\n id: 'billable',\n label: labels.filters.billable,\n type: 'select',\n options: [\n { value: 'true', label: labels.filters.billableYes },\n { value: 'false', label: labels.filters.billableNo },\n ],\n },\n {\n id: 'locked',\n label: labels.filters.locked,\n type: 'select',\n options: [\n { value: 'true', label: labels.filters.lockedYes },\n { value: 'false', label: labels.filters.lockedNo },\n ],\n },\n { id: 'q', label: labels.filters.text, type: 'text' },\n ],\n [labels.filters, canManageAll, projectOptions, customerOptions, peopleOptions, tagOptions],\n )\n\n /**\n * Filters that can be applied but own no control in the overlay: the self\n * filter, which is seeded for everyone but only steerable by a `manage_all`\n * holder, and the two deep-link narrowings. Naming them here is what keeps\n * their chips reading as words instead of as a parameter name and a uuid.\n */\n const unlistedFilters = React.useMemo<FilterDef[]>(\n () => [\n { id: 'staffMemberId', label: labels.filters.person, type: 'select', options: peopleOptions },\n { id: 'taskId', label: labels.filters.task, type: 'text' },\n { id: 'ids', label: labels.filters.entry, type: 'text' },\n ],\n [labels.filters, peopleOptions],\n )\n\n /** Applied filters, as the reader would say them out loud. */\n const appliedChips = React.useMemo<AppliedFilterChip[]>(() => {\n const defOf = (id: string) =>\n filters.find((filter) => filter.id === id) ?? unlistedFilters.find((filter) => filter.id === id)\n const label = (id: string) => defOf(id)?.label ?? id\n const optionLabel = (id: string, value: string) =>\n defOf(id)?.options?.find((option) => option.value === value)?.label ?? value\n const chips: AppliedFilterChip[] = []\n for (const [id, raw] of Object.entries(filterValues)) {\n if (raw === undefined || raw === null || raw === '') continue\n if (id === 'period') {\n const range = raw as { from?: string; to?: string }\n if (!range?.from && !range?.to) continue\n chips.push({ id, label: label(id), value: `${range.from ?? '\u2026'} \u2192 ${range.to ?? '\u2026'}` })\n continue\n }\n if (typeof raw !== 'string') continue\n // A deep link arrives as an id, so its chip reads the loaded rows back for\n // the task or the entry it names \u2014 a uuid on a chip explains nothing.\n if (id === 'taskId') {\n const first = raw.split(',')[0] ?? raw\n const match = rows.find((row) => row.taskId === first)\n chips.push({ id, label: label(id), value: match?.taskTitle ?? first })\n continue\n }\n if (id === 'ids') {\n const list = raw.split(',').map((value) => value.trim()).filter(Boolean)\n if (list.length === 0) continue\n const single = list.length === 1 ? rows.find((row) => row.id === list[0]) : undefined\n chips.push({\n id,\n label: label(id),\n value: single\n ? (single.taskTitle ?? single.description ?? labels.noTask)\n : labels.filters.entrySelection.replace('{count}', String(list.length)),\n })\n continue\n }\n chips.push({ id, label: label(id), value: optionLabel(id, raw) })\n }\n return chips\n }, [filterValues, filters, labels.filters.entrySelection, labels.noTask, rows, unlistedFilters])\n\n const columns = React.useMemo<ColumnDef<TimeEntryListRow>[]>(() => {\n const rightHeader = (label: string) => () => <span className=\"block text-right\">{label}</span>\n const defs: ColumnDef<TimeEntryListRow>[] = []\n\n if (canManage) {\n defs.push({\n id: 'select',\n accessorKey: 'id',\n header: () => (\n <Checkbox\n checked={allSelected ? true : someSelected ? 'indeterminate' : false}\n onCheckedChange={(checked) => toggleAllSelection(Boolean(checked))}\n aria-label={labels.selectAll}\n />\n ),\n enableSorting: false,\n meta: { priority: 1 },\n cell: ({ row }) =>\n row.original.isLocked ? null : (\n <Checkbox\n checked={selectedIds.includes(row.original.id)}\n onCheckedChange={(checked) => toggleRowSelection(row.original.id, Boolean(checked))}\n onClick={(event) => event.stopPropagation()}\n aria-label={labels.selectRow}\n />\n ),\n })\n }\n\n defs.push({\n accessorKey: 'date',\n header: labels.columns.date,\n meta: { priority: 1 },\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap text-sm text-muted-foreground\">\n {formatEntryDay(row.original.date)}\n </span>\n ),\n })\n\n defs.push({\n id: 'task',\n accessorKey: 'taskTitle',\n header: labels.columns.task,\n enableSorting: false,\n // `taskTitle` matches none of DataTable's wide-column keys, so it fell to the\n // 150px default and truncated to \"Consulting / Worksh\u2026\" while the nowrap\n // numeric columns kept their slack. This is the row's primary content.\n meta: { priority: 1, maxWidth: '520px' },\n cell: ({ row }) => {\n const entry = row.original\n const rateOverrideLabel =\n canSeeMoney && entry.rateOverrideAmount !== null\n ? formatCurrency(entry.rateOverrideAmount, entry.currencyCode) ?? String(entry.rateOverrideAmount)\n : null\n return (\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <div className=\"flex flex-wrap items-center gap-1.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">\n {entry.taskTitle ?? labels.noTask}\n </span>\n {!entry.isBillable ? (\n <StatusBadge variant=\"neutral\">{labels.badges.nonBillable}</StatusBadge>\n ) : null}\n {rateOverrideLabel ? (\n <span data-testid=\"entry-rate-override\">\n <StatusBadge variant=\"info\">\n {t('staff.time_tracking.entries.badges.rateOverride', '{amount}/h', { amount: rateOverrideLabel })}\n </StatusBadge>\n </span>\n ) : null}\n {entry.isLocked ? (\n <span data-testid=\"entry-locked-badge\">\n <StatusBadge variant=\"neutral\">\n <Lock className=\"h-3 w-3\" aria-hidden=\"true\" />\n {labels.badges.locked}\n </StatusBadge>\n </span>\n ) : null}\n </div>\n {entry.description ? (\n <span className=\"truncate text-xs text-muted-foreground\">{entry.description}</span>\n ) : null}\n </div>\n )\n },\n })\n\n defs.push({\n id: 'project',\n accessorKey: 'projectLabel',\n header: labels.columns.project,\n enableSorting: false,\n meta: { priority: 2, maxWidth: '240px' },\n cell: ({ row }) =>\n row.original.projectLabel ? (\n <span className=\"truncate text-sm text-muted-foreground\">{row.original.projectLabel}</span>\n ) : (\n <span className=\"text-xs text-muted-foreground/70\">\u2014</span>\n ),\n })\n\n defs.push({\n id: 'clock',\n accessorKey: 'startText',\n header: labels.columns.clock,\n enableSorting: false,\n meta: { priority: 3, maxWidth: '160px' },\n cell: ({ row }) => {\n const range = formatEntryClockRange(row.original)\n return range ? (\n <span className=\"whitespace-nowrap font-mono text-sm tabular-nums text-muted-foreground\">{range}</span>\n ) : (\n <span className=\"text-xs text-muted-foreground/70\">\u2014</span>\n )\n },\n })\n\n defs.push({\n id: 'durationMinutes',\n accessorKey: 'durationMinutes',\n header: rightHeader(labels.columns.duration),\n meta: { priority: 1, maxWidth: '120px' },\n cell: ({ row }) => (\n <TimeEntryDurationCell\n value={row.original.durationMinutes}\n readOnly={row.original.isLocked || !canManage}\n saving={savingEntryId === row.original.id}\n ariaLabel={labels.durationAria}\n lockedTitle={row.original.isLocked ? labels.errors.locked : undefined}\n onCommit={(minutes) => handleDurationCommit(row.original, minutes)}\n />\n ),\n })\n\n if (canSeeMoney) {\n defs.push({\n id: 'cost',\n accessorKey: 'cost',\n header: rightHeader(labels.columns.cost),\n enableSorting: false,\n meta: { priority: 4, maxWidth: '140px' },\n cell: ({ row }) => {\n const formatted =\n row.original.cost === null ? null : formatCurrency(row.original.cost, row.original.currencyCode)\n return formatted ? (\n <span\n className=\"block text-right font-mono text-sm tabular-nums text-foreground\"\n data-testid=\"entry-cost\"\n >\n {formatted}\n </span>\n ) : (\n <span className=\"block text-right text-xs text-muted-foreground/70\" data-testid=\"entry-cost-empty\">\n \u2014\n </span>\n )\n },\n })\n }\n\n return defs\n }, [\n allSelected,\n canManage,\n canSeeMoney,\n handleDurationCommit,\n labels,\n savingEntryId,\n selectedIds,\n someSelected,\n t,\n toggleAllSelection,\n toggleRowSelection,\n ])\n\n const summary = React.useMemo(() => summarizeTimeEntries(rows), [rows])\n\n return (\n <Page>\n <PageBody>\n <DataTable<TimeEntryListRow>\n extensionTableId={extensionPoints.hosts.timeEntriesTable.tableId}\n title={labels.title}\n titleHeadingLevel={1}\n data={rows}\n columns={columns}\n isLoading={isLoading}\n filters={filters}\n filterValues={filterValues}\n onFiltersApply={(values) => {\n setFilterValues(values)\n setPage(1)\n // The overlay does not carry the deep-link filters, so applying it\n // drops them \u2014 the address bar has to agree with what is on screen.\n dropDeepLinkParams(DEEP_LINK_PARAMS.filter((id) => !values[id]))\n }}\n onFiltersClear={clearAllFilters}\n activeFilterChips={\n <div className=\"flex flex-col gap-2\">\n <EntryFilterPresets\n presets={presets}\n activePresetId={activePresetId}\n onApplyPreset={applyPreset}\n />\n <EntryFilterChips\n chips={appliedChips}\n onRemove={removeChip}\n onClearAll={clearAllFilters}\n clearAllLabel={labels.filters.clearAll}\n removeLabel={(name) => labels.filters.remove.replace('{name}', name)}\n />\n </div>\n }\n filterAwareEmptyState={{\n active: appliedChips.length > 0,\n entityNamePlural: labels.title,\n canRemoveLast: appliedChips.length > 0,\n onClearAll: clearAllFilters,\n onRemoveLast: () => {\n const last = appliedChips[appliedChips.length - 1]\n if (last) removeChip(last.id)\n },\n }}\n emptyState={<div className=\"py-12 text-center text-sm text-muted-foreground\">{labels.empty}</div>}\n actions={\n canManage ? (\n <div className=\"flex items-center gap-2\">\n <Button size=\"sm\" variant=\"outline\" onClick={() => void handleCopyDay()}>\n <Copy className=\"h-4 w-4\" aria-hidden=\"true\" />\n {labels.copyDay}\n </Button>\n <Button size=\"sm\" onClick={openCreate}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {labels.add}\n </Button>\n </div>\n ) : undefined\n }\n refreshButton={{\n label: labels.refresh,\n onRefresh: handleRefresh,\n isRefreshing: isLoading || isRefreshing,\n }}\n sortable\n manualSorting\n sorting={sorting}\n onSortingChange={setSorting}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalIsCapped,\n totalPages,\n onPageChange: setPage,\n }}\n stickyActionsColumn\n rowActions={(row) =>\n row.isLocked ? (\n <RowActions\n items={[\n {\n id: 'show-report',\n label: labels.showReport,\n href: row.lockedReportId\n ? reportDetailHref(row.lockedReportId)\n : REPORTS_PATH,\n },\n ]}\n />\n ) : canManage ? (\n <RowActions\n items={[\n {\n id: 'duplicate',\n label: labels.duplicate,\n onSelect: () => {\n void handleDuplicate(row)\n },\n },\n {\n id: 'delete',\n label: labels.delete,\n destructive: true,\n onSelect: () => {\n void handleDelete(row)\n },\n },\n ]}\n />\n ) : null\n }\n onRowClick={(row) => {\n if (row.isLocked) return\n openEdit(row.id)\n }}\n />\n\n <TimeEntriesSummaryFooter summary={summary} totalCount={total} canSeeMoney={canSeeMoney} />\n\n {!canManage ? (\n <p className=\"mt-2 text-xs text-muted-foreground\">\n {t('staff.time_tracking.entries.readOnlyNotice', 'You can view these entries but not change them.')}\n </p>\n ) : null}\n </PageBody>\n\n <TimeEntryDialog\n open={dialogOpen}\n onOpenChange={setDialogOpen}\n entryId={dialogEntryId}\n onSaved={({ keptOpen }) => {\n handleRefresh()\n if (!keptOpen) setDialogOpen(false)\n }}\n onShowEntry={(entryId) => openEdit(entryId)}\n />\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|