@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/staff/lib/time-tracking-ui/ProjectTeamDrawer.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Lock, Save } from 'lucide-react'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { SearchInput } from '@open-mercato/ui/primitives/search-input'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useCurrentUserId } from '@open-mercato/ui/backend/utils/useCurrentUserId'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n countPendingChanges,\n diffTeamSelection,\n formatProjectMinutes,\n resolveAssignmentState,\n resolveGraceDays,\n selectChangeCountKey,\n toIsoDate,\n type AssignedMember,\n type AssignmentState,\n} from './projectTeamAssignment'\n\nconst logger = createLogger('staff').child({ component: 'ProjectTeamDrawer' })\n\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.project_team'\nconst DIRECTORY_PAGE_SIZE = 100\nconst ENTRIES_PAGE_SIZE = 100\nconst MAX_ENTRY_PAGES = 20\nconst SEARCH_DEBOUNCE_MS = 250\n\ntype ApiRow = Record<string, unknown>\n\ntype PersonProfile = {\n id: string\n name: string\n userId: string | null\n}\n\ntype ProjectHours = {\n minutesByStaffMemberId: Map<string, number>\n partial: boolean\n}\n\nexport type ProjectTeamDrawerProps = {\n projectId: string\n projectName: string\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Pre-checks the person who requested access (notification deep link, D-6). */\n preselectUserId?: string | null\n onSaved?: () => void\n}\n\nfunction readString(row: ApiRow, ...keys: string[]): string | null {\n for (const key of keys) {\n const value = row[key]\n if (typeof value === 'string' && value.trim().length > 0) return value\n if (value instanceof Date) return value.toISOString()\n }\n return null\n}\n\nfunction readNumber(row: ApiRow, ...keys: string[]): number {\n for (const key of keys) {\n const value = row[key]\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length > 0) {\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n }\n }\n return 0\n}\n\nfunction readItems(payload: { items?: unknown } | null | undefined): ApiRow[] {\n const items = payload?.items\n return Array.isArray(items) ? (items as ApiRow[]) : []\n}\n\nfunction toProfile(row: ApiRow): PersonProfile | null {\n const id = readString(row, 'id')\n if (!id) return null\n return {\n id,\n name: readString(row, 'display_name', 'displayName') ?? id,\n userId: readString(row, 'user_id', 'userId'),\n }\n}\n\nfunction toAssignedMember(row: ApiRow): AssignedMember | null {\n const membershipId = readString(row, 'id')\n const staffMemberId = readString(row, 'staff_member_id', 'staffMemberId')\n if (!membershipId || !staffMemberId) return null\n return {\n membershipId,\n staffMemberId,\n role: readString(row, 'role'),\n status: readString(row, 'status'),\n startDate: toIsoDate(readString(row, 'assigned_start_date', 'assignedStartDate')),\n endDate: toIsoDate(readString(row, 'assigned_end_date', 'assignedEndDate')),\n }\n}\n\nfunction todayIsoDate(): string {\n const now = new Date()\n const month = String(now.getMonth() + 1).padStart(2, '0')\n const day = String(now.getDate()).padStart(2, '0')\n return `${now.getFullYear()}-${month}-${day}`\n}\n\n/**\n * Hours logged per person in this project. There is no aggregate endpoint yet,\n * so the drawer pages the (tenant-scoped) entries list and sums client-side;\n * beyond `MAX_ENTRY_PAGES` the totals are reported as a lower bound rather than\n * silently under-reporting.\n */\nasync function loadProjectHours(projectId: string): Promise<ProjectHours> {\n const minutesByStaffMemberId = new Map<string, number>()\n let partial = false\n for (let page = 1; page <= MAX_ENTRY_PAGES; page += 1) {\n const params = new URLSearchParams({\n projectId,\n page: String(page),\n pageSize: String(ENTRIES_PAGE_SIZE),\n })\n const res = await apiCall<{ items?: unknown; total?: number }>(\n `/api/staff/timesheets/time-entries?${params.toString()}`,\n )\n if (!res.ok) return { minutesByStaffMemberId, partial: true }\n const rows = readItems(res.result)\n for (const row of rows) {\n const staffMemberId = readString(row, 'staff_member_id', 'staffMemberId')\n if (!staffMemberId) continue\n const minutes = readNumber(row, 'duration_minutes', 'durationMinutes')\n minutesByStaffMemberId.set(staffMemberId, (minutesByStaffMemberId.get(staffMemberId) ?? 0) + minutes)\n }\n if (rows.length < ENTRIES_PAGE_SIZE) return { minutesByStaffMemberId, partial: false }\n const total = typeof res.result?.total === 'number' ? res.result.total : null\n if (total !== null && page * ENTRIES_PAGE_SIZE >= total) return { minutesByStaffMemberId, partial: false }\n partial = true\n }\n return { minutesByStaffMemberId, partial }\n}\n\ntype TeamPickRowProps = {\n profile: PersonProfile\n hint: string\n checked: boolean\n locked: boolean\n lockedHint: string\n lockLabel: string\n badge: React.ReactNode\n windowLine: React.ReactNode\n endDateControl: React.ReactNode\n selectLabel: string\n onToggle: (next: boolean) => void\n}\n\nfunction TeamPickRow({\n profile,\n hint,\n checked,\n locked,\n lockedHint,\n lockLabel,\n badge,\n windowLine,\n endDateControl,\n selectLabel,\n onToggle,\n}: TeamPickRowProps) {\n return (\n <li className=\"rounded-md border border-border\" data-staff-member-id={profile.id}>\n <label className=\"flex cursor-pointer items-start gap-3 p-3\">\n <span className=\"mt-0.5 shrink-0\">\n <Checkbox\n checked={checked}\n disabled={locked}\n aria-label={locked ? `${selectLabel} \u2014 ${lockedHint}` : selectLabel}\n onCheckedChange={(next) => onToggle(next === true)}\n />\n </span>\n <Avatar label={profile.name} size=\"sm\" />\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">{profile.name}</span>\n <span className=\"text-xs text-muted-foreground\">{hint}</span>\n {windowLine}\n </span>\n <span className=\"flex shrink-0 flex-col items-end gap-1\">\n {locked ? (\n <SimpleTooltip content={lockedHint}>\n <Badge variant=\"neutral\" className=\"gap-1\">\n <Lock className=\"size-3\" aria-hidden />\n {lockLabel}\n </Badge>\n </SimpleTooltip>\n ) : null}\n {badge}\n </span>\n </label>\n {endDateControl}\n </li>\n )\n}\n\nexport function ProjectTeamDrawer({\n projectId,\n projectName,\n open,\n onOpenChange,\n preselectUserId,\n onSaved,\n}: ProjectTeamDrawerProps) {\n const t = useT()\n const currentUserId = useCurrentUserId()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n\n const [loading, setLoading] = React.useState(true)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [assigned, setAssigned] = React.useState<AssignedMember[]>([])\n const [profiles, setProfiles] = React.useState<Map<string, PersonProfile>>(new Map())\n const [directoryIds, setDirectoryIds] = React.useState<string[]>([])\n const [directoryTotal, setDirectoryTotal] = React.useState(0)\n const [selfStaffMemberId, setSelfStaffMemberId] = React.useState<string | null>(null)\n const [graceDays, setGraceDays] = React.useState<number>(resolveGraceDays(null))\n const [hours, setHours] = React.useState<ProjectHours | null>(null)\n const [hoursLoading, setHoursLoading] = React.useState(true)\n const [selected, setSelected] = React.useState<Set<string>>(new Set())\n const [endDrafts, setEndDrafts] = React.useState<Map<string, string | null>>(new Map())\n const [query, setQuery] = React.useState('')\n const [debouncedQuery, setDebouncedQuery] = React.useState('')\n\n const mergeProfiles = React.useCallback((rows: ApiRow[]) => {\n setProfiles((previous) => {\n const next = new Map(previous)\n for (const row of rows) {\n const profile = toProfile(row)\n if (profile) next.set(profile.id, profile)\n }\n return next\n })\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n const handle = setTimeout(() => setDebouncedQuery(query.trim()), SEARCH_DEBOUNCE_MS)\n return () => clearTimeout(handle)\n }, [open, query])\n\n React.useEffect(() => {\n if (!open) {\n setQuery('')\n setDebouncedQuery('')\n }\n }, [open])\n\n const loadTeam = React.useCallback(async () => {\n setLoading(true)\n setLoadError(null)\n try {\n const [membersRes, selfRes, settingsRes] = await Promise.all([\n apiCall<{ items?: unknown }>(\n `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&status=active`,\n ),\n apiCall<{ member?: { id?: string; displayName?: string; userId?: string | null } | null }>(\n '/api/staff/team-members/self',\n ),\n apiCall<{ access?: { assignmentGraceDays?: unknown } | null }>('/api/staff/timesheets/settings'),\n ])\n\n if (!membersRes.ok) {\n setLoadError(t('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))\n return\n }\n\n const members = readItems(membersRes.result)\n .map(toAssignedMember)\n .filter((member): member is AssignedMember => member !== null)\n setAssigned(members)\n setSelected(new Set(members.map((member) => member.staffMemberId)))\n setEndDrafts(new Map(members.map((member) => [member.staffMemberId, member.endDate])))\n\n const selfMember = selfRes.ok ? selfRes.result?.member ?? null : null\n if (selfMember?.id) {\n setSelfStaffMemberId(selfMember.id)\n mergeProfiles([\n { id: selfMember.id, display_name: selfMember.displayName ?? selfMember.id, user_id: selfMember.userId ?? null },\n ])\n }\n\n if (settingsRes.ok) {\n setGraceDays(resolveGraceDays(settingsRes.result?.access?.assignmentGraceDays))\n }\n\n const assignedIds = members.map((member) => member.staffMemberId)\n if (assignedIds.length > 0) {\n const profileRes = await apiCall<{ items?: unknown }>(\n `/api/staff/team-members?ids=${assignedIds.join(',')}&pageSize=${DIRECTORY_PAGE_SIZE}`,\n )\n if (profileRes.ok) mergeProfiles(readItems(profileRes.result))\n }\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.load failed', { err: error })\n setLoadError(t('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))\n } finally {\n setLoading(false)\n }\n }, [projectId, mergeProfiles, t])\n\n React.useEffect(() => {\n if (!open) return\n void loadTeam()\n }, [open, loadTeam])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n setHoursLoading(true)\n void (async () => {\n try {\n const result = await loadProjectHours(projectId)\n if (!cancelled) setHours(result)\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.hours failed', { err: error })\n if (!cancelled) setHours(null)\n } finally {\n if (!cancelled) setHoursLoading(false)\n }\n })()\n return () => { cancelled = true }\n }, [open, projectId])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n void (async () => {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(DIRECTORY_PAGE_SIZE),\n isActive: 'true',\n })\n if (debouncedQuery.length > 0) params.set('search', debouncedQuery)\n const res = await apiCall<{ items?: unknown; total?: number }>(`/api/staff/team-members?${params.toString()}`)\n if (cancelled || !res.ok) return\n const rows = readItems(res.result)\n mergeProfiles(rows)\n setDirectoryIds(rows.map((row) => readString(row, 'id')).filter((id): id is string => id !== null))\n setDirectoryTotal(typeof res.result?.total === 'number' ? res.result.total : rows.length)\n })()\n return () => { cancelled = true }\n }, [open, debouncedQuery, mergeProfiles])\n\n const lockedStaffMemberId = React.useMemo(() => {\n if (selfStaffMemberId) return selfStaffMemberId\n if (!currentUserId) return null\n for (const profile of profiles.values()) {\n if (profile.userId && profile.userId === currentUserId) return profile.id\n }\n return null\n }, [selfStaffMemberId, currentUserId, profiles])\n\n const lockedStaffMemberIds = React.useMemo(\n () => new Set(lockedStaffMemberId ? [lockedStaffMemberId] : []),\n [lockedStaffMemberId],\n )\n\n React.useEffect(() => {\n if (!open || !preselectUserId || loading) return\n for (const profile of profiles.values()) {\n if (profile.userId !== preselectUserId) continue\n setSelected((previous) => (previous.has(profile.id) ? previous : new Set(previous).add(profile.id)))\n return\n }\n }, [open, preselectUserId, loading, profiles])\n\n const assignedByStaffMemberId = React.useMemo(() => {\n const map = new Map<string, AssignedMember>()\n for (const member of assigned) {\n if (!map.has(member.staffMemberId)) map.set(member.staffMemberId, member)\n }\n return map\n }, [assigned])\n\n const changes = React.useMemo(\n () => diffTeamSelection(assigned, { selectedStaffMemberIds: selected, endDateByStaffMemberId: endDrafts }, lockedStaffMemberIds),\n [assigned, selected, endDrafts, lockedStaffMemberIds],\n )\n const pendingChanges = countPendingChanges(changes)\n\n const memberMinutes = React.useCallback(\n (staffMemberId: string): number | null => {\n if (!hours) return null\n return hours.minutesByStaffMemberId.get(staffMemberId) ?? 0\n },\n [hours],\n )\n\n const hoursLabel = React.useCallback(\n (staffMemberId: string): string => {\n if (hoursLoading) return '\u2026'\n const minutes = memberMinutes(staffMemberId)\n if (minutes === null) return formatProjectMinutes(0)\n const formatted = formatProjectMinutes(minutes)\n if (!hours?.partial) return formatted\n return t('staff.time_tracking.projects.team.drawer.hoursAtLeast', 'at least {hours}', { hours: formatted })\n },\n [hours, hoursLoading, memberMinutes, t],\n )\n\n const roleLabel = React.useCallback(\n (staffMemberId: string): string => {\n const member = assignedByStaffMemberId.get(staffMemberId)\n if (member?.role && member.role.trim().length > 0) return member.role\n if (staffMemberId === lockedStaffMemberId) {\n return t('staff.time_tracking.projects.team.drawer.roleLeader', 'Team Leader')\n }\n return t('staff.time_tracking.projects.team.drawer.roleMember', 'Team Member')\n },\n [assignedByStaffMemberId, lockedStaffMemberId, t],\n )\n\n const toggleMember = React.useCallback(\n async (staffMemberId: string, next: boolean) => {\n if (lockedStaffMemberIds.has(staffMemberId)) return\n if (!next && assignedByStaffMemberId.has(staffMemberId)) {\n const profile = profiles.get(staffMemberId)\n const name = profile?.name ?? staffMemberId\n const minutes = memberMinutes(staffMemberId)\n const needsConfirm = minutes === null || minutes > 0\n if (needsConfirm) {\n const text = minutes === null || hoursLoading\n ? t(\n 'staff.time_tracking.projects.team.drawer.removeConfirm.textUnknown',\n '{name} may have time logged in this project \u2014 revoke access?',\n { name },\n )\n : t(\n 'staff.time_tracking.projects.team.drawer.removeConfirm.text',\n '{name} has {hours} in this project \u2014 revoke access?',\n { name, hours: formatProjectMinutes(minutes) },\n )\n const confirmed = await confirm({\n title: t('staff.time_tracking.projects.team.drawer.removeConfirm.title', 'Revoke access?'),\n text,\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n }\n setSelected((previous) => {\n const draft = new Set(previous)\n if (next) draft.add(staffMemberId)\n else draft.delete(staffMemberId)\n return draft\n })\n },\n [assignedByStaffMemberId, confirm, hoursLoading, lockedStaffMemberIds, memberMinutes, profiles, t],\n )\n\n const setEndDate = React.useCallback((staffMemberId: string, value: string | null) => {\n setEndDrafts((previous) => {\n const draft = new Map(previous)\n draft.set(staffMemberId, value && value.length > 0 ? value : null)\n return draft\n })\n }, [])\n\n // Membership rows are an assignment junction, so they are exempt from OSS\n // optimistic locking (data-integrity contract); the drawer never PATCHes the\n // project aggregate itself, so no expected-updated-at header applies here.\n const persistChanges = React.useCallback(async () => {\n const employeesPath = `staff/timesheets/time-projects/${projectId}/employees`\n const startDate = todayIsoDate()\n\n for (const staffMemberId of changes.additions) {\n await createCrud(employeesPath, {\n timeProjectId: projectId,\n staffMemberId,\n assignedStartDate: startDate,\n assignedEndDate: endDrafts.get(staffMemberId) ?? null,\n }, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n\n // Re-dating is an update of the membership row itself (T2.12): the audit\n // trail must read \"assignment end date changed\", not \"member removed,\n // member added\". Only the changed field is sent, so the recorded diff names\n // `assignedEndDate` alone.\n //\n // No optimistic-lock header, deliberately. The endpoint accepts and enforces\n // one (the CRUD factory registers a reader for this entity), but this drawer\n // saves a whole batch \u2014 additions, re-datings and removals \u2014 with no\n // rollback. A per-row version round-trip belongs with a per-row conflict UX;\n // the batch save is not it.\n //\n // What this loop guarantees and what it does not (C6):\n // * GUARANTEED \u2014 the loop stops at the first failure, so no write is\n // attempted after one fails, and `handleSave` reloads the drawer from the\n // server before showing the error, so what the user then sees is the\n // persisted team rather than the optimistic local snapshot.\n // * NOT GUARANTEED \u2014 atomicity. There is no transaction across these three\n // passes: whatever was written before the failure stays written and is\n // not rolled back. The reload therefore also drops the part of the batch\n // that never reached the server; the user re-applies it and saves again.\n for (const update of changes.endDateUpdates) {\n await updateCrud(employeesPath, {\n id: update.member.membershipId,\n assignedEndDate: update.endDate,\n }, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n\n for (const member of changes.removals) {\n await deleteCrud(employeesPath, member.membershipId, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n }, [changes, endDrafts, projectId, t])\n\n const handleSave = React.useCallback(async () => {\n if (saving || pendingChanges === 0) return\n setSaving(true)\n try {\n await runMutation({\n operation: persistChanges,\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: projectId,\n action: 'assign-team',\n retryLastMutation,\n },\n mutationPayload: {\n timeProjectId: projectId,\n additions: changes.additions,\n removals: changes.removals.map((member) => member.membershipId),\n endDateUpdates: changes.endDateUpdates.map((update) => ({\n id: update.member.membershipId,\n assignedEndDate: update.endDate,\n })),\n },\n })\n flash(t('staff.time_tracking.projects.team.drawer.saved', 'Project team updated.'), 'success')\n onSaved?.()\n onOpenChange(false)\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.save failed', { err: error })\n // The batch is not transactional (see `persistChanges`), so the local\n // snapshot can no longer be trusted: re-read the memberships first and\n // only then report, so the error and the rows underneath it agree.\n await loadTeam()\n const reason =\n error instanceof Error && error.message.trim().length > 0\n ? error.message\n : t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.')\n flash(\n t(\n 'staff.time_tracking.projects.team.drawer.partialSaveError',\n '{reason} Saving stopped part-way through, so the team shown below was reloaded from the server \u2014 check it and apply the remaining changes again.',\n { reason },\n ),\n 'error',\n )\n } finally {\n setSaving(false)\n }\n }, [\n changes,\n loadTeam,\n onOpenChange,\n onSaved,\n pendingChanges,\n persistChanges,\n projectId,\n retryLastMutation,\n runMutation,\n saving,\n t,\n ])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n void handleSave()\n }\n },\n [handleSave],\n )\n\n /**\n * The drawer batches the whole team edit and writes nothing until Save, so\n * Escape, the \u00D7, the overlay and Cancel each stand to discard a set of changes\n * the footer is at that moment counting out loud. They all reach `onOpenChange`,\n * so the guard sits there once rather than on each of them.\n */\n const requestClose = React.useCallback(async () => {\n if (saving) return\n if (pendingChanges === 0) {\n onOpenChange(false)\n return\n }\n const confirmed = await confirm({\n title: t('staff.time_tracking.projects.team.drawer.discard.title', 'Discard the team changes?'),\n text: t(\n 'staff.time_tracking.projects.team.drawer.discard.text',\n 'Nothing has been saved yet, so closing now loses every change in this drawer.',\n ),\n confirmText: t('staff.time_tracking.projects.team.drawer.discard.confirm', 'Discard'),\n cancelText: t('staff.time_tracking.projects.team.drawer.discard.cancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (confirmed) onOpenChange(false)\n }, [confirm, onOpenChange, pendingChanges, saving, t])\n\n const matchesQuery = React.useCallback(\n (profile: PersonProfile) => {\n if (debouncedQuery.length === 0) return true\n return profile.name.toLocaleLowerCase().includes(debouncedQuery.toLocaleLowerCase())\n },\n [debouncedQuery],\n )\n\n const assignedRowIds = React.useMemo(() => {\n const ids = new Set<string>(assignedByStaffMemberId.keys())\n if (lockedStaffMemberId) ids.add(lockedStaffMemberId)\n return Array.from(ids)\n }, [assignedByStaffMemberId, lockedStaffMemberId])\n\n const assignedRows = React.useMemo(\n () =>\n assignedRowIds\n .map((id) => profiles.get(id) ?? { id, name: id, userId: null })\n .filter(matchesQuery)\n .sort((left, right) => left.name.localeCompare(right.name)),\n [assignedRowIds, profiles, matchesQuery],\n )\n\n const otherRows = React.useMemo(\n () =>\n directoryIds\n .filter((id) => !assignedRowIds.includes(id))\n .map((id) => profiles.get(id) ?? { id, name: id, userId: null })\n .sort((left, right) => left.name.localeCompare(right.name)),\n [directoryIds, assignedRowIds, profiles],\n )\n\n const changeCountLabel = React.useMemo(() => {\n if (pendingChanges === 0) {\n return t('staff.time_tracking.projects.team.drawer.footer.noChanges', 'No changes')\n }\n const bucket = selectChangeCountKey(pendingChanges)\n if (bucket === 'one') {\n return t('staff.time_tracking.projects.team.drawer.footer.changes.one', '1 change to save', { count: pendingChanges })\n }\n if (bucket === 'few') {\n return t('staff.time_tracking.projects.team.drawer.footer.changes.few', '{count} changes to save', { count: pendingChanges })\n }\n return t('staff.time_tracking.projects.team.drawer.footer.changes.many', '{count} changes to save', { count: pendingChanges })\n }, [pendingChanges, t])\n\n const renderStateBadge = React.useCallback(\n (staffMemberId: string, isAssigned: boolean, isChecked: boolean, state: AssignmentState, endDate: string | null) => {\n if (isAssigned && !isChecked) {\n return (\n <Badge variant=\"error\">\n {t('staff.time_tracking.projects.team.drawer.badges.removing', 'being removed')}\n </Badge>\n )\n }\n if (!isAssigned && isChecked) {\n return (\n <Badge variant=\"success\">\n {t('staff.time_tracking.projects.team.drawer.badges.adding', 'being added')}\n </Badge>\n )\n }\n if (!isAssigned) return null\n if (state === 'expired') {\n return (\n <Badge variant=\"warning\">\n {t('staff.time_tracking.projects.team.drawer.badges.expired', 'expired {date}', { date: endDate ?? '' })}\n </Badge>\n )\n }\n if (state === 'scheduled') {\n return (\n <Badge variant=\"neutral\">\n {t('staff.time_tracking.projects.team.drawer.badges.scheduled', 'from {date}', {\n date: assignedByStaffMemberId.get(staffMemberId)?.startDate ?? '',\n })}\n </Badge>\n )\n }\n return null\n },\n [assignedByStaffMemberId, t],\n )\n\n const renderRow = React.useCallback(\n (profile: PersonProfile) => {\n const member = assignedByStaffMemberId.get(profile.id) ?? null\n const isLocked = lockedStaffMemberIds.has(profile.id)\n const isChecked = isLocked || selected.has(profile.id)\n const draftEndDate = endDrafts.has(profile.id) ? endDrafts.get(profile.id) ?? null : member?.endDate ?? null\n const state = member\n ? resolveAssignmentState({ startDate: member.startDate, endDate: draftEndDate }, graceDays)\n : 'active'\n const role = roleLabel(profile.id)\n const hint = isLocked\n ? t('staff.time_tracking.projects.team.drawer.hintFromRole', '{role} \u00B7 access from role', { role })\n : member\n ? t('staff.time_tracking.projects.team.drawer.hintInProject', '{role} \u00B7 {hours} in this project', {\n role,\n hours: hoursLabel(profile.id),\n })\n : t('staff.time_tracking.projects.team.drawer.hintPerson', '{role} \u00B7 {hours}', {\n role,\n hours: hoursLabel(profile.id),\n })\n\n const windowLine = member && (member.startDate || draftEndDate) ? (\n <span className=\"text-xs text-muted-foreground\">\n {draftEndDate\n ? t('staff.time_tracking.projects.team.drawer.window.range', 'from {start} \u00B7 until {end}', {\n start: member.startDate ?? '',\n end: draftEndDate,\n })\n : t('staff.time_tracking.projects.team.drawer.window.startOnly', 'from {start}', {\n start: member.startDate ?? '',\n })}\n {state === 'expired' ? (\n <span className=\"block text-status-warning-text\">\n {t(\n 'staff.time_tracking.projects.team.drawer.window.expiredHint',\n 'The assignment expired \u2014 this person no longer has access to the project.',\n )}\n </span>\n ) : null}\n </span>\n ) : null\n\n const endDateControl = member && !isLocked ? (\n <div className=\"flex items-center gap-2 border-t border-border px-3 py-2\">\n <label className=\"text-xs text-muted-foreground\" htmlFor={`team-end-date-${profile.id}`}>\n {t('staff.time_tracking.projects.team.drawer.window.label', 'Assignment end')}\n </label>\n <Input\n id={`team-end-date-${profile.id}`}\n type=\"date\"\n size=\"sm\"\n className=\"w-40\"\n value={draftEndDate ?? ''}\n onChange={(event) => setEndDate(profile.id, event.target.value)}\n />\n {draftEndDate ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setEndDate(profile.id, null)}>\n {t('staff.time_tracking.projects.team.drawer.window.clear', 'Clear')}\n </Button>\n ) : null}\n </div>\n ) : null\n\n return (\n <TeamPickRow\n key={profile.id}\n profile={profile}\n hint={hint}\n checked={isChecked}\n locked={isLocked}\n lockLabel={t('staff.time_tracking.projects.team.drawer.badges.always', 'always')}\n lockedHint={t(\n 'staff.time_tracking.projects.team.drawer.lockedHint',\n \"The Team Leader's access comes from the projects permission, not from an assignment, so it cannot be changed here.\",\n )}\n badge={renderStateBadge(profile.id, member !== null, isChecked, state, draftEndDate)}\n windowLine={windowLine}\n endDateControl={endDateControl}\n selectLabel={t('staff.time_tracking.projects.team.drawer.selectPerson', 'Assign {name}', { name: profile.name })}\n onToggle={(next) => { void toggleMember(profile.id, next) }}\n />\n )\n },\n [\n assignedByStaffMemberId,\n endDrafts,\n graceDays,\n hoursLabel,\n lockedStaffMemberIds,\n renderStateBadge,\n roleLabel,\n selected,\n setEndDate,\n t,\n toggleMember,\n ],\n )\n\n return (\n <Drawer\n open={open}\n onOpenChange={(next) => {\n if (next) {\n onOpenChange(true)\n return\n }\n void requestClose()\n }}\n >\n <DrawerContent\n className=\"sm:max-w-lg\"\n onKeyDown={handleKeyDown}\n closeAriaLabel={t('staff.time_tracking.projects.team.drawer.close', 'Close')}\n >\n <DrawerHeader>\n <DrawerTitle>{t('staff.time_tracking.projects.team.drawer.title', 'Project team')}</DrawerTitle>\n <DrawerDescription>{projectName}</DrawerDescription>\n </DrawerHeader>\n <DrawerBody className=\"flex flex-col gap-4\">\n {loading ? (\n <LoadingMessage label={t('staff.time_tracking.projects.team.drawer.loading', 'Loading the team\u2026')} />\n ) : loadError ? (\n <ErrorMessage label={loadError} />\n ) : (\n <>\n <SearchInput\n value={query}\n onChange={setQuery}\n placeholder={t('staff.time_tracking.projects.team.drawer.search', 'Search for a person\u2026')}\n />\n\n <section className=\"flex flex-col gap-2\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.assigned', 'Assigned ({count})', {\n count: assignedRowIds.length,\n })}\n </h3>\n {assignedRows.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.projects.team.empty', 'Nobody is assigned yet.')}\n </p>\n ) : (\n <ul className=\"flex flex-col gap-2\">{assignedRows.map(renderRow)}</ul>\n )}\n </section>\n\n <section className=\"flex flex-col gap-2\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.others', 'Everyone else in the organization')}\n </h3>\n {otherRows.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.othersEmpty', 'No other people found.')}\n </p>\n ) : (\n <ul className=\"flex flex-col gap-2\">{otherRows.map(renderRow)}</ul>\n )}\n {directoryTotal > directoryIds.length ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.projects.team.drawer.directoryTruncated',\n 'Showing the first {count} people \u2014 use the search box to find the rest.',\n { count: directoryIds.length },\n )}\n </p>\n ) : null}\n </section>\n\n <Alert status=\"information\">\n <AlertDescription>\n {t(\n 'staff.time_tracking.projects.team.drawer.info.keepsTime',\n 'Revoking access does not delete logged time \u2014 the entries stay in the project and in reports.',\n )}\n </AlertDescription>\n <AlertDescription>\n {t(\n 'staff.time_tracking.projects.team.drawer.info.immediate',\n 'Access works immediately after saving \u2014 no re-login needed.',\n )}\n </AlertDescription>\n </Alert>\n </>\n )}\n </DrawerBody>\n <DrawerFooter leading={<span className=\"text-xs text-muted-foreground\">{changeCountLabel}</span>}>\n <Button type=\"button\" variant=\"ghost\" onClick={() => { void requestClose() }}>\n {t('staff.time_tracking.projects.team.drawer.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" disabled={saving || pendingChanges === 0} onClick={() => { void handleSave() }}>\n <Save className=\"size-4\" aria-hidden />\n {saving\n ? t('staff.time_tracking.projects.team.drawer.saving', 'Saving\u2026')\n : t('staff.time_tracking.projects.team.drawer.save', 'Save')}\n </Button>\n </DrawerFooter>\n {ConfirmDialogElement}\n </DrawerContent>\n </Drawer>\n )\n}\n\nexport default ProjectTeamDrawer\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Lock, Save } from 'lucide-react'\nimport {\n Drawer,\n DrawerBody,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n} from '@open-mercato/ui/primitives/drawer'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { SearchInput } from '@open-mercato/ui/primitives/search-input'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useCurrentUserId } from '@open-mercato/ui/backend/utils/useCurrentUserId'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport {\n countPendingChanges,\n diffTeamSelection,\n formatProjectMinutes,\n resolveAssignmentState,\n resolveGraceDays,\n selectChangeCountKey,\n toIsoDate,\n type AssignedMember,\n type AssignmentState,\n} from './projectTeamAssignment'\n\nconst logger = createLogger('staff').child({ component: 'ProjectTeamDrawer' })\n\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.project_team'\nconst DIRECTORY_PAGE_SIZE = 100\nconst ENTRIES_PAGE_SIZE = 100\nconst MAX_ENTRY_PAGES = 20\nconst SEARCH_DEBOUNCE_MS = 250\n\ntype ApiRow = Record<string, unknown>\n\ntype PersonProfile = {\n id: string\n name: string\n userId: string | null\n}\n\ntype ProjectHours = {\n minutesByStaffMemberId: Map<string, number>\n partial: boolean\n}\n\nexport type ProjectTeamDrawerProps = {\n projectId: string\n projectName: string\n open: boolean\n onOpenChange: (open: boolean) => void\n /** Pre-checks the person who requested access (notification deep link, D-6). */\n preselectUserId?: string | null\n onSaved?: () => void\n}\n\nfunction readString(row: ApiRow, ...keys: string[]): string | null {\n for (const key of keys) {\n const value = row[key]\n if (typeof value === 'string' && value.trim().length > 0) return value\n if (value instanceof Date) return value.toISOString()\n }\n return null\n}\n\nfunction readNumber(row: ApiRow, ...keys: string[]): number {\n for (const key of keys) {\n const value = row[key]\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length > 0) {\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n }\n }\n return 0\n}\n\nfunction readItems(payload: { items?: unknown } | null | undefined): ApiRow[] {\n const items = payload?.items\n return Array.isArray(items) ? (items as ApiRow[]) : []\n}\n\nfunction toProfile(row: ApiRow): PersonProfile | null {\n const id = readString(row, 'id')\n if (!id) return null\n return {\n id,\n name: readString(row, 'display_name', 'displayName') ?? id,\n userId: readString(row, 'user_id', 'userId'),\n }\n}\n\nfunction toAssignedMember(row: ApiRow): AssignedMember | null {\n const membershipId = readString(row, 'id')\n const staffMemberId = readString(row, 'staff_member_id', 'staffMemberId')\n if (!membershipId || !staffMemberId) return null\n return {\n membershipId,\n staffMemberId,\n role: readString(row, 'role'),\n status: readString(row, 'status'),\n startDate: toIsoDate(readString(row, 'assigned_start_date', 'assignedStartDate')),\n endDate: toIsoDate(readString(row, 'assigned_end_date', 'assignedEndDate')),\n }\n}\n\nfunction todayIsoDate(): string {\n const now = new Date()\n const month = String(now.getMonth() + 1).padStart(2, '0')\n const day = String(now.getDate()).padStart(2, '0')\n return `${now.getFullYear()}-${month}-${day}`\n}\n\n/**\n * Hours logged per person in this project. There is no aggregate endpoint yet,\n * so the drawer pages the (tenant-scoped) entries list and sums client-side;\n * beyond `MAX_ENTRY_PAGES` the totals are reported as a lower bound rather than\n * silently under-reporting.\n */\nasync function loadProjectHours(projectId: string): Promise<ProjectHours> {\n const minutesByStaffMemberId = new Map<string, number>()\n let partial = false\n for (let page = 1; page <= MAX_ENTRY_PAGES; page += 1) {\n const params = new URLSearchParams({\n projectId,\n page: String(page),\n pageSize: String(ENTRIES_PAGE_SIZE),\n })\n const res = await apiCall<{ items?: unknown; total?: number }>(\n `/api/staff/timesheets/time-entries?${params.toString()}`,\n )\n if (!res.ok) return { minutesByStaffMemberId, partial: true }\n const rows = readItems(res.result)\n for (const row of rows) {\n const staffMemberId = readString(row, 'staff_member_id', 'staffMemberId')\n if (!staffMemberId) continue\n const minutes = readNumber(row, 'duration_minutes', 'durationMinutes')\n minutesByStaffMemberId.set(staffMemberId, (minutesByStaffMemberId.get(staffMemberId) ?? 0) + minutes)\n }\n if (rows.length < ENTRIES_PAGE_SIZE) return { minutesByStaffMemberId, partial: false }\n const total = typeof res.result?.total === 'number' ? res.result.total : null\n if (total !== null && page * ENTRIES_PAGE_SIZE >= total) return { minutesByStaffMemberId, partial: false }\n partial = true\n }\n return { minutesByStaffMemberId, partial }\n}\n\ntype TeamPickRowProps = {\n profile: PersonProfile\n hint: string\n checked: boolean\n locked: boolean\n lockedHint: string\n lockLabel: string\n badge: React.ReactNode\n windowLine: React.ReactNode\n endDateControl: React.ReactNode\n selectLabel: string\n onToggle: (next: boolean) => void\n}\n\nfunction TeamPickRow({\n profile,\n hint,\n checked,\n locked,\n lockedHint,\n lockLabel,\n badge,\n windowLine,\n endDateControl,\n selectLabel,\n onToggle,\n}: TeamPickRowProps) {\n return (\n <li className=\"rounded-md border border-border\" data-staff-member-id={profile.id}>\n <label className=\"flex cursor-pointer items-start gap-3 p-3\">\n <span className=\"mt-0.5 shrink-0\">\n <Checkbox\n checked={checked}\n disabled={locked}\n aria-label={locked ? `${selectLabel} \u2014 ${lockedHint}` : selectLabel}\n onCheckedChange={(next) => onToggle(next === true)}\n />\n </span>\n <Avatar label={profile.name} size=\"sm\" />\n <span className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span className=\"truncate text-sm font-medium text-foreground\">{profile.name}</span>\n <span className=\"text-xs text-muted-foreground\">{hint}</span>\n {windowLine}\n </span>\n <span className=\"flex shrink-0 flex-col items-end gap-1\">\n {locked ? (\n <SimpleTooltip content={lockedHint}>\n <Badge variant=\"neutral\" className=\"gap-1\">\n <Lock className=\"size-3\" aria-hidden />\n {lockLabel}\n </Badge>\n </SimpleTooltip>\n ) : null}\n {badge}\n </span>\n </label>\n {endDateControl}\n </li>\n )\n}\n\nexport function ProjectTeamDrawer({\n projectId,\n projectName,\n open,\n onOpenChange,\n preselectUserId,\n onSaved,\n}: ProjectTeamDrawerProps) {\n const t = useT()\n const translateRef = React.useRef(t)\n React.useEffect(() => { translateRef.current = t }, [t])\n const currentUserId = useCurrentUserId()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const { runMutation, retryLastMutation } = useGuardedMutation({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n\n const [loading, setLoading] = React.useState(true)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [assigned, setAssigned] = React.useState<AssignedMember[]>([])\n const [profiles, setProfiles] = React.useState<Map<string, PersonProfile>>(new Map())\n const [directoryIds, setDirectoryIds] = React.useState<string[]>([])\n const [directoryTotal, setDirectoryTotal] = React.useState(0)\n const [selfStaffMemberId, setSelfStaffMemberId] = React.useState<string | null>(null)\n const [graceDays, setGraceDays] = React.useState<number>(resolveGraceDays(null))\n const [hours, setHours] = React.useState<ProjectHours | null>(null)\n const [hoursLoading, setHoursLoading] = React.useState(true)\n const [selected, setSelected] = React.useState<Set<string>>(new Set())\n const [endDrafts, setEndDrafts] = React.useState<Map<string, string | null>>(new Map())\n const [query, setQuery] = React.useState('')\n const [debouncedQuery, setDebouncedQuery] = React.useState('')\n\n const mergeProfiles = React.useCallback((rows: ApiRow[]) => {\n setProfiles((previous) => {\n const next = new Map(previous)\n for (const row of rows) {\n const profile = toProfile(row)\n if (profile) next.set(profile.id, profile)\n }\n return next\n })\n }, [])\n\n React.useEffect(() => {\n if (!open) return\n const handle = setTimeout(() => setDebouncedQuery(query.trim()), SEARCH_DEBOUNCE_MS)\n return () => clearTimeout(handle)\n }, [open, query])\n\n React.useEffect(() => {\n if (!open) {\n setQuery('')\n setDebouncedQuery('')\n }\n }, [open])\n\n const loadTeam = React.useCallback(async () => {\n setLoading(true)\n setLoadError(null)\n try {\n const [membersRes, selfRes, settingsRes] = await Promise.all([\n apiCall<{ items?: unknown }>(\n `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=${DIRECTORY_PAGE_SIZE}&status=active`,\n ),\n apiCall<{ member?: { id?: string; displayName?: string; userId?: string | null } | null }>(\n '/api/staff/team-members/self',\n ),\n apiCall<{ access?: { assignmentGraceDays?: unknown } | null }>('/api/staff/timesheets/settings'),\n ])\n\n if (!membersRes.ok) {\n setLoadError(translateRef.current('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))\n return\n }\n\n const members = readItems(membersRes.result)\n .map(toAssignedMember)\n .filter((member): member is AssignedMember => member !== null)\n setAssigned(members)\n setSelected(new Set(members.map((member) => member.staffMemberId)))\n setEndDrafts(new Map(members.map((member) => [member.staffMemberId, member.endDate])))\n\n const selfMember = selfRes.ok ? selfRes.result?.member ?? null : null\n if (selfMember?.id) {\n setSelfStaffMemberId(selfMember.id)\n mergeProfiles([\n { id: selfMember.id, display_name: selfMember.displayName ?? selfMember.id, user_id: selfMember.userId ?? null },\n ])\n }\n\n if (settingsRes.ok) {\n setGraceDays(resolveGraceDays(settingsRes.result?.access?.assignmentGraceDays))\n }\n\n const assignedIds = members.map((member) => member.staffMemberId)\n if (assignedIds.length > 0) {\n const profileRes = await apiCall<{ items?: unknown }>(\n `/api/staff/team-members?ids=${assignedIds.join(',')}&pageSize=${DIRECTORY_PAGE_SIZE}`,\n )\n if (profileRes.ok) mergeProfiles(readItems(profileRes.result))\n }\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.load failed', { err: error })\n setLoadError(translateRef.current('staff.time_tracking.projects.team.drawer.loadError', 'Could not load the project team.'))\n } finally {\n setLoading(false)\n }\n }, [projectId, mergeProfiles])\n\n React.useEffect(() => {\n if (!open) return\n void loadTeam()\n }, [open, loadTeam])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n setHoursLoading(true)\n void (async () => {\n try {\n const result = await loadProjectHours(projectId)\n if (!cancelled) setHours(result)\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.hours failed', { err: error })\n if (!cancelled) setHours(null)\n } finally {\n if (!cancelled) setHoursLoading(false)\n }\n })()\n return () => { cancelled = true }\n }, [open, projectId])\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n void (async () => {\n const params = new URLSearchParams({\n page: '1',\n pageSize: String(DIRECTORY_PAGE_SIZE),\n isActive: 'true',\n })\n if (debouncedQuery.length > 0) params.set('search', debouncedQuery)\n const res = await apiCall<{ items?: unknown; total?: number }>(`/api/staff/team-members?${params.toString()}`)\n if (cancelled || !res.ok) return\n const rows = readItems(res.result)\n mergeProfiles(rows)\n setDirectoryIds(rows.map((row) => readString(row, 'id')).filter((id): id is string => id !== null))\n setDirectoryTotal(typeof res.result?.total === 'number' ? res.result.total : rows.length)\n })()\n return () => { cancelled = true }\n }, [open, debouncedQuery, mergeProfiles])\n\n const lockedStaffMemberId = React.useMemo(() => {\n if (selfStaffMemberId) return selfStaffMemberId\n if (!currentUserId) return null\n for (const profile of profiles.values()) {\n if (profile.userId && profile.userId === currentUserId) return profile.id\n }\n return null\n }, [selfStaffMemberId, currentUserId, profiles])\n\n const lockedStaffMemberIds = React.useMemo(\n () => new Set(lockedStaffMemberId ? [lockedStaffMemberId] : []),\n [lockedStaffMemberId],\n )\n\n React.useEffect(() => {\n if (!open || !preselectUserId || loading) return\n for (const profile of profiles.values()) {\n if (profile.userId !== preselectUserId) continue\n setSelected((previous) => (previous.has(profile.id) ? previous : new Set(previous).add(profile.id)))\n return\n }\n }, [open, preselectUserId, loading, profiles])\n\n const assignedByStaffMemberId = React.useMemo(() => {\n const map = new Map<string, AssignedMember>()\n for (const member of assigned) {\n if (!map.has(member.staffMemberId)) map.set(member.staffMemberId, member)\n }\n return map\n }, [assigned])\n\n const changes = React.useMemo(\n () => diffTeamSelection(assigned, { selectedStaffMemberIds: selected, endDateByStaffMemberId: endDrafts }, lockedStaffMemberIds),\n [assigned, selected, endDrafts, lockedStaffMemberIds],\n )\n const pendingChanges = countPendingChanges(changes)\n\n const memberMinutes = React.useCallback(\n (staffMemberId: string): number | null => {\n if (!hours) return null\n return hours.minutesByStaffMemberId.get(staffMemberId) ?? 0\n },\n [hours],\n )\n\n const hoursLabel = React.useCallback(\n (staffMemberId: string): string => {\n if (hoursLoading) return '\u2026'\n const minutes = memberMinutes(staffMemberId)\n if (minutes === null) return formatProjectMinutes(0)\n const formatted = formatProjectMinutes(minutes)\n if (!hours?.partial) return formatted\n return t('staff.time_tracking.projects.team.drawer.hoursAtLeast', 'at least {hours}', { hours: formatted })\n },\n [hours, hoursLoading, memberMinutes, t],\n )\n\n const roleLabel = React.useCallback(\n (staffMemberId: string): string => {\n const member = assignedByStaffMemberId.get(staffMemberId)\n if (member?.role && member.role.trim().length > 0) return member.role\n if (staffMemberId === lockedStaffMemberId) {\n return t('staff.time_tracking.projects.team.drawer.roleLeader', 'Team Leader')\n }\n return t('staff.time_tracking.projects.team.drawer.roleMember', 'Team Member')\n },\n [assignedByStaffMemberId, lockedStaffMemberId, t],\n )\n\n const toggleMember = React.useCallback(\n async (staffMemberId: string, next: boolean) => {\n if (lockedStaffMemberIds.has(staffMemberId)) return\n if (!next && assignedByStaffMemberId.has(staffMemberId)) {\n const profile = profiles.get(staffMemberId)\n const name = profile?.name ?? staffMemberId\n const minutes = memberMinutes(staffMemberId)\n const needsConfirm = minutes === null || minutes > 0\n if (needsConfirm) {\n const text = minutes === null || hoursLoading\n ? t(\n 'staff.time_tracking.projects.team.drawer.removeConfirm.textUnknown',\n '{name} may have time logged in this project \u2014 revoke access?',\n { name },\n )\n : t(\n 'staff.time_tracking.projects.team.drawer.removeConfirm.text',\n '{name} has {hours} in this project \u2014 revoke access?',\n { name, hours: formatProjectMinutes(minutes) },\n )\n const confirmed = await confirm({\n title: t('staff.time_tracking.projects.team.drawer.removeConfirm.title', 'Revoke access?'),\n text,\n variant: 'destructive',\n })\n if (!confirmed) return\n }\n }\n setSelected((previous) => {\n const draft = new Set(previous)\n if (next) draft.add(staffMemberId)\n else draft.delete(staffMemberId)\n return draft\n })\n },\n [assignedByStaffMemberId, confirm, hoursLoading, lockedStaffMemberIds, memberMinutes, profiles, t],\n )\n\n const setEndDate = React.useCallback((staffMemberId: string, value: string | null) => {\n setEndDrafts((previous) => {\n const draft = new Map(previous)\n draft.set(staffMemberId, value && value.length > 0 ? value : null)\n return draft\n })\n }, [])\n\n // Membership rows are an assignment junction, so they are exempt from OSS\n // optimistic locking (data-integrity contract); the drawer never PATCHes the\n // project aggregate itself, so no expected-updated-at header applies here.\n const persistChanges = React.useCallback(async () => {\n const employeesPath = `staff/timesheets/time-projects/${projectId}/employees`\n const startDate = todayIsoDate()\n\n for (const staffMemberId of changes.additions) {\n await createCrud(employeesPath, {\n timeProjectId: projectId,\n staffMemberId,\n assignedStartDate: startDate,\n assignedEndDate: endDrafts.get(staffMemberId) ?? null,\n }, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n\n // Re-dating is an update of the membership row itself (T2.12): the audit\n // trail must read \"assignment end date changed\", not \"member removed,\n // member added\". Only the changed field is sent, so the recorded diff names\n // `assignedEndDate` alone.\n //\n // No optimistic-lock header, deliberately. The endpoint accepts and enforces\n // one (the CRUD factory registers a reader for this entity), but this drawer\n // saves a whole batch \u2014 additions, re-datings and removals \u2014 with no\n // rollback. A per-row version round-trip belongs with a per-row conflict UX;\n // the batch save is not it.\n //\n // What this loop guarantees and what it does not (C6):\n // * GUARANTEED \u2014 the loop stops at the first failure, so no write is\n // attempted after one fails, and `handleSave` reloads the drawer from the\n // server before showing the error, so what the user then sees is the\n // persisted team rather than the optimistic local snapshot.\n // * NOT GUARANTEED \u2014 atomicity. There is no transaction across these three\n // passes: whatever was written before the failure stays written and is\n // not rolled back. The reload therefore also drops the part of the batch\n // that never reached the server; the user re-applies it and saves again.\n for (const update of changes.endDateUpdates) {\n await updateCrud(employeesPath, {\n id: update.member.membershipId,\n assignedEndDate: update.endDate,\n }, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n\n for (const member of changes.removals) {\n await deleteCrud(employeesPath, member.membershipId, {\n errorMessage: t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.'),\n })\n }\n }, [changes, endDrafts, projectId, t])\n\n const handleSave = React.useCallback(async () => {\n if (saving || pendingChanges === 0) return\n setSaving(true)\n try {\n await runMutation({\n operation: persistChanges,\n context: {\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'staff.timesheets.time_project_member',\n resourceId: projectId,\n action: 'assign-team',\n retryLastMutation,\n },\n mutationPayload: {\n timeProjectId: projectId,\n additions: changes.additions,\n removals: changes.removals.map((member) => member.membershipId),\n endDateUpdates: changes.endDateUpdates.map((update) => ({\n id: update.member.membershipId,\n assignedEndDate: update.endDate,\n })),\n },\n })\n flash(t('staff.time_tracking.projects.team.drawer.saved', 'Project team updated.'), 'success')\n onSaved?.()\n onOpenChange(false)\n } catch (error) {\n logger.error('staff.time_tracking.projects.team.save failed', { err: error })\n // The batch is not transactional (see `persistChanges`), so the local\n // snapshot can no longer be trusted: re-read the memberships first and\n // only then report, so the error and the rows underneath it agree.\n await loadTeam()\n const reason =\n error instanceof Error && error.message.trim().length > 0\n ? error.message\n : t('staff.time_tracking.projects.team.drawer.saveError', 'Could not save the project team.')\n flash(\n t(\n 'staff.time_tracking.projects.team.drawer.partialSaveError',\n '{reason} Saving stopped part-way through, so the team shown below was reloaded from the server \u2014 check it and apply the remaining changes again.',\n { reason },\n ),\n 'error',\n )\n } finally {\n setSaving(false)\n }\n }, [\n changes,\n loadTeam,\n onOpenChange,\n onSaved,\n pendingChanges,\n persistChanges,\n projectId,\n retryLastMutation,\n runMutation,\n saving,\n t,\n ])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n void handleSave()\n }\n },\n [handleSave],\n )\n\n /**\n * The drawer batches the whole team edit and writes nothing until Save, so\n * Escape, the \u00D7, the overlay and Cancel each stand to discard a set of changes\n * the footer is at that moment counting out loud. They all reach `onOpenChange`,\n * so the guard sits there once rather than on each of them.\n */\n const requestClose = React.useCallback(async () => {\n if (saving) return\n if (pendingChanges === 0) {\n onOpenChange(false)\n return\n }\n const confirmed = await confirm({\n title: t('staff.time_tracking.projects.team.drawer.discard.title', 'Discard the team changes?'),\n text: t(\n 'staff.time_tracking.projects.team.drawer.discard.text',\n 'Nothing has been saved yet, so closing now loses every change in this drawer.',\n ),\n confirmText: t('staff.time_tracking.projects.team.drawer.discard.confirm', 'Discard'),\n cancelText: t('staff.time_tracking.projects.team.drawer.discard.cancel', 'Keep editing'),\n variant: 'destructive',\n })\n if (confirmed) onOpenChange(false)\n }, [confirm, onOpenChange, pendingChanges, saving, t])\n\n const matchesQuery = React.useCallback(\n (profile: PersonProfile) => {\n if (debouncedQuery.length === 0) return true\n return profile.name.toLocaleLowerCase().includes(debouncedQuery.toLocaleLowerCase())\n },\n [debouncedQuery],\n )\n\n const assignedRowIds = React.useMemo(() => {\n const ids = new Set<string>(assignedByStaffMemberId.keys())\n if (lockedStaffMemberId) ids.add(lockedStaffMemberId)\n return Array.from(ids)\n }, [assignedByStaffMemberId, lockedStaffMemberId])\n\n const assignedRows = React.useMemo(\n () =>\n assignedRowIds\n .map((id) => profiles.get(id) ?? { id, name: id, userId: null })\n .filter(matchesQuery)\n .sort((left, right) => left.name.localeCompare(right.name)),\n [assignedRowIds, profiles, matchesQuery],\n )\n\n const otherRows = React.useMemo(\n () =>\n directoryIds\n .filter((id) => !assignedRowIds.includes(id))\n .map((id) => profiles.get(id) ?? { id, name: id, userId: null })\n .sort((left, right) => left.name.localeCompare(right.name)),\n [directoryIds, assignedRowIds, profiles],\n )\n\n const changeCountLabel = React.useMemo(() => {\n if (pendingChanges === 0) {\n return t('staff.time_tracking.projects.team.drawer.footer.noChanges', 'No changes')\n }\n const bucket = selectChangeCountKey(pendingChanges)\n if (bucket === 'one') {\n return t('staff.time_tracking.projects.team.drawer.footer.changes.one', '1 change to save', { count: pendingChanges })\n }\n if (bucket === 'few') {\n return t('staff.time_tracking.projects.team.drawer.footer.changes.few', '{count} changes to save', { count: pendingChanges })\n }\n return t('staff.time_tracking.projects.team.drawer.footer.changes.many', '{count} changes to save', { count: pendingChanges })\n }, [pendingChanges, t])\n\n const renderStateBadge = React.useCallback(\n (staffMemberId: string, isAssigned: boolean, isChecked: boolean, state: AssignmentState, endDate: string | null) => {\n if (isAssigned && !isChecked) {\n return (\n <Badge variant=\"error\">\n {t('staff.time_tracking.projects.team.drawer.badges.removing', 'being removed')}\n </Badge>\n )\n }\n if (!isAssigned && isChecked) {\n return (\n <Badge variant=\"success\">\n {t('staff.time_tracking.projects.team.drawer.badges.adding', 'being added')}\n </Badge>\n )\n }\n if (!isAssigned) return null\n if (state === 'expired') {\n return (\n <Badge variant=\"warning\">\n {t('staff.time_tracking.projects.team.drawer.badges.expired', 'expired {date}', { date: endDate ?? '' })}\n </Badge>\n )\n }\n if (state === 'scheduled') {\n return (\n <Badge variant=\"neutral\">\n {t('staff.time_tracking.projects.team.drawer.badges.scheduled', 'from {date}', {\n date: assignedByStaffMemberId.get(staffMemberId)?.startDate ?? '',\n })}\n </Badge>\n )\n }\n return null\n },\n [assignedByStaffMemberId, t],\n )\n\n const renderRow = React.useCallback(\n (profile: PersonProfile) => {\n const member = assignedByStaffMemberId.get(profile.id) ?? null\n const isLocked = lockedStaffMemberIds.has(profile.id)\n const isChecked = isLocked || selected.has(profile.id)\n const draftEndDate = endDrafts.has(profile.id) ? endDrafts.get(profile.id) ?? null : member?.endDate ?? null\n const state = member\n ? resolveAssignmentState({ startDate: member.startDate, endDate: draftEndDate }, graceDays)\n : 'active'\n const role = roleLabel(profile.id)\n const hint = isLocked\n ? t('staff.time_tracking.projects.team.drawer.hintFromRole', '{role} \u00B7 access from role', { role })\n : member\n ? t('staff.time_tracking.projects.team.drawer.hintInProject', '{role} \u00B7 {hours} in this project', {\n role,\n hours: hoursLabel(profile.id),\n })\n : t('staff.time_tracking.projects.team.drawer.hintPerson', '{role} \u00B7 {hours}', {\n role,\n hours: hoursLabel(profile.id),\n })\n\n const windowLine = member && (member.startDate || draftEndDate) ? (\n <span className=\"text-xs text-muted-foreground\">\n {draftEndDate\n ? t('staff.time_tracking.projects.team.drawer.window.range', 'from {start} \u00B7 until {end}', {\n start: member.startDate ?? '',\n end: draftEndDate,\n })\n : t('staff.time_tracking.projects.team.drawer.window.startOnly', 'from {start}', {\n start: member.startDate ?? '',\n })}\n {state === 'expired' ? (\n <span className=\"block text-status-warning-text\">\n {t(\n 'staff.time_tracking.projects.team.drawer.window.expiredHint',\n 'The assignment expired \u2014 this person no longer has access to the project.',\n )}\n </span>\n ) : null}\n </span>\n ) : null\n\n const endDateControl = member && !isLocked ? (\n <div className=\"flex items-center gap-2 border-t border-border px-3 py-2\">\n <label className=\"text-xs text-muted-foreground\" htmlFor={`team-end-date-${profile.id}`}>\n {t('staff.time_tracking.projects.team.drawer.window.label', 'Assignment end')}\n </label>\n <Input\n id={`team-end-date-${profile.id}`}\n type=\"date\"\n size=\"sm\"\n className=\"w-40\"\n value={draftEndDate ?? ''}\n onChange={(event) => setEndDate(profile.id, event.target.value)}\n />\n {draftEndDate ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setEndDate(profile.id, null)}>\n {t('staff.time_tracking.projects.team.drawer.window.clear', 'Clear')}\n </Button>\n ) : null}\n </div>\n ) : null\n\n return (\n <TeamPickRow\n key={profile.id}\n profile={profile}\n hint={hint}\n checked={isChecked}\n locked={isLocked}\n lockLabel={t('staff.time_tracking.projects.team.drawer.badges.always', 'always')}\n lockedHint={t(\n 'staff.time_tracking.projects.team.drawer.lockedHint',\n \"The Team Leader's access comes from the projects permission, not from an assignment, so it cannot be changed here.\",\n )}\n badge={renderStateBadge(profile.id, member !== null, isChecked, state, draftEndDate)}\n windowLine={windowLine}\n endDateControl={endDateControl}\n selectLabel={t('staff.time_tracking.projects.team.drawer.selectPerson', 'Assign {name}', { name: profile.name })}\n onToggle={(next) => { void toggleMember(profile.id, next) }}\n />\n )\n },\n [\n assignedByStaffMemberId,\n endDrafts,\n graceDays,\n hoursLabel,\n lockedStaffMemberIds,\n renderStateBadge,\n roleLabel,\n selected,\n setEndDate,\n t,\n toggleMember,\n ],\n )\n\n return (\n <Drawer\n open={open}\n onOpenChange={(next) => {\n if (next) {\n onOpenChange(true)\n return\n }\n void requestClose()\n }}\n >\n <DrawerContent\n className=\"sm:max-w-lg\"\n onKeyDown={handleKeyDown}\n closeAriaLabel={t('staff.time_tracking.projects.team.drawer.close', 'Close')}\n >\n <DrawerHeader>\n <DrawerTitle>{t('staff.time_tracking.projects.team.drawer.title', 'Project team')}</DrawerTitle>\n <DrawerDescription>{projectName}</DrawerDescription>\n </DrawerHeader>\n <DrawerBody className=\"flex flex-col gap-4\">\n {loading ? (\n <LoadingMessage label={t('staff.time_tracking.projects.team.drawer.loading', 'Loading the team\u2026')} />\n ) : loadError ? (\n <ErrorMessage label={loadError} />\n ) : (\n <>\n <SearchInput\n value={query}\n onChange={setQuery}\n placeholder={t('staff.time_tracking.projects.team.drawer.search', 'Search for a person\u2026')}\n />\n\n <section className=\"flex flex-col gap-2\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.assigned', 'Assigned ({count})', {\n count: assignedRowIds.length,\n })}\n </h3>\n {assignedRows.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.projects.team.empty', 'Nobody is assigned yet.')}\n </p>\n ) : (\n <ul className=\"flex flex-col gap-2\">{assignedRows.map(renderRow)}</ul>\n )}\n </section>\n\n <section className=\"flex flex-col gap-2\">\n <h3 className=\"text-xs font-medium uppercase tracking-wide text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.others', 'Everyone else in the organization')}\n </h3>\n {otherRows.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.projects.team.drawer.othersEmpty', 'No other people found.')}\n </p>\n ) : (\n <ul className=\"flex flex-col gap-2\">{otherRows.map(renderRow)}</ul>\n )}\n {directoryTotal > directoryIds.length ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'staff.time_tracking.projects.team.drawer.directoryTruncated',\n 'Showing the first {count} people \u2014 use the search box to find the rest.',\n { count: directoryIds.length },\n )}\n </p>\n ) : null}\n </section>\n\n <Alert status=\"information\">\n <AlertDescription>\n {t(\n 'staff.time_tracking.projects.team.drawer.info.keepsTime',\n 'Revoking access does not delete logged time \u2014 the entries stay in the project and in reports.',\n )}\n </AlertDescription>\n <AlertDescription>\n {t(\n 'staff.time_tracking.projects.team.drawer.info.immediate',\n 'Access works immediately after saving \u2014 no re-login needed.',\n )}\n </AlertDescription>\n </Alert>\n </>\n )}\n </DrawerBody>\n <DrawerFooter leading={<span className=\"text-xs text-muted-foreground\">{changeCountLabel}</span>}>\n <Button type=\"button\" variant=\"ghost\" onClick={() => { void requestClose() }}>\n {t('staff.time_tracking.projects.team.drawer.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" disabled={saving || pendingChanges === 0} onClick={() => { void handleSave() }}>\n <Save className=\"size-4\" aria-hidden />\n {saving\n ? t('staff.time_tracking.projects.team.drawer.saving', 'Saving\u2026')\n : t('staff.time_tracking.projects.team.drawer.save', 'Save')}\n </Button>\n </DrawerFooter>\n {ConfirmDialogElement}\n </DrawerContent>\n </Drawer>\n )\n}\n\nexport default ProjectTeamDrawer\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.10.1-develop.
|
|
3
|
+
"version": "0.10.1-develop.7765.1.391b8a0e16",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -266,16 +266,16 @@
|
|
|
266
266
|
"zod": "^4.4.3"
|
|
267
267
|
},
|
|
268
268
|
"peerDependencies": {
|
|
269
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
270
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
271
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
269
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7765.1.391b8a0e16",
|
|
270
|
+
"@open-mercato/shared": "0.10.1-develop.7765.1.391b8a0e16",
|
|
271
|
+
"@open-mercato/ui": "0.10.1-develop.7765.1.391b8a0e16",
|
|
272
272
|
"react": "^19.0.0",
|
|
273
273
|
"react-dom": "^19.0.0"
|
|
274
274
|
},
|
|
275
275
|
"devDependencies": {
|
|
276
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
277
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
278
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
276
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7765.1.391b8a0e16",
|
|
277
|
+
"@open-mercato/shared": "0.10.1-develop.7765.1.391b8a0e16",
|
|
278
|
+
"@open-mercato/ui": "0.10.1-develop.7765.1.391b8a0e16",
|
|
279
279
|
"@testing-library/dom": "^10.4.1",
|
|
280
280
|
"@testing-library/jest-dom": "^7.0.1",
|
|
281
281
|
"@testing-library/react": "^16.3.3",
|
|
@@ -28,6 +28,7 @@ import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
|
28
28
|
import { isUuidListParam, splitIdListParam } from '@open-mercato/shared/lib/crud/ids'
|
|
29
29
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
30
30
|
import { consumeAdvancedFilterState, mergeAdvancedFilterTree } from '@open-mercato/shared/lib/crud/advanced-filter-integration'
|
|
31
|
+
import { expandCreatedAtDayRules } from '../../lib/createdAtDayFilter'
|
|
31
32
|
import {
|
|
32
33
|
createCustomersCrudOpenApi,
|
|
33
34
|
createPagedListResponseSchema,
|
|
@@ -135,7 +136,7 @@ const crud = makeCrudRoute({
|
|
|
135
136
|
updatedAt: 'updated_at',
|
|
136
137
|
},
|
|
137
138
|
buildFilters: async (query, ctx) => {
|
|
138
|
-
const advancedFilterTree = consumeAdvancedFilterState(query)
|
|
139
|
+
const advancedFilterTree = expandCreatedAtDayRules(consumeAdvancedFilterState(query), 'UTC')
|
|
139
140
|
const filters: Record<string, unknown> = { kind: { $eq: 'company' } }
|
|
140
141
|
if (query.id) filters.id = { $eq: query.id }
|
|
141
142
|
if (query.search) {
|
|
@@ -24,6 +24,7 @@ import { parseBooleanToken } from '@open-mercato/shared/lib/boolean'
|
|
|
24
24
|
import { isUuidListParam, splitIdListParam } from '@open-mercato/shared/lib/crud/ids'
|
|
25
25
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
26
26
|
import { consumeAdvancedFilterState, mergeAdvancedFilterTree } from '@open-mercato/shared/lib/crud/advanced-filter-integration'
|
|
27
|
+
import { expandCreatedAtDayRules } from '../../lib/createdAtDayFilter'
|
|
27
28
|
import {
|
|
28
29
|
createCustomersCrudOpenApi,
|
|
29
30
|
createPagedListResponseSchema,
|
|
@@ -128,7 +129,7 @@ const crud = makeCrudRoute({
|
|
|
128
129
|
updatedAt: 'updated_at',
|
|
129
130
|
},
|
|
130
131
|
buildFilters: async (query, ctx) => {
|
|
131
|
-
const advancedFilterTree = consumeAdvancedFilterState(query)
|
|
132
|
+
const advancedFilterTree = expandCreatedAtDayRules(consumeAdvancedFilterState(query), 'UTC')
|
|
132
133
|
const filters: Record<string, unknown> = { kind: { $eq: 'person' } }
|
|
133
134
|
if (query.id) filters.id = { $eq: query.id }
|
|
134
135
|
if (query.search) {
|
|
@@ -58,6 +58,9 @@ import {
|
|
|
58
58
|
} from '../../../components/detail/assignableStaff'
|
|
59
59
|
import { CollectionPreviewCell, normalizeCollectionLabels } from '../../../components/list/CollectionPreviewCell'
|
|
60
60
|
import { appendCustomerListSortParams } from '../listSorting'
|
|
61
|
+
import { expandCreatedAtDayRules } from '../../../lib/createdAtDayFilter'
|
|
62
|
+
import { USER_TIMEZONE } from '../../../lib/localDay'
|
|
63
|
+
import { formatInTimeZone } from 'date-fns-tz'
|
|
61
64
|
|
|
62
65
|
type DictionaryOptionWithTone = AdvancedFilterOption & FilterOption
|
|
63
66
|
|
|
@@ -74,8 +77,9 @@ function makeCompaniesPresets(): FilterPreset[] {
|
|
|
74
77
|
labelKey: 'customers.companies.presets.recentlyCreated',
|
|
75
78
|
iconName: 'clock',
|
|
76
79
|
build: ({ now }) => {
|
|
77
|
-
const
|
|
78
|
-
|
|
80
|
+
const dayBeforeWindow = new Date(`${formatInTimeZone(now, USER_TIMEZONE, 'yyyy-MM-dd')}T00:00:00.000Z`)
|
|
81
|
+
dayBeforeWindow.setUTCDate(dayBeforeWindow.getUTCDate() - 8)
|
|
82
|
+
return makeRuleTree({ field: 'created_at', operator: 'is_after', value: dayBeforeWindow.toISOString().slice(0, 10) })
|
|
79
83
|
},
|
|
80
84
|
},
|
|
81
85
|
{
|
|
@@ -112,6 +116,7 @@ type CompanyRow = {
|
|
|
112
116
|
organizationId?: string | null
|
|
113
117
|
source?: string | null
|
|
114
118
|
ownerUserId?: string | null
|
|
119
|
+
createdAt?: string | null
|
|
115
120
|
} & Record<string, unknown>
|
|
116
121
|
|
|
117
122
|
type CompaniesResponse = {
|
|
@@ -159,6 +164,7 @@ function mapApiItem(item: Record<string, unknown>): CompanyRow | null {
|
|
|
159
164
|
const nextInteractionColor = typeof item.next_interaction_color === 'string' ? item.next_interaction_color : null
|
|
160
165
|
const organizationId = typeof item.organization_id === 'string' ? item.organization_id : null
|
|
161
166
|
const source = typeof item.source === 'string' ? item.source : null
|
|
167
|
+
const createdAt = typeof item.created_at === 'string' ? item.created_at : null
|
|
162
168
|
const ownerUserId = typeof item.owner_user_id === 'string' ? item.owner_user_id : null
|
|
163
169
|
const customFields: Record<string, unknown> = {}
|
|
164
170
|
for (const [key, value] of Object.entries(item)) {
|
|
@@ -188,6 +194,7 @@ function mapApiItem(item: Record<string, unknown>): CompanyRow | null {
|
|
|
188
194
|
organizationId,
|
|
189
195
|
source,
|
|
190
196
|
ownerUserId,
|
|
197
|
+
createdAt,
|
|
191
198
|
...customFields,
|
|
192
199
|
}, item)
|
|
193
200
|
}
|
|
@@ -364,7 +371,7 @@ export default function CustomersCompaniesPage() {
|
|
|
364
371
|
params.set('pageSize', String(pageSize))
|
|
365
372
|
appendCustomerListSortParams(params, sorting)
|
|
366
373
|
if (search.trim()) params.set('search', search.trim())
|
|
367
|
-
const advancedParams = serializeTree(advancedFilterState)
|
|
374
|
+
const advancedParams = serializeTree(expandCreatedAtDayRules(advancedFilterState, USER_TIMEZONE))
|
|
368
375
|
for (const [key, val] of Object.entries(advancedParams)) {
|
|
369
376
|
params.set(key, val)
|
|
370
377
|
}
|
|
@@ -707,6 +714,22 @@ export default function CustomersCompaniesPage() {
|
|
|
707
714
|
)
|
|
708
715
|
: noValue,
|
|
709
716
|
},
|
|
717
|
+
{
|
|
718
|
+
accessorKey: 'createdAt',
|
|
719
|
+
header: t('customers.companies.list.columns.createdAt', 'Created'),
|
|
720
|
+
meta: {
|
|
721
|
+
columnChooserGroup: t('customers.columnGroups.dates', 'Dates'),
|
|
722
|
+
filterKey: 'created_at',
|
|
723
|
+
filterType: 'date' as const,
|
|
724
|
+
filterGroup: t('customers.columnGroups.activity', 'Activity'),
|
|
725
|
+
filterIconName: 'calendar',
|
|
726
|
+
},
|
|
727
|
+
cell: ({ row }) => (
|
|
728
|
+
<span className="text-sm">
|
|
729
|
+
{formatDate(row.original.createdAt, t('customers.companies.list.noValue'))}
|
|
730
|
+
</span>
|
|
731
|
+
),
|
|
732
|
+
},
|
|
710
733
|
{
|
|
711
734
|
accessorKey: 'source',
|
|
712
735
|
header: t('customers.companies.list.columns.source'),
|
|
@@ -7,6 +7,7 @@ const CUSTOMER_LIST_SORT_FIELDS: Record<string, string> = {
|
|
|
7
7
|
lifecycleStage: 'lifecycleStage',
|
|
8
8
|
source: 'source',
|
|
9
9
|
nextInteractionAt: 'nextInteractionAt',
|
|
10
|
+
createdAt: 'createdAt',
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export function resolveCustomerListSortField(columnId: string): string | null {
|
|
@@ -58,6 +58,8 @@ import {
|
|
|
58
58
|
} from '../../../components/detail/assignableStaff'
|
|
59
59
|
import { CollectionPreviewCell, normalizeCollectionLabels } from '../../../components/list/CollectionPreviewCell'
|
|
60
60
|
import { appendCustomerListSortParams } from '../listSorting'
|
|
61
|
+
import { expandCreatedAtDayRules } from '../../../lib/createdAtDayFilter'
|
|
62
|
+
import { USER_TIMEZONE } from '../../../lib/localDay'
|
|
61
63
|
|
|
62
64
|
type DictionaryOptionWithTone = AdvancedFilterOption & FilterOption
|
|
63
65
|
|
|
@@ -119,6 +121,7 @@ type PersonRow = {
|
|
|
119
121
|
organizationId?: string | null
|
|
120
122
|
source?: string | null
|
|
121
123
|
ownerUserId?: string | null
|
|
124
|
+
createdAt?: string | null
|
|
122
125
|
} & Record<string, unknown>
|
|
123
126
|
|
|
124
127
|
type PeopleResponse = {
|
|
@@ -166,6 +169,7 @@ function mapApiItem(item: Record<string, unknown>): PersonRow | null {
|
|
|
166
169
|
const nextInteractionColor = typeof item.next_interaction_color === 'string' ? item.next_interaction_color : null
|
|
167
170
|
const organizationId = typeof item.organization_id === 'string' ? item.organization_id : null
|
|
168
171
|
const source = typeof item.source === 'string' ? item.source : null
|
|
172
|
+
const createdAt = typeof item.created_at === 'string' ? item.created_at : null
|
|
169
173
|
const customFields: Record<string, unknown> = {}
|
|
170
174
|
for (const [key, value] of Object.entries(item)) {
|
|
171
175
|
if (key.startsWith('cf_')) {
|
|
@@ -196,6 +200,7 @@ function mapApiItem(item: Record<string, unknown>): PersonRow | null {
|
|
|
196
200
|
nextInteractionColor,
|
|
197
201
|
organizationId,
|
|
198
202
|
source,
|
|
203
|
+
createdAt,
|
|
199
204
|
...customFields,
|
|
200
205
|
}, item)
|
|
201
206
|
}
|
|
@@ -375,7 +380,7 @@ export default function CustomersPeoplePage() {
|
|
|
375
380
|
params.set('pageSize', String(pageSize))
|
|
376
381
|
appendCustomerListSortParams(params, sorting)
|
|
377
382
|
if (search.trim()) params.set('search', search.trim())
|
|
378
|
-
const advancedParams = serializeTree(advancedFilterState)
|
|
383
|
+
const advancedParams = serializeTree(expandCreatedAtDayRules(advancedFilterState, USER_TIMEZONE))
|
|
379
384
|
for (const [key, val] of Object.entries(advancedParams)) {
|
|
380
385
|
params.set(key, val)
|
|
381
386
|
}
|
|
@@ -713,6 +718,22 @@ export default function CustomersPeoplePage() {
|
|
|
713
718
|
)
|
|
714
719
|
: <span className="text-muted-foreground text-sm">{t('customers.people.list.noValue')}</span>,
|
|
715
720
|
},
|
|
721
|
+
{
|
|
722
|
+
accessorKey: 'createdAt',
|
|
723
|
+
header: t('customers.people.list.columns.createdAt', 'Created'),
|
|
724
|
+
meta: {
|
|
725
|
+
columnChooserGroup: t('customers.columnGroups.dates', 'Dates'),
|
|
726
|
+
filterKey: 'created_at',
|
|
727
|
+
filterType: 'date' as const,
|
|
728
|
+
filterGroup: t('customers.columnGroups.activity', 'Activity'),
|
|
729
|
+
filterIconName: 'calendar',
|
|
730
|
+
},
|
|
731
|
+
cell: ({ row }) => (
|
|
732
|
+
<span className="text-sm">
|
|
733
|
+
{formatDate(row.original.createdAt, t('customers.people.list.noValue'))}
|
|
734
|
+
</span>
|
|
735
|
+
),
|
|
736
|
+
},
|
|
716
737
|
{
|
|
717
738
|
accessorKey: 'source',
|
|
718
739
|
header: t('customers.people.list.columns.source'),
|
|
@@ -797,6 +797,7 @@
|
|
|
797
797
|
"customers.companies.list.bulkDelete.progressName": "Ausgewählte Unternehmen löschen",
|
|
798
798
|
"customers.companies.list.bulkDelete.success": "{count} companies deleted",
|
|
799
799
|
"customers.companies.list.bulkDelete.title": "Delete {count} companies?",
|
|
800
|
+
"customers.companies.list.columns.createdAt": "Erstellt am",
|
|
800
801
|
"customers.companies.list.columns.email": "E-Mail",
|
|
801
802
|
"customers.companies.list.columns.lifecycleStage": "Lebenszyklusphase",
|
|
802
803
|
"customers.companies.list.columns.name": "Name",
|
|
@@ -2512,6 +2513,7 @@
|
|
|
2512
2513
|
"customers.people.list.bulkDelete.progressName": "Ausgewählte Personen löschen",
|
|
2513
2514
|
"customers.people.list.bulkDelete.success": "{count} people deleted",
|
|
2514
2515
|
"customers.people.list.bulkDelete.title": "Delete {count} people?",
|
|
2516
|
+
"customers.people.list.columns.createdAt": "Erstellt am",
|
|
2515
2517
|
"customers.people.list.columns.email": "E-Mail",
|
|
2516
2518
|
"customers.people.list.columns.lifecycleStage": "Lebenszyklusphase",
|
|
2517
2519
|
"customers.people.list.columns.name": "Name",
|
|
@@ -797,6 +797,7 @@
|
|
|
797
797
|
"customers.companies.list.bulkDelete.progressName": "Delete selected companies",
|
|
798
798
|
"customers.companies.list.bulkDelete.success": "{count} companies deleted",
|
|
799
799
|
"customers.companies.list.bulkDelete.title": "Delete {count} companies?",
|
|
800
|
+
"customers.companies.list.columns.createdAt": "Created",
|
|
800
801
|
"customers.companies.list.columns.email": "Email",
|
|
801
802
|
"customers.companies.list.columns.lifecycleStage": "Lifecycle stage",
|
|
802
803
|
"customers.companies.list.columns.name": "Name",
|
|
@@ -2512,6 +2513,7 @@
|
|
|
2512
2513
|
"customers.people.list.bulkDelete.progressName": "Delete selected people",
|
|
2513
2514
|
"customers.people.list.bulkDelete.success": "{count} people deleted",
|
|
2514
2515
|
"customers.people.list.bulkDelete.title": "Delete {count} people?",
|
|
2516
|
+
"customers.people.list.columns.createdAt": "Created",
|
|
2515
2517
|
"customers.people.list.columns.email": "Email",
|
|
2516
2518
|
"customers.people.list.columns.lifecycleStage": "Lifecycle stage",
|
|
2517
2519
|
"customers.people.list.columns.name": "Name",
|
|
@@ -797,6 +797,7 @@
|
|
|
797
797
|
"customers.companies.list.bulkDelete.progressName": "Eliminar empresas seleccionadas",
|
|
798
798
|
"customers.companies.list.bulkDelete.success": "{count} companies deleted",
|
|
799
799
|
"customers.companies.list.bulkDelete.title": "Delete {count} companies?",
|
|
800
|
+
"customers.companies.list.columns.createdAt": "Fecha de creación",
|
|
800
801
|
"customers.companies.list.columns.email": "Correo electrónico",
|
|
801
802
|
"customers.companies.list.columns.lifecycleStage": "Etapa del ciclo de vida",
|
|
802
803
|
"customers.companies.list.columns.name": "Nombre",
|
|
@@ -2512,6 +2513,7 @@
|
|
|
2512
2513
|
"customers.people.list.bulkDelete.progressName": "Eliminar personas seleccionadas",
|
|
2513
2514
|
"customers.people.list.bulkDelete.success": "{count} people deleted",
|
|
2514
2515
|
"customers.people.list.bulkDelete.title": "Delete {count} people?",
|
|
2516
|
+
"customers.people.list.columns.createdAt": "Fecha de creación",
|
|
2515
2517
|
"customers.people.list.columns.email": "Correo electrónico",
|
|
2516
2518
|
"customers.people.list.columns.lifecycleStage": "Etapa del ciclo de vida",
|
|
2517
2519
|
"customers.people.list.columns.name": "Nombre",
|
|
@@ -797,6 +797,7 @@
|
|
|
797
797
|
"customers.companies.list.bulkDelete.progressName": "선택한 회사 삭제",
|
|
798
798
|
"customers.companies.list.bulkDelete.success": "회사 {count}개가 삭제되었습니다",
|
|
799
799
|
"customers.companies.list.bulkDelete.title": "회사 {count}개를 삭제하시겠습니까?",
|
|
800
|
+
"customers.companies.list.columns.createdAt": "생성일",
|
|
800
801
|
"customers.companies.list.columns.email": "이메일",
|
|
801
802
|
"customers.companies.list.columns.lifecycleStage": "라이프사이클 단계",
|
|
802
803
|
"customers.companies.list.columns.name": "이름",
|
|
@@ -2512,6 +2513,7 @@
|
|
|
2512
2513
|
"customers.people.list.bulkDelete.progressName": "선택한 연락처 삭제",
|
|
2513
2514
|
"customers.people.list.bulkDelete.success": "{count}명의 연락처가 삭제되었습니다",
|
|
2514
2515
|
"customers.people.list.bulkDelete.title": "{count}명의 연락처를 삭제하시겠습니까?",
|
|
2516
|
+
"customers.people.list.columns.createdAt": "생성일",
|
|
2515
2517
|
"customers.people.list.columns.email": "이메일",
|
|
2516
2518
|
"customers.people.list.columns.lifecycleStage": "라이프사이클 단계",
|
|
2517
2519
|
"customers.people.list.columns.name": "이름",
|
|
@@ -797,6 +797,7 @@
|
|
|
797
797
|
"customers.companies.list.bulkDelete.progressName": "Usuń zaznaczone firmy",
|
|
798
798
|
"customers.companies.list.bulkDelete.success": "Usunięto {count} firm",
|
|
799
799
|
"customers.companies.list.bulkDelete.title": "Usunąć {count} firm?",
|
|
800
|
+
"customers.companies.list.columns.createdAt": "Data utworzenia",
|
|
800
801
|
"customers.companies.list.columns.email": "E-mail",
|
|
801
802
|
"customers.companies.list.columns.lifecycleStage": "Etap cyklu życia",
|
|
802
803
|
"customers.companies.list.columns.name": "Nazwa",
|
|
@@ -2512,6 +2513,7 @@
|
|
|
2512
2513
|
"customers.people.list.bulkDelete.progressName": "Usuń zaznaczone osoby",
|
|
2513
2514
|
"customers.people.list.bulkDelete.success": "Usunięto {count} osób",
|
|
2514
2515
|
"customers.people.list.bulkDelete.title": "Usunąć {count} osób?",
|
|
2516
|
+
"customers.people.list.columns.createdAt": "Data utworzenia",
|
|
2515
2517
|
"customers.people.list.columns.email": "E-mail",
|
|
2516
2518
|
"customers.people.list.columns.lifecycleStage": "Etap cyklu życia",
|
|
2517
2519
|
"customers.people.list.columns.name": "Nazwa",
|