@open-mercato/core 0.10.1-develop.7763.1.caea616ebf → 0.10.1-develop.7765.1.391b8a0e16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/api/companies/route.js +2 -1
  3. package/dist/modules/customers/api/companies/route.js.map +2 -2
  4. package/dist/modules/customers/api/people/route.js +2 -1
  5. package/dist/modules/customers/api/people/route.js.map +2 -2
  6. package/dist/modules/customers/backend/customers/companies/page.js +21 -3
  7. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  8. package/dist/modules/customers/backend/customers/listSorting.js +2 -1
  9. package/dist/modules/customers/backend/customers/listSorting.js.map +2 -2
  10. package/dist/modules/customers/backend/customers/people/page.js +17 -1
  11. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  12. package/dist/modules/customers/lib/createdAtDayFilter.js +64 -0
  13. package/dist/modules/customers/lib/createdAtDayFilter.js.map +7 -0
  14. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +9 -5
  15. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  16. package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js +7 -3
  17. package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js.map +2 -2
  18. package/package.json +7 -7
  19. package/src/modules/customers/api/companies/route.ts +2 -1
  20. package/src/modules/customers/api/people/route.ts +2 -1
  21. package/src/modules/customers/backend/customers/companies/page.tsx +26 -3
  22. package/src/modules/customers/backend/customers/listSorting.ts +1 -0
  23. package/src/modules/customers/backend/customers/people/page.tsx +22 -1
  24. package/src/modules/customers/i18n/de.json +2 -0
  25. package/src/modules/customers/i18n/en.json +2 -0
  26. package/src/modules/customers/i18n/es.json +2 -0
  27. package/src/modules/customers/i18n/ko.json +2 -0
  28. package/src/modules/customers/i18n/pl.json +2 -0
  29. package/src/modules/customers/lib/createdAtDayFilter.ts +68 -0
  30. package/src/modules/staff/AGENTS.md +4 -0
  31. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +7 -5
  32. package/src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx +5 -3
@@ -0,0 +1,68 @@
1
+ import { fromZonedTime } from 'date-fns-tz'
2
+ import type { AdvancedFilterTree, FilterGroup, FilterRule } from '@open-mercato/shared/lib/query/advanced-filter-tree'
3
+
4
+ const CREATED_AT_FIELD = 'created_at'
5
+ const DATE_ONLY_PATTERN = /^\d{4}-\d{2}-\d{2}$/
6
+
7
+ function isDateOnly(value: unknown): value is string {
8
+ return typeof value === 'string' && DATE_ONLY_PATTERN.test(value.trim())
9
+ }
10
+
11
+ function startOfDay(value: string, timeZone: string): string {
12
+ return fromZonedTime(`${value.trim()}T00:00:00.000`, timeZone).toISOString()
13
+ }
14
+
15
+ function endOfDay(value: string, timeZone: string): string {
16
+ return fromZonedTime(`${value.trim()}T23:59:59.999`, timeZone).toISOString()
17
+ }
18
+
19
+ function expandRule(rule: FilterRule, timeZone: string): FilterRule {
20
+ if (rule.field !== CREATED_AT_FIELD) return rule
21
+ switch (rule.operator) {
22
+ case 'is':
23
+ case 'equals':
24
+ if (!isDateOnly(rule.value)) return rule
25
+ return { ...rule, operator: 'between', value: [startOfDay(rule.value, timeZone), endOfDay(rule.value, timeZone)] }
26
+ case 'is_after':
27
+ case 'greater_than':
28
+ if (!isDateOnly(rule.value)) return rule
29
+ return { ...rule, operator: 'greater_than', value: endOfDay(rule.value, timeZone) }
30
+ case 'less_or_equal':
31
+ if (!isDateOnly(rule.value)) return rule
32
+ return { ...rule, value: endOfDay(rule.value, timeZone) }
33
+ case 'is_before':
34
+ case 'less_than':
35
+ if (!isDateOnly(rule.value)) return rule
36
+ return { ...rule, operator: 'less_than', value: startOfDay(rule.value, timeZone) }
37
+ case 'greater_or_equal':
38
+ if (!isDateOnly(rule.value)) return rule
39
+ return { ...rule, value: startOfDay(rule.value, timeZone) }
40
+ case 'between': {
41
+ if (!Array.isArray(rule.value) || rule.value.length !== 2) return rule
42
+ const [start, end] = rule.value
43
+ return {
44
+ ...rule,
45
+ value: [
46
+ isDateOnly(start) ? startOfDay(start, timeZone) : start,
47
+ isDateOnly(end) ? endOfDay(end, timeZone) : end,
48
+ ],
49
+ }
50
+ }
51
+ default:
52
+ return rule
53
+ }
54
+ }
55
+
56
+ function expandGroup(group: FilterGroup, timeZone: string): FilterGroup {
57
+ return {
58
+ ...group,
59
+ children: group.children.map((child) =>
60
+ child.type === 'group' ? expandGroup(child, timeZone) : expandRule(child, timeZone),
61
+ ),
62
+ }
63
+ }
64
+
65
+ export function expandCreatedAtDayRules<T extends AdvancedFilterTree | null>(tree: T, timeZone: string): T {
66
+ if (!tree) return tree
67
+ return { ...tree, root: expandGroup(tree.root, timeZone) } as T
68
+ }
@@ -4,6 +4,10 @@ The `staff` module is **optional** and slated for extraction into a standalone `
4
4
 
5
5
  See [`.ai/specs/implemented/2026-05-08-staff-decouple-from-core.md`](../../../../../.ai/specs/implemented/2026-05-08-staff-decouple-from-core.md) for the decoupling plan, and [`BACKWARD_COMPATIBILITY.md`](../../../../../BACKWARD_COMPATIBILITY.md) for the contract-surface taxonomy referenced below.
6
6
 
7
+ ## Always
8
+
9
+ - Preserve unsaved project-team drafts across translation-context updates. Tie data reloads to the project, organization scope, opening the drawer, or an explicit retry; read the current translator for asynchronous error copy without making translator identity a reload trigger.
10
+
7
11
  ## MUST Rules
8
12
 
9
13
  1. **MUST NOT import staff entities (`StaffTeam`, `StaffTeamMember`, etc.) from non-staff core modules.** Use the public surfaces below.
@@ -174,6 +174,8 @@ function RailFact({ label, value }: { label: string; value: React.ReactNode }) {
174
174
  export default function TimesheetProjectDetailPage({ params }: { params?: { id?: string } }) {
175
175
  const projectId = params?.id
176
176
  const t = useT()
177
+ const translateRef = React.useRef(t)
178
+ React.useEffect(() => { translateRef.current = t }, [t])
177
179
  const scopeVersion = useOrganizationScopeVersion()
178
180
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
179
181
  const router = useRouter()
@@ -413,7 +415,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
413
415
  }
414
416
  if (!cancelled) setAccessDeniedProjectId(null)
415
417
  if (!call.ok) {
416
- throw new Error(t('staff.timesheets.projects.errors.load', 'Failed to load project.'))
418
+ throw new Error(translateRef.current('staff.timesheets.projects.errors.load', 'Failed to load project.'))
417
419
  }
418
420
  const payload = call.result
419
421
  const record = Array.isArray(payload?.items) ? payload.items[0] : null
@@ -424,7 +426,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
424
426
  if (!cancelled) setProject(record)
425
427
  } catch (loadError) {
426
428
  if (!cancelled) {
427
- setError(loadError instanceof Error ? loadError.message : t('staff.timesheets.projects.errors.load', 'Failed to load project.'))
429
+ setError(loadError instanceof Error ? loadError.message : translateRef.current('staff.timesheets.projects.errors.load', 'Failed to load project.'))
428
430
  }
429
431
  } finally {
430
432
  if (!cancelled) {
@@ -435,7 +437,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
435
437
  }
436
438
  loadProject()
437
439
  return () => { cancelled = true }
438
- }, [projectId, t, scopeVersion])
440
+ }, [projectId, scopeVersion])
439
441
 
440
442
  // --- Load employees with name resolution ---
441
443
  const loadEmployees = React.useCallback(async () => {
@@ -445,7 +447,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
445
447
  const payload = await readApiResultOrThrow<EmployeesResponse>(
446
448
  `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=100`,
447
449
  undefined,
448
- { errorMessage: t('staff.timesheets.projects.employees.empty', 'No employees assigned yet.'), fallback: { items: [], total: 0 } },
450
+ { errorMessage: translateRef.current('staff.timesheets.projects.employees.empty', 'No employees assigned yet.'), fallback: { items: [], total: 0 } },
449
451
  )
450
452
  const items = Array.isArray(payload.items) ? payload.items : []
451
453
 
@@ -494,7 +496,7 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
494
496
  } finally {
495
497
  setEmployeesLoading(false)
496
498
  }
497
- }, [projectId, accessDenied, t])
499
+ }, [projectId, accessDenied])
498
500
 
499
501
  React.useEffect(() => {
500
502
  void loadEmployees()
@@ -232,6 +232,8 @@ export function ProjectTeamDrawer({
232
232
  onSaved,
233
233
  }: ProjectTeamDrawerProps) {
234
234
  const t = useT()
235
+ const translateRef = React.useRef(t)
236
+ React.useEffect(() => { translateRef.current = t }, [t])
235
237
  const currentUserId = useCurrentUserId()
236
238
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
237
239
  const { runMutation, retryLastMutation } = useGuardedMutation({
@@ -294,7 +296,7 @@ export function ProjectTeamDrawer({
294
296
  ])
295
297
 
296
298
  if (!membersRes.ok) {
297
- setLoadError(t('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))
299
+ setLoadError(translateRef.current('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))
298
300
  return
299
301
  }
300
302
 
@@ -326,11 +328,11 @@ export function ProjectTeamDrawer({
326
328
  }
327
329
  } catch (error) {
328
330
  logger.error('staff.time_tracking.projects.team.load failed', { err: error })
329
- setLoadError(t('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))
331
+ setLoadError(translateRef.current('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))
330
332
  } finally {
331
333
  setLoading(false)
332
334
  }
333
- }, [projectId, mergeProfiles, t])
335
+ }, [projectId, mergeProfiles])
334
336
 
335
337
  React.useEffect(() => {
336
338
  if (!open) return