@burdenoff/microfe-crewfoundry 2026.1029.2 → 2026.1030.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/crewfoundry/lib/useFocusedRecordParam.d.ts +51 -0
  2. package/dist/crewfoundry/lib/useFocusedRecordParam.d.ts.map +1 -0
  3. package/dist/crewfoundry/lib/useFocusedRecordParam.js +38 -0
  4. package/dist/crewfoundry/lib/useFocusedRecordParam.js.map +1 -0
  5. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.d.ts.map +1 -1
  6. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +119 -112
  7. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
  8. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.d.ts.map +1 -1
  9. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +78 -71
  10. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  11. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.d.ts.map +1 -1
  12. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +88 -81
  13. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
  14. package/dist/crewfoundry/pages/HiringCandidatesPagePage.d.ts.map +1 -1
  15. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +133 -126
  16. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  17. package/dist/crewfoundry/pages/HiringInterviewsPagePage.d.ts.map +1 -1
  18. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +150 -143
  19. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
  20. package/dist/crewfoundry/pages/HrDepartmentsPagePage.d.ts.map +1 -1
  21. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +110 -103
  22. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  23. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.d.ts.map +1 -1
  24. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +173 -162
  25. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  26. package/dist/crewfoundry/pages/HrPayrollPagePage.d.ts.map +1 -1
  27. package/dist/crewfoundry/pages/HrPayrollPagePage.js +169 -158
  28. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  29. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.d.ts.map +1 -1
  30. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +179 -163
  31. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  32. package/dist/utils/pageContext.d.ts +43 -4
  33. package/dist/utils/pageContext.d.ts.map +1 -1
  34. package/dist/utils/pageContext.js +10 -3
  35. package/dist/utils/pageContext.js.map +1 -1
  36. package/package.json +3 -2
@@ -1 +1 @@
1
- {"version":3,"file":"HrTimeTrackingPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrTimeTrackingPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GradientText,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { CheckCircle, ChevronLeft, ChevronRight, Clock, LogIn, LogOut, Timer } from 'lucide-react';\nimport { useCrewFoundryStore, type Employee, type TimeEntry } from '../store/crewfoundryStore';\nimport { AvatarStack, DonutChart, StatCard } from '../components/viz';\nimport { billableSplit } from '../lib/analytics';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { getBool, getNumber, getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nconst STATUS_OPTIONS = [\n { label: 'Pending', value: 'pending' },\n { label: 'Approved', value: 'approved' },\n { label: 'Rejected', value: 'rejected' },\n];\n\nconst CLOCK_OUT_FIELDS: FieldConfig[] = [\n {\n name: 'clockOut',\n label: 'Clock-out time',\n type: 'text',\n required: true,\n placeholder: 'e.g. 17:30',\n },\n { name: 'totalHours', label: 'Total hours', type: 'number', required: true },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n];\n\nconst MONTH_ABBR = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\nconst DOW = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];\n\nconst isoLocal = (date: Date): string =>\n `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(\n date.getDate()\n ).padStart(2, '0')}`;\n\n/** Local Monday that opens the week containing `date`. */\nconst startOfWeek = (date: Date): Date => {\n const x = new Date(date.getFullYear(), date.getMonth(), date.getDate());\n const day = (x.getDay() + 6) % 7; // Mon = 0\n x.setDate(x.getDate() - day);\n return x;\n};\n\nconst parseDay = (value: string): Date => {\n const [y, m, d] = value.slice(0, 10).split('-').map(Number);\n return new Date(y, (m || 1) - 1, d || 1);\n};\n\nexport function HrTimeTrackingPagePage() {\n const { timeEntries, employees, addTimeEntry, updateTimeEntry } = useCrewFoundryStore();\n const createForm = useFormModal<void>();\n const clockOutForm = useFormModal<TimeEntry>();\n const [entryPrefill, setEntryPrefill] = useState<{ employeeId: string; date: string } | null>(\n null\n );\n\n const employeeById = useMemo(\n () => new Map<string, Employee>(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const entryFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n { name: 'date', label: 'Date', type: 'date', required: true },\n { name: 'project', label: 'Project', type: 'text', required: true },\n {\n name: 'clockIn',\n label: 'Clock-in time',\n type: 'text',\n required: true,\n placeholder: 'e.g. 09:00',\n },\n { name: 'clockOut', label: 'Clock-out time', type: 'text', placeholder: 'e.g. 17:30' },\n { name: 'totalHours', label: 'Total hours', type: 'number' },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n { name: 'billable', label: 'Billable to client', type: 'switch', colSpan: 2 },\n ],\n [employeeOptions]\n );\n\n // ── Week scoping ───────────────────────────────────────────────────────────\n const [weekOffset, setWeekOffset] = useState(0);\n\n // Anchor the initial week on the most recent entry so the grid opens on data.\n const baseWeekStart = useMemo(() => {\n const latest = timeEntries\n .map((entry) => entry.date.slice(0, 10))\n .filter(Boolean)\n .sort()\n .slice(-1)[0];\n return startOfWeek(latest ? parseDay(latest) : new Date());\n }, [timeEntries]);\n\n const weekStart = useMemo(() => {\n const start = new Date(baseWeekStart);\n start.setDate(start.getDate() + weekOffset * 7);\n return start;\n }, [baseWeekStart, weekOffset]);\n\n const weekDays = useMemo(\n () =>\n Array.from({ length: 7 }, (_, i) => {\n const date = new Date(weekStart);\n date.setDate(weekStart.getDate() + i);\n return { iso: isoLocal(date), label: `${DOW[i]} ${date.getDate()}` };\n }),\n [weekStart]\n );\n const weekDaySet = useMemo(() => new Set(weekDays.map((d) => d.iso)), [weekDays]);\n\n const weekEnd = weekDays[6];\n const weekRangeLabel = `${MONTH_ABBR[weekStart.getMonth()]} ${weekStart.getDate()} – ${\n MONTH_ABBR[parseDay(weekEnd.iso).getMonth()]\n } ${parseDay(weekEnd.iso).getDate()}`;\n\n const weekEntries = useMemo(\n () => timeEntries.filter((entry) => weekDaySet.has(entry.date.slice(0, 10))),\n [timeEntries, weekDaySet]\n );\n\n // KPIs scoped to the selected week.\n const weekTotalHours = weekEntries.reduce((s, t) => s + t.totalHours, 0);\n const weekOvertime = weekEntries.reduce((s, t) => s + t.overtime, 0);\n const weekApproved = weekEntries.filter((t) => t.status === 'approved').length;\n const weekPending = weekEntries.filter((t) => t.status === 'pending').length;\n\n // Weekly timesheet rows: one per employee with entries this week.\n const timesheetRows = useMemo(() => {\n const byEmployee = new Map<\n string,\n {\n name: string;\n avatar?: string;\n perDay: Map<string, number>;\n total: number;\n statuses: Set<string>;\n }\n >();\n for (const entry of weekEntries) {\n let row = byEmployee.get(entry.employeeId);\n if (!row) {\n row = {\n name: entry.employeeName || employeeById.get(entry.employeeId)?.firstName || 'Unknown',\n avatar: employeeById.get(entry.employeeId)?.avatar,\n perDay: new Map<string, number>(),\n total: 0,\n statuses: new Set<string>(),\n };\n byEmployee.set(entry.employeeId, row);\n }\n const day = entry.date.slice(0, 10);\n row.perDay.set(day, (row.perDay.get(day) ?? 0) + entry.totalHours);\n row.total += entry.totalHours;\n row.statuses.add(entry.status);\n }\n return [...byEmployee.entries()]\n .map(([employeeId, row]) => ({\n employeeId,\n name: row.name,\n avatar: row.avatar,\n perDay: row.perDay,\n total: row.total,\n status: row.statuses.has('pending')\n ? 'pending'\n : row.statuses.has('rejected')\n ? 'rejected'\n : 'approved',\n }))\n .sort((a, b) => b.total - a.total);\n }, [weekEntries, employeeById]);\n\n // Donut: billable vs non-billable hours for the week, from the real\n // TimeEntry.billable flag. Null (empty state) when no hours are logged —\n // never a fabricated ratio.\n const billableDonut = useMemo(() => {\n const segments = billableSplit(weekEntries);\n if (!segments) {\n return null;\n }\n const total = segments.reduce((sum, seg) => sum + seg.value, 0);\n const billableHours = segments.find((seg) => seg.label === 'Billable')?.value ?? 0;\n const billablePct = total > 0 ? Math.round((billableHours / total) * 100) : 0;\n return { segments, centerValue: `${billablePct}%`, caption: 'Billable' };\n }, [weekEntries]);\n\n // Hours-by-project for the selected week.\n const projectBreakdown = weekEntries.reduce<Record<string, number>>((acc, t) => {\n acc[t.project] = (acc[t.project] || 0) + t.totalHours;\n return acc;\n }, {});\n const maxProjectHours = Math.max(...Object.values(projectBreakdown), 1);\n\n const recentEntries = useMemo(\n () => [...timeEntries].sort((a, b) => b.date.localeCompare(a.date)),\n [timeEntries]\n );\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 8 });\n const activityPageItems = recentEntries.slice(offset, offset + pageSize);\n\n const openCreate = (prefill?: { employeeId: string; date: string }) => {\n setEntryPrefill(prefill ?? null);\n createForm.openModal();\n };\n\n const handleCreate = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const payload: TimeEntry = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n date: getText(values, 'date'),\n clockIn: getText(values, 'clockIn'),\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n project: getText(values, 'project'),\n status: (getText(values, 'status') as TimeEntry['status']) || 'pending',\n billable: getBool(values, 'billable'),\n };\n await addTimeEntry(payload);\n };\n\n const handleClockOut = async (values: FormValues) => {\n const entry = clockOutForm.context;\n if (!entry) {\n return;\n }\n await updateTimeEntry(entry.id, {\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n status: (getText(values, 'status') as TimeEntry['status']) || entry.status,\n });\n };\n\n // Default `billable` on to mirror the backend default (true) for new entries.\n const createInitial: FormValues = {\n billable: true,\n ...(entryPrefill ? { employeeId: entryPrefill.employeeId, date: entryPrefill.date } : {}),\n };\n\n const clockOutInitial: FormValues | undefined = clockOutForm.context\n ? {\n clockOut: clockOutForm.context.clockOut,\n totalHours: clockOutForm.context.totalHours,\n overtime: clockOutForm.context.overtime,\n status: clockOutForm.context.status,\n }\n : undefined;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Time Tracking</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Monitor employee work hours and overtime</p>\n <PagePurpose className=\"mt-1\">\n Track hours and attendance across your team — use it to monitor logged time, overtime,\n and timesheet approvals.\n </PagePurpose>\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex items-center gap-1 rounded-lg border border-border-subtle p-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label=\"Previous week\"\n onClick={() => setWeekOffset((o) => o - 1)}\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </Button>\n <span className=\"min-w-[8.5rem] text-center text-sm font-medium tabular-nums\">\n {weekRangeLabel}\n </span>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label=\"Next week\"\n onClick={() => setWeekOffset((o) => o + 1)}\n >\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n </div>\n <Button size=\"sm\" onClick={() => openCreate()}>\n <Timer className=\"h-4 w-4 mr-1\" />\n Log Time\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Hours\"\n value={weekTotalHours.toFixed(1)}\n icon={<Clock className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Overtime\"\n value={`${weekOvertime.toFixed(1)}h`}\n icon={<Timer className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Approved\"\n value={weekApproved}\n icon={<CheckCircle className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Pending\" value={weekPending} icon={<Clock className=\"h-4 w-4\" />} />\n </div>\n\n <div className=\"grid gap-4 xl:grid-cols-[minmax(0,2.2fr)_minmax(0,1fr)]\">\n <Card>\n <CardHeader>\n <CardTitle>Timesheet</CardTitle>\n <CardDescription>\n {weekRangeLabel} · tap a day cell to log time for that person\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[46rem] border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-xs text-text-secondary\">\n <th className=\"px-2 py-2 text-left font-medium\">Employee</th>\n {weekDays.map((day) => (\n <th key={day.iso} className=\"px-1 py-2 text-center font-medium\">\n {day.label}\n </th>\n ))}\n <th className=\"px-2 py-2 text-right font-medium\">Total</th>\n <th className=\"px-2 py-2 text-center font-medium\">Status</th>\n </tr>\n </thead>\n <tbody>\n {timesheetRows.map((row) => (\n <tr key={row.employeeId} className=\"border-b border-border-subtle/60\">\n <td className=\"px-2 py-2\">\n <div className=\"flex items-center gap-2\">\n <AvatarStack\n people={[{ name: row.name, avatarUrl: row.avatar }]}\n size={28}\n />\n <span className=\"truncate font-medium\">{row.name}</span>\n </div>\n </td>\n {weekDays.map((day) => {\n const hours = row.perDay.get(day.iso) ?? 0;\n return (\n <td key={day.iso} className=\"px-1 py-2 text-center\">\n <button\n type=\"button\"\n onClick={() =>\n openCreate({ employeeId: row.employeeId, date: day.iso })\n }\n className={`w-full rounded-md border px-1 py-1.5 text-xs tabular-nums transition-colors ${\n hours > 0\n ? 'border-border-subtle text-text-primary hover:bg-bg-sunken'\n : 'border-transparent text-text-muted hover:border-border-subtle hover:bg-bg-sunken'\n }`}\n aria-label={`Log time for ${row.name} on ${day.label}`}\n >\n {hours.toFixed(1)}\n </button>\n </td>\n );\n })}\n <td className=\"px-2 py-2 text-right font-semibold tabular-nums\">\n {row.total.toFixed(1)}\n </td>\n <td className=\"px-2 py-2 text-center\">\n <Badge\n variant={\n row.status === 'approved'\n ? 'default'\n : row.status === 'pending'\n ? 'secondary'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {formatEnumLabel(row.status)}\n </Badge>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n {timesheetRows.length === 0 && (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n No time logged for {weekRangeLabel}.\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Billable split</CardTitle>\n <CardDescription>Billable vs non-billable hours for {weekRangeLabel}</CardDescription>\n </CardHeader>\n <CardContent className=\"flex flex-col items-center\">\n {billableDonut ? (\n <DonutChart\n segments={billableDonut.segments}\n centerLabel={billableDonut.centerValue}\n centerCaption={billableDonut.caption}\n valueFormatter={(v) => `${v.toFixed(1)}h`}\n title=\"Billable vs non-billable hours\"\n />\n ) : (\n <p className=\"py-10 text-center text-sm text-text-secondary\">\n No hours logged this week.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Hours by Project</CardTitle>\n <CardDescription>Time allocation for {weekRangeLabel}</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(projectBreakdown)\n .sort(([, a], [, b]) => b - a)\n .map(([project, hours]) => (\n <div key={project} className=\"flex items-center gap-3\">\n <span className=\"w-40 truncate text-sm\">{project}</span>\n <div className=\"h-4 flex-1 overflow-hidden rounded bg-bg-sunken\">\n <div\n className=\"h-4 rounded bg-action-primary-bg\"\n style={{ width: `${Math.round((hours / maxProjectHours) * 100)}%` }}\n />\n </div>\n <span className=\"w-14 text-right text-sm font-medium tabular-nums\">\n {hours.toFixed(1)}h\n </span>\n </div>\n ))}\n {Object.keys(projectBreakdown).length === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No project hours this week.\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Recent Activity</CardTitle>\n <CardDescription>Latest employee clock-in/out entries</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {activityPageItems.map((entry) => (\n <div key={entry.id} className=\"flex items-center justify-between rounded border p-3\">\n <div className=\"flex items-center gap-2.5\">\n <AvatarStack\n people={[\n {\n name: entry.employeeName,\n avatarUrl: employeeById.get(entry.employeeId)?.avatar,\n },\n ]}\n size={30}\n />\n <div>\n <p className=\"text-sm font-medium\">{entry.employeeName}</p>\n <p className=\"text-xs text-text-secondary\">{entry.project}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right text-xs\">\n <div className=\"flex items-center gap-1\">\n <LogIn className=\"h-3 w-3 text-status-success-text\" />\n {entry.clockIn}\n </div>\n <div className=\"flex items-center gap-1\">\n <LogOut className=\"h-3 w-3 text-status-error-text\" />\n {entry.clockOut}\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">{entry.totalHours}h</p>\n {entry.overtime > 0 && (\n <p className=\"text-xs text-status-warning-text\">+{entry.overtime}h OT</p>\n )}\n </div>\n <Badge\n variant={entry.status === 'approved' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(entry.status)}\n </Badge>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Clock out ${entry.employeeName}`}\n onClick={() => clockOutForm.openModal(entry)}\n >\n <LogOut className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n {recentEntries.length === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No time entries logged yet.\n </p>\n )}\n {recentEntries.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={recentEntries.length}\n onPageChange={setPage}\n />\n )}\n </CardContent>\n </Card>\n </div>\n\n <FormModal\n open={createForm.open}\n onOpenChange={createForm.onOpenChange}\n title=\"Log time\"\n description=\"Log a work session for a team member.\"\n fields={entryFields}\n initialValues={createInitial}\n submitLabel=\"Add entry\"\n onSubmit={handleCreate}\n />\n\n <FormModal\n open={clockOutForm.open}\n onOpenChange={clockOutForm.onOpenChange}\n title=\"Clock out\"\n description={\n clockOutForm.context\n ? `Record clock-out for ${clockOutForm.context.employeeName}.`\n : undefined\n }\n fields={CLOCK_OUT_FIELDS}\n initialValues={clockOutInitial}\n submitLabel=\"Save\"\n onSubmit={handleClockOut}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HrTimeTrackingPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrTimeTrackingPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GradientText,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { CheckCircle, ChevronLeft, ChevronRight, Clock, LogIn, LogOut, Timer } from 'lucide-react';\nimport { useCrewFoundryStore, type Employee, type TimeEntry } from '../store/crewfoundryStore';\nimport { AvatarStack, DonutChart, StatCard } from '../components/viz';\nimport { billableSplit } from '../lib/analytics';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { getBool, getNumber, getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { pageContaining, useFocusedRecordParam } from '../lib/useFocusedRecordParam';\n\nconst STATUS_OPTIONS = [\n { label: 'Pending', value: 'pending' },\n { label: 'Approved', value: 'approved' },\n { label: 'Rejected', value: 'rejected' },\n];\n\nconst CLOCK_OUT_FIELDS: FieldConfig[] = [\n {\n name: 'clockOut',\n label: 'Clock-out time',\n type: 'text',\n required: true,\n placeholder: 'e.g. 17:30',\n },\n { name: 'totalHours', label: 'Total hours', type: 'number', required: true },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n];\n\nconst MONTH_ABBR = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\nconst DOW = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];\n\nconst isoLocal = (date: Date): string =>\n `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(\n date.getDate()\n ).padStart(2, '0')}`;\n\n/** Local Monday that opens the week containing `date`. */\nconst startOfWeek = (date: Date): Date => {\n const x = new Date(date.getFullYear(), date.getMonth(), date.getDate());\n const day = (x.getDay() + 6) % 7; // Mon = 0\n x.setDate(x.getDate() - day);\n return x;\n};\n\nconst parseDay = (value: string): Date => {\n const [y, m, d] = value.slice(0, 10).split('-').map(Number);\n return new Date(y, (m || 1) - 1, d || 1);\n};\n\nexport function HrTimeTrackingPagePage() {\n const { timeEntries, employees, addTimeEntry, updateTimeEntry } = useCrewFoundryStore();\n const createForm = useFormModal<void>();\n const clockOutForm = useFormModal<TimeEntry>();\n const [entryPrefill, setEntryPrefill] = useState<{ employeeId: string; date: string } | null>(\n null\n );\n\n const employeeById = useMemo(\n () => new Map<string, Employee>(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const entryFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n { name: 'date', label: 'Date', type: 'date', required: true },\n { name: 'project', label: 'Project', type: 'text', required: true },\n {\n name: 'clockIn',\n label: 'Clock-in time',\n type: 'text',\n required: true,\n placeholder: 'e.g. 09:00',\n },\n { name: 'clockOut', label: 'Clock-out time', type: 'text', placeholder: 'e.g. 17:30' },\n { name: 'totalHours', label: 'Total hours', type: 'number' },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n { name: 'billable', label: 'Billable to client', type: 'switch', colSpan: 2 },\n ],\n [employeeOptions]\n );\n\n // ── Week scoping ───────────────────────────────────────────────────────────\n const [weekOffset, setWeekOffset] = useState(0);\n\n // Anchor the initial week on the most recent entry so the grid opens on data.\n const baseWeekStart = useMemo(() => {\n const latest = timeEntries\n .map((entry) => entry.date.slice(0, 10))\n .filter(Boolean)\n .sort()\n .slice(-1)[0];\n return startOfWeek(latest ? parseDay(latest) : new Date());\n }, [timeEntries]);\n\n const weekStart = useMemo(() => {\n const start = new Date(baseWeekStart);\n start.setDate(start.getDate() + weekOffset * 7);\n return start;\n }, [baseWeekStart, weekOffset]);\n\n const weekDays = useMemo(\n () =>\n Array.from({ length: 7 }, (_, i) => {\n const date = new Date(weekStart);\n date.setDate(weekStart.getDate() + i);\n return { iso: isoLocal(date), label: `${DOW[i]} ${date.getDate()}` };\n }),\n [weekStart]\n );\n const weekDaySet = useMemo(() => new Set(weekDays.map((d) => d.iso)), [weekDays]);\n\n const weekEnd = weekDays[6];\n const weekRangeLabel = `${MONTH_ABBR[weekStart.getMonth()]} ${weekStart.getDate()} – ${\n MONTH_ABBR[parseDay(weekEnd.iso).getMonth()]\n } ${parseDay(weekEnd.iso).getDate()}`;\n\n const weekEntries = useMemo(\n () => timeEntries.filter((entry) => weekDaySet.has(entry.date.slice(0, 10))),\n [timeEntries, weekDaySet]\n );\n\n // KPIs scoped to the selected week.\n const weekTotalHours = weekEntries.reduce((s, t) => s + t.totalHours, 0);\n const weekOvertime = weekEntries.reduce((s, t) => s + t.overtime, 0);\n const weekApproved = weekEntries.filter((t) => t.status === 'approved').length;\n const weekPending = weekEntries.filter((t) => t.status === 'pending').length;\n\n // Weekly timesheet rows: one per employee with entries this week.\n const timesheetRows = useMemo(() => {\n const byEmployee = new Map<\n string,\n {\n name: string;\n avatar?: string;\n perDay: Map<string, number>;\n total: number;\n statuses: Set<string>;\n }\n >();\n for (const entry of weekEntries) {\n let row = byEmployee.get(entry.employeeId);\n if (!row) {\n row = {\n name: entry.employeeName || employeeById.get(entry.employeeId)?.firstName || 'Unknown',\n avatar: employeeById.get(entry.employeeId)?.avatar,\n perDay: new Map<string, number>(),\n total: 0,\n statuses: new Set<string>(),\n };\n byEmployee.set(entry.employeeId, row);\n }\n const day = entry.date.slice(0, 10);\n row.perDay.set(day, (row.perDay.get(day) ?? 0) + entry.totalHours);\n row.total += entry.totalHours;\n row.statuses.add(entry.status);\n }\n return [...byEmployee.entries()]\n .map(([employeeId, row]) => ({\n employeeId,\n name: row.name,\n avatar: row.avatar,\n perDay: row.perDay,\n total: row.total,\n status: row.statuses.has('pending')\n ? 'pending'\n : row.statuses.has('rejected')\n ? 'rejected'\n : 'approved',\n }))\n .sort((a, b) => b.total - a.total);\n }, [weekEntries, employeeById]);\n\n // Donut: billable vs non-billable hours for the week, from the real\n // TimeEntry.billable flag. Null (empty state) when no hours are logged —\n // never a fabricated ratio.\n const billableDonut = useMemo(() => {\n const segments = billableSplit(weekEntries);\n if (!segments) {\n return null;\n }\n const total = segments.reduce((sum, seg) => sum + seg.value, 0);\n const billableHours = segments.find((seg) => seg.label === 'Billable')?.value ?? 0;\n const billablePct = total > 0 ? Math.round((billableHours / total) * 100) : 0;\n return { segments, centerValue: `${billablePct}%`, caption: 'Billable' };\n }, [weekEntries]);\n\n // Hours-by-project for the selected week.\n const projectBreakdown = weekEntries.reduce<Record<string, number>>((acc, t) => {\n acc[t.project] = (acc[t.project] || 0) + t.totalHours;\n return acc;\n }, {});\n const maxProjectHours = Math.max(...Object.values(projectBreakdown), 1);\n\n const recentEntries = useMemo(\n () => [...timeEntries].sort((a, b) => b.date.localeCompare(a.date)),\n [timeEntries]\n );\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 8 });\n const activityPageItems = recentEntries.slice(offset, offset + pageSize);\n\n // Selecting an entry (or opening its clock-out dialog) focuses it: it becomes\n // \"this time entry\" for the assistant (`?id=`) and the page is linkable to it.\n const [focusedId, setFocusedId] = useState<string | null>(null);\n const focusedEntryId = clockOutForm.open\n ? (clockOutForm.context?.id ?? null)\n : focusedId && timeEntries.some((entry) => entry.id === focusedId)\n ? focusedId\n : null;\n useFocusedRecordParam({\n records: timeEntries,\n activeId: focusedEntryId,\n onRestore: (entry) => {\n setFocusedId(entry.id);\n setPage(pageContaining(recentEntries, entry.id, pageSize));\n },\n });\n\n const openCreate = (prefill?: { employeeId: string; date: string }) => {\n setEntryPrefill(prefill ?? null);\n createForm.openModal();\n };\n\n const handleCreate = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const payload: TimeEntry = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n date: getText(values, 'date'),\n clockIn: getText(values, 'clockIn'),\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n project: getText(values, 'project'),\n status: (getText(values, 'status') as TimeEntry['status']) || 'pending',\n billable: getBool(values, 'billable'),\n };\n await addTimeEntry(payload);\n };\n\n const handleClockOut = async (values: FormValues) => {\n const entry = clockOutForm.context;\n if (!entry) {\n return;\n }\n await updateTimeEntry(entry.id, {\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n status: (getText(values, 'status') as TimeEntry['status']) || entry.status,\n });\n };\n\n // Default `billable` on to mirror the backend default (true) for new entries.\n const createInitial: FormValues = {\n billable: true,\n ...(entryPrefill ? { employeeId: entryPrefill.employeeId, date: entryPrefill.date } : {}),\n };\n\n const clockOutInitial: FormValues | undefined = clockOutForm.context\n ? {\n clockOut: clockOutForm.context.clockOut,\n totalHours: clockOutForm.context.totalHours,\n overtime: clockOutForm.context.overtime,\n status: clockOutForm.context.status,\n }\n : undefined;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Time Tracking</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Monitor employee work hours and overtime</p>\n <PagePurpose className=\"mt-1\">\n Track hours and attendance across your team — use it to monitor logged time, overtime,\n and timesheet approvals.\n </PagePurpose>\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex items-center gap-1 rounded-lg border border-border-subtle p-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label=\"Previous week\"\n onClick={() => setWeekOffset((o) => o - 1)}\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </Button>\n <span className=\"min-w-[8.5rem] text-center text-sm font-medium tabular-nums\">\n {weekRangeLabel}\n </span>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label=\"Next week\"\n onClick={() => setWeekOffset((o) => o + 1)}\n >\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n </div>\n <Button size=\"sm\" onClick={() => openCreate()}>\n <Timer className=\"h-4 w-4 mr-1\" />\n Log Time\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Hours\"\n value={weekTotalHours.toFixed(1)}\n icon={<Clock className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Overtime\"\n value={`${weekOvertime.toFixed(1)}h`}\n icon={<Timer className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Approved\"\n value={weekApproved}\n icon={<CheckCircle className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Pending\" value={weekPending} icon={<Clock className=\"h-4 w-4\" />} />\n </div>\n\n <div className=\"grid gap-4 xl:grid-cols-[minmax(0,2.2fr)_minmax(0,1fr)]\">\n <Card>\n <CardHeader>\n <CardTitle>Timesheet</CardTitle>\n <CardDescription>\n {weekRangeLabel} · tap a day cell to log time for that person\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[46rem] border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-xs text-text-secondary\">\n <th className=\"px-2 py-2 text-left font-medium\">Employee</th>\n {weekDays.map((day) => (\n <th key={day.iso} className=\"px-1 py-2 text-center font-medium\">\n {day.label}\n </th>\n ))}\n <th className=\"px-2 py-2 text-right font-medium\">Total</th>\n <th className=\"px-2 py-2 text-center font-medium\">Status</th>\n </tr>\n </thead>\n <tbody>\n {timesheetRows.map((row) => (\n <tr key={row.employeeId} className=\"border-b border-border-subtle/60\">\n <td className=\"px-2 py-2\">\n <div className=\"flex items-center gap-2\">\n <AvatarStack\n people={[{ name: row.name, avatarUrl: row.avatar }]}\n size={28}\n />\n <span className=\"truncate font-medium\">{row.name}</span>\n </div>\n </td>\n {weekDays.map((day) => {\n const hours = row.perDay.get(day.iso) ?? 0;\n return (\n <td key={day.iso} className=\"px-1 py-2 text-center\">\n <button\n type=\"button\"\n onClick={() =>\n openCreate({ employeeId: row.employeeId, date: day.iso })\n }\n className={`w-full rounded-md border px-1 py-1.5 text-xs tabular-nums transition-colors ${\n hours > 0\n ? 'border-border-subtle text-text-primary hover:bg-bg-sunken'\n : 'border-transparent text-text-muted hover:border-border-subtle hover:bg-bg-sunken'\n }`}\n aria-label={`Log time for ${row.name} on ${day.label}`}\n >\n {hours.toFixed(1)}\n </button>\n </td>\n );\n })}\n <td className=\"px-2 py-2 text-right font-semibold tabular-nums\">\n {row.total.toFixed(1)}\n </td>\n <td className=\"px-2 py-2 text-center\">\n <Badge\n variant={\n row.status === 'approved'\n ? 'default'\n : row.status === 'pending'\n ? 'secondary'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {formatEnumLabel(row.status)}\n </Badge>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n {timesheetRows.length === 0 && (\n <p className=\"py-8 text-center text-sm text-text-secondary\">\n No time logged for {weekRangeLabel}.\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Billable split</CardTitle>\n <CardDescription>Billable vs non-billable hours for {weekRangeLabel}</CardDescription>\n </CardHeader>\n <CardContent className=\"flex flex-col items-center\">\n {billableDonut ? (\n <DonutChart\n segments={billableDonut.segments}\n centerLabel={billableDonut.centerValue}\n centerCaption={billableDonut.caption}\n valueFormatter={(v) => `${v.toFixed(1)}h`}\n title=\"Billable vs non-billable hours\"\n />\n ) : (\n <p className=\"py-10 text-center text-sm text-text-secondary\">\n No hours logged this week.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Hours by Project</CardTitle>\n <CardDescription>Time allocation for {weekRangeLabel}</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(projectBreakdown)\n .sort(([, a], [, b]) => b - a)\n .map(([project, hours]) => (\n <div key={project} className=\"flex items-center gap-3\">\n <span className=\"w-40 truncate text-sm\">{project}</span>\n <div className=\"h-4 flex-1 overflow-hidden rounded bg-bg-sunken\">\n <div\n className=\"h-4 rounded bg-action-primary-bg\"\n style={{ width: `${Math.round((hours / maxProjectHours) * 100)}%` }}\n />\n </div>\n <span className=\"w-14 text-right text-sm font-medium tabular-nums\">\n {hours.toFixed(1)}h\n </span>\n </div>\n ))}\n {Object.keys(projectBreakdown).length === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No project hours this week.\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Recent Activity</CardTitle>\n <CardDescription>Latest employee clock-in/out entries</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {activityPageItems.map((entry) => (\n <div\n key={entry.id}\n aria-current={entry.id === focusedEntryId ? 'true' : undefined}\n className={`flex items-center justify-between rounded border p-3 transition-colors ${\n entry.id === focusedEntryId\n ? 'border-action-primary-bg bg-action-primary-bg/10'\n : ''\n }`}\n >\n <button\n type=\"button\"\n aria-pressed={entry.id === focusedEntryId}\n aria-label={`Select time entry for ${entry.employeeName}`}\n onClick={() => setFocusedId(entry.id)}\n className=\"flex min-w-0 items-center gap-2.5 text-left\"\n >\n <AvatarStack\n people={[\n {\n name: entry.employeeName,\n avatarUrl: employeeById.get(entry.employeeId)?.avatar,\n },\n ]}\n size={30}\n />\n <div>\n <p className=\"text-sm font-medium\">{entry.employeeName}</p>\n <p className=\"text-xs text-text-secondary\">{entry.project}</p>\n </div>\n </button>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right text-xs\">\n <div className=\"flex items-center gap-1\">\n <LogIn className=\"h-3 w-3 text-status-success-text\" />\n {entry.clockIn}\n </div>\n <div className=\"flex items-center gap-1\">\n <LogOut className=\"h-3 w-3 text-status-error-text\" />\n {entry.clockOut}\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">{entry.totalHours}h</p>\n {entry.overtime > 0 && (\n <p className=\"text-xs text-status-warning-text\">+{entry.overtime}h OT</p>\n )}\n </div>\n <Badge\n variant={entry.status === 'approved' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(entry.status)}\n </Badge>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Clock out ${entry.employeeName}`}\n onClick={() => {\n setFocusedId(entry.id);\n clockOutForm.openModal(entry);\n }}\n >\n <LogOut className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n {recentEntries.length === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No time entries logged yet.\n </p>\n )}\n {recentEntries.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={recentEntries.length}\n onPageChange={setPage}\n />\n )}\n </CardContent>\n </Card>\n </div>\n\n <FormModal\n open={createForm.open}\n onOpenChange={createForm.onOpenChange}\n title=\"Log time\"\n description=\"Log a work session for a team member.\"\n fields={entryFields}\n initialValues={createInitial}\n submitLabel=\"Add entry\"\n onSubmit={handleCreate}\n />\n\n <FormModal\n open={clockOutForm.open}\n onOpenChange={clockOutForm.onOpenChange}\n title=\"Clock out\"\n description={\n clockOutForm.context\n ? `Record clock-out for ${clockOutForm.context.employeeName}.`\n : undefined\n }\n fields={CLOCK_OUT_FIELDS}\n initialValues={clockOutInitial}\n submitLabel=\"Save\"\n onSubmit={handleClockOut}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -10,7 +10,17 @@ interface ConversationContextShape {
10
10
  activeTab?: string;
11
11
  metadata?: Record<string, unknown>;
12
12
  }
13
- export interface PageContext {
13
+ /**
14
+ * What the assistant is told about the page.
15
+ *
16
+ * A `type` alias rather than an interface so it stays assignable to the shared
17
+ * transport's index-signature `PageContext`. Every key here is on the agent's
18
+ * `ALLOWED_CONTEXT_KEYS` list (ai-assistant `src/context-loader.ts`); keys off
19
+ * that list are transmitted and then dropped, so the platform contract
20
+ * (HOW_TO_INTEGRATE_AI_ASSISTANT.md, Step 3) forbids sending `userAgent`,
21
+ * `timestamp` or anything else that would not be logged.
22
+ */
23
+ export type PageContext = {
14
24
  currentUrl: string;
15
25
  currentPage: string;
16
26
  entityType?: string;
@@ -18,10 +28,39 @@ export interface PageContext {
18
28
  platform: string;
19
29
  version: string;
20
30
  viewportSize: string;
21
- userAgent: string;
22
- timestamp: string;
23
- }
31
+ };
24
32
  export declare function gatherPageContext(): PageContext;
33
+ /**
34
+ * Every page-context entity type the assistant can receive from CrewFoundry.
35
+ *
36
+ * Each one is a record `wspace-crewfoundry-svc` owns, with a single-record
37
+ * read the assistant's CrewFoundry guide names (ai-assistant
38
+ * `context/api-calls/products/crewfoundry.md`, "Page context"). Keep the two in
39
+ * step: the guide's tests pin this exact list.
40
+ */
41
+ export declare const CREWFOUNDRY_ENTITY_TYPES: readonly ["CREWFOUNDRY_JOB_POSTING", "CREWFOUNDRY_CANDIDATE", "CREWFOUNDRY_INTERVIEW", "CREWFOUNDRY_EMPLOYEE", "CREWFOUNDRY_DEPARTMENT", "CREWFOUNDRY_TEAM", "CREWFOUNDRY_PROJECT", "CREWFOUNDRY_LEAVE_REQUEST", "CREWFOUNDRY_TIME_ENTRY", "CREWFOUNDRY_PAYROLL_RECORD", "CREWFOUNDRY_CLIENT", "CREWFOUNDRY_VENDOR", "CREWFOUNDRY_ANNOUNCEMENT", "CREWFOUNDRY_COURSE", "CREWFOUNDRY_SERVICE"];
42
+ export type CrewFoundryEntityType = (typeof CREWFOUNDRY_ENTITY_TYPES)[number];
43
+ /**
44
+ * Pages that identify a single record, keyed by their `<section>/<page>` route.
45
+ *
46
+ * Every route in `CrewFoundryRoutes` is a flat two-segment path with the record
47
+ * id in the `id` SEARCH PARAM, never a path segment:
48
+ *
49
+ * - detail pages read it (`hiring/job-details-page?id=…`);
50
+ * - list pages write it while a record is focused in place — a selected row or
51
+ * an open edit dialog — through `useFocusedRecordParam`
52
+ * (`src/crewfoundry/lib/useFocusedRecordParam.ts`).
53
+ *
54
+ * Matching is done on the last two path segments rather than an absolute path
55
+ * so the mapping survives the shell mounting the MFE under a different
56
+ * `basePath`.
57
+ *
58
+ * `CREWFOUNDRY_COURSE` is a `CrewTrainingCourse` and `CREWFOUNDRY_SERVICE` a
59
+ * `CrewServiceListing`: both are owned by the service's Learning and
60
+ * talent-marketplace modules since BOFF-4201/BOFF-4203 (BOFF-3401 retired the
61
+ * mocked surfaces that used to sit on these routes).
62
+ */
63
+ export declare const ENTITY_ROUTES: ReadonlyArray<readonly [route: string, entityType: CrewFoundryEntityType]>;
25
64
  export declare function resolveCrewFoundryConversationContext(pathname: string, search: string): ConversationContextShape;
26
65
  export {};
27
66
  //# sourceMappingURL=pageContext.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pageContext.d.ts","sourceRoot":"","sources":["../../src/utils/pageContext.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAE1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAoB/C;AA2BD,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,wBAAwB,CA2B1B"}
1
+ {"version":3,"file":"pageContext.d.ts","sourceRoot":"","sources":["../../src/utils/pageContext.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,WAAW,GAAG;IAExB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,iBAAiB,IAAI,WAAW,CAkB/C;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,+XAgB3B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,aAAa,EAAE,aAAa,CACvC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,qBAAqB,CAAC,CAkB5D,CAAC;AAEF,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,wBAAwB,CA2B1B"}
@@ -8,17 +8,24 @@ function e() {
8
8
  entityId: r.entityId,
9
9
  platform: "crewfoundry",
10
10
  version: "2026.704.1",
11
- viewportSize: `${window.innerWidth}x${window.innerHeight}`,
12
- userAgent: navigator.userAgent,
13
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
11
+ viewportSize: `${window.innerWidth}x${window.innerHeight}`
14
12
  };
15
13
  }
16
14
  var t = [
17
15
  ["hiring/job-details-page", "CREWFOUNDRY_JOB_POSTING"],
16
+ ["hiring/candidates-page", "CREWFOUNDRY_CANDIDATE"],
17
+ ["hiring/interviews-page", "CREWFOUNDRY_INTERVIEW"],
18
18
  ["hr/employee-profile-page", "CREWFOUNDRY_EMPLOYEE"],
19
+ ["hr/departments-page", "CREWFOUNDRY_DEPARTMENT"],
20
+ ["hr/leave-management-page", "CREWFOUNDRY_LEAVE_REQUEST"],
21
+ ["hr/time-tracking-page", "CREWFOUNDRY_TIME_ENTRY"],
22
+ ["hr/payroll-page", "CREWFOUNDRY_PAYROLL_RECORD"],
19
23
  ["teams/team-detail-page", "CREWFOUNDRY_TEAM"],
20
24
  ["teams/team-settings-page", "CREWFOUNDRY_TEAM"],
21
25
  ["projects/project-details-page", "CREWFOUNDRY_PROJECT"],
26
+ ["company/client-management-page", "CREWFOUNDRY_CLIENT"],
27
+ ["company/vendor-management-page", "CREWFOUNDRY_VENDOR"],
28
+ ["communications/announcements-page", "CREWFOUNDRY_ANNOUNCEMENT"],
22
29
  ["learning/course-details-page", "CREWFOUNDRY_COURSE"],
23
30
  ["marketplace/service-details-page", "CREWFOUNDRY_SERVICE"]
24
31
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering for CrewFoundry.\n *\n * Captures the user's current browser context to be sent alongside every\n * assistant prompt for better AI awareness.\n */\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface PageContext {\n // `currentUrl` matches the agent's allowed-context-key contract.\n currentUrl: string;\n currentPage: string;\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveCrewFoundryConversationContext(\n window.location.pathname,\n window.location.search\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'crewfoundry',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.704.1',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\n/**\n * Detail surfaces that identify a single record, keyed by their `<section>/<page>`\n * route.\n *\n * Every detail route in `CrewFoundryRoutes` is a flat two-segment path with the\n * record id in the `id` SEARCH PARAM — `/crewfoundry/hiring/job-details-page?id=…`\n * — never a path segment. Matching is done on the last two path segments rather\n * than an absolute path so the mapping survives the shell mounting the MFE under\n * a different `basePath`.\n *\n * `CREWFOUNDRY_CANDIDATE` is deliberately absent: candidates are selected inside\n * `hiring/candidates-page` without writing an id to the URL, so no navigable\n * location identifies one. Adding a pattern for it would only ever produce a\n * context key that never fires.\n */\nconst ENTITY_ROUTES: ReadonlyArray<readonly [route: string, entityType: string]> = [\n ['hiring/job-details-page', 'CREWFOUNDRY_JOB_POSTING'],\n ['hr/employee-profile-page', 'CREWFOUNDRY_EMPLOYEE'],\n ['teams/team-detail-page', 'CREWFOUNDRY_TEAM'],\n ['teams/team-settings-page', 'CREWFOUNDRY_TEAM'],\n ['projects/project-details-page', 'CREWFOUNDRY_PROJECT'],\n ['learning/course-details-page', 'CREWFOUNDRY_COURSE'],\n ['marketplace/service-details-page', 'CREWFOUNDRY_SERVICE'],\n];\n\nexport function resolveCrewFoundryConversationContext(\n pathname: string,\n search: string\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n const segments = pathname.split('/').filter(Boolean);\n const route = segments.slice(-2).join('/');\n const entityId = params.get('id');\n\n if (entityId) {\n const matched = ENTITY_ROUTES.find(([candidateRoute]) => candidateRoute === route);\n if (matched) {\n return {\n entityType: matched[1],\n entityId,\n metadata: { route },\n };\n }\n }\n\n const activeTab =\n params.get('tab') ?? (segments.length > 2 ? segments[segments.length - 1] : undefined);\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n ...(params.get('selectedId') ? { selectedId: params.get('selectedId') } : {}),\n },\n };\n}\n"],"mappings":";AA2BA,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CACvC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,IAAI,QACpD,IAAS,EACb,OAAO,SAAS,UAChB,OAAO,SAAS,OACjB;AAED,QAAO;EACL,YAAY;EACZ;EACA,YAAY,EAAO;EACnB,UAAU,EAAO;EACjB,UAAU;EACV,SAA6C;EAC7C,cAAc,GAAG,OAAO,WAAW,GAAG,OAAO;EAC7C,WAAW,UAAU;EACrB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC;;AAkBH,IAAM,IAA6E;CACjF,CAAC,2BAA2B,0BAA0B;CACtD,CAAC,4BAA4B,uBAAuB;CACpD,CAAC,0BAA0B,mBAAmB;CAC9C,CAAC,4BAA4B,mBAAmB;CAChD,CAAC,iCAAiC,sBAAsB;CACxD,CAAC,gCAAgC,qBAAqB;CACtD,CAAC,oCAAoC,sBAAsB;CAC5D;AAED,SAAgB,EACd,GACA,GAC0B;CAC1B,IAAM,IAAS,IAAI,gBAAgB,EAAO,EACpC,IAAW,EAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,EAC9C,IAAQ,EAAS,MAAM,GAAG,CAAC,KAAK,IAAI,EACpC,IAAW,EAAO,IAAI,KAAK;AAEjC,KAAI,GAAU;EACZ,IAAM,IAAU,EAAc,MAAM,CAAC,OAAoB,MAAmB,EAAM;AAClF,MAAI,EACF,QAAO;GACL,YAAY,EAAQ;GACpB;GACA,UAAU,EAAE,UAAO;GACpB;;CAIL,IAAM,IACJ,EAAO,IAAI,MAAM,KAAK,EAAS,SAAS,IAAI,EAAS,EAAS,SAAS,KAAK,KAAA;AAE9E,QAAO;EACL,WAAW,KAAa,KAAA;EACxB,UAAU;GACR,GAAI,IAAY,EAAE,cAAW,GAAG,EAAE;GAClC,GAAI,EAAO,IAAI,aAAa,GAAG,EAAE,YAAY,EAAO,IAAI,aAAa,EAAE,GAAG,EAAE;GAC7E;EACF"}
1
+ {"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering for CrewFoundry.\n *\n * Captures the user's current browser context to be sent alongside every\n * assistant prompt for better AI awareness.\n */\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * What the assistant is told about the page.\n *\n * A `type` alias rather than an interface so it stays assignable to the shared\n * transport's index-signature `PageContext`. Every key here is on the agent's\n * `ALLOWED_CONTEXT_KEYS` list (ai-assistant `src/context-loader.ts`); keys off\n * that list are transmitted and then dropped, so the platform contract\n * (HOW_TO_INTEGRATE_AI_ASSISTANT.md, Step 3) forbids sending `userAgent`,\n * `timestamp` or anything else that would not be logged.\n */\nexport type PageContext = {\n // `currentUrl` matches the agent's allowed-context-key contract.\n currentUrl: string;\n currentPage: string;\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n};\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveCrewFoundryConversationContext(\n window.location.pathname,\n window.location.search\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'crewfoundry',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.704.1',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n };\n}\n\n/**\n * Every page-context entity type the assistant can receive from CrewFoundry.\n *\n * Each one is a record `wspace-crewfoundry-svc` owns, with a single-record\n * read the assistant's CrewFoundry guide names (ai-assistant\n * `context/api-calls/products/crewfoundry.md`, \"Page context\"). Keep the two in\n * step: the guide's tests pin this exact list.\n */\nexport const CREWFOUNDRY_ENTITY_TYPES = [\n 'CREWFOUNDRY_JOB_POSTING',\n 'CREWFOUNDRY_CANDIDATE',\n 'CREWFOUNDRY_INTERVIEW',\n 'CREWFOUNDRY_EMPLOYEE',\n 'CREWFOUNDRY_DEPARTMENT',\n 'CREWFOUNDRY_TEAM',\n 'CREWFOUNDRY_PROJECT',\n 'CREWFOUNDRY_LEAVE_REQUEST',\n 'CREWFOUNDRY_TIME_ENTRY',\n 'CREWFOUNDRY_PAYROLL_RECORD',\n 'CREWFOUNDRY_CLIENT',\n 'CREWFOUNDRY_VENDOR',\n 'CREWFOUNDRY_ANNOUNCEMENT',\n 'CREWFOUNDRY_COURSE',\n 'CREWFOUNDRY_SERVICE',\n] as const;\n\nexport type CrewFoundryEntityType = (typeof CREWFOUNDRY_ENTITY_TYPES)[number];\n\n/**\n * Pages that identify a single record, keyed by their `<section>/<page>` route.\n *\n * Every route in `CrewFoundryRoutes` is a flat two-segment path with the record\n * id in the `id` SEARCH PARAM, never a path segment:\n *\n * - detail pages read it (`hiring/job-details-page?id=…`);\n * - list pages write it while a record is focused in place — a selected row or\n * an open edit dialog — through `useFocusedRecordParam`\n * (`src/crewfoundry/lib/useFocusedRecordParam.ts`).\n *\n * Matching is done on the last two path segments rather than an absolute path\n * so the mapping survives the shell mounting the MFE under a different\n * `basePath`.\n *\n * `CREWFOUNDRY_COURSE` is a `CrewTrainingCourse` and `CREWFOUNDRY_SERVICE` a\n * `CrewServiceListing`: both are owned by the service's Learning and\n * talent-marketplace modules since BOFF-4201/BOFF-4203 (BOFF-3401 retired the\n * mocked surfaces that used to sit on these routes).\n */\nexport const ENTITY_ROUTES: ReadonlyArray<\n readonly [route: string, entityType: CrewFoundryEntityType]\n> = [\n ['hiring/job-details-page', 'CREWFOUNDRY_JOB_POSTING'],\n ['hiring/candidates-page', 'CREWFOUNDRY_CANDIDATE'],\n ['hiring/interviews-page', 'CREWFOUNDRY_INTERVIEW'],\n ['hr/employee-profile-page', 'CREWFOUNDRY_EMPLOYEE'],\n ['hr/departments-page', 'CREWFOUNDRY_DEPARTMENT'],\n ['hr/leave-management-page', 'CREWFOUNDRY_LEAVE_REQUEST'],\n ['hr/time-tracking-page', 'CREWFOUNDRY_TIME_ENTRY'],\n ['hr/payroll-page', 'CREWFOUNDRY_PAYROLL_RECORD'],\n ['teams/team-detail-page', 'CREWFOUNDRY_TEAM'],\n ['teams/team-settings-page', 'CREWFOUNDRY_TEAM'],\n ['projects/project-details-page', 'CREWFOUNDRY_PROJECT'],\n ['company/client-management-page', 'CREWFOUNDRY_CLIENT'],\n ['company/vendor-management-page', 'CREWFOUNDRY_VENDOR'],\n ['communications/announcements-page', 'CREWFOUNDRY_ANNOUNCEMENT'],\n ['learning/course-details-page', 'CREWFOUNDRY_COURSE'],\n ['marketplace/service-details-page', 'CREWFOUNDRY_SERVICE'],\n];\n\nexport function resolveCrewFoundryConversationContext(\n pathname: string,\n search: string\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n const segments = pathname.split('/').filter(Boolean);\n const route = segments.slice(-2).join('/');\n const entityId = params.get('id');\n\n if (entityId) {\n const matched = ENTITY_ROUTES.find(([candidateRoute]) => candidateRoute === route);\n if (matched) {\n return {\n entityType: matched[1],\n entityId,\n metadata: { route },\n };\n }\n }\n\n const activeTab =\n params.get('tab') ?? (segments.length > 2 ? segments[segments.length - 1] : undefined);\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n ...(params.get('selectedId') ? { selectedId: params.get('selectedId') } : {}),\n },\n };\n}\n"],"mappings":";AAmCA,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CACvC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,IAAI,QACpD,IAAS,EACb,OAAO,SAAS,UAChB,OAAO,SAAS,OACjB;AAED,QAAO;EACL,YAAY;EACZ;EACA,YAAY,EAAO;EACnB,UAAU,EAAO;EACjB,UAAU;EACV,SAA6C;EAC7C,cAAc,GAAG,OAAO,WAAW,GAAG,OAAO;EAC9C;;AAmDH,IAAa,IAET;CACF,CAAC,2BAA2B,0BAA0B;CACtD,CAAC,0BAA0B,wBAAwB;CACnD,CAAC,0BAA0B,wBAAwB;CACnD,CAAC,4BAA4B,uBAAuB;CACpD,CAAC,uBAAuB,yBAAyB;CACjD,CAAC,4BAA4B,4BAA4B;CACzD,CAAC,yBAAyB,yBAAyB;CACnD,CAAC,mBAAmB,6BAA6B;CACjD,CAAC,0BAA0B,mBAAmB;CAC9C,CAAC,4BAA4B,mBAAmB;CAChD,CAAC,iCAAiC,sBAAsB;CACxD,CAAC,kCAAkC,qBAAqB;CACxD,CAAC,kCAAkC,qBAAqB;CACxD,CAAC,qCAAqC,2BAA2B;CACjE,CAAC,gCAAgC,qBAAqB;CACtD,CAAC,oCAAoC,sBAAsB;CAC5D;AAED,SAAgB,EACd,GACA,GAC0B;CAC1B,IAAM,IAAS,IAAI,gBAAgB,EAAO,EACpC,IAAW,EAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,EAC9C,IAAQ,EAAS,MAAM,GAAG,CAAC,KAAK,IAAI,EACpC,IAAW,EAAO,IAAI,KAAK;AAEjC,KAAI,GAAU;EACZ,IAAM,IAAU,EAAc,MAAM,CAAC,OAAoB,MAAmB,EAAM;AAClF,MAAI,EACF,QAAO;GACL,YAAY,EAAQ;GACpB;GACA,UAAU,EAAE,UAAO;GACpB;;CAIL,IAAM,IACJ,EAAO,IAAI,MAAM,KAAK,EAAS,SAAS,IAAI,EAAS,EAAS,SAAS,KAAK,KAAA;AAE9E,QAAO;EACL,WAAW,KAAa,KAAA;EACxB,UAAU;GACR,GAAI,IAAY,EAAE,cAAW,GAAG,EAAE;GAClC,GAAI,EAAO,IAAI,aAAa,GAAG,EAAE,YAAY,EAAO,IAAI,aAAa,EAAE,GAAG,EAAE;GAC7E;EACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-crewfoundry",
3
- "version": "2026.1029.2",
3
+ "version": "2026.1030.1",
4
4
  "description": "Burdenoff CrewFoundry Microfrontend",
5
5
  "type": "module",
6
6
  "files": [
@@ -31,7 +31,8 @@
31
31
  "lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",
32
32
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
33
33
  "type:check": "bash scripts/build-limits.sh tsc --noEmit",
34
- "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run build'",
34
+ "test": "bun test ./tests",
35
+ "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run test && bun run build'",
35
36
  "schema:pull": "graphql-codegen --project schema-wspace && graphql-codegen --project schema-global",
36
37
  "codegen": "graphql-codegen --project wspace && graphql-codegen --project global",
37
38
  "codegen:all": "bun run schema:pull && bun run codegen",