@open-mercato/core 0.6.8-develop.7082.1.f2a845b020 → 0.6.8-develop.7090.1.4dbca7b350

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.
Files changed (62) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/components/DealsKpiStrip.js +7 -6
  3. package/dist/modules/customers/components/DealsKpiStrip.js.map +2 -2
  4. package/dist/modules/customers/components/detail/CompanyKpiBar.js +5 -4
  5. package/dist/modules/customers/components/detail/CompanyKpiBar.js.map +2 -2
  6. package/dist/modules/customers/components/detail/DealsSection.js +6 -5
  7. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  8. package/dist/modules/customers/components/detail/utils.js +5 -5
  9. package/dist/modules/customers/components/detail/utils.js.map +2 -2
  10. package/dist/modules/sales/backend/sales/documents/[id]/page.js +10 -8
  11. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  12. package/dist/modules/sales/components/PriceWithCurrency.js +7 -5
  13. package/dist/modules/sales/components/PriceWithCurrency.js.map +2 -2
  14. package/dist/modules/sales/components/documents/AdjustmentsSection.js +8 -6
  15. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  16. package/dist/modules/sales/components/documents/ItemsSection.js +14 -7
  17. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  18. package/dist/modules/sales/components/documents/LineItemDialog.js +8 -7
  19. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  20. package/dist/modules/sales/components/documents/PaymentsSection.js +9 -8
  21. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  22. package/dist/modules/sales/components/documents/ReturnsSection.js +8 -6
  23. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  24. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js +5 -4
  25. package/dist/modules/sales/components/documents/SalesOrderDraftLines.js.map +2 -2
  26. package/dist/modules/sales/components/documents/ShipmentDialog.js +5 -4
  27. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  28. package/dist/modules/sales/components/documents/ShipmentsSection.js +8 -6
  29. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  30. package/dist/modules/sales/components/documents/lineItemUtils.js +2 -2
  31. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  32. package/dist/modules/sales/data/entities.js +8 -2
  33. package/dist/modules/sales/data/entities.js.map +2 -2
  34. package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js +32 -0
  35. package/dist/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.js.map +7 -0
  36. package/dist/modules/staff/backend/staff/timesheets/page.js +29 -18
  37. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  38. package/package.json +7 -7
  39. package/src/modules/auth/i18n/de.json +3 -0
  40. package/src/modules/auth/i18n/en.json +3 -0
  41. package/src/modules/auth/i18n/es.json +3 -0
  42. package/src/modules/auth/i18n/ko.json +3 -0
  43. package/src/modules/auth/i18n/pl.json +3 -0
  44. package/src/modules/customers/components/DealsKpiStrip.tsx +7 -7
  45. package/src/modules/customers/components/detail/CompanyKpiBar.tsx +5 -4
  46. package/src/modules/customers/components/detail/DealsSection.tsx +11 -5
  47. package/src/modules/customers/components/detail/utils.ts +15 -5
  48. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +14 -8
  49. package/src/modules/sales/components/PriceWithCurrency.tsx +17 -5
  50. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +6 -5
  51. package/src/modules/sales/components/documents/ItemsSection.tsx +8 -1
  52. package/src/modules/sales/components/documents/LineItemDialog.tsx +9 -8
  53. package/src/modules/sales/components/documents/PaymentsSection.tsx +9 -8
  54. package/src/modules/sales/components/documents/ReturnsSection.tsx +6 -5
  55. package/src/modules/sales/components/documents/SalesOrderDraftLines.tsx +5 -4
  56. package/src/modules/sales/components/documents/ShipmentDialog.tsx +5 -4
  57. package/src/modules/sales/components/documents/ShipmentsSection.tsx +6 -5
  58. package/src/modules/sales/components/documents/lineItemUtils.ts +7 -2
  59. package/src/modules/sales/data/entities.ts +6 -0
  60. package/src/modules/sales/migrations/.snapshot-open-mercato.json +60 -0
  61. package/src/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.ts +87 -0
  62. package/src/modules/staff/backend/staff/timesheets/page.tsx +12 -1
@@ -0,0 +1,32 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260821120000_sales_notes_addresses_context_idx extends Migration {
3
+ isTransactional() {
4
+ return false;
5
+ }
6
+ up() {
7
+ this.addSql(`drop index concurrently if exists "sales_notes_context_idx";`);
8
+ this.addSql(`create index concurrently "sales_notes_context_idx" on "sales_notes" ("context_id");`);
9
+ this.addSql(`drop index concurrently if exists "sales_notes_order_idx";`);
10
+ this.addSql(`create index concurrently "sales_notes_order_idx" on "sales_notes" ("order_id");`);
11
+ this.addSql(`drop index concurrently if exists "sales_notes_quote_idx";`);
12
+ this.addSql(`create index concurrently "sales_notes_quote_idx" on "sales_notes" ("quote_id");`);
13
+ this.addSql(`drop index concurrently if exists "sales_document_addresses_document_idx";`);
14
+ this.addSql(`create index concurrently "sales_document_addresses_document_idx" on "sales_document_addresses" ("document_id");`);
15
+ this.addSql(`drop index concurrently if exists "sales_document_addresses_order_idx";`);
16
+ this.addSql(`create index concurrently "sales_document_addresses_order_idx" on "sales_document_addresses" ("order_id");`);
17
+ this.addSql(`drop index concurrently if exists "sales_document_addresses_quote_idx";`);
18
+ this.addSql(`create index concurrently "sales_document_addresses_quote_idx" on "sales_document_addresses" ("quote_id");`);
19
+ }
20
+ down() {
21
+ this.addSql(`drop index concurrently if exists "sales_document_addresses_quote_idx";`);
22
+ this.addSql(`drop index concurrently if exists "sales_document_addresses_order_idx";`);
23
+ this.addSql(`drop index concurrently if exists "sales_document_addresses_document_idx";`);
24
+ this.addSql(`drop index concurrently if exists "sales_notes_quote_idx";`);
25
+ this.addSql(`drop index concurrently if exists "sales_notes_order_idx";`);
26
+ this.addSql(`drop index concurrently if exists "sales_notes_context_idx";`);
27
+ }
28
+ }
29
+ export {
30
+ Migration20260821120000_sales_notes_addresses_context_idx
31
+ };
32
+ //# sourceMappingURL=Migration20260821120000_sales_notes_addresses_context_idx.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/sales/migrations/Migration20260821120000_sales_notes_addresses_context_idx.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\n// sales_notes and sales_document_addresses each carried a single index \u2014\n// (organization_id, tenant_id) \u2014 and are never read by that predicate alone.\n// loadOrderSnapshot / loadQuoteSnapshot (commands/documents.ts) read them by\n// context in the same Promise.all:\n//\n// findWithDecryption(em, SalesNote, { contextType, contextId, ...scope })\n// findWithDecryption(em, SalesDocumentAddress, { documentId, documentKind, ...scope })\n//\n// On a single-organization deployment the scope index matches every row, so the\n// planner discards it and scans. The cost is CPU filtering, so a warm cache does\n// not reduce it. Measured on a table of ~331k rows / 110 MB: a parallel\n// sequential scan removing 110,457 rows per worker, 82 ms per read to return 0\n// rows. The command bus runs prepare() and captureAfter() around every command\n// and both load a document snapshot, so one written document \u2014 itself several\n// commands (the document, one per changed line, the payment reconcile) \u2014\n// performs roughly 20 of these reads. Counters over one bulk-import batch of 100\n// records showed 1,971 sequential scans reading 217,670,528 rows, 2.18M rows per\n// imported record; repeatable across consecutive batches (1,971 / 1,974 / 1,911).\n//\n// context_type and document_kind are deliberately omitted, as they were for\n// sales_document_tag_assignments_document_idx (Migration20260806120000), and the\n// scope columns with them. sales_orders.id and sales_quotes.id are both\n// gen_random_uuid() from their own tables, so a context_id already determines its\n// context_type and its tenant; the seek returns the handful of rows belonging to\n// one document and the remaining terms are rechecked over those. Folding three\n// more columns in \u2014 one text and two uuids \u2014 roughly triples the index entry for\n// no selectivity, on tables that take a write per document save and a note per\n// order status change. The sibling scope indexes that do fold scope in\n// (sales_order_lines_scope_idx, sales_payments_scope_idx) predate that reasoning.\n//\n// order_id and quote_id are separate indexes rather than a widening of the above:\n// both tables are the child side of `on delete set null` constraints, which\n// PostgreSQL does not index automatically, and the constraint check probes\n// order_id / quote_id \u2014 columns the context index cannot serve even though they\n// hold the same uuid. Without them every delete of a parent order or quote scans\n// the child table in full.\n//\n// Both tables are high-churn, so the indexes are built CONCURRENTLY to avoid\n// blocking writes during the build. CREATE INDEX CONCURRENTLY cannot run inside a\n// transaction, hence isTransactional() => false; the migration runner applies\n// migrations one-by-one, so this opt-out is safe. Drop first so retrying a failed\n// concurrent build removes PostgreSQL's invalid index stub instead of letting IF\n// NOT EXISTS silently accept it: a half-built concurrent index is left INVALID,\n// the planner ignores it, and IF NOT EXISTS would report success while the table\n// stays unindexed. Same shape as Migration20260806120000 and\n// Migration20260731105052_query_index.\n//\n// On a database that does not have these indexes yet \u2014 every fresh install \u2014 the\n// drops are no-ops and there is no window without them. An operator who already\n// created one out of band should expect it to be rebuilt here, and the reads that\n// depend on it to fall back to their pre-index cost until the concurrent build\n// finishes; on a large table, run this while the writers that depend on it are\n// paused.\nexport class Migration20260821120000_sales_notes_addresses_context_idx extends Migration {\n\n override isTransactional(): boolean {\n return false;\n }\n\n override up(): void | Promise<void> {\n this.addSql(`drop index concurrently if exists \"sales_notes_context_idx\";`);\n this.addSql(`create index concurrently \"sales_notes_context_idx\" on \"sales_notes\" (\"context_id\");`);\n this.addSql(`drop index concurrently if exists \"sales_notes_order_idx\";`);\n this.addSql(`create index concurrently \"sales_notes_order_idx\" on \"sales_notes\" (\"order_id\");`);\n this.addSql(`drop index concurrently if exists \"sales_notes_quote_idx\";`);\n this.addSql(`create index concurrently \"sales_notes_quote_idx\" on \"sales_notes\" (\"quote_id\");`);\n\n this.addSql(`drop index concurrently if exists \"sales_document_addresses_document_idx\";`);\n this.addSql(`create index concurrently \"sales_document_addresses_document_idx\" on \"sales_document_addresses\" (\"document_id\");`);\n this.addSql(`drop index concurrently if exists \"sales_document_addresses_order_idx\";`);\n this.addSql(`create index concurrently \"sales_document_addresses_order_idx\" on \"sales_document_addresses\" (\"order_id\");`);\n this.addSql(`drop index concurrently if exists \"sales_document_addresses_quote_idx\";`);\n this.addSql(`create index concurrently \"sales_document_addresses_quote_idx\" on \"sales_document_addresses\" (\"quote_id\");`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`drop index concurrently if exists \"sales_document_addresses_quote_idx\";`);\n this.addSql(`drop index concurrently if exists \"sales_document_addresses_order_idx\";`);\n this.addSql(`drop index concurrently if exists \"sales_document_addresses_document_idx\";`);\n this.addSql(`drop index concurrently if exists \"sales_notes_quote_idx\";`);\n this.addSql(`drop index concurrently if exists \"sales_notes_order_idx\";`);\n this.addSql(`drop index concurrently if exists \"sales_notes_context_idx\";`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAuDnB,MAAM,kEAAkE,UAAU;AAAA,EAE9E,kBAA2B;AAClC,WAAO;AAAA,EACT;AAAA,EAES,KAA2B;AAClC,SAAK,OAAO,8DAA8D;AAC1E,SAAK,OAAO,sFAAsF;AAClG,SAAK,OAAO,4DAA4D;AACxE,SAAK,OAAO,kFAAkF;AAC9F,SAAK,OAAO,4DAA4D;AACxE,SAAK,OAAO,kFAAkF;AAE9F,SAAK,OAAO,4EAA4E;AACxF,SAAK,OAAO,kHAAkH;AAC9H,SAAK,OAAO,yEAAyE;AACrF,SAAK,OAAO,4GAA4G;AACxH,SAAK,OAAO,yEAAyE;AACrF,SAAK,OAAO,4GAA4G;AAAA,EAC1H;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,yEAAyE;AACrF,SAAK,OAAO,yEAAyE;AACrF,SAAK,OAAO,4EAA4E;AACxF,SAAK,OAAO,4DAA4D;AACxE,SAAK,OAAO,4DAA4D;AACxE,SAAK,OAAO,8DAA8D;AAAA,EAC5E;AAEF;",
6
+ "names": []
7
+ }
@@ -127,6 +127,12 @@ function MyTimesheetsPage() {
127
127
  contextId: mutationContextId,
128
128
  blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
129
129
  });
130
+ const gridInstanceId = React.useId();
131
+ const durationHintId = `${gridInstanceId}-duration-hint`;
132
+ const buildDurationErrorId = React.useCallback(
133
+ (projectId, dateKey) => `${gridInstanceId}-duration-error-${projectId}-${dateKey}`,
134
+ [gridInstanceId]
135
+ );
130
136
  const durationFormatHint = t(
131
137
  "staff.timesheets.my.duration.hint",
132
138
  "Enter hours (8 or 1.5), h:mm (1:30) or minutes (90m). Max 24h per day."
@@ -722,26 +728,31 @@ function MyTimesheetsPage() {
722
728
  const isDirty = dirty[project.id]?.[dateKey] !== void 0;
723
729
  const cellError = cellErrors[project.id]?.[dateKey];
724
730
  const cellErrorMessage = cellError ? describeDurationError(cellError) : void 0;
725
- return /* @__PURE__ */ jsx("td", { className: `px-0.5 py-0.5 ${weekend ? "bg-muted/40" : ""}`, children: weekend ? /* @__PURE__ */ jsx("div", { className: "rounded px-1 py-1 text-center text-xs text-muted-foreground/50", children: "-" }) : /* @__PURE__ */ jsx(
726
- InlineInput,
727
- {
728
- type: "text",
729
- inputMode: "text",
730
- showBorderOnHover: false,
731
- className: `mx-auto flex h-6 border transition-colors
731
+ const cellErrorId = cellErrorMessage ? buildDurationErrorId(project.id, dateKey) : void 0;
732
+ return /* @__PURE__ */ jsxs("td", { className: `px-0.5 py-0.5 ${weekend ? "bg-muted/40" : ""}`, children: [
733
+ weekend ? /* @__PURE__ */ jsx("div", { className: "rounded px-1 py-1 text-center text-xs text-muted-foreground/50", children: "-" }) : /* @__PURE__ */ jsx(
734
+ InlineInput,
735
+ {
736
+ type: "text",
737
+ inputMode: "text",
738
+ showBorderOnHover: false,
739
+ className: `mx-auto flex h-6 border transition-colors
732
740
  ${viewMode === "weekly" ? "w-12 px-1" : "w-8 px-0"}
733
741
  ${cellError ? "bg-status-error-bg" : isDirty ? "border-status-warning-border bg-status-warning-bg" : "border-muted-foreground/20 bg-transparent hover:border-muted-foreground/40"}`,
734
- inputClassName: `text-center text-xs tabular-nums
742
+ inputClassName: `text-center text-xs tabular-nums
735
743
  ${cellError ? "text-status-error-text" : cellMinutes > 0 ? "font-semibold" : "text-muted-foreground"}`,
736
- value: rawText[project.id]?.[dateKey] ?? formatMinutesAsDecimal(cellMinutes),
737
- onChange: (e) => handleCellChange(project.id, dateKey, e.target.value),
738
- onBlur: (event) => handleCellBlur(project.id, dateKey, event.currentTarget.value),
739
- placeholder: t("staff.timesheets.my.durationPlaceholder", "0"),
740
- "aria-invalid": cellError !== void 0,
741
- "aria-label": describeDurationCell(project.name, date),
742
- title: cellErrorMessage ?? durationFormatHint
743
- }
744
- ) }, dateKey);
744
+ value: rawText[project.id]?.[dateKey] ?? formatMinutesAsDecimal(cellMinutes),
745
+ onChange: (e) => handleCellChange(project.id, dateKey, e.target.value),
746
+ onBlur: (event) => handleCellBlur(project.id, dateKey, event.currentTarget.value),
747
+ placeholder: t("staff.timesheets.my.durationPlaceholder", "0"),
748
+ "aria-invalid": cellError !== void 0,
749
+ "aria-label": describeDurationCell(project.name, date),
750
+ "aria-describedby": [durationHintId, cellErrorId].filter(Boolean).join(" "),
751
+ title: cellErrorMessage ?? durationFormatHint
752
+ }
753
+ ),
754
+ cellErrorMessage ? /* @__PURE__ */ jsx("span", { id: cellErrorId, role: "alert", className: "sr-only", children: cellErrorMessage }) : null
755
+ ] }, dateKey);
745
756
  }),
746
757
  /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-right font-semibold text-xs tabular-nums", children: formatMinutesAsDecimal(getRowTotal(project.id)) || "0" })
747
758
  ] }, project.id)),
@@ -766,7 +777,7 @@ function MyTimesheetsPage() {
766
777
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-right tabular-nums font-semibold", children: formatMinutesAsDecimal(grandTotal) || "0" })
767
778
  ] }) })
768
779
  ] }),
769
- /* @__PURE__ */ jsx("p", { className: "px-3 py-2 text-xs text-muted-foreground", children: durationFormatHint })
780
+ /* @__PURE__ */ jsx("p", { id: durationHintId, className: "px-3 py-2 text-xs text-muted-foreground", children: durationFormatHint })
770
781
  ] }) })
771
782
  ] }),
772
783
  ConfirmDialogElement,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/staff/backend/staff/timesheets/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { InlineInput } from '@open-mercato/ui/primitives/inline-input'\nimport { apiCall, apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ChevronLeft, ChevronRight, X } from 'lucide-react'\nimport Link from 'next/link'\nimport { ViewSwitcher } from '../../../lib/timesheets-ui/ViewSwitcher'\nimport { CalendarPicker } from '../../../lib/timesheets-ui/CalendarPicker'\nimport { ListView } from '../../../lib/timesheets-ui/ListView'\nimport { TimerBar } from '../../../lib/timesheets-ui/TimerBar'\nimport { AddRowDropdown } from '../../../lib/timesheets-ui/AddRowDropdown'\nimport { CreateProjectDialog } from '../../../lib/timesheets-ui/CreateProjectDialog'\nimport { ProjectColorDot } from '../../../lib/timesheets-ui/ProjectColorDot'\nimport {\n formatMinutesAsDecimal,\n parseDurationInput,\n type DurationParseError,\n} from '../../../lib/timesheetsDuration'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff')\n\n// --- Types ---\n\ntype ProjectRow = { id: string; name: string; code: string | null; color?: string | null }\ntype CellEntry = { id?: string; minutes: number }\ntype EntryMap = Record<string, Record<string, CellEntry[]>>\ntype DirtyMap = Record<string, Record<string, CellEntry>>\ntype RawTextMap = Record<string, Record<string, string>>\ntype CellErrorMap = Record<string, Record<string, DurationParseError>>\ntype ViewMode = 'weekly' | 'monthly'\ntype ViewType = 'timesheet' | 'list'\n\ntype RawTimeEntry = Record<string, unknown>\n\n// --- Date Helpers ---\n\nfunction getMonday(date: Date): Date {\n const d = new Date(date)\n const day = d.getDay()\n const diff = day === 0 ? -6 : 1 - day\n d.setDate(d.getDate() + diff)\n d.setHours(0, 0, 0, 0)\n return d\n}\n\nfunction getSunday(monday: Date): Date {\n const d = new Date(monday)\n d.setDate(d.getDate() + 6)\n return d\n}\n\nfunction getWeekNumber(date: Date): number {\n const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))\n d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7))\n const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))\n return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7)\n}\n\nfunction formatDateKey(date: Date): string {\n const y = date.getFullYear()\n const m = String(date.getMonth() + 1).padStart(2, '0')\n const d = String(date.getDate()).padStart(2, '0')\n return `${y}-${m}-${d}`\n}\n\nfunction formatDateKeyFromParts(year: number, month: number, day: number): string {\n const m = String(month + 1).padStart(2, '0')\n const d = String(day).padStart(2, '0')\n return `${year}-${m}-${d}`\n}\n\nfunction getDaysInMonth(year: number, month: number): number {\n return new Date(year, month + 1, 0).getDate()\n}\n\nfunction isWeekendDay(date: Date): boolean {\n const d = date.getDay()\n return d === 0 || d === 6\n}\n\nfunction getLocalizedDayName(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'short' })\n}\n\nfunction getLocalizedCellDate(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' })\n}\n\n// --- Derived date ranges ---\n\nfunction getWeekDays(weekStart: Date): Date[] {\n return Array.from({ length: 7 }, (_, i) => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + i)\n return d\n })\n}\n\nfunction getMonthDays(year: number, month: number): Date[] {\n const count = getDaysInMonth(year, month)\n return Array.from({ length: count }, (_, i) => new Date(year, month, i + 1))\n}\n\n// --- Week label ---\n\nfunction formatWeekLabel(weekStart: Date): string {\n const weekEnd = getSunday(weekStart)\n const weekNum = getWeekNumber(weekStart)\n const startDay = weekStart.getDate()\n const endDay = weekEnd.getDate()\n const startMonth = weekStart.toLocaleString(undefined, { month: 'short' })\n const endMonth = weekEnd.toLocaleString(undefined, { month: 'short' })\n const year = weekStart.getFullYear()\n\n const dateRange = weekStart.getMonth() === weekEnd.getMonth()\n ? `${startDay} - ${endDay} ${startMonth} ${year}`\n : `${startDay} ${startMonth} - ${endDay} ${endMonth} ${year}`\n\n return `${dateRange} \\u00b7 W${weekNum}`\n}\n\nfunction formatMonthLabel(year: number, month: number): string {\n const date = new Date(year, month, 1)\n return date.toLocaleString(undefined, { month: 'long', year: 'numeric' })\n}\n\n// --- Component ---\n\nexport default function MyTimesheetsPage() {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const now = new Date()\n const [viewMode, setViewMode] = React.useState<ViewMode>('weekly')\n const [viewType, setViewType] = React.useState<ViewType>('timesheet')\n const [weekStart, setWeekStart] = React.useState<Date>(() => getMonday(now))\n const [monthYear, setMonthYear] = React.useState(now.getFullYear())\n const [monthIndex, setMonthIndex] = React.useState(now.getMonth())\n\n const [projects, setProjects] = React.useState<ProjectRow[]>([])\n const [entries, setEntries] = React.useState<EntryMap>({})\n const [rawEntries, setRawEntries] = React.useState<RawTimeEntry[]>([])\n const [dirty, setDirty] = React.useState<DirtyMap>({})\n const [rawText, setRawText] = React.useState<RawTextMap>({})\n const [cellErrors, setCellErrors] = React.useState<CellErrorMap>({})\n const [staffMemberId, setStaffMemberId] = React.useState<string | null>(null)\n const [staffMemberMissing, setStaffMemberMissing] = React.useState(false)\n const [isInitialLoad, setIsInitialLoad] = React.useState(true)\n const [isRefreshing, setIsRefreshing] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const [canManageProjects, setCanManageProjects] = React.useState(false)\n const [createDialogOpen, setCreateDialogOpen] = React.useState(false)\n const [allAssignedProjects, setAllAssignedProjects] = React.useState<ProjectRow[]>([])\n\n const mutationContextId = React.useMemo(\n () => (staffMemberId ? `staff-timesheets:${staffMemberId}` : 'staff-timesheets:pending'),\n [staffMemberId],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId?: string\n staffMemberId: string | null\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const durationFormatHint = t(\n 'staff.timesheets.my.duration.hint',\n 'Enter hours (8 or 1.5), h:mm (1:30) or minutes (90m). Max 24h per day.',\n )\n const describeDurationCell = React.useCallback((projectName: string, date: Date): string => (\n t('staff.timesheets.my.duration.cellLabel', 'Duration for {project} on {date}')\n .replace('{project}', projectName)\n .replace('{date}', getLocalizedCellDate(date))\n ), [t])\n const describeDurationError = React.useCallback((reason: DurationParseError): string => (\n reason === 'out_of_range'\n ? t('staff.timesheets.my.duration.errors.out_of_range', 'Maximum 24h per day. Use 1:30 or 90m for shorter entries.')\n : t('staff.timesheets.my.duration.errors.invalid', 'Unrecognised duration. Use 8, 1.5, 1:30, 90m or 1h 30m.')\n ), [t])\n\n // --- Feature check ---\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n try {\n const res = await apiCall<{ ok: boolean; granted: string[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['staff.timesheets.projects.manage'] }),\n })\n if (!cancelled) {\n setCanManageProjects(new Set(res.result?.granted ?? []).has('staff.timesheets.projects.manage'))\n }\n } catch {\n // default: no manage access\n }\n })()\n return () => { cancelled = true }\n }, [])\n\n // --- Computed days for current view ---\n const visibleDays = React.useMemo(() => {\n if (viewMode === 'weekly') return getWeekDays(weekStart)\n return getMonthDays(monthYear, monthIndex)\n }, [viewMode, weekStart, monthYear, monthIndex])\n\n const dateRange = React.useMemo(() => {\n if (viewMode === 'weekly') {\n return { from: formatDateKey(weekStart), to: formatDateKey(getSunday(weekStart)) }\n }\n const daysInMonth = getDaysInMonth(monthYear, monthIndex)\n return {\n from: formatDateKeyFromParts(monthYear, monthIndex, 1),\n to: formatDateKeyFromParts(monthYear, monthIndex, daysInMonth),\n }\n }, [viewMode, weekStart, monthYear, monthIndex])\n\n // --- Navigation ---\n const goToPrev = React.useCallback(() => {\n if (viewMode === 'weekly') {\n setWeekStart((prev) => {\n const d = new Date(prev)\n d.setDate(d.getDate() - 7)\n return d\n })\n } else {\n setMonthIndex((prev) => {\n if (prev === 0) { setMonthYear((y) => y - 1); return 11 }\n return prev - 1\n })\n }\n }, [viewMode])\n\n const goToNext = React.useCallback(() => {\n if (viewMode === 'weekly') {\n setWeekStart((prev) => {\n const d = new Date(prev)\n d.setDate(d.getDate() + 7)\n return d\n })\n } else {\n setMonthIndex((prev) => {\n if (prev === 11) { setMonthYear((y) => y + 1); return 0 }\n return prev + 1\n })\n }\n }, [viewMode])\n\n const navigationLabel = React.useMemo(() => {\n if (viewMode === 'weekly') return formatWeekLabel(weekStart)\n return formatMonthLabel(monthYear, monthIndex)\n }, [viewMode, weekStart, monthYear, monthIndex])\n\n // --- Data loading ---\n const isInitialLoadRef = React.useRef(true)\n\n const loadData = React.useCallback(async () => {\n if (!isInitialLoadRef.current) setIsRefreshing(true)\n try {\n const selfRes = await readApiResultOrThrow<{ member?: { id: string; displayName: string } | null }>(\n '/api/staff/team-members/self',\n undefined,\n { errorMessage: t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), fallback: { member: null } },\n )\n const memberId = selfRes.member?.id ?? null\n setStaffMemberId(memberId)\n if (!memberId) {\n setStaffMemberMissing(true)\n setProjects([])\n setEntries({})\n setRawEntries([])\n setIsInitialLoad(false)\n setIsRefreshing(false)\n return\n }\n setStaffMemberMissing(false)\n\n const assignmentsRes = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n '/api/staff/timesheets/my-projects?pageSize=100',\n undefined,\n { errorMessage: t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), fallback: { items: [] } },\n )\n const assignmentItems = Array.isArray(assignmentsRes.items) ? assignmentsRes.items : []\n const assignedProjectIds = assignmentItems\n .map((item) => String(item.time_project_id ?? item.timeProjectId ?? ''))\n .filter((id) => id.length > 0)\n const visibleProjectIdSet = new Set(\n assignmentItems\n .filter((item) => item.show_in_grid === true || item.showInGrid === true)\n .map((item) => String(item.time_project_id ?? item.timeProjectId ?? ''))\n .filter((id) => id.length > 0),\n )\n\n const entriesParams = new URLSearchParams({ pageSize: '100', staffMemberId: memberId })\n if (dateRange.from) entriesParams.set('from', dateRange.from)\n if (dateRange.to) entriesParams.set('to', dateRange.to)\n\n const [projectsRes, entriesRes] = await Promise.all([\n assignedProjectIds.length > 0\n ? readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/staff/timesheets/time-projects?ids=${assignedProjectIds.join(',')}&pageSize=100`,\n undefined,\n { errorMessage: t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), fallback: { items: [] } },\n )\n : Promise.resolve({ items: [] as Array<Record<string, unknown>> }),\n readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/staff/timesheets/time-entries?${entriesParams.toString()}`,\n undefined,\n { errorMessage: t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), fallback: { items: [] } },\n ),\n ])\n\n const projectItems = Array.isArray(projectsRes.items) ? projectsRes.items : []\n const mappedProjects = projectItems.map((item) => ({\n id: String(item.id ?? ''),\n name: String(item.name ?? ''),\n code: typeof item.code === 'string' ? item.code : null,\n color: typeof item.color === 'string' ? item.color : null,\n }))\n setAllAssignedProjects(mappedProjects)\n const visibleProjects = mappedProjects.filter((p) => visibleProjectIdSet.has(p.id))\n setProjects(visibleProjects)\n\n const entryItems = Array.isArray(entriesRes.items) ? entriesRes.items : []\n setRawEntries(entryItems)\n const map: EntryMap = {}\n for (const item of entryItems) {\n const projectId = String(item.time_project_id ?? item.timeProjectId ?? '')\n const rawDate = String(item.date ?? '')\n const dateKey = rawDate.slice(0, 10)\n const minutes = typeof item.duration_minutes === 'number'\n ? item.duration_minutes\n : typeof item.durationMinutes === 'number'\n ? item.durationMinutes\n : 0\n const entryId = String(item.id ?? '')\n if (!map[projectId]) map[projectId] = {}\n if (!map[projectId][dateKey]) map[projectId][dateKey] = []\n map[projectId][dateKey].push({ id: entryId || undefined, minutes })\n }\n setEntries(map)\n setDirty({})\n setRawText({})\n setCellErrors({})\n } catch (error) {\n logger.error('staff.timesheets.my.load', { err: error })\n flash(t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), 'error')\n } finally {\n isInitialLoadRef.current = false\n setIsInitialLoad(false)\n setIsRefreshing(false)\n }\n }, [dateRange.from, dateRange.to, t])\n\n React.useEffect(() => {\n void loadData()\n }, [loadData, scopeVersion])\n\n // --- Cell handlers ---\n const clearCellError = React.useCallback((projectId: string, dateKey: string) => {\n setCellErrors((prev) => {\n const projectErrors = prev[projectId]\n if (!projectErrors || projectErrors[dateKey] === undefined) return prev\n const nextProjectErrors = { ...projectErrors }\n delete nextProjectErrors[dateKey]\n const next = { ...prev }\n if (Object.keys(nextProjectErrors).length > 0) next[projectId] = nextProjectErrors\n else delete next[projectId]\n return next\n })\n }, [])\n\n const handleCellChange = React.useCallback((projectId: string, dateKey: string, value: string) => {\n clearCellError(projectId, dateKey)\n setRawText((prev) => {\n const projectTexts = { ...(prev[projectId] ?? {}) }\n projectTexts[dateKey] = value\n return { ...prev, [projectId]: projectTexts }\n })\n }, [clearCellError])\n\n const dropDirtyCell = React.useCallback((projectId: string, dateKey: string) => {\n setDirty((prev) => {\n const projectEntries = prev[projectId]\n if (!projectEntries || projectEntries[dateKey] === undefined) return prev\n const nextProjectEntries = { ...projectEntries }\n delete nextProjectEntries[dateKey]\n const next = { ...prev }\n if (Object.keys(nextProjectEntries).length > 0) next[projectId] = nextProjectEntries\n else delete next[projectId]\n return next\n })\n }, [])\n\n const handleCellBlur = React.useCallback((projectId: string, dateKey: string, currentValue: string) => {\n const editedText = rawText[projectId]?.[dateKey]\n const text = editedText ?? currentValue\n if (text === undefined) return\n const parsed = parseDurationInput(text)\n if (!parsed.ok) {\n dropDirtyCell(projectId, dateKey)\n setCellErrors((prev) => {\n const projectErrors = { ...(prev[projectId] ?? {}) }\n projectErrors[dateKey] = parsed.reason\n return { ...prev, [projectId]: projectErrors }\n })\n return\n }\n clearCellError(projectId, dateKey)\n const minutes = parsed.minutes\n const cellEntries = entries[projectId]?.[dateKey] ?? []\n const existingMinutes = cellEntries.reduce((sum, e) => sum + e.minutes, 0)\n\n if (minutes !== existingMinutes || (editedText !== undefined && cellEntries.length > 0)) {\n setDirty((prev) => {\n const projectEntries: Record<string, CellEntry> = { ...(prev[projectId] ?? {}) }\n const firstId = cellEntries[0]?.id\n projectEntries[dateKey] = { id: firstId, minutes }\n return { ...prev, [projectId]: projectEntries }\n })\n }\n setRawText((prev) => {\n const projectTexts = { ...(prev[projectId] ?? {}) }\n delete projectTexts[dateKey]\n const hasKeys = Object.keys(projectTexts).length > 0\n if (!hasKeys) {\n const next = { ...prev }\n delete next[projectId]\n return next\n }\n return { ...prev, [projectId]: projectTexts }\n })\n }, [rawText, entries, clearCellError, dropDirtyCell])\n\n const getCellValue = React.useCallback((projectId: string, dateKey: string): number => {\n const dirtyCell = dirty[projectId]?.[dateKey] as CellEntry | undefined\n if (dirtyCell !== undefined) return dirtyCell.minutes\n const cellEntries = entries[projectId]?.[dateKey] ?? []\n return cellEntries.reduce((sum, e) => sum + e.minutes, 0)\n }, [dirty, entries])\n\n // --- Save ---\n const hasChanges = Object.keys(dirty).length > 0 || Object.keys(rawText).length > 0\n const invalidCellCount = React.useMemo(\n () => Object.values(cellErrors).reduce((sum, projectErrors) => sum + Object.keys(projectErrors).length, 0),\n [cellErrors],\n )\n const pendingSummary = React.useMemo(() => {\n let cells = 0\n let minutes = 0\n for (const dateMap of Object.values(dirty)) {\n for (const cell of Object.values(dateMap)) {\n cells += 1\n minutes += cell.minutes\n }\n }\n return { cells, minutes }\n }, [dirty])\n\n const handleSave = React.useCallback(async () => {\n if (!hasChanges || invalidCellCount > 0) return\n const summary = t('staff.timesheets.my.confirm_save.summary', '{count} cell(s), {total}h in total.')\n .replace('{count}', String(pendingSummary.cells))\n .replace('{total}', formatMinutesAsDecimal(pendingSummary.minutes) || '0')\n const confirmed = await confirm({\n title: t('staff.timesheets.my.confirm_save.title', 'Save changes?'),\n text: `${t('staff.timesheets.my.confirm_save.body', 'Your timesheet entries will be saved.')} ${summary}`,\n })\n if (!confirmed) return\n\n setIsSaving(true)\n try {\n const bulkEntries: Array<{ id?: string; date: string; timeProjectId: string; durationMinutes: number }> = []\n for (const [projectId, dateMap] of Object.entries(dirty)) {\n for (const [dateKey, cellValue] of Object.entries(dateMap)) {\n const cell = cellValue as CellEntry\n const cellEntries = entries[projectId]?.[dateKey] ?? []\n const firstId = cell.id ?? cellEntries[0]?.id\n bulkEntries.push({ id: firstId, date: dateKey, timeProjectId: projectId, durationMinutes: cell.minutes })\n }\n }\n if (bulkEntries.length === 0) return\n\n const payload = { entries: bulkEntries }\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/staff/timesheets/time-entries/bulk', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: staffMemberId ?? undefined,\n staffMemberId,\n retryLastMutation,\n },\n mutationPayload: payload as unknown as Record<string, unknown>,\n })\n\n flash(t('staff.timesheets.my.saved', 'Timesheet saved.'), 'success')\n await loadData()\n } catch (error) {\n logger.error('staff.timesheets.my.save', { err: error })\n flash(t('staff.timesheets.my.errors.save', 'Failed to save timesheets.'), 'error')\n } finally {\n setIsSaving(false)\n }\n }, [dirty, entries, hasChanges, invalidCellCount, pendingSummary, confirm, t, loadData, runMutation, mutationContextId, staffMemberId, retryLastMutation])\n\n // --- Totals ---\n const getRowTotal = React.useCallback((projectId: string): number => {\n let total = 0\n for (const day of visibleDays) {\n total += getCellValue(projectId, formatDateKey(day))\n }\n return total\n }, [visibleDays, getCellValue])\n\n const getDayTotal = React.useCallback((date: Date): number => {\n const dateKey = formatDateKey(date)\n let total = 0\n for (const project of projects) {\n total += getCellValue(project.id, dateKey)\n }\n return total\n }, [projects, getCellValue])\n\n const grandTotal = React.useMemo(() => {\n let total = 0\n for (const project of projects) {\n total += getRowTotal(project.id)\n }\n return total\n }, [projects, getRowTotal])\n\n const workingDays = React.useMemo(() => {\n let count = 0\n for (const day of visibleDays) {\n if (!isWeekendDay(day)) {\n const dateKey = formatDateKey(day)\n for (const project of projects) {\n if (getCellValue(project.id, dateKey) > 0) { count++; break }\n }\n }\n }\n return count\n }, [visibleDays, projects, getCellValue])\n\n const dailyAverage = React.useMemo(() => {\n if (workingDays === 0) return 0\n return grandTotal / workingDays\n }, [grandTotal, workingDays])\n\n // --- List view entries ---\n const listViewEntries = React.useMemo(() => {\n return rawEntries.map((item) => ({\n id: String(item.id ?? ''),\n date: String(item.date ?? '').slice(0, 10),\n durationMinutes: typeof item.duration_minutes === 'number' ? item.duration_minutes\n : typeof item.durationMinutes === 'number' ? item.durationMinutes : 0,\n projectId: String(item.time_project_id ?? item.timeProjectId ?? ''),\n projectName: projects.find((p) => p.id === String(item.time_project_id ?? item.timeProjectId ?? ''))?.name ?? '',\n projectCode: projects.find((p) => p.id === String(item.time_project_id ?? item.timeProjectId ?? ''))?.code ?? null,\n projectColor: projects.find((p) => p.id === String(item.time_project_id ?? item.timeProjectId ?? ''))?.color ?? null,\n notes: typeof item.notes === 'string' ? item.notes : null,\n source: typeof item.source === 'string' ? item.source : 'manual',\n startedAt: typeof item.started_at === 'string' ? item.started_at : typeof item.startedAt === 'string' ? item.startedAt : null,\n endedAt: typeof item.ended_at === 'string' ? item.ended_at : typeof item.endedAt === 'string' ? item.endedAt : null,\n }))\n }, [rawEntries, projects])\n\n // --- Handle view mode change ---\n const handleViewModeChange = React.useCallback((mode: ViewMode) => {\n setViewMode(mode)\n if (mode === 'monthly') {\n // Use Thursday of the week to determine month (handles cross-month weeks)\n const thursday = new Date(weekStart)\n thursday.setDate(thursday.getDate() + 3)\n setMonthYear(thursday.getFullYear())\n setMonthIndex(thursday.getMonth())\n } else {\n // Find the Monday of the week containing the 15th (mid-month, always stable)\n const midMonth = new Date(monthYear, monthIndex, 15)\n setWeekStart(getMonday(midMonth))\n }\n }, [weekStart, monthYear, monthIndex])\n\n // --- Add row handler ---\n const visibleProjectIds = React.useMemo(() => new Set(projects.map((p) => p.id)), [projects])\n\n // optimistic-lock-exempt: the my-projects PATCH calls below only toggle the\n // caller's own `showInGrid` preference on the staff.timesheets.time_project_member\n // junction (a per-user membership flag). There is no shared, multi-user-editable\n // state to lose, so version-locking the toggle would surface false 409s without\n // protecting any data.\n const handleAddProject = React.useCallback(async (project: ProjectRow) => {\n try {\n const payload = { showInGrid: true }\n await runMutation({\n operation: () =>\n apiCallOrThrow(`/api/staff/timesheets/my-projects/${project.id}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: project.id,\n staffMemberId,\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n setProjects((prev) => {\n if (prev.some((p) => p.id === project.id)) return prev\n return [...prev, project]\n })\n } catch (error) {\n logger.error('staff.timesheets.my.addRow', { err: error })\n flash(t('staff.timesheets.my.addRow.error', 'Could not add the project. Please try again.'), 'error')\n }\n }, [t, runMutation, mutationContextId, staffMemberId, retryLastMutation])\n\n const handleRemoveProject = React.useCallback(async (project: ProjectRow) => {\n const confirmed = await confirm({\n title: t('staff.timesheets.my.removeRow', 'Remove from grid'),\n text: t(\n 'staff.timesheets.my.removeRow.confirm',\n 'Remove {projectName} from your timesheet grid? You can re-add it anytime via \"+ Add row\".',\n ).replace('{projectName}', project.name),\n })\n if (!confirmed) return\n\n try {\n const payload = { showInGrid: false }\n await runMutation({\n operation: () =>\n apiCallOrThrow(`/api/staff/timesheets/my-projects/${project.id}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: project.id,\n staffMemberId,\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n setProjects((prev) => prev.filter((p) => p.id !== project.id))\n setDirty((prev) => {\n if (!prev[project.id]) return prev\n const next = { ...prev }\n delete next[project.id]\n return next\n })\n setRawText((prev) => {\n if (!prev[project.id]) return prev\n const next = { ...prev }\n delete next[project.id]\n return next\n })\n setCellErrors((prev) => {\n if (!prev[project.id]) return prev\n const next = { ...prev }\n delete next[project.id]\n return next\n })\n } catch (error) {\n logger.error('staff.timesheets.my.removeRow', { err: error })\n flash(t('staff.timesheets.my.removeRow.error', 'Could not remove the project. Please try again.'), 'error')\n }\n }, [confirm, t, runMutation, mutationContextId, staffMemberId, retryLastMutation])\n\n const handleProjectCreated = React.useCallback(async (project: { id: string; name: string; code: string | null }) => {\n setAllAssignedProjects((prev) => [...prev, project])\n // New projects start hidden; immediately opt them into the grid for the creator.\n try {\n const payload = { showInGrid: true }\n await runMutation({\n operation: () =>\n apiCallOrThrow(`/api/staff/timesheets/my-projects/${project.id}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: project.id,\n staffMemberId,\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n } catch (error) {\n logger.error('staff.timesheets.my.createProject.visibility', { err: error })\n }\n setProjects((prev) => [...prev, project])\n setCreateDialogOpen(false)\n }, [runMutation, mutationContextId, staffMemberId, retryLastMutation])\n\n // --- Loading ---\n if (isInitialLoad) {\n return <Page><PageBody><LoadingMessage label={t('staff.timesheets.my.loading', 'Loading timesheets...')} /></PageBody></Page>\n }\n\n // --- No profile ---\n if (staffMemberMissing) {\n return (\n <Page>\n <PageBody>\n <div className=\"py-12 text-center\">\n <p className=\"text-lg font-semibold mb-2\">\n {t('staff.timesheets.my.noProfile.title', 'Set up your profile to start tracking time')}\n </p>\n <p className=\"text-sm text-muted-foreground mb-6\">\n {t('staff.timesheets.my.noProfile', 'You need a Team Member profile to track time.')}\n </p>\n <Button asChild>\n <Link href=\"/backend/staff/profile/create\">\n {t('staff.timesheets.my.createProfile', 'Create My Profile')}\n </Link>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n {/* Timer bar */}\n <TimerBar\n projects={allAssignedProjects}\n staffMemberId={staffMemberId}\n onTimerStopped={loadData}\n />\n\n {/* Summary cards */}\n <div className=\"mb-4 grid grid-cols-1 gap-4 sm:grid-cols-4\">\n <div className=\"rounded-lg border bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">\n {viewMode === 'weekly'\n ? t('staff.timesheets.my.weekTotal', 'Week Total')\n : t('staff.timesheets.my.total_hours', 'Total Hours')}\n </p>\n <p className=\"text-2xl font-semibold\">{formatMinutesAsDecimal(grandTotal) || '0'}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">{t('staff.timesheets.my.working_days', 'Working Days')}</p>\n <p className=\"text-2xl font-semibold\">{workingDays}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">{t('staff.timesheets.my.daily_average', 'Daily Average')}</p>\n <p className=\"text-2xl font-semibold\">{formatMinutesAsDecimal(Math.round(dailyAverage)) || '0'}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">{t('staff.timesheets.my.status', 'Status')}</p>\n <p className=\"text-2xl font-semibold\">\n <span className=\"inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800\">\n {t('staff.timesheets.my.status_open', 'Open')}\n </span>\n </p>\n </div>\n </div>\n\n {/* Navigation + controls */}\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"icon\" type=\"button\" onClick={goToPrev}>\n <ChevronLeft className=\"size-4\" />\n </Button>\n <span className=\"text-sm font-semibold min-w-[220px] text-center\">{navigationLabel}</span>\n <Button variant=\"outline\" size=\"icon\" type=\"button\" onClick={goToNext}>\n <ChevronRight className=\"size-4\" />\n </Button>\n {viewMode === 'weekly' && (\n <CalendarPicker selectedWeekStart={weekStart} onWeekSelect={setWeekStart} />\n )}\n </div>\n\n <div className=\"flex items-center gap-3\">\n <ViewSwitcher\n viewMode={viewMode}\n onViewModeChange={handleViewModeChange}\n viewType={viewType}\n onViewTypeChange={setViewType}\n />\n <div className=\"flex items-center gap-2\">\n {invalidCellCount > 0 ? (\n <span className=\"text-xs text-status-error-text font-medium\">\n {t('staff.timesheets.my.duration.blocked', 'Fix the highlighted durations to save')}\n </span>\n ) : hasChanges && (\n <span className=\"text-xs text-status-warning-text font-medium\">\n {t('staff.timesheets.my.unsaved', 'Unsaved changes')}\n </span>\n )}\n <Button size=\"sm\" type=\"button\" onClick={handleSave} disabled={!hasChanges || isSaving || invalidCellCount > 0}>\n {isSaving ? t('staff.timesheets.my.saving', 'Saving...') : t('staff.timesheets.my.save_changes', 'Save Changes')}\n </Button>\n </div>\n </div>\n </div>\n\n {/* Content: Grid or List */}\n <div className={isRefreshing ? 'opacity-50 pointer-events-none transition-opacity' : 'transition-opacity'}>\n {allAssignedProjects.length === 0 ? (\n <div className=\"py-12 text-center\">\n <p className=\"text-lg font-semibold mb-2\">\n {t('staff.timesheets.my.noProjects.title', 'No projects assigned yet')}\n </p>\n <p className=\"text-sm text-muted-foreground mb-6\">\n {canManageProjects\n ? t('staff.timesheets.my.noProjects.admin', 'Create a project and assign yourself to start tracking time.')\n : t('staff.timesheets.my.noProjects.employee', 'Ask your manager to assign you to a project.')}\n </p>\n <div className=\"flex items-center justify-center gap-3\">\n {canManageProjects && (\n <Button asChild>\n <Link href=\"/backend/staff/timesheets/projects/create\">\n {t('staff.timesheets.my.noProjects.createProject', 'Create Project')}\n </Link>\n </Button>\n )}\n <Button variant=\"outline\" asChild>\n <Link href=\"/backend/staff/timesheets/projects\">\n {t('staff.timesheets.my.noProjects.viewProjects', 'View Projects')}\n </Link>\n </Button>\n </div>\n </div>\n ) : viewType === 'list' ? (\n <ListView entries={listViewEntries} onEntryUpdated={loadData} />\n ) : (\n <div className=\"overflow-x-auto rounded-lg border\">\n <table className=\"w-full text-sm table-fixed\">\n <colgroup>\n <col className={viewMode === 'weekly' ? 'w-[35%]' : 'w-[140px] min-w-[140px]'} />\n {visibleDays.map((date) => (\n <col key={formatDateKey(date)} className={viewMode === 'weekly' ? '' : 'w-[36px] min-w-[36px]'} />\n ))}\n <col className={viewMode === 'weekly' ? 'w-[72px]' : 'w-[56px] min-w-[56px]'} />\n </colgroup>\n <thead>\n <tr className=\"border-b bg-muted/50\">\n <th className=\"sticky left-0 z-10 bg-muted px-3 py-2 text-left font-medium\">\n {t('staff.timesheets.my.project', 'Project')}\n </th>\n {visibleDays.map((date) => {\n const dayName = getLocalizedDayName(date)\n const weekend = isWeekendDay(date)\n return (\n <th\n key={formatDateKey(date)}\n className={`py-2 text-center font-medium px-1 ${weekend ? 'bg-muted/80 text-muted-foreground' : ''}`}\n >\n <div className=\"text-[10px] uppercase text-muted-foreground\">{dayName}</div>\n <div className=\"text-xs\">{date.getDate()}</div>\n </th>\n )\n })}\n <th className=\"px-3 py-2 text-right font-medium\">\n {t('staff.timesheets.my.total', 'Total')}\n </th>\n </tr>\n </thead>\n <tbody>\n {projects.map((project) => (\n <tr key={project.id} className=\"group border-b hover:bg-muted/30\">\n <td className=\"sticky left-0 z-10 bg-background px-3 py-1.5 font-medium text-foreground\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 truncate\" title={project.name}>\n <ProjectColorDot colorKey={project.color} projectName={project.name} size=\"sm\" />\n <span className=\"truncate\">{project.name}</span>\n </div>\n {project.code && (\n <div className=\"text-[10px] text-muted-foreground\">{project.code}</div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => { void handleRemoveProject(project) }}\n aria-label={t('staff.timesheets.my.removeRow', 'Remove from grid')}\n title={t('staff.timesheets.my.removeRow', 'Remove from grid')}\n className=\"opacity-0 group-hover:opacity-100 focus:opacity-100 inline-flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground transition-opacity\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n </td>\n {visibleDays.map((date) => {\n const dateKey = formatDateKey(date)\n const weekend = isWeekendDay(date)\n const cellMinutes = getCellValue(project.id, dateKey)\n const isDirty = dirty[project.id]?.[dateKey] !== undefined\n const cellError = cellErrors[project.id]?.[dateKey]\n const cellErrorMessage = cellError ? describeDurationError(cellError) : undefined\n return (\n <td key={dateKey} className={`px-0.5 py-0.5 ${weekend ? 'bg-muted/40' : ''}`}>\n {weekend ? (\n <div className=\"rounded px-1 py-1 text-center text-xs text-muted-foreground/50\">-</div>\n ) : (\n <InlineInput\n type=\"text\"\n inputMode=\"text\"\n showBorderOnHover={false}\n className={`mx-auto flex h-6 border transition-colors\n ${viewMode === 'weekly' ? 'w-12 px-1' : 'w-8 px-0'}\n ${cellError\n ? 'bg-status-error-bg'\n : isDirty\n ? 'border-status-warning-border bg-status-warning-bg'\n : 'border-muted-foreground/20 bg-transparent hover:border-muted-foreground/40'}`}\n inputClassName={`text-center text-xs tabular-nums\n ${cellError ? 'text-status-error-text' : cellMinutes > 0 ? 'font-semibold' : 'text-muted-foreground'}`}\n value={rawText[project.id]?.[dateKey] ?? formatMinutesAsDecimal(cellMinutes)}\n onChange={(e) => handleCellChange(project.id, dateKey, e.target.value)}\n onBlur={(event) => handleCellBlur(project.id, dateKey, event.currentTarget.value)}\n placeholder={t('staff.timesheets.my.durationPlaceholder', '0')}\n aria-invalid={cellError !== undefined}\n aria-label={describeDurationCell(project.name, date)}\n title={cellErrorMessage ?? durationFormatHint}\n />\n )}\n </td>\n )\n })}\n <td className=\"px-3 py-1.5 text-right font-semibold text-xs tabular-nums\">\n {formatMinutesAsDecimal(getRowTotal(project.id)) || '0'}\n </td>\n </tr>\n ))}\n <tr className=\"border-b\">\n <td colSpan={visibleDays.length + 2} className=\"sticky left-0 bg-background px-1 py-0.5\">\n <AddRowDropdown\n assignedProjects={allAssignedProjects}\n visibleProjectIds={visibleProjectIds}\n canCreateProject={canManageProjects}\n onAddProject={handleAddProject}\n onCreateProject={() => setCreateDialogOpen(true)}\n />\n </td>\n </tr>\n </tbody>\n <tfoot>\n <tr className=\"border-t bg-muted/50 font-semibold\">\n <td className=\"sticky left-0 z-10 bg-muted px-3 py-2\">\n {t('staff.timesheets.my.daily_total', 'Daily Total')}\n </td>\n {visibleDays.map((date) => {\n const weekend = isWeekendDay(date)\n const dayMinutes = getDayTotal(date)\n return (\n <td key={formatDateKey(date)} className={`py-2 text-center text-xs tabular-nums ${weekend ? 'text-muted-foreground/50' : ''}`}>\n {weekend ? '-' : (formatMinutesAsDecimal(dayMinutes) || '-')}\n </td>\n )\n })}\n <td className=\"px-3 py-2 text-right tabular-nums font-semibold\">{formatMinutesAsDecimal(grandTotal) || '0'}</td>\n </tr>\n </tfoot>\n </table>\n <p className=\"px-3 py-2 text-xs text-muted-foreground\">{durationFormatHint}</p>\n </div>\n )}\n </div>\n </PageBody>\n {ConfirmDialogElement}\n <CreateProjectDialog\n open={createDialogOpen}\n onOpenChange={setCreateDialogOpen}\n onProjectCreated={handleProjectCreated}\n />\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { InlineInput } from '@open-mercato/ui/primitives/inline-input'\nimport { apiCall, apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { ChevronLeft, ChevronRight, X } from 'lucide-react'\nimport Link from 'next/link'\nimport { ViewSwitcher } from '../../../lib/timesheets-ui/ViewSwitcher'\nimport { CalendarPicker } from '../../../lib/timesheets-ui/CalendarPicker'\nimport { ListView } from '../../../lib/timesheets-ui/ListView'\nimport { TimerBar } from '../../../lib/timesheets-ui/TimerBar'\nimport { AddRowDropdown } from '../../../lib/timesheets-ui/AddRowDropdown'\nimport { CreateProjectDialog } from '../../../lib/timesheets-ui/CreateProjectDialog'\nimport { ProjectColorDot } from '../../../lib/timesheets-ui/ProjectColorDot'\nimport {\n formatMinutesAsDecimal,\n parseDurationInput,\n type DurationParseError,\n} from '../../../lib/timesheetsDuration'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('staff')\n\n// --- Types ---\n\ntype ProjectRow = { id: string; name: string; code: string | null; color?: string | null }\ntype CellEntry = { id?: string; minutes: number }\ntype EntryMap = Record<string, Record<string, CellEntry[]>>\ntype DirtyMap = Record<string, Record<string, CellEntry>>\ntype RawTextMap = Record<string, Record<string, string>>\ntype CellErrorMap = Record<string, Record<string, DurationParseError>>\ntype ViewMode = 'weekly' | 'monthly'\ntype ViewType = 'timesheet' | 'list'\n\ntype RawTimeEntry = Record<string, unknown>\n\n// --- Date Helpers ---\n\nfunction getMonday(date: Date): Date {\n const d = new Date(date)\n const day = d.getDay()\n const diff = day === 0 ? -6 : 1 - day\n d.setDate(d.getDate() + diff)\n d.setHours(0, 0, 0, 0)\n return d\n}\n\nfunction getSunday(monday: Date): Date {\n const d = new Date(monday)\n d.setDate(d.getDate() + 6)\n return d\n}\n\nfunction getWeekNumber(date: Date): number {\n const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))\n d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7))\n const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))\n return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7)\n}\n\nfunction formatDateKey(date: Date): string {\n const y = date.getFullYear()\n const m = String(date.getMonth() + 1).padStart(2, '0')\n const d = String(date.getDate()).padStart(2, '0')\n return `${y}-${m}-${d}`\n}\n\nfunction formatDateKeyFromParts(year: number, month: number, day: number): string {\n const m = String(month + 1).padStart(2, '0')\n const d = String(day).padStart(2, '0')\n return `${year}-${m}-${d}`\n}\n\nfunction getDaysInMonth(year: number, month: number): number {\n return new Date(year, month + 1, 0).getDate()\n}\n\nfunction isWeekendDay(date: Date): boolean {\n const d = date.getDay()\n return d === 0 || d === 6\n}\n\nfunction getLocalizedDayName(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'short' })\n}\n\nfunction getLocalizedCellDate(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' })\n}\n\n// --- Derived date ranges ---\n\nfunction getWeekDays(weekStart: Date): Date[] {\n return Array.from({ length: 7 }, (_, i) => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + i)\n return d\n })\n}\n\nfunction getMonthDays(year: number, month: number): Date[] {\n const count = getDaysInMonth(year, month)\n return Array.from({ length: count }, (_, i) => new Date(year, month, i + 1))\n}\n\n// --- Week label ---\n\nfunction formatWeekLabel(weekStart: Date): string {\n const weekEnd = getSunday(weekStart)\n const weekNum = getWeekNumber(weekStart)\n const startDay = weekStart.getDate()\n const endDay = weekEnd.getDate()\n const startMonth = weekStart.toLocaleString(undefined, { month: 'short' })\n const endMonth = weekEnd.toLocaleString(undefined, { month: 'short' })\n const year = weekStart.getFullYear()\n\n const dateRange = weekStart.getMonth() === weekEnd.getMonth()\n ? `${startDay} - ${endDay} ${startMonth} ${year}`\n : `${startDay} ${startMonth} - ${endDay} ${endMonth} ${year}`\n\n return `${dateRange} \\u00b7 W${weekNum}`\n}\n\nfunction formatMonthLabel(year: number, month: number): string {\n const date = new Date(year, month, 1)\n return date.toLocaleString(undefined, { month: 'long', year: 'numeric' })\n}\n\n// --- Component ---\n\nexport default function MyTimesheetsPage() {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const now = new Date()\n const [viewMode, setViewMode] = React.useState<ViewMode>('weekly')\n const [viewType, setViewType] = React.useState<ViewType>('timesheet')\n const [weekStart, setWeekStart] = React.useState<Date>(() => getMonday(now))\n const [monthYear, setMonthYear] = React.useState(now.getFullYear())\n const [monthIndex, setMonthIndex] = React.useState(now.getMonth())\n\n const [projects, setProjects] = React.useState<ProjectRow[]>([])\n const [entries, setEntries] = React.useState<EntryMap>({})\n const [rawEntries, setRawEntries] = React.useState<RawTimeEntry[]>([])\n const [dirty, setDirty] = React.useState<DirtyMap>({})\n const [rawText, setRawText] = React.useState<RawTextMap>({})\n const [cellErrors, setCellErrors] = React.useState<CellErrorMap>({})\n const [staffMemberId, setStaffMemberId] = React.useState<string | null>(null)\n const [staffMemberMissing, setStaffMemberMissing] = React.useState(false)\n const [isInitialLoad, setIsInitialLoad] = React.useState(true)\n const [isRefreshing, setIsRefreshing] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const [canManageProjects, setCanManageProjects] = React.useState(false)\n const [createDialogOpen, setCreateDialogOpen] = React.useState(false)\n const [allAssignedProjects, setAllAssignedProjects] = React.useState<ProjectRow[]>([])\n\n const mutationContextId = React.useMemo(\n () => (staffMemberId ? `staff-timesheets:${staffMemberId}` : 'staff-timesheets:pending'),\n [staffMemberId],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n resourceId?: string\n staffMemberId: string | null\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const gridInstanceId = React.useId()\n const durationHintId = `${gridInstanceId}-duration-hint`\n const buildDurationErrorId = React.useCallback(\n (projectId: string, dateKey: string): string => `${gridInstanceId}-duration-error-${projectId}-${dateKey}`,\n [gridInstanceId],\n )\n const durationFormatHint = t(\n 'staff.timesheets.my.duration.hint',\n 'Enter hours (8 or 1.5), h:mm (1:30) or minutes (90m). Max 24h per day.',\n )\n const describeDurationCell = React.useCallback((projectName: string, date: Date): string => (\n t('staff.timesheets.my.duration.cellLabel', 'Duration for {project} on {date}')\n .replace('{project}', projectName)\n .replace('{date}', getLocalizedCellDate(date))\n ), [t])\n const describeDurationError = React.useCallback((reason: DurationParseError): string => (\n reason === 'out_of_range'\n ? t('staff.timesheets.my.duration.errors.out_of_range', 'Maximum 24h per day. Use 1:30 or 90m for shorter entries.')\n : t('staff.timesheets.my.duration.errors.invalid', 'Unrecognised duration. Use 8, 1.5, 1:30, 90m or 1h 30m.')\n ), [t])\n\n // --- Feature check ---\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n try {\n const res = await apiCall<{ ok: boolean; granted: string[] }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['staff.timesheets.projects.manage'] }),\n })\n if (!cancelled) {\n setCanManageProjects(new Set(res.result?.granted ?? []).has('staff.timesheets.projects.manage'))\n }\n } catch {\n // default: no manage access\n }\n })()\n return () => { cancelled = true }\n }, [])\n\n // --- Computed days for current view ---\n const visibleDays = React.useMemo(() => {\n if (viewMode === 'weekly') return getWeekDays(weekStart)\n return getMonthDays(monthYear, monthIndex)\n }, [viewMode, weekStart, monthYear, monthIndex])\n\n const dateRange = React.useMemo(() => {\n if (viewMode === 'weekly') {\n return { from: formatDateKey(weekStart), to: formatDateKey(getSunday(weekStart)) }\n }\n const daysInMonth = getDaysInMonth(monthYear, monthIndex)\n return {\n from: formatDateKeyFromParts(monthYear, monthIndex, 1),\n to: formatDateKeyFromParts(monthYear, monthIndex, daysInMonth),\n }\n }, [viewMode, weekStart, monthYear, monthIndex])\n\n // --- Navigation ---\n const goToPrev = React.useCallback(() => {\n if (viewMode === 'weekly') {\n setWeekStart((prev) => {\n const d = new Date(prev)\n d.setDate(d.getDate() - 7)\n return d\n })\n } else {\n setMonthIndex((prev) => {\n if (prev === 0) { setMonthYear((y) => y - 1); return 11 }\n return prev - 1\n })\n }\n }, [viewMode])\n\n const goToNext = React.useCallback(() => {\n if (viewMode === 'weekly') {\n setWeekStart((prev) => {\n const d = new Date(prev)\n d.setDate(d.getDate() + 7)\n return d\n })\n } else {\n setMonthIndex((prev) => {\n if (prev === 11) { setMonthYear((y) => y + 1); return 0 }\n return prev + 1\n })\n }\n }, [viewMode])\n\n const navigationLabel = React.useMemo(() => {\n if (viewMode === 'weekly') return formatWeekLabel(weekStart)\n return formatMonthLabel(monthYear, monthIndex)\n }, [viewMode, weekStart, monthYear, monthIndex])\n\n // --- Data loading ---\n const isInitialLoadRef = React.useRef(true)\n\n const loadData = React.useCallback(async () => {\n if (!isInitialLoadRef.current) setIsRefreshing(true)\n try {\n const selfRes = await readApiResultOrThrow<{ member?: { id: string; displayName: string } | null }>(\n '/api/staff/team-members/self',\n undefined,\n { errorMessage: t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), fallback: { member: null } },\n )\n const memberId = selfRes.member?.id ?? null\n setStaffMemberId(memberId)\n if (!memberId) {\n setStaffMemberMissing(true)\n setProjects([])\n setEntries({})\n setRawEntries([])\n setIsInitialLoad(false)\n setIsRefreshing(false)\n return\n }\n setStaffMemberMissing(false)\n\n const assignmentsRes = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n '/api/staff/timesheets/my-projects?pageSize=100',\n undefined,\n { errorMessage: t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), fallback: { items: [] } },\n )\n const assignmentItems = Array.isArray(assignmentsRes.items) ? assignmentsRes.items : []\n const assignedProjectIds = assignmentItems\n .map((item) => String(item.time_project_id ?? item.timeProjectId ?? ''))\n .filter((id) => id.length > 0)\n const visibleProjectIdSet = new Set(\n assignmentItems\n .filter((item) => item.show_in_grid === true || item.showInGrid === true)\n .map((item) => String(item.time_project_id ?? item.timeProjectId ?? ''))\n .filter((id) => id.length > 0),\n )\n\n const entriesParams = new URLSearchParams({ pageSize: '100', staffMemberId: memberId })\n if (dateRange.from) entriesParams.set('from', dateRange.from)\n if (dateRange.to) entriesParams.set('to', dateRange.to)\n\n const [projectsRes, entriesRes] = await Promise.all([\n assignedProjectIds.length > 0\n ? readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/staff/timesheets/time-projects?ids=${assignedProjectIds.join(',')}&pageSize=100`,\n undefined,\n { errorMessage: t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), fallback: { items: [] } },\n )\n : Promise.resolve({ items: [] as Array<Record<string, unknown>> }),\n readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/staff/timesheets/time-entries?${entriesParams.toString()}`,\n undefined,\n { errorMessage: t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), fallback: { items: [] } },\n ),\n ])\n\n const projectItems = Array.isArray(projectsRes.items) ? projectsRes.items : []\n const mappedProjects = projectItems.map((item) => ({\n id: String(item.id ?? ''),\n name: String(item.name ?? ''),\n code: typeof item.code === 'string' ? item.code : null,\n color: typeof item.color === 'string' ? item.color : null,\n }))\n setAllAssignedProjects(mappedProjects)\n const visibleProjects = mappedProjects.filter((p) => visibleProjectIdSet.has(p.id))\n setProjects(visibleProjects)\n\n const entryItems = Array.isArray(entriesRes.items) ? entriesRes.items : []\n setRawEntries(entryItems)\n const map: EntryMap = {}\n for (const item of entryItems) {\n const projectId = String(item.time_project_id ?? item.timeProjectId ?? '')\n const rawDate = String(item.date ?? '')\n const dateKey = rawDate.slice(0, 10)\n const minutes = typeof item.duration_minutes === 'number'\n ? item.duration_minutes\n : typeof item.durationMinutes === 'number'\n ? item.durationMinutes\n : 0\n const entryId = String(item.id ?? '')\n if (!map[projectId]) map[projectId] = {}\n if (!map[projectId][dateKey]) map[projectId][dateKey] = []\n map[projectId][dateKey].push({ id: entryId || undefined, minutes })\n }\n setEntries(map)\n setDirty({})\n setRawText({})\n setCellErrors({})\n } catch (error) {\n logger.error('staff.timesheets.my.load', { err: error })\n flash(t('staff.timesheets.my.errors.load', 'Failed to load timesheets.'), 'error')\n } finally {\n isInitialLoadRef.current = false\n setIsInitialLoad(false)\n setIsRefreshing(false)\n }\n }, [dateRange.from, dateRange.to, t])\n\n React.useEffect(() => {\n void loadData()\n }, [loadData, scopeVersion])\n\n // --- Cell handlers ---\n const clearCellError = React.useCallback((projectId: string, dateKey: string) => {\n setCellErrors((prev) => {\n const projectErrors = prev[projectId]\n if (!projectErrors || projectErrors[dateKey] === undefined) return prev\n const nextProjectErrors = { ...projectErrors }\n delete nextProjectErrors[dateKey]\n const next = { ...prev }\n if (Object.keys(nextProjectErrors).length > 0) next[projectId] = nextProjectErrors\n else delete next[projectId]\n return next\n })\n }, [])\n\n const handleCellChange = React.useCallback((projectId: string, dateKey: string, value: string) => {\n clearCellError(projectId, dateKey)\n setRawText((prev) => {\n const projectTexts = { ...(prev[projectId] ?? {}) }\n projectTexts[dateKey] = value\n return { ...prev, [projectId]: projectTexts }\n })\n }, [clearCellError])\n\n const dropDirtyCell = React.useCallback((projectId: string, dateKey: string) => {\n setDirty((prev) => {\n const projectEntries = prev[projectId]\n if (!projectEntries || projectEntries[dateKey] === undefined) return prev\n const nextProjectEntries = { ...projectEntries }\n delete nextProjectEntries[dateKey]\n const next = { ...prev }\n if (Object.keys(nextProjectEntries).length > 0) next[projectId] = nextProjectEntries\n else delete next[projectId]\n return next\n })\n }, [])\n\n const handleCellBlur = React.useCallback((projectId: string, dateKey: string, currentValue: string) => {\n const editedText = rawText[projectId]?.[dateKey]\n const text = editedText ?? currentValue\n if (text === undefined) return\n const parsed = parseDurationInput(text)\n if (!parsed.ok) {\n dropDirtyCell(projectId, dateKey)\n setCellErrors((prev) => {\n const projectErrors = { ...(prev[projectId] ?? {}) }\n projectErrors[dateKey] = parsed.reason\n return { ...prev, [projectId]: projectErrors }\n })\n return\n }\n clearCellError(projectId, dateKey)\n const minutes = parsed.minutes\n const cellEntries = entries[projectId]?.[dateKey] ?? []\n const existingMinutes = cellEntries.reduce((sum, e) => sum + e.minutes, 0)\n\n if (minutes !== existingMinutes || (editedText !== undefined && cellEntries.length > 0)) {\n setDirty((prev) => {\n const projectEntries: Record<string, CellEntry> = { ...(prev[projectId] ?? {}) }\n const firstId = cellEntries[0]?.id\n projectEntries[dateKey] = { id: firstId, minutes }\n return { ...prev, [projectId]: projectEntries }\n })\n }\n setRawText((prev) => {\n const projectTexts = { ...(prev[projectId] ?? {}) }\n delete projectTexts[dateKey]\n const hasKeys = Object.keys(projectTexts).length > 0\n if (!hasKeys) {\n const next = { ...prev }\n delete next[projectId]\n return next\n }\n return { ...prev, [projectId]: projectTexts }\n })\n }, [rawText, entries, clearCellError, dropDirtyCell])\n\n const getCellValue = React.useCallback((projectId: string, dateKey: string): number => {\n const dirtyCell = dirty[projectId]?.[dateKey] as CellEntry | undefined\n if (dirtyCell !== undefined) return dirtyCell.minutes\n const cellEntries = entries[projectId]?.[dateKey] ?? []\n return cellEntries.reduce((sum, e) => sum + e.minutes, 0)\n }, [dirty, entries])\n\n // --- Save ---\n const hasChanges = Object.keys(dirty).length > 0 || Object.keys(rawText).length > 0\n const invalidCellCount = React.useMemo(\n () => Object.values(cellErrors).reduce((sum, projectErrors) => sum + Object.keys(projectErrors).length, 0),\n [cellErrors],\n )\n const pendingSummary = React.useMemo(() => {\n let cells = 0\n let minutes = 0\n for (const dateMap of Object.values(dirty)) {\n for (const cell of Object.values(dateMap)) {\n cells += 1\n minutes += cell.minutes\n }\n }\n return { cells, minutes }\n }, [dirty])\n\n const handleSave = React.useCallback(async () => {\n if (!hasChanges || invalidCellCount > 0) return\n const summary = t('staff.timesheets.my.confirm_save.summary', '{count} cell(s), {total}h in total.')\n .replace('{count}', String(pendingSummary.cells))\n .replace('{total}', formatMinutesAsDecimal(pendingSummary.minutes) || '0')\n const confirmed = await confirm({\n title: t('staff.timesheets.my.confirm_save.title', 'Save changes?'),\n text: `${t('staff.timesheets.my.confirm_save.body', 'Your timesheet entries will be saved.')} ${summary}`,\n })\n if (!confirmed) return\n\n setIsSaving(true)\n try {\n const bulkEntries: Array<{ id?: string; date: string; timeProjectId: string; durationMinutes: number }> = []\n for (const [projectId, dateMap] of Object.entries(dirty)) {\n for (const [dateKey, cellValue] of Object.entries(dateMap)) {\n const cell = cellValue as CellEntry\n const cellEntries = entries[projectId]?.[dateKey] ?? []\n const firstId = cell.id ?? cellEntries[0]?.id\n bulkEntries.push({ id: firstId, date: dateKey, timeProjectId: projectId, durationMinutes: cell.minutes })\n }\n }\n if (bulkEntries.length === 0) return\n\n const payload = { entries: bulkEntries }\n await runMutation({\n operation: () =>\n apiCallOrThrow('/api/staff/timesheets/time-entries/bulk', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'staff.timesheets.time_entry',\n resourceId: staffMemberId ?? undefined,\n staffMemberId,\n retryLastMutation,\n },\n mutationPayload: payload as unknown as Record<string, unknown>,\n })\n\n flash(t('staff.timesheets.my.saved', 'Timesheet saved.'), 'success')\n await loadData()\n } catch (error) {\n logger.error('staff.timesheets.my.save', { err: error })\n flash(t('staff.timesheets.my.errors.save', 'Failed to save timesheets.'), 'error')\n } finally {\n setIsSaving(false)\n }\n }, [dirty, entries, hasChanges, invalidCellCount, pendingSummary, confirm, t, loadData, runMutation, mutationContextId, staffMemberId, retryLastMutation])\n\n // --- Totals ---\n const getRowTotal = React.useCallback((projectId: string): number => {\n let total = 0\n for (const day of visibleDays) {\n total += getCellValue(projectId, formatDateKey(day))\n }\n return total\n }, [visibleDays, getCellValue])\n\n const getDayTotal = React.useCallback((date: Date): number => {\n const dateKey = formatDateKey(date)\n let total = 0\n for (const project of projects) {\n total += getCellValue(project.id, dateKey)\n }\n return total\n }, [projects, getCellValue])\n\n const grandTotal = React.useMemo(() => {\n let total = 0\n for (const project of projects) {\n total += getRowTotal(project.id)\n }\n return total\n }, [projects, getRowTotal])\n\n const workingDays = React.useMemo(() => {\n let count = 0\n for (const day of visibleDays) {\n if (!isWeekendDay(day)) {\n const dateKey = formatDateKey(day)\n for (const project of projects) {\n if (getCellValue(project.id, dateKey) > 0) { count++; break }\n }\n }\n }\n return count\n }, [visibleDays, projects, getCellValue])\n\n const dailyAverage = React.useMemo(() => {\n if (workingDays === 0) return 0\n return grandTotal / workingDays\n }, [grandTotal, workingDays])\n\n // --- List view entries ---\n const listViewEntries = React.useMemo(() => {\n return rawEntries.map((item) => ({\n id: String(item.id ?? ''),\n date: String(item.date ?? '').slice(0, 10),\n durationMinutes: typeof item.duration_minutes === 'number' ? item.duration_minutes\n : typeof item.durationMinutes === 'number' ? item.durationMinutes : 0,\n projectId: String(item.time_project_id ?? item.timeProjectId ?? ''),\n projectName: projects.find((p) => p.id === String(item.time_project_id ?? item.timeProjectId ?? ''))?.name ?? '',\n projectCode: projects.find((p) => p.id === String(item.time_project_id ?? item.timeProjectId ?? ''))?.code ?? null,\n projectColor: projects.find((p) => p.id === String(item.time_project_id ?? item.timeProjectId ?? ''))?.color ?? null,\n notes: typeof item.notes === 'string' ? item.notes : null,\n source: typeof item.source === 'string' ? item.source : 'manual',\n startedAt: typeof item.started_at === 'string' ? item.started_at : typeof item.startedAt === 'string' ? item.startedAt : null,\n endedAt: typeof item.ended_at === 'string' ? item.ended_at : typeof item.endedAt === 'string' ? item.endedAt : null,\n }))\n }, [rawEntries, projects])\n\n // --- Handle view mode change ---\n const handleViewModeChange = React.useCallback((mode: ViewMode) => {\n setViewMode(mode)\n if (mode === 'monthly') {\n // Use Thursday of the week to determine month (handles cross-month weeks)\n const thursday = new Date(weekStart)\n thursday.setDate(thursday.getDate() + 3)\n setMonthYear(thursday.getFullYear())\n setMonthIndex(thursday.getMonth())\n } else {\n // Find the Monday of the week containing the 15th (mid-month, always stable)\n const midMonth = new Date(monthYear, monthIndex, 15)\n setWeekStart(getMonday(midMonth))\n }\n }, [weekStart, monthYear, monthIndex])\n\n // --- Add row handler ---\n const visibleProjectIds = React.useMemo(() => new Set(projects.map((p) => p.id)), [projects])\n\n // optimistic-lock-exempt: the my-projects PATCH calls below only toggle the\n // caller's own `showInGrid` preference on the staff.timesheets.time_project_member\n // junction (a per-user membership flag). There is no shared, multi-user-editable\n // state to lose, so version-locking the toggle would surface false 409s without\n // protecting any data.\n const handleAddProject = React.useCallback(async (project: ProjectRow) => {\n try {\n const payload = { showInGrid: true }\n await runMutation({\n operation: () =>\n apiCallOrThrow(`/api/staff/timesheets/my-projects/${project.id}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: project.id,\n staffMemberId,\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n setProjects((prev) => {\n if (prev.some((p) => p.id === project.id)) return prev\n return [...prev, project]\n })\n } catch (error) {\n logger.error('staff.timesheets.my.addRow', { err: error })\n flash(t('staff.timesheets.my.addRow.error', 'Could not add the project. Please try again.'), 'error')\n }\n }, [t, runMutation, mutationContextId, staffMemberId, retryLastMutation])\n\n const handleRemoveProject = React.useCallback(async (project: ProjectRow) => {\n const confirmed = await confirm({\n title: t('staff.timesheets.my.removeRow', 'Remove from grid'),\n text: t(\n 'staff.timesheets.my.removeRow.confirm',\n 'Remove {projectName} from your timesheet grid? You can re-add it anytime via \"+ Add row\".',\n ).replace('{projectName}', project.name),\n })\n if (!confirmed) return\n\n try {\n const payload = { showInGrid: false }\n await runMutation({\n operation: () =>\n apiCallOrThrow(`/api/staff/timesheets/my-projects/${project.id}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: project.id,\n staffMemberId,\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n setProjects((prev) => prev.filter((p) => p.id !== project.id))\n setDirty((prev) => {\n if (!prev[project.id]) return prev\n const next = { ...prev }\n delete next[project.id]\n return next\n })\n setRawText((prev) => {\n if (!prev[project.id]) return prev\n const next = { ...prev }\n delete next[project.id]\n return next\n })\n setCellErrors((prev) => {\n if (!prev[project.id]) return prev\n const next = { ...prev }\n delete next[project.id]\n return next\n })\n } catch (error) {\n logger.error('staff.timesheets.my.removeRow', { err: error })\n flash(t('staff.timesheets.my.removeRow.error', 'Could not remove the project. Please try again.'), 'error')\n }\n }, [confirm, t, runMutation, mutationContextId, staffMemberId, retryLastMutation])\n\n const handleProjectCreated = React.useCallback(async (project: { id: string; name: string; code: string | null }) => {\n setAllAssignedProjects((prev) => [...prev, project])\n // New projects start hidden; immediately opt them into the grid for the creator.\n try {\n const payload = { showInGrid: true }\n await runMutation({\n operation: () =>\n apiCallOrThrow(`/api/staff/timesheets/my-projects/${project.id}`, {\n method: 'PATCH',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }),\n context: {\n formId: mutationContextId,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: project.id,\n staffMemberId,\n retryLastMutation,\n },\n mutationPayload: payload,\n })\n } catch (error) {\n logger.error('staff.timesheets.my.createProject.visibility', { err: error })\n }\n setProjects((prev) => [...prev, project])\n setCreateDialogOpen(false)\n }, [runMutation, mutationContextId, staffMemberId, retryLastMutation])\n\n // --- Loading ---\n if (isInitialLoad) {\n return <Page><PageBody><LoadingMessage label={t('staff.timesheets.my.loading', 'Loading timesheets...')} /></PageBody></Page>\n }\n\n // --- No profile ---\n if (staffMemberMissing) {\n return (\n <Page>\n <PageBody>\n <div className=\"py-12 text-center\">\n <p className=\"text-lg font-semibold mb-2\">\n {t('staff.timesheets.my.noProfile.title', 'Set up your profile to start tracking time')}\n </p>\n <p className=\"text-sm text-muted-foreground mb-6\">\n {t('staff.timesheets.my.noProfile', 'You need a Team Member profile to track time.')}\n </p>\n <Button asChild>\n <Link href=\"/backend/staff/profile/create\">\n {t('staff.timesheets.my.createProfile', 'Create My Profile')}\n </Link>\n </Button>\n </div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n {/* Timer bar */}\n <TimerBar\n projects={allAssignedProjects}\n staffMemberId={staffMemberId}\n onTimerStopped={loadData}\n />\n\n {/* Summary cards */}\n <div className=\"mb-4 grid grid-cols-1 gap-4 sm:grid-cols-4\">\n <div className=\"rounded-lg border bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">\n {viewMode === 'weekly'\n ? t('staff.timesheets.my.weekTotal', 'Week Total')\n : t('staff.timesheets.my.total_hours', 'Total Hours')}\n </p>\n <p className=\"text-2xl font-semibold\">{formatMinutesAsDecimal(grandTotal) || '0'}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">{t('staff.timesheets.my.working_days', 'Working Days')}</p>\n <p className=\"text-2xl font-semibold\">{workingDays}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">{t('staff.timesheets.my.daily_average', 'Daily Average')}</p>\n <p className=\"text-2xl font-semibold\">{formatMinutesAsDecimal(Math.round(dailyAverage)) || '0'}</p>\n </div>\n <div className=\"rounded-lg border bg-card p-4\">\n <p className=\"text-sm text-muted-foreground\">{t('staff.timesheets.my.status', 'Status')}</p>\n <p className=\"text-2xl font-semibold\">\n <span className=\"inline-flex items-center rounded-full bg-green-100 px-2.5 py-0.5 text-xs font-medium text-green-800\">\n {t('staff.timesheets.my.status_open', 'Open')}\n </span>\n </p>\n </div>\n </div>\n\n {/* Navigation + controls */}\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" size=\"icon\" type=\"button\" onClick={goToPrev}>\n <ChevronLeft className=\"size-4\" />\n </Button>\n <span className=\"text-sm font-semibold min-w-[220px] text-center\">{navigationLabel}</span>\n <Button variant=\"outline\" size=\"icon\" type=\"button\" onClick={goToNext}>\n <ChevronRight className=\"size-4\" />\n </Button>\n {viewMode === 'weekly' && (\n <CalendarPicker selectedWeekStart={weekStart} onWeekSelect={setWeekStart} />\n )}\n </div>\n\n <div className=\"flex items-center gap-3\">\n <ViewSwitcher\n viewMode={viewMode}\n onViewModeChange={handleViewModeChange}\n viewType={viewType}\n onViewTypeChange={setViewType}\n />\n <div className=\"flex items-center gap-2\">\n {invalidCellCount > 0 ? (\n <span className=\"text-xs text-status-error-text font-medium\">\n {t('staff.timesheets.my.duration.blocked', 'Fix the highlighted durations to save')}\n </span>\n ) : hasChanges && (\n <span className=\"text-xs text-status-warning-text font-medium\">\n {t('staff.timesheets.my.unsaved', 'Unsaved changes')}\n </span>\n )}\n <Button size=\"sm\" type=\"button\" onClick={handleSave} disabled={!hasChanges || isSaving || invalidCellCount > 0}>\n {isSaving ? t('staff.timesheets.my.saving', 'Saving...') : t('staff.timesheets.my.save_changes', 'Save Changes')}\n </Button>\n </div>\n </div>\n </div>\n\n {/* Content: Grid or List */}\n <div className={isRefreshing ? 'opacity-50 pointer-events-none transition-opacity' : 'transition-opacity'}>\n {allAssignedProjects.length === 0 ? (\n <div className=\"py-12 text-center\">\n <p className=\"text-lg font-semibold mb-2\">\n {t('staff.timesheets.my.noProjects.title', 'No projects assigned yet')}\n </p>\n <p className=\"text-sm text-muted-foreground mb-6\">\n {canManageProjects\n ? t('staff.timesheets.my.noProjects.admin', 'Create a project and assign yourself to start tracking time.')\n : t('staff.timesheets.my.noProjects.employee', 'Ask your manager to assign you to a project.')}\n </p>\n <div className=\"flex items-center justify-center gap-3\">\n {canManageProjects && (\n <Button asChild>\n <Link href=\"/backend/staff/timesheets/projects/create\">\n {t('staff.timesheets.my.noProjects.createProject', 'Create Project')}\n </Link>\n </Button>\n )}\n <Button variant=\"outline\" asChild>\n <Link href=\"/backend/staff/timesheets/projects\">\n {t('staff.timesheets.my.noProjects.viewProjects', 'View Projects')}\n </Link>\n </Button>\n </div>\n </div>\n ) : viewType === 'list' ? (\n <ListView entries={listViewEntries} onEntryUpdated={loadData} />\n ) : (\n <div className=\"overflow-x-auto rounded-lg border\">\n <table className=\"w-full text-sm table-fixed\">\n <colgroup>\n <col className={viewMode === 'weekly' ? 'w-[35%]' : 'w-[140px] min-w-[140px]'} />\n {visibleDays.map((date) => (\n <col key={formatDateKey(date)} className={viewMode === 'weekly' ? '' : 'w-[36px] min-w-[36px]'} />\n ))}\n <col className={viewMode === 'weekly' ? 'w-[72px]' : 'w-[56px] min-w-[56px]'} />\n </colgroup>\n <thead>\n <tr className=\"border-b bg-muted/50\">\n <th className=\"sticky left-0 z-10 bg-muted px-3 py-2 text-left font-medium\">\n {t('staff.timesheets.my.project', 'Project')}\n </th>\n {visibleDays.map((date) => {\n const dayName = getLocalizedDayName(date)\n const weekend = isWeekendDay(date)\n return (\n <th\n key={formatDateKey(date)}\n className={`py-2 text-center font-medium px-1 ${weekend ? 'bg-muted/80 text-muted-foreground' : ''}`}\n >\n <div className=\"text-[10px] uppercase text-muted-foreground\">{dayName}</div>\n <div className=\"text-xs\">{date.getDate()}</div>\n </th>\n )\n })}\n <th className=\"px-3 py-2 text-right font-medium\">\n {t('staff.timesheets.my.total', 'Total')}\n </th>\n </tr>\n </thead>\n <tbody>\n {projects.map((project) => (\n <tr key={project.id} className=\"group border-b hover:bg-muted/30\">\n <td className=\"sticky left-0 z-10 bg-background px-3 py-1.5 font-medium text-foreground\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1.5 truncate\" title={project.name}>\n <ProjectColorDot colorKey={project.color} projectName={project.name} size=\"sm\" />\n <span className=\"truncate\">{project.name}</span>\n </div>\n {project.code && (\n <div className=\"text-[10px] text-muted-foreground\">{project.code}</div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => { void handleRemoveProject(project) }}\n aria-label={t('staff.timesheets.my.removeRow', 'Remove from grid')}\n title={t('staff.timesheets.my.removeRow', 'Remove from grid')}\n className=\"opacity-0 group-hover:opacity-100 focus:opacity-100 inline-flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground transition-opacity\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n </td>\n {visibleDays.map((date) => {\n const dateKey = formatDateKey(date)\n const weekend = isWeekendDay(date)\n const cellMinutes = getCellValue(project.id, dateKey)\n const isDirty = dirty[project.id]?.[dateKey] !== undefined\n const cellError = cellErrors[project.id]?.[dateKey]\n const cellErrorMessage = cellError ? describeDurationError(cellError) : undefined\n const cellErrorId = cellErrorMessage ? buildDurationErrorId(project.id, dateKey) : undefined\n return (\n <td key={dateKey} className={`px-0.5 py-0.5 ${weekend ? 'bg-muted/40' : ''}`}>\n {weekend ? (\n <div className=\"rounded px-1 py-1 text-center text-xs text-muted-foreground/50\">-</div>\n ) : (\n <InlineInput\n type=\"text\"\n inputMode=\"text\"\n showBorderOnHover={false}\n className={`mx-auto flex h-6 border transition-colors\n ${viewMode === 'weekly' ? 'w-12 px-1' : 'w-8 px-0'}\n ${cellError\n ? 'bg-status-error-bg'\n : isDirty\n ? 'border-status-warning-border bg-status-warning-bg'\n : 'border-muted-foreground/20 bg-transparent hover:border-muted-foreground/40'}`}\n inputClassName={`text-center text-xs tabular-nums\n ${cellError ? 'text-status-error-text' : cellMinutes > 0 ? 'font-semibold' : 'text-muted-foreground'}`}\n value={rawText[project.id]?.[dateKey] ?? formatMinutesAsDecimal(cellMinutes)}\n onChange={(e) => handleCellChange(project.id, dateKey, e.target.value)}\n onBlur={(event) => handleCellBlur(project.id, dateKey, event.currentTarget.value)}\n placeholder={t('staff.timesheets.my.durationPlaceholder', '0')}\n aria-invalid={cellError !== undefined}\n aria-label={describeDurationCell(project.name, date)}\n aria-describedby={[durationHintId, cellErrorId].filter(Boolean).join(' ')}\n title={cellErrorMessage ?? durationFormatHint}\n />\n )}\n {cellErrorMessage ? (\n <span id={cellErrorId} role=\"alert\" className=\"sr-only\">{cellErrorMessage}</span>\n ) : null}\n </td>\n )\n })}\n <td className=\"px-3 py-1.5 text-right font-semibold text-xs tabular-nums\">\n {formatMinutesAsDecimal(getRowTotal(project.id)) || '0'}\n </td>\n </tr>\n ))}\n <tr className=\"border-b\">\n <td colSpan={visibleDays.length + 2} className=\"sticky left-0 bg-background px-1 py-0.5\">\n <AddRowDropdown\n assignedProjects={allAssignedProjects}\n visibleProjectIds={visibleProjectIds}\n canCreateProject={canManageProjects}\n onAddProject={handleAddProject}\n onCreateProject={() => setCreateDialogOpen(true)}\n />\n </td>\n </tr>\n </tbody>\n <tfoot>\n <tr className=\"border-t bg-muted/50 font-semibold\">\n <td className=\"sticky left-0 z-10 bg-muted px-3 py-2\">\n {t('staff.timesheets.my.daily_total', 'Daily Total')}\n </td>\n {visibleDays.map((date) => {\n const weekend = isWeekendDay(date)\n const dayMinutes = getDayTotal(date)\n return (\n <td key={formatDateKey(date)} className={`py-2 text-center text-xs tabular-nums ${weekend ? 'text-muted-foreground/50' : ''}`}>\n {weekend ? '-' : (formatMinutesAsDecimal(dayMinutes) || '-')}\n </td>\n )\n })}\n <td className=\"px-3 py-2 text-right tabular-nums font-semibold\">{formatMinutesAsDecimal(grandTotal) || '0'}</td>\n </tr>\n </tfoot>\n </table>\n <p id={durationHintId} className=\"px-3 py-2 text-xs text-muted-foreground\">{durationFormatHint}</p>\n </div>\n )}\n </div>\n </PageBody>\n {ConfirmDialogElement}\n <CreateProjectDialog\n open={createDialogOpen}\n onOpenChange={setCreateDialogOpen}\n onProjectCreated={handleProjectCreated}\n />\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.8-develop.7082.1.f2a845b020",
3
+ "version": "0.6.8-develop.7090.1.4dbca7b350",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -252,16 +252,16 @@
252
252
  "zod": "^4.4.3"
253
253
  },
254
254
  "peerDependencies": {
255
- "@open-mercato/ai-assistant": "0.6.8-develop.7082.1.f2a845b020",
256
- "@open-mercato/shared": "0.6.8-develop.7082.1.f2a845b020",
257
- "@open-mercato/ui": "0.6.8-develop.7082.1.f2a845b020",
255
+ "@open-mercato/ai-assistant": "0.6.8-develop.7090.1.4dbca7b350",
256
+ "@open-mercato/shared": "0.6.8-develop.7090.1.4dbca7b350",
257
+ "@open-mercato/ui": "0.6.8-develop.7090.1.4dbca7b350",
258
258
  "react": "^19.0.0",
259
259
  "react-dom": "^19.0.0"
260
260
  },
261
261
  "devDependencies": {
262
- "@open-mercato/ai-assistant": "0.6.8-develop.7082.1.f2a845b020",
263
- "@open-mercato/shared": "0.6.8-develop.7082.1.f2a845b020",
264
- "@open-mercato/ui": "0.6.8-develop.7082.1.f2a845b020",
262
+ "@open-mercato/ai-assistant": "0.6.8-develop.7090.1.4dbca7b350",
263
+ "@open-mercato/shared": "0.6.8-develop.7090.1.4dbca7b350",
264
+ "@open-mercato/ui": "0.6.8-develop.7090.1.4dbca7b350",
265
265
  "@testing-library/dom": "^10.4.1",
266
266
  "@testing-library/jest-dom": "^7.0.0",
267
267
  "@testing-library/react": "^16.3.1",
@@ -55,6 +55,9 @@
55
55
  "auth.acl.features.catalog.variants.manage": "Manage catalog variants",
56
56
  "auth.acl.features.channel_apns.configure": "Configure APNs credentials",
57
57
  "auth.acl.features.channel_apns.view": "View APNs channel configuration",
58
+ "auth.acl.features.channel_discord.ai_auto_reply.run": "Discord-KI-Autoantwort-Agenten ausführen",
59
+ "auth.acl.features.channel_discord.configure": "Configure Discord bot channel",
60
+ "auth.acl.features.channel_discord.view": "View Discord channel configuration",
58
61
  "auth.acl.features.channel_expo.configure": "Configure Expo push credentials",
59
62
  "auth.acl.features.channel_expo.view": "View Expo push channel configuration",
60
63
  "auth.acl.features.channel_fcm.configure": "Configure FCM credentials",
@@ -55,6 +55,9 @@
55
55
  "auth.acl.features.catalog.variants.manage": "Manage catalog variants",
56
56
  "auth.acl.features.channel_apns.configure": "Configure APNs credentials",
57
57
  "auth.acl.features.channel_apns.view": "View APNs channel configuration",
58
+ "auth.acl.features.channel_discord.ai_auto_reply.run": "Run the Discord AI auto-reply agent",
59
+ "auth.acl.features.channel_discord.configure": "Configure Discord bot channel",
60
+ "auth.acl.features.channel_discord.view": "View Discord channel configuration",
58
61
  "auth.acl.features.channel_expo.configure": "Configure Expo push credentials",
59
62
  "auth.acl.features.channel_expo.view": "View Expo push channel configuration",
60
63
  "auth.acl.features.channel_fcm.configure": "Configure FCM credentials",
@@ -55,6 +55,9 @@
55
55
  "auth.acl.features.catalog.variants.manage": "Manage catalog variants",
56
56
  "auth.acl.features.channel_apns.configure": "Configure APNs credentials",
57
57
  "auth.acl.features.channel_apns.view": "View APNs channel configuration",
58
+ "auth.acl.features.channel_discord.ai_auto_reply.run": "Ejecutar el agente de respuesta automática de IA en Discord",
59
+ "auth.acl.features.channel_discord.configure": "Configure Discord bot channel",
60
+ "auth.acl.features.channel_discord.view": "View Discord channel configuration",
58
61
  "auth.acl.features.channel_expo.configure": "Configure Expo push credentials",
59
62
  "auth.acl.features.channel_expo.view": "View Expo push channel configuration",
60
63
  "auth.acl.features.channel_fcm.configure": "Configure FCM credentials",
@@ -55,6 +55,9 @@
55
55
  "auth.acl.features.catalog.variants.manage": "Manage catalog variants",
56
56
  "auth.acl.features.channel_apns.configure": "Configure APNs credentials",
57
57
  "auth.acl.features.channel_apns.view": "View APNs channel configuration",
58
+ "auth.acl.features.channel_discord.ai_auto_reply.run": "Discord AI 자동 응답 에이전트 실행",
59
+ "auth.acl.features.channel_discord.configure": "Configure Discord bot channel",
60
+ "auth.acl.features.channel_discord.view": "View Discord channel configuration",
58
61
  "auth.acl.features.channel_expo.configure": "Configure Expo push credentials",
59
62
  "auth.acl.features.channel_expo.view": "View Expo push channel configuration",
60
63
  "auth.acl.features.channel_fcm.configure": "Configure FCM credentials",
@@ -55,6 +55,9 @@
55
55
  "auth.acl.features.catalog.variants.manage": "Zarządzaj wariantami w katalogu",
56
56
  "auth.acl.features.channel_apns.configure": "Konfiguruj dane dostępowe APNs",
57
57
  "auth.acl.features.channel_apns.view": "Wyświetl konfigurację kanału APNs",
58
+ "auth.acl.features.channel_discord.ai_auto_reply.run": "Uruchamiaj agenta automatycznych odpowiedzi AI na Discordzie",
59
+ "auth.acl.features.channel_discord.configure": "Konfiguruj kanał bota Discord",
60
+ "auth.acl.features.channel_discord.view": "Wyświetl konfigurację kanału Discord",
58
61
  "auth.acl.features.channel_expo.configure": "Konfiguruj dane dostępowe powiadomień push Expo",
59
62
  "auth.acl.features.channel_expo.view": "Wyświetl konfigurację kanału push Expo",
60
63
  "auth.acl.features.channel_fcm.configure": "Konfiguruj dane dostępowe FCM",
@@ -63,13 +63,12 @@ export type DealsKpiStripProps = {
63
63
  onNeedsAttentionClick?: () => void
64
64
  }
65
65
 
66
- const compactNumberFormatter = new Intl.NumberFormat(undefined, {
67
- notation: 'compact',
68
- maximumFractionDigits: 1,
69
- })
70
-
71
- function formatCompact(value: number): string {
72
- return compactNumberFormatter.format(value)
66
+ function createCompactFormatter(locale: string): (value: number) => string {
67
+ const formatter = new Intl.NumberFormat(locale, {
68
+ notation: 'compact',
69
+ maximumFractionDigits: 1,
70
+ })
71
+ return (value: number) => formatter.format(value)
73
72
  }
74
73
 
75
74
  function buildCurrencySuffix(code: string | null, convertedAll: boolean): string {
@@ -136,6 +135,7 @@ export function DealsKpiStrip({
136
135
  }: DealsKpiStripProps) {
137
136
  const t = useT()
138
137
  const locale = useLocale()
138
+ const formatCompact = React.useMemo(() => createCompactFormatter(locale), [locale])
139
139
  const pluralCat = React.useCallback((count: number): string => {
140
140
  try {
141
141
  return new Intl.PluralRules(locale).select(count)