@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/customers/api/companies/route.js +2 -1
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +2 -1
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +21 -3
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/listSorting.js +2 -1
- package/dist/modules/customers/backend/customers/listSorting.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +17 -1
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/lib/createdAtDayFilter.js +64 -0
- package/dist/modules/customers/lib/createdAtDayFilter.js.map +7 -0
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +9 -5
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js +7 -3
- package/dist/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/api/companies/route.ts +2 -1
- package/src/modules/customers/api/people/route.ts +2 -1
- package/src/modules/customers/backend/customers/companies/page.tsx +26 -3
- package/src/modules/customers/backend/customers/listSorting.ts +1 -0
- package/src/modules/customers/backend/customers/people/page.tsx +22 -1
- package/src/modules/customers/i18n/de.json +2 -0
- package/src/modules/customers/i18n/en.json +2 -0
- package/src/modules/customers/i18n/es.json +2 -0
- package/src/modules/customers/i18n/ko.json +2 -0
- package/src/modules/customers/i18n/pl.json +2 -0
- package/src/modules/customers/lib/createdAtDayFilter.ts +68 -0
- package/src/modules/staff/AGENTS.md +4 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +7 -5
- 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(
|
|
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 :
|
|
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,
|
|
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:
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
335
|
+
}, [projectId, mergeProfiles])
|
|
334
336
|
|
|
335
337
|
React.useEffect(() => {
|
|
336
338
|
if (!open) return
|