@burdenoff/microfe-proserviceworld 2026.1028.1 → 2026.1031.2
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/dist/generated/global-types.d.ts +9699 -583
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +18076 -2999
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/proserviceworld/__tests__/schemaStub.d.ts +20 -0
- package/dist/proserviceworld/__tests__/schemaStub.d.ts.map +1 -0
- package/dist/proserviceworld/components/CredentialDirectory.js +44 -43
- package/dist/proserviceworld/components/CredentialDirectory.js.map +1 -1
- package/dist/proserviceworld/components/CredentialReminderPanel.js +5 -4
- package/dist/proserviceworld/components/CredentialReminderPanel.js.map +1 -1
- package/dist/proserviceworld/components/ui.d.ts +7 -3
- package/dist/proserviceworld/components/ui.d.ts.map +1 -1
- package/dist/proserviceworld/components/ui.js +135 -121
- package/dist/proserviceworld/components/ui.js.map +1 -1
- package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts.map +1 -1
- package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -1
- package/dist/proserviceworld/hooks/useTr.d.ts +49 -8
- package/dist/proserviceworld/hooks/useTr.d.ts.map +1 -1
- package/dist/proserviceworld/hooks/useTr.js +45 -21
- package/dist/proserviceworld/hooks/useTr.js.map +1 -1
- package/dist/proserviceworld/i18n/catalog/analytics.d.ts +5 -0
- package/dist/proserviceworld/i18n/catalog/analytics.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/analytics.js +271 -0
- package/dist/proserviceworld/i18n/catalog/analytics.js.map +1 -0
- package/dist/proserviceworld/i18n/catalog/common.d.ts +9 -0
- package/dist/proserviceworld/i18n/catalog/common.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/common.js +163 -0
- package/dist/proserviceworld/i18n/catalog/common.js.map +1 -0
- package/dist/proserviceworld/i18n/catalog/compliance.d.ts +5 -0
- package/dist/proserviceworld/i18n/catalog/compliance.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/compliance.js +157 -0
- package/dist/proserviceworld/i18n/catalog/compliance.js.map +1 -0
- package/dist/proserviceworld/i18n/catalog/conflicts.d.ts +5 -0
- package/dist/proserviceworld/i18n/catalog/conflicts.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/conflicts.js +145 -0
- package/dist/proserviceworld/i18n/catalog/conflicts.js.map +1 -0
- package/dist/proserviceworld/i18n/catalog/documents.d.ts +5 -0
- package/dist/proserviceworld/i18n/catalog/documents.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/documents.js +157 -0
- package/dist/proserviceworld/i18n/catalog/documents.js.map +1 -0
- package/dist/proserviceworld/i18n/catalog/enums.d.ts +10 -0
- package/dist/proserviceworld/i18n/catalog/enums.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/enums.js +481 -0
- package/dist/proserviceworld/i18n/catalog/enums.js.map +1 -0
- package/dist/proserviceworld/i18n/catalog/index.d.ts +9 -0
- package/dist/proserviceworld/i18n/catalog/index.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/index.js +25 -0
- package/dist/proserviceworld/i18n/catalog/index.js.map +1 -0
- package/dist/proserviceworld/i18n/catalog/invoices.d.ts +5 -0
- package/dist/proserviceworld/i18n/catalog/invoices.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/invoices.js +247 -0
- package/dist/proserviceworld/i18n/catalog/invoices.js.map +1 -0
- package/dist/proserviceworld/i18n/catalog/practiceAreas.d.ts +5 -0
- package/dist/proserviceworld/i18n/catalog/practiceAreas.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/practiceAreas.js +97 -0
- package/dist/proserviceworld/i18n/catalog/practiceAreas.js.map +1 -0
- package/dist/proserviceworld/i18n/catalog/time.d.ts +5 -0
- package/dist/proserviceworld/i18n/catalog/time.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/catalog/time.js +301 -0
- package/dist/proserviceworld/i18n/catalog/time.js.map +1 -0
- package/dist/proserviceworld/i18n/index.d.ts +10 -9
- package/dist/proserviceworld/i18n/index.d.ts.map +1 -1
- package/dist/proserviceworld/i18n/index.js +16 -12
- package/dist/proserviceworld/i18n/index.js.map +1 -1
- package/dist/proserviceworld/i18n/types.d.ts +13 -0
- package/dist/proserviceworld/i18n/types.d.ts.map +1 -0
- package/dist/proserviceworld/i18n/types.js +18 -0
- package/dist/proserviceworld/i18n/types.js.map +1 -0
- package/dist/proserviceworld/pages/AnalyticsPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/AnalyticsPage.js +209 -197
- package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
- package/dist/proserviceworld/pages/ClientDetailPage.js +99 -98
- package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/ClientsPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/ClientsPage.js +181 -181
- package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
- package/dist/proserviceworld/pages/CompliancePage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/CompliancePage.js +210 -209
- package/dist/proserviceworld/pages/CompliancePage.js.map +1 -1
- package/dist/proserviceworld/pages/ConflictsPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/ConflictsPage.js +139 -138
- package/dist/proserviceworld/pages/ConflictsPage.js.map +1 -1
- package/dist/proserviceworld/pages/DocumentsPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/DocumentsPage.js +150 -149
- package/dist/proserviceworld/pages/DocumentsPage.js.map +1 -1
- package/dist/proserviceworld/pages/EngagementDetailPage.js +105 -104
- package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -1
- package/dist/proserviceworld/pages/EngagementsPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/EngagementsPage.js +137 -137
- package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
- package/dist/proserviceworld/pages/InvoicesPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/InvoicesPage.js +257 -256
- package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
- package/dist/proserviceworld/pages/MattersPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/MattersPage.js +166 -166
- package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
- package/dist/proserviceworld/pages/PracticeAreasPage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/PracticeAreasPage.js +100 -99
- package/dist/proserviceworld/pages/PracticeAreasPage.js.map +1 -1
- package/dist/proserviceworld/pages/PractitionerCredentialsPage.js +30 -29
- package/dist/proserviceworld/pages/PractitionerCredentialsPage.js.map +1 -1
- package/dist/proserviceworld/pages/TimePage.d.ts.map +1 -1
- package/dist/proserviceworld/pages/TimePage.js +343 -350
- package/dist/proserviceworld/pages/TimePage.js.map +1 -1
- package/dist/proserviceworld/utils/humanize.d.ts +3 -0
- package/dist/proserviceworld/utils/humanize.d.ts.map +1 -0
- package/dist/proserviceworld/utils/humanize.js +6 -0
- package/dist/proserviceworld/utils/humanize.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimePage.js","names":[],"sources":["../../../src/proserviceworld/pages/TimePage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useDeferredValue, useState, type ChangeEvent } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n Plus,\n Clock,\n Send,\n CheckCircle2,\n ListChecks,\n Wallet,\n CircleSlash,\n Search,\n Play,\n Square,\n Trash2,\n} from 'lucide-react';\nimport {\n useTimeEntries,\n useLogTimeEntry,\n useSubmitTimeEntry,\n useApproveTimeEntry,\n useUpdateTimeEntry,\n useRejectTimeEntry,\n useEngagements,\n useMatters,\n type PswTimeEntry,\n type PswTimeEntryStatus,\n} from '../hooks/useProServiceWorldApi';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n MetricCard,\n TableWrap,\n Th,\n Td,\n enumOptions,\n fmtDate,\n fmtMinutesAsHours,\n} from '../components/ui';\nimport { useAnalytics } from '../hooks/useAnalytics';\nimport {\n elapsedMinutes,\n formatTimerDuration,\n useTimeTimer,\n type TimeTimer,\n type TimeTimerDraft,\n} from '../hooks/useTimeTimer';\n\nconst STATUSES: PswTimeEntryStatus[] = ['DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED', 'BILLED'];\n\n/** Compact billable / non-billable tag. */\nconst BillableTag: FC<{ billable: boolean }> = ({ billable }) =>\n billable ? (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current opacity-70\" />\n Billable\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-tertiary\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current opacity-60\" />\n Non-billable\n </span>\n );\n\nexport const TimePage: FC = () => {\n const [statusFilter, setStatusFilter] = useState<PswTimeEntryStatus | ''>('');\n const [search, setSearch] = useState('');\n const deferredSearch = useDeferredValue(search);\n const [logging, setLogging] = useState(false);\n const [starting, setStarting] = useState(false);\n const [stoppedTimer, setStoppedTimer] = useState<TimeTimer | null>(null);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n const { data, isLoading, error, refetch } = useTimeEntries({\n status: statusFilter || undefined,\n search: deferredSearch.trim() || undefined,\n });\n const { emitFact } = useAnalytics();\n const submit = useSubmitTimeEntry();\n const approve = useApproveTimeEntry();\n const update = useUpdateTimeEntry();\n const reject = useRejectTimeEntry();\n const [editing, setEditing] = useState<PswTimeEntry | null>(null);\n const [rejecting, setRejecting] = useState<PswTimeEntry | null>(null);\n const timer = useTimeTimer();\n\n const totalMinutes = (data?.items ?? []).reduce((s, t) => s + t.minutes, 0);\n const billableMinutes = (data?.items ?? [])\n .filter((t) => t.billable)\n .reduce((s, t) => s + t.minutes, 0);\n const utilization = totalMinutes > 0 ? Math.round((billableMinutes / totalMinutes) * 100) : 0;\n\n const act = (\n fn: {\n mutate: (id: string, opts: { onSuccess: () => void; onError: (e: unknown) => void }) => void;\n },\n id: string,\n verb: string\n ) =>\n fn.mutate(id, {\n onSuccess: () => setBanner({ tone: 'success', text: `Time entry ${verb}.` }),\n onError: (e) =>\n setBanner({ tone: 'danger', text: e instanceof Error ? e.message : 'Failed.' }),\n });\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Time\"\n subtitle=\"Capture expert effort against engagements and matters — the raw material of billing and utilization.\"\n icon={<Clock className=\"h-6 w-6\" />}\n actions={\n timer.timer ? (\n <TimerControls\n seconds={timer.seconds}\n onStop={() => {\n const stopped = timer.stop();\n if (stopped) {\n setStoppedTimer(stopped);\n setLogging(true);\n }\n }}\n onDiscard={timer.discard}\n />\n ) : (\n <>\n <Button variant=\"outline\" onClick={() => setStarting(true)}>\n <Play className=\"mr-1.5 h-4 w-4\" />\n Start timer\n </Button>\n <Button onClick={() => setLogging(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n </>\n )\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <MetricCard\n label=\"Entries\"\n value={data?.totalCount ?? 0}\n icon={<ListChecks className=\"h-4 w-4\" />}\n />\n <MetricCard\n label=\"Total hours\"\n value={fmtMinutesAsHours(totalMinutes)}\n icon={<Clock className=\"h-4 w-4\" />}\n />\n <MetricCard\n label=\"Billable hours\"\n value={fmtMinutesAsHours(billableMinutes)}\n hint={`${utilization}% utilization`}\n icon={<Wallet className=\"h-4 w-4\" />}\n tone=\"accent\"\n />\n <MetricCard\n label=\"Non-billable\"\n value={fmtMinutesAsHours(totalMinutes - billableMinutes)}\n icon={<CircleSlash className=\"h-4 w-4\" />}\n />\n </div>\n\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\" />\n <TextInput\n placeholder=\"Search time entries…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && search.trim()) {\n emitFact('psw.search.performed', {\n entityType: 'time-entry',\n queryLength: search.trim().length,\n });\n }\n }}\n className=\"pl-9\"\n />\n </div>\n <NativeSelect\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as PswTimeEntryStatus | '')}\n className=\"sm:w-52\"\n >\n <option value=\"\">All statuses</option>\n {enumOptions(STATUSES)}\n </NativeSelect>\n </div>\n\n {isLoading ? (\n <LoadingBlock />\n ) : error ? (\n <ErrorState error={error} onRetry={() => void refetch()} />\n ) : !data || data.items.length === 0 ? (\n <Empty\n title=\"No time logged\"\n description=\"Log your first time entry against an engagement or matter.\"\n icon={<Clock className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setLogging(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n />\n ) : (\n <SectionCard title=\"Time entries\">\n <TableWrap>\n <thead>\n <tr>\n <Th>Date</Th>\n <Th>Narrative</Th>\n <Th>Hours</Th>\n <Th>Billable</Th>\n <Th>Status</Th>\n <Th className=\"text-right\">Actions</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((t) => (\n <tr key={t.id} className=\"transition-colors hover:bg-bg-sunken/60\">\n <Td className=\"whitespace-nowrap\">{fmtDate(t.date)}</Td>\n <Td className=\"max-w-xs truncate text-text-primary\">{t.narrative || '—'}</Td>\n <Td className=\"whitespace-nowrap font-medium text-text-primary\">\n {fmtMinutesAsHours(t.minutes)}\n </Td>\n <Td>\n <BillableTag billable={t.billable} />\n </Td>\n <Td>\n <StatusPill status={t.status} />\n </Td>\n <Td className=\"text-right\">\n <div className=\"flex justify-end gap-1\">\n {t.status === 'DRAFT' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={submit.isPending}\n onClick={() => act(submit, t.id, 'submitted')}\n >\n <Send className=\"mr-1 h-3.5 w-3.5\" />\n Submit\n </Button>\n )}\n {(['DRAFT', 'SUBMITTED', 'REJECTED'] as PswTimeEntryStatus[]).includes(\n t.status\n ) && (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setEditing(t)}>\n Edit\n </Button>\n )}\n {t.status === 'SUBMITTED' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={approve.isPending}\n onClick={() => act(approve, t.id, 'approved')}\n >\n <CheckCircle2 className=\"mr-1 h-3.5 w-3.5\" />\n Approve\n </Button>\n )}\n {t.status === 'SUBMITTED' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={reject.isPending}\n onClick={() => setRejecting(t)}\n >\n Reject\n </Button>\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {logging && (\n <LogTimeModal\n initialTimer={stoppedTimer}\n onClose={() => setLogging(false)}\n onDone={() => {\n setStoppedTimer(null);\n setBanner({ tone: 'success', text: 'Time logged.' });\n }}\n />\n )}\n {editing && (\n <EditTimeModal entry={editing} mutation={update} onClose={() => setEditing(null)} />\n )}\n {rejecting && (\n <RejectTimeModal entry={rejecting} mutation={reject} onClose={() => setRejecting(null)} />\n )}\n {starting && (\n <StartTimerModal\n onClose={() => setStarting(false)}\n onStart={(draft) => {\n timer.start(draft);\n setStarting(false);\n setBanner({ tone: 'success', text: 'Timer started.' });\n }}\n />\n )}\n </div>\n );\n};\n\nconst EditTimeModal: FC<{\n entry: PswTimeEntry;\n mutation: ReturnType<typeof useUpdateTimeEntry>;\n onClose: () => void;\n}> = ({ entry, mutation, onClose }) => {\n const [form, setForm] = useState({\n date: entry.date.slice(0, 10),\n minutes: String(entry.minutes),\n rate: String(entry.rate),\n narrative: entry.narrative ?? '',\n billable: entry.billable,\n });\n const [error, setError] = useState<string | null>(null);\n const save = () => {\n const minutes = Number(form.minutes);\n if (!Number.isInteger(minutes) || minutes <= 0) return setError('Enter positive minutes.');\n mutation.mutate(\n {\n id: entry.id,\n input: {\n date: new Date(form.date).toISOString(),\n minutes,\n rate: Number(form.rate) || 0,\n narrative: form.narrative.trim() || null,\n billable: form.billable,\n },\n },\n {\n onSuccess: onClose,\n onError: (e) => setError(e instanceof Error ? e.message : 'Failed to save.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Edit time entry\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose}>\n Cancel\n </Button>\n <Button onClick={save} disabled={mutation.isPending}>\n Save\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n {error && <Banner tone=\"danger\">{error}</Banner>}\n <Field label=\"Date\">\n <TextInput\n type=\"date\"\n value={form.date}\n onChange={(e) => setForm({ ...form, date: e.target.value })}\n />\n </Field>\n <Field label=\"Minutes\">\n <TextInput\n type=\"number\"\n min={1}\n value={form.minutes}\n onChange={(e) => setForm({ ...form, minutes: e.target.value })}\n />\n </Field>\n <Field label=\"Rate\">\n <TextInput\n type=\"number\"\n min={0}\n value={form.rate}\n onChange={(e) => setForm({ ...form, rate: e.target.value })}\n />\n </Field>\n <Field label=\"Narrative\">\n <TextArea\n value={form.narrative}\n onChange={(e) => setForm({ ...form, narrative: e.target.value })}\n />\n </Field>\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(e) => setForm({ ...form, billable: e.target.checked })}\n />{' '}\n Billable\n </label>\n </div>\n </Modal>\n );\n};\n\nconst RejectTimeModal: FC<{\n entry: PswTimeEntry;\n mutation: ReturnType<typeof useRejectTimeEntry>;\n onClose: () => void;\n}> = ({ entry, mutation, onClose }) => {\n const [reason, setReason] = useState('');\n const [error, setError] = useState<string | null>(null);\n const reject = () => {\n if (!reason.trim()) return setError('Enter a rejection reason.');\n mutation.mutate(\n { id: entry.id, reason: reason.trim() },\n {\n onSuccess: onClose,\n onError: (e) => setError(e instanceof Error ? e.message : 'Failed to reject.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Reject time entry\"\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose}>\n Cancel\n </Button>\n <Button onClick={reject} disabled={mutation.isPending}>\n Reject\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n {error && <Banner tone=\"danger\">{error}</Banner>}\n <Field label=\"Reason\">\n <TextArea\n autoFocus\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n placeholder=\"Explain what needs correction.\"\n />\n </Field>\n </div>\n </Modal>\n );\n};\n\nconst TimerControls: FC<{\n seconds: number;\n onStop: () => void;\n onDiscard: () => void;\n}> = ({ seconds, onStop, onDiscard }) => (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-2 py-1\">\n <span\n className=\"font-mono text-sm font-semibold tabular-nums text-text-primary\"\n aria-live=\"polite\"\n >\n {formatTimerDuration(seconds)}\n </span>\n <Button size=\"sm\" onClick={onStop}>\n <Square className=\"mr-1 h-3.5 w-3.5 fill-current\" />\n Stop\n </Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={onDiscard} aria-label=\"Discard timer\">\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n);\n\ntype TimeForm = TimeTimerDraft & { hours: string; minutes: string };\n\nconst emptyTimeForm = (): TimeForm => ({\n against: 'engagement',\n engagementId: '',\n matterId: '',\n date: new Date().toISOString().slice(0, 10),\n hours: '',\n minutes: '',\n rate: '',\n billable: true,\n narrative: '',\n});\n\nexport const timerToLogForm = (timer: TimeTimer, now = Date.now()): TimeForm => {\n const minutes = elapsedMinutes(timer, now);\n return {\n ...timer,\n hours: String(Math.floor(minutes / 60)),\n minutes: String(minutes % 60),\n };\n};\n\nconst ReferenceFields: FC<{\n form: TimeForm;\n set: (\n key: keyof TimeForm\n ) => (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => void;\n}> = ({ form, set }) => {\n const engagements = useEngagements({ pagination: { limit: 100 } });\n const matters = useMatters({ pagination: { limit: 100 } });\n\n return (\n <>\n <Field label=\"Against\">\n <NativeSelect value={form.against} onChange={set('against')}>\n <option value=\"engagement\">Engagement</option>\n <option value=\"matter\">Matter</option>\n </NativeSelect>\n </Field>\n {form.against === 'engagement' ? (\n <Field label=\"Engagement\" required>\n <NativeSelect value={form.engagementId} onChange={set('engagementId')}>\n <option value=\"\">Select an engagement…</option>\n {engagements.data?.items.map((e) => (\n <option key={e.id} value={e.id}>\n {e.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n ) : (\n <Field label=\"Matter\" required>\n <NativeSelect value={form.matterId} onChange={set('matterId')}>\n <option value=\"\">Select a matter…</option>\n {matters.data?.items.map((m) => (\n <option key={m.id} value={m.id}>\n {m.title}\n </option>\n ))}\n </NativeSelect>\n </Field>\n )}\n </>\n );\n};\n\nconst StartTimerModal: FC<{ onClose: () => void; onStart: (draft: TimeTimerDraft) => void }> = ({\n onClose,\n onStart,\n}) => {\n const [form, setForm] = useState<TimeForm>(emptyTimeForm);\n const [err, setErr] = useState<string | null>(null);\n const set =\n (key: keyof TimeForm) =>\n (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) =>\n setForm((current) => ({ ...current, [key]: event.target.value }));\n\n const submit = () => {\n if (form.against === 'engagement' && !form.engagementId) return setErr('Select an engagement.');\n if (form.against === 'matter' && !form.matterId) return setErr('Select a matter.');\n const draft: TimeTimerDraft = {\n against: form.against,\n engagementId: form.engagementId,\n matterId: form.matterId,\n date: form.date,\n rate: form.rate,\n billable: form.billable,\n narrative: form.narrative,\n };\n onStart(draft);\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Start timer\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose}>\n Cancel\n </Button>\n <Button onClick={submit}>\n <Play className=\"mr-1.5 h-4 w-4\" />\n Start timer\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <ReferenceFields form={form} set={set} />\n <Field label=\"Rate (per hour)\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.rate}\n onChange={set('rate')}\n placeholder=\"450\"\n />\n </Field>\n <label className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-canvas px-3 py-2.5 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(event) =>\n setForm((current) => ({ ...current, billable: event.target.checked }))\n }\n />\n <span>Billable</span>\n </label>\n <Field label=\"Narrative\">\n <TextArea\n value={form.narrative}\n onChange={set('narrative')}\n placeholder=\"Describe the work…\"\n />\n </Field>\n </div>\n </Modal>\n );\n};\n\nconst LogTimeModal: FC<{\n initialTimer: TimeTimer | null;\n onClose: () => void;\n onDone: () => void;\n}> = ({ initialTimer, onClose, onDone }) => {\n const log = useLogTimeEntry();\n const [form, setForm] = useState<TimeForm>(() =>\n initialTimer ? timerToLogForm(initialTimer) : emptyTimeForm()\n );\n const [err, setErr] = useState<string | null>(null);\n const set =\n (k: keyof TimeForm) =>\n (e: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n\n const submit = () => {\n setErr(null);\n const totalMinutes = (Number(form.hours) || 0) * 60 + (Number(form.minutes) || 0);\n if (totalMinutes <= 0) return setErr('Enter the time spent.');\n const ref =\n form.against === 'engagement'\n ? { engagementId: form.engagementId }\n : { matterId: form.matterId };\n if (form.against === 'engagement' && !form.engagementId) return setErr('Select an engagement.');\n if (form.against === 'matter' && !form.matterId) return setErr('Select a matter.');\n log.mutate(\n {\n ...ref,\n date: new Date(form.date).toISOString(),\n minutes: totalMinutes,\n rate: form.rate ? Number(form.rate) : 0,\n billable: form.billable,\n narrative: form.narrative.trim() || null,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Log time\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={log.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={log.isPending}>\n {log.isPending ? 'Logging…' : 'Log time'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Against\">\n <NativeSelect value={form.against} onChange={set('against')}>\n <option value=\"engagement\">Engagement</option>\n <option value=\"matter\">Matter</option>\n </NativeSelect>\n </Field>\n <ReferenceFields form={form} set={set} />\n <div className=\"grid grid-cols-3 gap-3\">\n <Field label=\"Date\">\n <TextInput type=\"date\" value={form.date} onChange={set('date')} />\n </Field>\n <Field label=\"Hours\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.hours}\n onChange={set('hours')}\n placeholder=\"2\"\n />\n </Field>\n <Field label=\"Minutes\">\n <TextInput\n type=\"number\"\n min=\"0\"\n max=\"59\"\n value={form.minutes}\n onChange={set('minutes')}\n placeholder=\"30\"\n />\n </Field>\n </div>\n <Field label=\"Rate (per hour)\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.rate}\n onChange={set('rate')}\n placeholder=\"450\"\n />\n </Field>\n <label className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-canvas px-3 py-2.5 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(e) => setForm((f) => ({ ...f, billable: e.target.checked }))}\n />\n <span className=\"flex items-center gap-1.5\">\n <Wallet className=\"h-3.5 w-3.5 text-status-success-text\" />\n Billable\n </span>\n </label>\n <Field label=\"Narrative\">\n <TextArea\n value={form.narrative}\n onChange={set('narrative')}\n placeholder=\"Reviewed working papers and drafted findings memo…\"\n />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":";;;;;;;;;AA0DA,IAAM,KAAiC;CAAC;CAAS;CAAa;CAAY;CAAY;AAAQ,GAGxF,MAA0C,EAAE,kBAChD,IACE,kBAAC,QAAD;CAAM,WAAU;WAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,iDAAkD,CAAA,GAAC,UAE/D;KAEN,kBAAC,QAAD;CAAM,WAAU;WAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,iDAAkD,CAAA,GAAC,cAE/D;IAGG,UAAqB;CAChC,IAAM,CAAC,GAAc,KAAmB,EAAkC,EAAE,GACtE,CAAC,GAAQ,KAAa,EAAS,EAAE,GACjC,IAAiB,EAAiB,CAAM,GACxC,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAU,KAAe,EAAS,EAAK,GACxC,CAAC,GAAc,KAAmB,EAA2B,IAAI,GACjE,CAAC,GAAQ,KAAa,EAA8D,IAAI,GACxF,EAAE,SAAM,eAAW,UAAO,gBAAY,EAAe;EACzD,QAAQ,KAAgB,KAAA;EACxB,QAAQ,EAAe,KAAK,KAAK,KAAA;CACnC,CAAC,GACK,EAAE,iBAAa,GAAa,GAC5B,IAAS,EAAmB,GAC5B,IAAU,EAAoB,GAC9B,KAAS,EAAmB,GAC5B,IAAS,EAAmB,GAC5B,CAAC,GAAS,KAAc,EAA8B,IAAI,GAC1D,CAAC,GAAW,KAAgB,EAA8B,IAAI,GAC9D,IAAQ,GAAa,GAErB,KAAgB,GAAM,SAAS,CAAC,EAAA,CAAG,QAAQ,GAAG,MAAM,IAAI,EAAE,SAAS,CAAC,GACpE,KAAmB,GAAM,SAAS,CAAC,EAAA,CACtC,QAAQ,MAAM,EAAE,QAAQ,CAAC,CACzB,QAAQ,GAAG,MAAM,IAAI,EAAE,SAAS,CAAC,GAC9B,KAAc,IAAe,IAAI,KAAK,MAAO,IAAkB,IAAgB,GAAG,IAAI,GAEtF,KACJ,GAGA,GACA,MAEA,EAAG,OAAO,GAAI;EACZ,iBAAiB,EAAU;GAAE,MAAM;GAAW,MAAM,cAAc,EAAK;EAAG,CAAC;EAC3E,UAAU,MACR,EAAU;GAAE,MAAM;GAAU,MAAM,aAAa,QAAQ,EAAE,UAAU;EAAU,CAAC;CAClF,CAAC;CAEH,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,IAAD;IACE,OAAM;IACN,UAAS;IACT,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;IAClC,SACE,EAAM,QACJ,kBAAC,GAAD;KACE,SAAS,EAAM;KACf,cAAc;MACZ,IAAM,IAAU,EAAM,KAAK;MAC3B,AAAI,MACF,EAAgB,CAAO,GACvB,EAAW,EAAI;KAEnB;KACA,WAAW,EAAM;IAClB,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAY,EAAI;eAAzD,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,aAE7B;QACR,kBAAC,GAAD;KAAQ,eAAe,EAAW,EAAI;eAAtC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,UAE7B;MACR,EAAA,CAAA;GAGP,CAAA;GAEA,KACC,kBAAC,GAAD;IAAQ,MAAM,EAAO;IAAM,eAAe,EAAU,IAAI;cACrD,EAAO;GACF,CAAA;GAGV,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,GAAM,cAAc;MAC3B,MAAM,kBAAC,IAAD,EAAY,WAAU,UAAW,CAAA;KACxC,CAAA;KACD,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAkB,CAAY;MACrC,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;KACnC,CAAA;KACD,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAkB,CAAe;MACxC,MAAM,GAAG,GAAY;MACrB,MAAM,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;MACnC,MAAK;KACN,CAAA;KACD,kBAAC,GAAD;MACE,OAAM;MACN,OAAO,EAAkB,IAAe,CAAe;MACvD,MAAM,kBAAC,IAAD,EAAa,WAAU,UAAW,CAAA;KACzC,CAAA;IACE;;GAEL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,IAAD,EAAQ,WAAU,0FAA2F,CAAA,GAC7G,kBAAC,GAAD;MACE,aAAY;MACZ,OAAO;MACP,WAAW,MAAM,EAAU,EAAE,OAAO,KAAK;MACzC,YAAY,MAAM;OAChB,AAAI,EAAE,QAAQ,WAAW,EAAO,KAAK,KACnC,GAAS,wBAAwB;QAC/B,YAAY;QACZ,aAAa,EAAO,KAAK,CAAC,CAAC;OAC7B,CAAC;MAEL;MACA,WAAU;KACX,CAAA,CACE;QACL,kBAAC,GAAD;KACE,OAAO;KACP,WAAW,MAAM,EAAgB,EAAE,OAAO,KAAgC;KAC1E,WAAU;eAHZ,CAKE,kBAAC,UAAD;MAAQ,OAAM;gBAAG;KAAoB,CAAA,GACpC,GAAY,EAAQ,CACT;MACX;;GAEJ,KACC,kBAAC,GAAD,CAAe,CAAA,IACb,IACF,kBAAC,GAAD;IAAmB;IAAO,eAAe,KAAK,GAAQ;GAAI,CAAA,IACxD,CAAC,KAAQ,EAAK,MAAM,WAAW,IACjC,kBAAC,GAAD;IACE,OAAM;IACN,aAAY;IACZ,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;IAClC,QACE,kBAAC,GAAD;KAAQ,eAAe,EAAW,EAAI;eAAtC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,UAE7B;;GAEX,CAAA,IAED,kBAAC,IAAD;IAAa,OAAM;cACjB,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD,EAAA,UAAA;KACE,kBAAC,GAAD,EAAA,UAAI,OAAQ,CAAA;KACZ,kBAAC,GAAD,EAAA,UAAI,YAAa,CAAA;KACjB,kBAAC,GAAD,EAAA,UAAI,QAAS,CAAA;KACb,kBAAC,GAAD,EAAA,UAAI,WAAY,CAAA;KAChB,kBAAC,GAAD,EAAA,UAAI,SAAU,CAAA;KACd,kBAAC,GAAD;MAAI,WAAU;gBAAa;KAAW,CAAA;IACpC,EAAA,CAAA,EACC,CAAA,GACP,kBAAC,SAAD,EAAA,UACG,EAAK,MAAM,KAAK,MACf,kBAAC,MAAD;KAAe,WAAU;eAAzB;MACE,kBAAC,GAAD;OAAI,WAAU;iBAAqB,GAAQ,EAAE,IAAI;MAAM,CAAA;MACvD,kBAAC,GAAD;OAAI,WAAU;iBAAuC,EAAE,aAAa;MAAQ,CAAA;MAC5E,kBAAC,GAAD;OAAI,WAAU;iBACX,EAAkB,EAAE,OAAO;MAC1B,CAAA;MACJ,kBAAC,GAAD,EAAA,UACE,kBAAC,IAAD,EAAa,UAAU,EAAE,SAAW,CAAA,EAClC,CAAA;MACJ,kBAAC,GAAD,EAAA,UACE,kBAAC,IAAD,EAAY,QAAQ,EAAE,OAAS,CAAA,EAC7B,CAAA;MACJ,kBAAC,GAAD;OAAI,WAAU;iBACZ,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACG,EAAE,WAAW,WACZ,kBAAC,GAAD;UACE,SAAQ;UACR,MAAK;UACL,UAAU,EAAO;UACjB,eAAe,EAAI,GAAQ,EAAE,IAAI,WAAW;oBAJ9C,CAME,kBAAC,IAAD,EAAM,WAAU,mBAAoB,CAAA,GAAC,QAE/B;;SAER;UAAC;UAAS;UAAa;SAAU,CAAC,CAA0B,SAC5D,EAAE,MACJ,KACE,kBAAC,GAAD;UAAQ,SAAQ;UAAQ,MAAK;UAAK,eAAe,EAAW,CAAC;oBAAG;SAExD,CAAA;SAET,EAAE,WAAW,eACZ,kBAAC,GAAD;UACE,SAAQ;UACR,MAAK;UACL,UAAU,EAAQ;UAClB,eAAe,EAAI,GAAS,EAAE,IAAI,UAAU;oBAJ9C,CAME,kBAAC,IAAD,EAAc,WAAU,mBAAoB,CAAA,GAAC,SAEvC;;SAET,EAAE,WAAW,eACZ,kBAAC,GAAD;UACE,SAAQ;UACR,MAAK;UACL,UAAU,EAAO;UACjB,eAAe,EAAa,CAAC;oBAC9B;SAEO,CAAA;QAEP;;MACH,CAAA;KACF;OAvDK,EAAE,EAuDP,CACL,EACI,CAAA,CACE,EAAA,CAAA;GACA,CAAA;GAGd,KACC,kBAAC,IAAD;IACE,cAAc;IACd,eAAe,EAAW,EAAK;IAC/B,cAAc;KAEZ,AADA,EAAgB,IAAI,GACpB,EAAU;MAAE,MAAM;MAAW,MAAM;KAAe,CAAC;IACrD;GACD,CAAA;GAEF,KACC,kBAAC,GAAD;IAAe,OAAO;IAAS,UAAU;IAAQ,eAAe,EAAW,IAAI;GAAI,CAAA;GAEpF,KACC,kBAAC,GAAD;IAAiB,OAAO;IAAW,UAAU;IAAQ,eAAe,EAAa,IAAI;GAAI,CAAA;GAE1F,KACC,kBAAC,IAAD;IACE,eAAe,EAAY,EAAK;IAChC,UAAU,MAAU;KAGlB,AAFA,EAAM,MAAM,CAAK,GACjB,EAAY,EAAK,GACjB,EAAU;MAAE,MAAM;MAAW,MAAM;KAAiB,CAAC;IACvD;GACD,CAAA;EAEA;;AAET,GAEM,KAIA,EAAE,UAAO,aAAU,iBAAc;CACrC,IAAM,CAAC,GAAM,KAAW,EAAS;EAC/B,MAAM,EAAM,KAAK,MAAM,GAAG,EAAE;EAC5B,SAAS,OAAO,EAAM,OAAO;EAC7B,MAAM,OAAO,EAAM,IAAI;EACvB,WAAW,EAAM,aAAa;EAC9B,UAAU,EAAM;CAClB,CAAC,GACK,CAAC,GAAO,KAAY,EAAwB,IAAI;CAqBtD,OACE,kBAAC,GAAD;EACE,MAAA;EACS;EACT,OAAM;EACN,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,SAAS;aAAS;EAElC,CAAA,GACR,kBAAC,GAAD;GAAQ,eA9BG;IACjB,IAAM,IAAU,OAAO,EAAK,OAAO;IACnC,IAAI,CAAC,OAAO,UAAU,CAAO,KAAK,KAAW,GAAG,OAAO,EAAS,yBAAyB;IACzF,EAAS,OACP;KACE,IAAI,EAAM;KACV,OAAO;MACL,MAAM,IAAI,KAAK,EAAK,IAAI,CAAC,CAAC,YAAY;MACtC;MACA,MAAM,OAAO,EAAK,IAAI,KAAK;MAC3B,WAAW,EAAK,UAAU,KAAK,KAAK;MACpC,UAAU,EAAK;KACjB;IACF,GACA;KACE,WAAW;KACX,UAAU,MAAM,EAAS,aAAa,QAAQ,EAAE,UAAU,iBAAiB;IAC7E,CACF;GACF;GAW+B,UAAU,EAAS;aAAW;EAE7C,CAAA,CACR,EAAA,CAAA;YAGJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KAAS,kBAAC,GAAD;KAAQ,MAAK;eAAU;IAAc,CAAA;IAC/C,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MACE,MAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,MAAM,EAAE,OAAO;MAAM,CAAC;KAC3D,CAAA;IACI,CAAA;IACP,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MACE,MAAK;MACL,KAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,SAAS,EAAE,OAAO;MAAM,CAAC;KAC9D,CAAA;IACI,CAAA;IACP,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MACE,MAAK;MACL,KAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,MAAM,EAAE,OAAO;MAAM,CAAC;KAC3D,CAAA;IACI,CAAA;IACP,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,WAAW,EAAE,OAAO;MAAM,CAAC;KAChE,CAAA;IACI,CAAA;IACP,kBAAC,SAAD;KAAO,WAAU;eAAjB;MACE,kBAAC,SAAD;OACE,MAAK;OACL,SAAS,EAAK;OACd,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,UAAU,EAAE,OAAO;OAAQ,CAAC;MACjE,CAAA;MAAE;MAAI;KAEF;;GACJ;;CACA,CAAA;AAEX,GAEM,KAIA,EAAE,UAAO,aAAU,iBAAc;CACrC,IAAM,CAAC,GAAQ,KAAa,EAAS,EAAE,GACjC,CAAC,GAAO,KAAY,EAAwB,IAAI;CAWtD,OACE,kBAAC,GAAD;EACE,MAAA;EACS;EACT,OAAM;EACN,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,SAAS;aAAS;EAElC,CAAA,GACR,kBAAC,GAAD;GAAQ,eApBK;IACnB,IAAI,CAAC,EAAO,KAAK,GAAG,OAAO,EAAS,2BAA2B;IAC/D,EAAS,OACP;KAAE,IAAI,EAAM;KAAI,QAAQ,EAAO,KAAK;IAAE,GACtC;KACE,WAAW;KACX,UAAU,MAAM,EAAS,aAAa,QAAQ,EAAE,UAAU,mBAAmB;IAC/E,CACF;GACF;GAWiC,UAAU,EAAS;aAAW;EAE/C,CAAA,CACR,EAAA,CAAA;YAGJ,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,KAAS,kBAAC,GAAD;IAAQ,MAAK;cAAU;GAAc,CAAA,GAC/C,kBAAC,GAAD;IAAO,OAAM;cACX,kBAAC,GAAD;KACE,WAAA;KACA,OAAO;KACP,WAAW,MAAM,EAAU,EAAE,OAAO,KAAK;KACzC,aAAY;IACb,CAAA;GACI,CAAA,CACJ;;CACA,CAAA;AAEX,GAEM,KAIA,EAAE,YAAS,WAAQ,mBACvB,kBAAC,OAAD;CAAK,WAAU;WAAf;EACE,kBAAC,QAAD;GACE,WAAU;GACV,aAAU;aAET,EAAoB,CAAO;EACxB,CAAA;EACN,kBAAC,GAAD;GAAQ,MAAK;GAAK,SAAS;aAA3B,CACE,kBAAC,GAAD,EAAQ,WAAU,gCAAiC,CAAA,GAAC,MAE9C;;EACR,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,MAAK;GAAK,SAAS;GAAW,cAAW;aAC/D,kBAAC,GAAD,EAAQ,WAAU,cAAe,CAAA;EAC3B,CAAA;CACL;IAKD,WAAiC;CACrC,SAAS;CACT,cAAc;CACd,UAAU;CACV,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;CAC1C,OAAO;CACP,SAAS;CACT,MAAM;CACN,UAAU;CACV,WAAW;AACb,IAEa,KAAkB,GAAkB,IAAM,KAAK,IAAI,MAAgB;CAC9E,IAAM,IAAU,EAAe,GAAO,CAAG;CACzC,OAAO;EACL,GAAG;EACH,OAAO,OAAO,KAAK,MAAM,IAAU,EAAE,CAAC;EACtC,SAAS,OAAO,IAAU,EAAE;CAC9B;AACF,GAEM,KAKA,EAAE,SAAM,aAAU;CACtB,IAAM,IAAc,EAAe,EAAE,YAAY,EAAE,OAAO,IAAI,EAAE,CAAC,GAC3D,IAAU,EAAW,EAAE,YAAY,EAAE,OAAO,IAAI,EAAE,CAAC;CAEzD,OACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EAAO,OAAM;YACX,kBAAC,GAAD;GAAc,OAAO,EAAK;GAAS,UAAU,EAAI,SAAS;aAA1D,CACE,kBAAC,UAAD;IAAQ,OAAM;cAAa;GAAkB,CAAA,GAC7C,kBAAC,UAAD;IAAQ,OAAM;cAAS;GAAc,CAAA,CACzB;;CACT,CAAA,GACN,EAAK,YAAY,eAChB,kBAAC,GAAD;EAAO,OAAM;EAAa,UAAA;YACxB,kBAAC,GAAD;GAAc,OAAO,EAAK;GAAc,UAAU,EAAI,cAAc;aAApE,CACE,kBAAC,UAAD;IAAQ,OAAM;cAAG;GAA6B,CAAA,GAC7C,EAAY,MAAM,MAAM,KAAK,MAC5B,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;GACG,GAFK,EAAE,EAEP,CACT,CACW;;CACT,CAAA,IAEP,kBAAC,GAAD;EAAO,OAAM;EAAS,UAAA;YACpB,kBAAC,GAAD;GAAc,OAAO,EAAK;GAAU,UAAU,EAAI,UAAU;aAA5D,CACE,kBAAC,UAAD;IAAQ,OAAM;cAAG;GAAwB,CAAA,GACxC,EAAQ,MAAM,MAAM,KAAK,MACxB,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;GACG,GAFK,EAAE,EAEP,CACT,CACW;;CACT,CAAA,CAET,EAAA,CAAA;AAEN,GAEM,MAA0F,EAC9F,YACA,iBACI;CACJ,IAAM,CAAC,GAAM,KAAW,EAAmB,CAAa,GAClD,CAAC,GAAK,KAAU,EAAwB,IAAI,GAC5C,KACH,OACA,MACC,GAAS,OAAa;EAAE,GAAG;GAAU,IAAM,EAAM,OAAO;CAAM,EAAE;CAiBpE,OACE,kBAAC,GAAD;EACE,MAAA;EACS;EACT,OAAM;EACN,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,SAAS;aAAS;EAEpC,CAAA,GACR,kBAAC,GAAD;GAAQ,eAzBK;IACnB,IAAI,EAAK,YAAY,gBAAgB,CAAC,EAAK,cAAc,OAAO,EAAO,uBAAuB;IAC9F,IAAI,EAAK,YAAY,YAAY,CAAC,EAAK,UAAU,OAAO,EAAO,kBAAkB;IAUjF,EAAQ;KARN,SAAS,EAAK;KACd,cAAc,EAAK;KACnB,UAAU,EAAK;KACf,MAAM,EAAK;KACX,MAAM,EAAK;KACX,UAAU,EAAK;KACf,WAAW,EAAK;IAEV,CAAK;GACf;aAYQ,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GAAC,aAE7B;IACR,EAAA,CAAA;YAGJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KAAO,kBAAC,GAAD;KAAQ,MAAK;eAAU;IAAY,CAAA;IAC3C,kBAAC,GAAD;KAAuB;KAAW;IAAM,CAAA;IACxC,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MACE,MAAK;MACL,KAAI;MACJ,OAAO,EAAK;MACZ,UAAU,EAAI,MAAM;MACpB,aAAY;KACb,CAAA;IACI,CAAA;IACP,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD;MACE,MAAK;MACL,SAAS,EAAK;MACd,WAAW,MACT,GAAS,OAAa;OAAE,GAAG;OAAS,UAAU,EAAM,OAAO;MAAQ,EAAE;KAExE,CAAA,GACD,kBAAC,QAAD,EAAA,UAAM,WAAc,CAAA,CACf;;IACP,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,UAAU,EAAI,WAAW;MACzB,aAAY;KACb,CAAA;IACI,CAAA;GACJ;;CACA,CAAA;AAEX,GAEM,MAIA,EAAE,iBAAc,YAAS,gBAAa;CAC1C,IAAM,IAAM,EAAgB,GACtB,CAAC,GAAM,KAAW,QACtB,IAAe,EAAe,CAAY,IAAI,EAAc,CAC9D,GACM,CAAC,GAAK,KAAU,EAAwB,IAAI,GAC5C,KACH,OACA,MACC,GAAS,OAAO;EAAE,GAAG;GAAI,IAAI,EAAE,OAAO;CAAM,EAAE;CA+BlD,OACE,kBAAC,GAAD;EACE,MAAA;EACS;EACT,OAAM;EACN,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,SAAS;GAAS,UAAU,EAAI;aAAW;EAE7D,CAAA,GACR,kBAAC,GAAD;GAAQ,eAvCK;IACnB,EAAO,IAAI;IACX,IAAM,KAAgB,OAAO,EAAK,KAAK,KAAK,KAAK,MAAM,OAAO,EAAK,OAAO,KAAK;IAC/E,IAAI,KAAgB,GAAG,OAAO,EAAO,uBAAuB;IAC5D,IAAM,IACJ,EAAK,YAAY,eACb,EAAE,cAAc,EAAK,aAAa,IAClC,EAAE,UAAU,EAAK,SAAS;IAChC,IAAI,EAAK,YAAY,gBAAgB,CAAC,EAAK,cAAc,OAAO,EAAO,uBAAuB;IAC9F,IAAI,EAAK,YAAY,YAAY,CAAC,EAAK,UAAU,OAAO,EAAO,kBAAkB;IACjF,EAAI,OACF;KACE,GAAG;KACH,MAAM,IAAI,KAAK,EAAK,IAAI,CAAC,CAAC,YAAY;KACtC,SAAS;KACT,MAAM,EAAK,OAAO,OAAO,EAAK,IAAI,IAAI;KACtC,UAAU,EAAK;KACf,WAAW,EAAK,UAAU,KAAK,KAAK;IACtC,GACA;KACE,iBAAiB;MAEf,AADA,EAAO,GACP,EAAQ;KACV;KACA,UAAU,MAAM,EAAO,aAAa,QAAQ,EAAE,UAAU,SAAS;IACnE,CACF;GACF;GAYiC,UAAU,EAAI;aACpC,EAAI,YAAY,aAAa;EACxB,CAAA,CACR,EAAA,CAAA;YAGJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KAAO,kBAAC,GAAD;KAAQ,MAAK;eAAU;IAAY,CAAA;IAC3C,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MAAc,OAAO,EAAK;MAAS,UAAU,EAAI,SAAS;gBAA1D,CACE,kBAAC,UAAD;OAAQ,OAAM;iBAAa;MAAkB,CAAA,GAC7C,kBAAC,UAAD;OAAQ,OAAM;iBAAS;MAAc,CAAA,CACzB;;IACT,CAAA;IACP,kBAAC,GAAD;KAAuB;KAAW;IAAM,CAAA;IACxC,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAO,OAAM;iBACX,kBAAC,GAAD;QAAW,MAAK;QAAO,OAAO,EAAK;QAAM,UAAU,EAAI,MAAM;OAAI,CAAA;MAC5D,CAAA;MACP,kBAAC,GAAD;OAAO,OAAM;iBACX,kBAAC,GAAD;QACE,MAAK;QACL,KAAI;QACJ,OAAO,EAAK;QACZ,UAAU,EAAI,OAAO;QACrB,aAAY;OACb,CAAA;MACI,CAAA;MACP,kBAAC,GAAD;OAAO,OAAM;iBACX,kBAAC,GAAD;QACE,MAAK;QACL,KAAI;QACJ,KAAI;QACJ,OAAO,EAAK;QACZ,UAAU,EAAI,SAAS;QACvB,aAAY;OACb,CAAA;MACI,CAAA;KACJ;;IACL,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MACE,MAAK;MACL,KAAI;MACJ,OAAO,EAAK;MACZ,UAAU,EAAI,MAAM;MACpB,aAAY;KACb,CAAA;IACI,CAAA;IACP,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD;MACE,MAAK;MACL,SAAS,EAAK;MACd,WAAW,MAAM,GAAS,OAAO;OAAE,GAAG;OAAG,UAAU,EAAE,OAAO;MAAQ,EAAE;KACvE,CAAA,GACD,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD,EAAQ,WAAU,uCAAwC,CAAA,GAAC,UAEvD;OACD;;IACP,kBAAC,GAAD;KAAO,OAAM;eACX,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,UAAU,EAAI,WAAW;MACzB,aAAY;KACb,CAAA;IACI,CAAA;GACJ;;CACA,CAAA;AAEX"}
|
|
1
|
+
{"version":3,"file":"TimePage.js","names":[],"sources":["../../../src/proserviceworld/pages/TimePage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useDeferredValue, useState, type ChangeEvent } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n Plus,\n Clock,\n Send,\n CheckCircle2,\n ListChecks,\n Wallet,\n CircleSlash,\n Search,\n Play,\n Square,\n Trash2,\n} from 'lucide-react';\nimport {\n useTimeEntries,\n useLogTimeEntry,\n useSubmitTimeEntry,\n useApproveTimeEntry,\n useUpdateTimeEntry,\n useRejectTimeEntry,\n useEngagements,\n useMatters,\n type PswTimeEntry,\n type PswTimeEntryStatus,\n} from '../hooks/useProServiceWorldApi';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n MetricCard,\n TableWrap,\n Th,\n Td,\n enumOptions,\n fmtDate,\n fmtMinutesAsHours,\n} from '../components/ui';\nimport { useAnalytics } from '../hooks/useAnalytics';\nimport {\n elapsedMinutes,\n formatTimerDuration,\n useTimeTimer,\n type TimeTimer,\n type TimeTimerDraft,\n} from '../hooks/useTimeTimer';\nimport { useEnumLabel, useTr } from '../hooks/useTr';\n\nconst STATUSES: PswTimeEntryStatus[] = ['DRAFT', 'SUBMITTED', 'APPROVED', 'REJECTED', 'BILLED'];\n\n/** Compact billable / non-billable tag. */\nconst BillableTag: FC<{ billable: boolean }> = ({ billable }) => {\n const tr = useTr();\n return billable ? (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current opacity-70\" />\n {tr('psw.time.billable', 'Billable')}\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-bg-sunken px-2.5 py-0.5 text-xs font-medium text-text-tertiary\">\n <span className=\"h-1.5 w-1.5 rounded-full bg-current opacity-60\" />\n {tr('psw.time.nonBillable', 'Non-billable')}\n </span>\n );\n};\n\nexport const TimePage: FC = () => {\n const tr = useTr();\n const enumLabel = useEnumLabel();\n const [statusFilter, setStatusFilter] = useState<PswTimeEntryStatus | ''>('');\n const [search, setSearch] = useState('');\n const deferredSearch = useDeferredValue(search);\n const [logging, setLogging] = useState(false);\n const [starting, setStarting] = useState(false);\n const [stoppedTimer, setStoppedTimer] = useState<TimeTimer | null>(null);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n const { data, isLoading, error, refetch } = useTimeEntries({\n status: statusFilter || undefined,\n search: deferredSearch.trim() || undefined,\n });\n const { emitFact } = useAnalytics();\n const submit = useSubmitTimeEntry();\n const approve = useApproveTimeEntry();\n const update = useUpdateTimeEntry();\n const reject = useRejectTimeEntry();\n const [editing, setEditing] = useState<PswTimeEntry | null>(null);\n const [rejecting, setRejecting] = useState<PswTimeEntry | null>(null);\n const timer = useTimeTimer();\n\n const totalMinutes = (data?.items ?? []).reduce((s, t) => s + t.minutes, 0);\n const billableMinutes = (data?.items ?? [])\n .filter((t) => t.billable)\n .reduce((s, t) => s + t.minutes, 0);\n const utilization = totalMinutes > 0 ? Math.round((billableMinutes / totalMinutes) * 100) : 0;\n\n const act = (\n fn: {\n mutate: (id: string, opts: { onSuccess: () => void; onError: (e: unknown) => void }) => void;\n },\n id: string,\n successText: string\n ) =>\n fn.mutate(id, {\n onSuccess: () => setBanner({ tone: 'success', text: successText }),\n onError: (e) =>\n setBanner({\n tone: 'danger',\n text: e instanceof Error ? e.message : tr('psw.common.failed', 'Failed.'),\n }),\n });\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title={tr('psw.time.title', 'Time')}\n subtitle={tr(\n 'psw.time.subtitle',\n 'Capture expert effort against engagements and matters — the raw material of billing and utilization.'\n )}\n icon={<Clock className=\"h-6 w-6\" />}\n actions={\n timer.timer ? (\n <TimerControls\n seconds={timer.seconds}\n onStop={() => {\n const stopped = timer.stop();\n if (stopped) {\n setStoppedTimer(stopped);\n setLogging(true);\n }\n }}\n onDiscard={timer.discard}\n />\n ) : (\n <>\n <Button variant=\"outline\" onClick={() => setStarting(true)}>\n <Play className=\"me-1.5 h-4 w-4\" />\n {tr('psw.time.startTimer', 'Start timer')}\n </Button>\n <Button onClick={() => setLogging(true)}>\n <Plus className=\"me-1.5 h-4 w-4\" />\n {tr('psw.time.logTime', 'Log time')}\n </Button>\n </>\n )\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"grid grid-cols-2 gap-3 sm:grid-cols-4\">\n <MetricCard\n label={tr('psw.time.metricEntries', 'Entries')}\n value={data?.totalCount ?? 0}\n icon={<ListChecks className=\"h-4 w-4\" />}\n />\n <MetricCard\n label={tr('psw.time.metricTotalHours', 'Total hours')}\n value={fmtMinutesAsHours(totalMinutes)}\n icon={<Clock className=\"h-4 w-4\" />}\n />\n <MetricCard\n label={tr('psw.time.metricBillableHours', 'Billable hours')}\n value={fmtMinutesAsHours(billableMinutes)}\n hint={tr('psw.time.utilizationHint', '{{pct}}% utilization', { pct: utilization })}\n icon={<Wallet className=\"h-4 w-4\" />}\n tone=\"accent\"\n />\n <MetricCard\n label={tr('psw.time.nonBillable', 'Non-billable')}\n value={fmtMinutesAsHours(totalMinutes - billableMinutes)}\n icon={<CircleSlash className=\"h-4 w-4\" />}\n />\n </div>\n\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute start-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary\" />\n <TextInput\n placeholder={tr('psw.time.searchPlaceholder', 'Search time entries…')}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && search.trim()) {\n emitFact('psw.search.performed', {\n entityType: 'time-entry',\n queryLength: search.trim().length,\n });\n }\n }}\n className=\"ps-9\"\n />\n </div>\n <NativeSelect\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as PswTimeEntryStatus | '')}\n className=\"sm:w-52\"\n >\n <option value=\"\">{tr('psw.common.allStatuses', 'All statuses')}</option>\n {enumOptions(STATUSES, enumLabel)}\n </NativeSelect>\n </div>\n\n {isLoading ? (\n <LoadingBlock />\n ) : error ? (\n <ErrorState error={error} onRetry={() => void refetch()} />\n ) : !data || data.items.length === 0 ? (\n <Empty\n title={tr('psw.time.emptyTitle', 'No time logged')}\n description={tr(\n 'psw.time.emptyDescription',\n 'Log your first time entry against an engagement or matter.'\n )}\n icon={<Clock className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setLogging(true)}>\n <Plus className=\"me-1.5 h-4 w-4\" />\n {tr('psw.time.logTime', 'Log time')}\n </Button>\n }\n />\n ) : (\n <SectionCard title={tr('psw.time.entriesTitle', 'Time entries')}>\n <TableWrap>\n <thead>\n <tr>\n <Th>{tr('psw.common.date', 'Date')}</Th>\n <Th>{tr('psw.time.narrative', 'Narrative')}</Th>\n <Th>{tr('psw.time.hours', 'Hours')}</Th>\n <Th>{tr('psw.time.billable', 'Billable')}</Th>\n <Th>{tr('psw.common.status', 'Status')}</Th>\n <Th className=\"text-end\">{tr('psw.common.actions', 'Actions')}</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((t) => (\n <tr key={t.id} className=\"transition-colors hover:bg-bg-sunken/60\">\n <Td className=\"whitespace-nowrap\">{fmtDate(t.date)}</Td>\n <Td className=\"max-w-xs truncate text-text-primary\">{t.narrative || '—'}</Td>\n <Td className=\"whitespace-nowrap font-medium text-text-primary\">\n {fmtMinutesAsHours(t.minutes)}\n </Td>\n <Td>\n <BillableTag billable={t.billable} />\n </Td>\n <Td>\n <StatusPill status={t.status} />\n </Td>\n <Td className=\"text-end\">\n <div className=\"flex justify-end gap-1\">\n {t.status === 'DRAFT' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={submit.isPending}\n onClick={() =>\n act(submit, t.id, tr('psw.time.submitted', 'Time entry submitted.'))\n }\n >\n <Send className=\"me-1 h-3.5 w-3.5\" />\n {tr('psw.time.submit', 'Submit')}\n </Button>\n )}\n {(['DRAFT', 'SUBMITTED', 'REJECTED'] as PswTimeEntryStatus[]).includes(\n t.status\n ) && (\n <Button variant=\"ghost\" size=\"sm\" onClick={() => setEditing(t)}>\n {tr('psw.common.edit', 'Edit')}\n </Button>\n )}\n {t.status === 'SUBMITTED' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={approve.isPending}\n onClick={() =>\n act(approve, t.id, tr('psw.time.approved', 'Time entry approved.'))\n }\n >\n <CheckCircle2 className=\"me-1 h-3.5 w-3.5\" />\n {tr('psw.time.approve', 'Approve')}\n </Button>\n )}\n {t.status === 'SUBMITTED' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={reject.isPending}\n onClick={() => setRejecting(t)}\n >\n {tr('psw.time.reject', 'Reject')}\n </Button>\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {logging && (\n <LogTimeModal\n initialTimer={stoppedTimer}\n onClose={() => setLogging(false)}\n onDone={() => {\n setStoppedTimer(null);\n setBanner({ tone: 'success', text: tr('psw.time.logged', 'Time logged.') });\n }}\n />\n )}\n {editing && (\n <EditTimeModal entry={editing} mutation={update} onClose={() => setEditing(null)} />\n )}\n {rejecting && (\n <RejectTimeModal entry={rejecting} mutation={reject} onClose={() => setRejecting(null)} />\n )}\n {starting && (\n <StartTimerModal\n onClose={() => setStarting(false)}\n onStart={(draft) => {\n timer.start(draft);\n setStarting(false);\n setBanner({ tone: 'success', text: tr('psw.time.timerStarted', 'Timer started.') });\n }}\n />\n )}\n </div>\n );\n};\n\nconst EditTimeModal: FC<{\n entry: PswTimeEntry;\n mutation: ReturnType<typeof useUpdateTimeEntry>;\n onClose: () => void;\n}> = ({ entry, mutation, onClose }) => {\n const tr = useTr();\n const [form, setForm] = useState({\n date: entry.date.slice(0, 10),\n minutes: String(entry.minutes),\n rate: String(entry.rate),\n narrative: entry.narrative ?? '',\n billable: entry.billable,\n });\n const [error, setError] = useState<string | null>(null);\n const save = () => {\n const minutes = Number(form.minutes);\n if (!Number.isInteger(minutes) || minutes <= 0)\n return setError(tr('psw.time.positiveMinutes', 'Enter positive minutes.'));\n mutation.mutate(\n {\n id: entry.id,\n input: {\n date: new Date(form.date).toISOString(),\n minutes,\n rate: Number(form.rate) || 0,\n narrative: form.narrative.trim() || null,\n billable: form.billable,\n },\n },\n {\n onSuccess: onClose,\n onError: (e) =>\n setError(e instanceof Error ? e.message : tr('psw.time.saveFailed', 'Failed to save.')),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title={tr('psw.time.editTitle', 'Edit time entry')}\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose}>\n {tr('psw.common.cancel', 'Cancel')}\n </Button>\n <Button onClick={save} disabled={mutation.isPending}>\n {tr('psw.common.save', 'Save')}\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n {error && <Banner tone=\"danger\">{error}</Banner>}\n <Field label={tr('psw.common.date', 'Date')}>\n <TextInput\n type=\"date\"\n value={form.date}\n onChange={(e) => setForm({ ...form, date: e.target.value })}\n />\n </Field>\n <Field label={tr('psw.time.minutes', 'Minutes')}>\n <TextInput\n type=\"number\"\n min={1}\n value={form.minutes}\n onChange={(e) => setForm({ ...form, minutes: e.target.value })}\n />\n </Field>\n <Field label={tr('psw.time.rate', 'Rate')}>\n <TextInput\n type=\"number\"\n min={0}\n value={form.rate}\n onChange={(e) => setForm({ ...form, rate: e.target.value })}\n />\n </Field>\n <Field label={tr('psw.time.narrative', 'Narrative')}>\n <TextArea\n value={form.narrative}\n onChange={(e) => setForm({ ...form, narrative: e.target.value })}\n />\n </Field>\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(e) => setForm({ ...form, billable: e.target.checked })}\n />{' '}\n {tr('psw.time.billable', 'Billable')}\n </label>\n </div>\n </Modal>\n );\n};\n\nconst RejectTimeModal: FC<{\n entry: PswTimeEntry;\n mutation: ReturnType<typeof useRejectTimeEntry>;\n onClose: () => void;\n}> = ({ entry, mutation, onClose }) => {\n const tr = useTr();\n const [reason, setReason] = useState('');\n const [error, setError] = useState<string | null>(null);\n const reject = () => {\n if (!reason.trim()) return setError(tr('psw.time.reasonRequired', 'Enter a rejection reason.'));\n mutation.mutate(\n { id: entry.id, reason: reason.trim() },\n {\n onSuccess: onClose,\n onError: (e) =>\n setError(\n e instanceof Error ? e.message : tr('psw.time.rejectFailed', 'Failed to reject.')\n ),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title={tr('psw.time.rejectTitle', 'Reject time entry')}\n footer={\n <>\n <Button variant=\"ghost\" onClick={onClose}>\n {tr('psw.common.cancel', 'Cancel')}\n </Button>\n <Button onClick={reject} disabled={mutation.isPending}>\n {tr('psw.time.reject', 'Reject')}\n </Button>\n </>\n }\n >\n <div className=\"space-y-4\">\n {error && <Banner tone=\"danger\">{error}</Banner>}\n <Field label={tr('psw.time.reason', 'Reason')}>\n <TextArea\n autoFocus\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n placeholder={tr('psw.time.reasonPlaceholder', 'Explain what needs correction.')}\n />\n </Field>\n </div>\n </Modal>\n );\n};\n\nconst TimerControls: FC<{\n seconds: number;\n onStop: () => void;\n onDiscard: () => void;\n}> = ({ seconds, onStop, onDiscard }) => {\n const tr = useTr();\n return (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-seam bg-bg-surface px-2 py-1\">\n <span\n className=\"font-mono text-sm font-semibold tabular-nums text-text-primary\"\n aria-live=\"polite\"\n >\n {formatTimerDuration(seconds)}\n </span>\n <Button size=\"sm\" onClick={onStop}>\n <Square className=\"me-1 h-3.5 w-3.5 fill-current\" />\n {tr('psw.time.stop', 'Stop')}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={onDiscard}\n aria-label={tr('psw.time.discardTimer', 'Discard timer')}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n );\n};\n\ntype TimeForm = TimeTimerDraft & { hours: string; minutes: string };\n\nconst emptyTimeForm = (): TimeForm => ({\n against: 'engagement',\n engagementId: '',\n matterId: '',\n date: new Date().toISOString().slice(0, 10),\n hours: '',\n minutes: '',\n rate: '',\n billable: true,\n narrative: '',\n});\n\nexport const timerToLogForm = (timer: TimeTimer, now = Date.now()): TimeForm => {\n const minutes = elapsedMinutes(timer, now);\n return {\n ...timer,\n hours: String(Math.floor(minutes / 60)),\n minutes: String(minutes % 60),\n };\n};\n\nconst ReferenceFields: FC<{\n form: TimeForm;\n set: (\n key: keyof TimeForm\n ) => (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => void;\n}> = ({ form, set }) => {\n const tr = useTr();\n const engagements = useEngagements({ pagination: { limit: 100 } });\n const matters = useMatters({ pagination: { limit: 100 } });\n\n return (\n <>\n <Field label={tr('psw.time.against', 'Against')}>\n <NativeSelect value={form.against} onChange={set('against')}>\n <option value=\"engagement\">{tr('psw.time.engagement', 'Engagement')}</option>\n <option value=\"matter\">{tr('psw.time.matter', 'Matter')}</option>\n </NativeSelect>\n </Field>\n {form.against === 'engagement' ? (\n <Field label={tr('psw.time.engagement', 'Engagement')} required>\n <NativeSelect value={form.engagementId} onChange={set('engagementId')}>\n <option value=\"\">\n {tr('psw.time.selectEngagementOption', 'Select an engagement…')}\n </option>\n {engagements.data?.items.map((e) => (\n <option key={e.id} value={e.id}>\n {e.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n ) : (\n <Field label={tr('psw.time.matter', 'Matter')} required>\n <NativeSelect value={form.matterId} onChange={set('matterId')}>\n <option value=\"\">{tr('psw.time.selectMatterOption', 'Select a matter…')}</option>\n {matters.data?.items.map((m) => (\n <option key={m.id} value={m.id}>\n {m.title}\n </option>\n ))}\n </NativeSelect>\n </Field>\n )}\n </>\n );\n};\n\nconst StartTimerModal: FC<{ onClose: () => void; onStart: (draft: TimeTimerDraft) => void }> = ({\n onClose,\n onStart,\n}) => {\n const tr = useTr();\n const [form, setForm] = useState<TimeForm>(emptyTimeForm);\n const [err, setErr] = useState<string | null>(null);\n const set =\n (key: keyof TimeForm) =>\n (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) =>\n setForm((current) => ({ ...current, [key]: event.target.value }));\n\n const submit = () => {\n if (form.against === 'engagement' && !form.engagementId)\n return setErr(tr('psw.time.selectEngagement', 'Select an engagement.'));\n if (form.against === 'matter' && !form.matterId)\n return setErr(tr('psw.time.selectMatter', 'Select a matter.'));\n const draft: TimeTimerDraft = {\n against: form.against,\n engagementId: form.engagementId,\n matterId: form.matterId,\n date: form.date,\n rate: form.rate,\n billable: form.billable,\n narrative: form.narrative,\n };\n onStart(draft);\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title={tr('psw.time.startTimer', 'Start timer')}\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose}>\n {tr('psw.common.cancel', 'Cancel')}\n </Button>\n <Button onClick={submit}>\n <Play className=\"me-1.5 h-4 w-4\" />\n {tr('psw.time.startTimer', 'Start timer')}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <ReferenceFields form={form} set={set} />\n <Field label={tr('psw.time.ratePerHour', 'Rate (per hour)')}>\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.rate}\n onChange={set('rate')}\n placeholder=\"450\"\n />\n </Field>\n <label className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-canvas px-3 py-2.5 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(event) =>\n setForm((current) => ({ ...current, billable: event.target.checked }))\n }\n />\n <span>{tr('psw.time.billable', 'Billable')}</span>\n </label>\n <Field label={tr('psw.time.narrative', 'Narrative')}>\n <TextArea\n value={form.narrative}\n onChange={set('narrative')}\n placeholder={tr('psw.time.narrativePlaceholderShort', 'Describe the work…')}\n />\n </Field>\n </div>\n </Modal>\n );\n};\n\nconst LogTimeModal: FC<{\n initialTimer: TimeTimer | null;\n onClose: () => void;\n onDone: () => void;\n}> = ({ initialTimer, onClose, onDone }) => {\n const tr = useTr();\n const log = useLogTimeEntry();\n const [form, setForm] = useState<TimeForm>(() =>\n initialTimer ? timerToLogForm(initialTimer) : emptyTimeForm()\n );\n const [err, setErr] = useState<string | null>(null);\n const set =\n (k: keyof TimeForm) =>\n (e: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n\n const submit = () => {\n setErr(null);\n const totalMinutes = (Number(form.hours) || 0) * 60 + (Number(form.minutes) || 0);\n if (totalMinutes <= 0) return setErr(tr('psw.time.timeRequired', 'Enter the time spent.'));\n const ref =\n form.against === 'engagement'\n ? { engagementId: form.engagementId }\n : { matterId: form.matterId };\n if (form.against === 'engagement' && !form.engagementId)\n return setErr(tr('psw.time.selectEngagement', 'Select an engagement.'));\n if (form.against === 'matter' && !form.matterId)\n return setErr(tr('psw.time.selectMatter', 'Select a matter.'));\n log.mutate(\n {\n ...ref,\n date: new Date(form.date).toISOString(),\n minutes: totalMinutes,\n rate: form.rate ? Number(form.rate) : 0,\n billable: form.billable,\n narrative: form.narrative.trim() || null,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : tr('psw.common.failed', 'Failed.')),\n }\n );\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title={tr('psw.time.logTime', 'Log time')}\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={log.isPending}>\n {tr('psw.common.cancel', 'Cancel')}\n </Button>\n <Button onClick={submit} disabled={log.isPending}>\n {log.isPending\n ? tr('psw.time.logging', 'Logging…')\n : tr('psw.time.logTime', 'Log time')}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <ReferenceFields form={form} set={set} />\n <div className=\"grid grid-cols-3 gap-3\">\n <Field label={tr('psw.common.date', 'Date')}>\n <TextInput type=\"date\" value={form.date} onChange={set('date')} />\n </Field>\n <Field label={tr('psw.time.hours', 'Hours')}>\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.hours}\n onChange={set('hours')}\n placeholder=\"2\"\n />\n </Field>\n <Field label={tr('psw.time.minutes', 'Minutes')}>\n <TextInput\n type=\"number\"\n min=\"0\"\n max=\"59\"\n value={form.minutes}\n onChange={set('minutes')}\n placeholder=\"30\"\n />\n </Field>\n </div>\n <Field label={tr('psw.time.ratePerHour', 'Rate (per hour)')}>\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.rate}\n onChange={set('rate')}\n placeholder=\"450\"\n />\n </Field>\n <label className=\"flex items-center gap-2.5 rounded-lg border border-border-seam bg-bg-canvas px-3 py-2.5 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.billable}\n onChange={(e) => setForm((f) => ({ ...f, billable: e.target.checked }))}\n />\n <span className=\"flex items-center gap-1.5\">\n <Wallet className=\"h-3.5 w-3.5 text-status-success-text\" />\n {tr('psw.time.billable', 'Billable')}\n </span>\n </label>\n <Field label={tr('psw.time.narrative', 'Narrative')}>\n <TextArea\n value={form.narrative}\n onChange={set('narrative')}\n placeholder={tr(\n 'psw.time.narrativePlaceholder',\n 'Reviewed working papers and drafted findings memo…'\n )}\n />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":";;;;;;;;;;AA2DA,IAAM,KAAiC;CAAC;CAAS;CAAa;CAAY;CAAY;AAAQ,GAGxF,MAA0C,EAAE,kBAAe;CAC/D,IAAM,IAAK,EAAM;CACjB,OAAO,IACL,kBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,iDAAkD,CAAA,GACjE,EAAG,qBAAqB,UAAU,CAC/B;MAEN,kBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,kBAAC,QAAD,EAAM,WAAU,iDAAkD,CAAA,GACjE,EAAG,wBAAwB,cAAc,CACtC;;AAEV,GAEa,UAAqB;CAChC,IAAM,IAAK,EAAM,GACX,IAAY,EAAa,GACzB,CAAC,GAAc,KAAmB,EAAkC,EAAE,GACtE,CAAC,GAAQ,KAAa,EAAS,EAAE,GACjC,IAAiB,GAAiB,CAAM,GACxC,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAU,KAAe,EAAS,EAAK,GACxC,CAAC,GAAc,KAAmB,EAA2B,IAAI,GACjE,CAAC,GAAQ,KAAa,EAA8D,IAAI,GACxF,EAAE,SAAM,eAAW,UAAO,gBAAY,EAAe;EACzD,QAAQ,KAAgB,KAAA;EACxB,QAAQ,EAAe,KAAK,KAAK,KAAA;CACnC,CAAC,GACK,EAAE,iBAAa,GAAa,GAC5B,IAAS,EAAmB,GAC5B,IAAU,EAAoB,GAC9B,KAAS,EAAmB,GAC5B,IAAS,EAAmB,GAC5B,CAAC,GAAS,KAAc,EAA8B,IAAI,GAC1D,CAAC,GAAW,KAAgB,EAA8B,IAAI,GAC9D,IAAQ,GAAa,GAErB,KAAgB,GAAM,SAAS,CAAC,EAAA,CAAG,QAAQ,GAAG,MAAM,IAAI,EAAE,SAAS,CAAC,GACpE,KAAmB,GAAM,SAAS,CAAC,EAAA,CACtC,QAAQ,MAAM,EAAE,QAAQ,CAAC,CACzB,QAAQ,GAAG,MAAM,IAAI,EAAE,SAAS,CAAC,GAC9B,KAAc,IAAe,IAAI,KAAK,MAAO,IAAkB,IAAgB,GAAG,IAAI,GAEtF,KACJ,GAGA,GACA,MAEA,EAAG,OAAO,GAAI;EACZ,iBAAiB,EAAU;GAAE,MAAM;GAAW,MAAM;EAAY,CAAC;EACjE,UAAU,MACR,EAAU;GACR,MAAM;GACN,MAAM,aAAa,QAAQ,EAAE,UAAU,EAAG,qBAAqB,SAAS;EAC1E,CAAC;CACL,CAAC;CAEH,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,IAAD;IACE,OAAO,EAAG,kBAAkB,MAAM;IAClC,UAAU,EACR,qBACA,sGACF;IACA,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;IAClC,SACE,EAAM,QACJ,kBAAC,IAAD;KACE,SAAS,EAAM;KACf,cAAc;MACZ,IAAM,IAAU,EAAM,KAAK;MAC3B,AAAI,MACF,EAAgB,CAAO,GACvB,EAAW,EAAI;KAEnB;KACA,WAAW,EAAM;IAClB,CAAA,IAED,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,eAAe,EAAY,EAAI;eAAzD,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GACjC,EAAG,uBAAuB,aAAa,CAClC;QACR,kBAAC,GAAD;KAAQ,eAAe,EAAW,EAAI;eAAtC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GACjC,EAAG,oBAAoB,UAAU,CAC5B;MACR,EAAA,CAAA;GAGP,CAAA;GAEA,KACC,kBAAC,GAAD;IAAQ,MAAM,EAAO;IAAM,eAAe,EAAU,IAAI;cACrD,EAAO;GACF,CAAA;GAGV,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,OAAO,EAAG,0BAA0B,SAAS;MAC7C,OAAO,GAAM,cAAc;MAC3B,MAAM,kBAAC,IAAD,EAAY,WAAU,UAAW,CAAA;KACxC,CAAA;KACD,kBAAC,GAAD;MACE,OAAO,EAAG,6BAA6B,aAAa;MACpD,OAAO,EAAkB,CAAY;MACrC,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;KACnC,CAAA;KACD,kBAAC,GAAD;MACE,OAAO,EAAG,gCAAgC,gBAAgB;MAC1D,OAAO,EAAkB,CAAe;MACxC,MAAM,EAAG,4BAA4B,wBAAwB,EAAE,KAAK,GAAY,CAAC;MACjF,MAAM,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;MACnC,MAAK;KACN,CAAA;KACD,kBAAC,GAAD;MACE,OAAO,EAAG,wBAAwB,cAAc;MAChD,OAAO,EAAkB,IAAe,CAAe;MACvD,MAAM,kBAAC,IAAD,EAAa,WAAU,UAAW,CAAA;KACzC,CAAA;IACE;;GAEL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,IAAD,EAAQ,WAAU,2FAA4F,CAAA,GAC9G,kBAAC,GAAD;MACE,aAAa,EAAG,8BAA8B,sBAAsB;MACpE,OAAO;MACP,WAAW,MAAM,EAAU,EAAE,OAAO,KAAK;MACzC,YAAY,MAAM;OAChB,AAAI,EAAE,QAAQ,WAAW,EAAO,KAAK,KACnC,GAAS,wBAAwB;QAC/B,YAAY;QACZ,aAAa,EAAO,KAAK,CAAC,CAAC;OAC7B,CAAC;MAEL;MACA,WAAU;KACX,CAAA,CACE;QACL,kBAAC,GAAD;KACE,OAAO;KACP,WAAW,MAAM,EAAgB,EAAE,OAAO,KAAgC;KAC1E,WAAU;eAHZ,CAKE,kBAAC,UAAD;MAAQ,OAAM;gBAAI,EAAG,0BAA0B,cAAc;KAAU,CAAA,GACtE,GAAY,IAAU,CAAS,CACpB;MACX;;GAEJ,KACC,kBAAC,IAAD,CAAe,CAAA,IACb,IACF,kBAAC,GAAD;IAAmB;IAAO,eAAe,KAAK,GAAQ;GAAI,CAAA,IACxD,CAAC,KAAQ,EAAK,MAAM,WAAW,IACjC,kBAAC,GAAD;IACE,OAAO,EAAG,uBAAuB,gBAAgB;IACjD,aAAa,EACX,6BACA,4DACF;IACA,MAAM,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;IAClC,QACE,kBAAC,GAAD;KAAQ,eAAe,EAAW,EAAI;eAAtC,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GACjC,EAAG,oBAAoB,UAAU,CAC5B;;GAEX,CAAA,IAED,kBAAC,IAAD;IAAa,OAAO,EAAG,yBAAyB,cAAc;cAC5D,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD,EAAA,UAAA;KACE,kBAAC,GAAD,EAAA,UAAK,EAAG,mBAAmB,MAAM,EAAM,CAAA;KACvC,kBAAC,GAAD,EAAA,UAAK,EAAG,sBAAsB,WAAW,EAAM,CAAA;KAC/C,kBAAC,GAAD,EAAA,UAAK,EAAG,kBAAkB,OAAO,EAAM,CAAA;KACvC,kBAAC,GAAD,EAAA,UAAK,EAAG,qBAAqB,UAAU,EAAM,CAAA;KAC7C,kBAAC,GAAD,EAAA,UAAK,EAAG,qBAAqB,QAAQ,EAAM,CAAA;KAC3C,kBAAC,GAAD;MAAI,WAAU;gBAAY,EAAG,sBAAsB,SAAS;KAAM,CAAA;IAChE,EAAA,CAAA,EACC,CAAA,GACP,kBAAC,SAAD,EAAA,UACG,EAAK,MAAM,KAAK,MACf,kBAAC,MAAD;KAAe,WAAU;eAAzB;MACE,kBAAC,GAAD;OAAI,WAAU;iBAAqB,GAAQ,EAAE,IAAI;MAAM,CAAA;MACvD,kBAAC,GAAD;OAAI,WAAU;iBAAuC,EAAE,aAAa;MAAQ,CAAA;MAC5E,kBAAC,GAAD;OAAI,WAAU;iBACX,EAAkB,EAAE,OAAO;MAC1B,CAAA;MACJ,kBAAC,GAAD,EAAA,UACE,kBAAC,IAAD,EAAa,UAAU,EAAE,SAAW,CAAA,EAClC,CAAA;MACJ,kBAAC,GAAD,EAAA,UACE,kBAAC,IAAD,EAAY,QAAQ,EAAE,OAAS,CAAA,EAC7B,CAAA;MACJ,kBAAC,GAAD;OAAI,WAAU;iBACZ,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACG,EAAE,WAAW,WACZ,kBAAC,GAAD;UACE,SAAQ;UACR,MAAK;UACL,UAAU,EAAO;UACjB,eACE,EAAI,GAAQ,EAAE,IAAI,EAAG,sBAAsB,uBAAuB,CAAC;oBALvE,CAQE,kBAAC,IAAD,EAAM,WAAU,mBAAoB,CAAA,GACnC,EAAG,mBAAmB,QAAQ,CACzB;;SAER;UAAC;UAAS;UAAa;SAAU,CAAC,CAA0B,SAC5D,EAAE,MACJ,KACE,kBAAC,GAAD;UAAQ,SAAQ;UAAQ,MAAK;UAAK,eAAe,EAAW,CAAC;oBAC1D,EAAG,mBAAmB,MAAM;SACvB,CAAA;SAET,EAAE,WAAW,eACZ,kBAAC,GAAD;UACE,SAAQ;UACR,MAAK;UACL,UAAU,EAAQ;UAClB,eACE,EAAI,GAAS,EAAE,IAAI,EAAG,qBAAqB,sBAAsB,CAAC;oBALtE,CAQE,kBAAC,GAAD,EAAc,WAAU,mBAAoB,CAAA,GAC3C,EAAG,oBAAoB,SAAS,CAC3B;;SAET,EAAE,WAAW,eACZ,kBAAC,GAAD;UACE,SAAQ;UACR,MAAK;UACL,UAAU,EAAO;UACjB,eAAe,EAAa,CAAC;oBAE5B,EAAG,mBAAmB,QAAQ;SACzB,CAAA;QAEP;;MACH,CAAA;KACF;OA3DK,EAAE,EA2DP,CACL,EACI,CAAA,CACE,EAAA,CAAA;GACA,CAAA;GAGd,KACC,kBAAC,IAAD;IACE,cAAc;IACd,eAAe,EAAW,EAAK;IAC/B,cAAc;KAEZ,AADA,EAAgB,IAAI,GACpB,EAAU;MAAE,MAAM;MAAW,MAAM,EAAG,mBAAmB,cAAc;KAAE,CAAC;IAC5E;GACD,CAAA;GAEF,KACC,kBAAC,IAAD;IAAe,OAAO;IAAS,UAAU;IAAQ,eAAe,EAAW,IAAI;GAAI,CAAA;GAEpF,KACC,kBAAC,IAAD;IAAiB,OAAO;IAAW,UAAU;IAAQ,eAAe,EAAa,IAAI;GAAI,CAAA;GAE1F,KACC,kBAAC,IAAD;IACE,eAAe,EAAY,EAAK;IAChC,UAAU,MAAU;KAGlB,AAFA,EAAM,MAAM,CAAK,GACjB,EAAY,EAAK,GACjB,EAAU;MAAE,MAAM;MAAW,MAAM,EAAG,yBAAyB,gBAAgB;KAAE,CAAC;IACpF;GACD,CAAA;EAEA;;AAET,GAEM,MAIA,EAAE,UAAO,aAAU,iBAAc;CACrC,IAAM,IAAK,EAAM,GACX,CAAC,GAAM,KAAW,EAAS;EAC/B,MAAM,EAAM,KAAK,MAAM,GAAG,EAAE;EAC5B,SAAS,OAAO,EAAM,OAAO;EAC7B,MAAM,OAAO,EAAM,IAAI;EACvB,WAAW,EAAM,aAAa;EAC9B,UAAU,EAAM;CAClB,CAAC,GACK,CAAC,GAAO,KAAY,EAAwB,IAAI;CAuBtD,OACE,kBAAC,GAAD;EACE,MAAA;EACS;EACT,OAAO,EAAG,sBAAsB,iBAAiB;EACjD,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,SAAS;aAC9B,EAAG,qBAAqB,QAAQ;EAC3B,CAAA,GACR,kBAAC,GAAD;GAAQ,eAhCG;IACjB,IAAM,IAAU,OAAO,EAAK,OAAO;IACnC,IAAI,CAAC,OAAO,UAAU,CAAO,KAAK,KAAW,GAC3C,OAAO,EAAS,EAAG,4BAA4B,yBAAyB,CAAC;IAC3E,EAAS,OACP;KACE,IAAI,EAAM;KACV,OAAO;MACL,MAAM,IAAI,KAAK,EAAK,IAAI,CAAC,CAAC,YAAY;MACtC;MACA,MAAM,OAAO,EAAK,IAAI,KAAK;MAC3B,WAAW,EAAK,UAAU,KAAK,KAAK;MACpC,UAAU,EAAK;KACjB;IACF,GACA;KACE,WAAW;KACX,UAAU,MACR,EAAS,aAAa,QAAQ,EAAE,UAAU,EAAG,uBAAuB,iBAAiB,CAAC;IAC1F,CACF;GACF;GAW+B,UAAU,EAAS;aACvC,EAAG,mBAAmB,MAAM;EACvB,CAAA,CACR,EAAA,CAAA;YAGJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KAAS,kBAAC,GAAD;KAAQ,MAAK;eAAU;IAAc,CAAA;IAC/C,kBAAC,GAAD;KAAO,OAAO,EAAG,mBAAmB,MAAM;eACxC,kBAAC,GAAD;MACE,MAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,MAAM,EAAE,OAAO;MAAM,CAAC;KAC3D,CAAA;IACI,CAAA;IACP,kBAAC,GAAD;KAAO,OAAO,EAAG,oBAAoB,SAAS;eAC5C,kBAAC,GAAD;MACE,MAAK;MACL,KAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,SAAS,EAAE,OAAO;MAAM,CAAC;KAC9D,CAAA;IACI,CAAA;IACP,kBAAC,GAAD;KAAO,OAAO,EAAG,iBAAiB,MAAM;eACtC,kBAAC,GAAD;MACE,MAAK;MACL,KAAK;MACL,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,MAAM,EAAE,OAAO;MAAM,CAAC;KAC3D,CAAA;IACI,CAAA;IACP,kBAAC,GAAD;KAAO,OAAO,EAAG,sBAAsB,WAAW;eAChD,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,WAAW,MAAM,EAAQ;OAAE,GAAG;OAAM,WAAW,EAAE,OAAO;MAAM,CAAC;KAChE,CAAA;IACI,CAAA;IACP,kBAAC,SAAD;KAAO,WAAU;eAAjB;MACE,kBAAC,SAAD;OACE,MAAK;OACL,SAAS,EAAK;OACd,WAAW,MAAM,EAAQ;QAAE,GAAG;QAAM,UAAU,EAAE,OAAO;OAAQ,CAAC;MACjE,CAAA;MAAE;MACF,EAAG,qBAAqB,UAAU;KAC9B;;GACJ;;CACA,CAAA;AAEX,GAEM,MAIA,EAAE,UAAO,aAAU,iBAAc;CACrC,IAAM,IAAK,EAAM,GACX,CAAC,GAAQ,KAAa,EAAS,EAAE,GACjC,CAAC,GAAO,KAAY,EAAwB,IAAI;CActD,OACE,kBAAC,GAAD;EACE,MAAA;EACS;EACT,OAAO,EAAG,wBAAwB,mBAAmB;EACrD,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAQ,SAAS;aAC9B,EAAG,qBAAqB,QAAQ;EAC3B,CAAA,GACR,kBAAC,GAAD;GAAQ,eAvBK;IACnB,IAAI,CAAC,EAAO,KAAK,GAAG,OAAO,EAAS,EAAG,2BAA2B,2BAA2B,CAAC;IAC9F,EAAS,OACP;KAAE,IAAI,EAAM;KAAI,QAAQ,EAAO,KAAK;IAAE,GACtC;KACE,WAAW;KACX,UAAU,MACR,EACE,aAAa,QAAQ,EAAE,UAAU,EAAG,yBAAyB,mBAAmB,CAClF;IACJ,CACF;GACF;GAWiC,UAAU,EAAS;aACzC,EAAG,mBAAmB,QAAQ;EACzB,CAAA,CACR,EAAA,CAAA;YAGJ,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,KAAS,kBAAC,GAAD;IAAQ,MAAK;cAAU;GAAc,CAAA,GAC/C,kBAAC,GAAD;IAAO,OAAO,EAAG,mBAAmB,QAAQ;cAC1C,kBAAC,GAAD;KACE,WAAA;KACA,OAAO;KACP,WAAW,MAAM,EAAU,EAAE,OAAO,KAAK;KACzC,aAAa,EAAG,8BAA8B,gCAAgC;IAC/E,CAAA;GACI,CAAA,CACJ;;CACA,CAAA;AAEX,GAEM,MAIA,EAAE,YAAS,WAAQ,mBAAgB;CACvC,IAAM,IAAK,EAAM;CACjB,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,QAAD;IACE,WAAU;IACV,aAAU;cAET,EAAoB,CAAO;GACxB,CAAA;GACN,kBAAC,GAAD;IAAQ,MAAK;IAAK,SAAS;cAA3B,CACE,kBAAC,GAAD,EAAQ,WAAU,gCAAiC,CAAA,GAClD,EAAG,iBAAiB,MAAM,CACrB;;GACR,kBAAC,GAAD;IACE,SAAQ;IACR,MAAK;IACL,SAAS;IACT,cAAY,EAAG,yBAAyB,eAAe;cAEvD,kBAAC,GAAD,EAAQ,WAAU,cAAe,CAAA;GAC3B,CAAA;EACL;;AAET,GAIM,WAAiC;CACrC,SAAS;CACT,cAAc;CACd,UAAU;CACV,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC,CAAC,MAAM,GAAG,EAAE;CAC1C,OAAO;CACP,SAAS;CACT,MAAM;CACN,UAAU;CACV,WAAW;AACb,IAEa,KAAkB,GAAkB,IAAM,KAAK,IAAI,MAAgB;CAC9E,IAAM,IAAU,EAAe,GAAO,CAAG;CACzC,OAAO;EACL,GAAG;EACH,OAAO,OAAO,KAAK,MAAM,IAAU,EAAE,CAAC;EACtC,SAAS,OAAO,IAAU,EAAE;CAC9B;AACF,GAEM,KAKA,EAAE,SAAM,aAAU;CACtB,IAAM,IAAK,EAAM,GACX,IAAc,EAAe,EAAE,YAAY,EAAE,OAAO,IAAI,EAAE,CAAC,GAC3D,IAAU,EAAW,EAAE,YAAY,EAAE,OAAO,IAAI,EAAE,CAAC;CAEzD,OACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EAAO,OAAO,EAAG,oBAAoB,SAAS;YAC5C,kBAAC,GAAD;GAAc,OAAO,EAAK;GAAS,UAAU,EAAI,SAAS;aAA1D,CACE,kBAAC,UAAD;IAAQ,OAAM;cAAc,EAAG,uBAAuB,YAAY;GAAU,CAAA,GAC5E,kBAAC,UAAD;IAAQ,OAAM;cAAU,EAAG,mBAAmB,QAAQ;GAAU,CAAA,CACpD;;CACT,CAAA,GACN,EAAK,YAAY,eAChB,kBAAC,GAAD;EAAO,OAAO,EAAG,uBAAuB,YAAY;EAAG,UAAA;YACrD,kBAAC,GAAD;GAAc,OAAO,EAAK;GAAc,UAAU,EAAI,cAAc;aAApE,CACE,kBAAC,UAAD;IAAQ,OAAM;cACX,EAAG,mCAAmC,uBAAuB;GACxD,CAAA,GACP,EAAY,MAAM,MAAM,KAAK,MAC5B,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;GACG,GAFK,EAAE,EAEP,CACT,CACW;;CACT,CAAA,IAEP,kBAAC,GAAD;EAAO,OAAO,EAAG,mBAAmB,QAAQ;EAAG,UAAA;YAC7C,kBAAC,GAAD;GAAc,OAAO,EAAK;GAAU,UAAU,EAAI,UAAU;aAA5D,CACE,kBAAC,UAAD;IAAQ,OAAM;cAAI,EAAG,+BAA+B,kBAAkB;GAAU,CAAA,GAC/E,EAAQ,MAAM,MAAM,KAAK,MACxB,kBAAC,UAAD;IAAmB,OAAO,EAAE;cACzB,EAAE;GACG,GAFK,EAAE,EAEP,CACT,CACW;;CACT,CAAA,CAET,EAAA,CAAA;AAEN,GAEM,MAA0F,EAC9F,YACA,iBACI;CACJ,IAAM,IAAK,EAAM,GACX,CAAC,GAAM,KAAW,EAAmB,CAAa,GAClD,CAAC,GAAK,KAAU,EAAwB,IAAI,GAC5C,KACH,OACA,MACC,GAAS,OAAa;EAAE,GAAG;GAAU,IAAM,EAAM,OAAO;CAAM,EAAE;CAmBpE,OACE,kBAAC,GAAD;EACE,MAAA;EACS;EACT,OAAO,EAAG,uBAAuB,aAAa;EAC9C,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,SAAS;aAChC,EAAG,qBAAqB,QAAQ;EAC3B,CAAA,GACR,kBAAC,GAAD;GAAQ,eA3BK;IACnB,IAAI,EAAK,YAAY,gBAAgB,CAAC,EAAK,cACzC,OAAO,EAAO,EAAG,6BAA6B,uBAAuB,CAAC;IACxE,IAAI,EAAK,YAAY,YAAY,CAAC,EAAK,UACrC,OAAO,EAAO,EAAG,yBAAyB,kBAAkB,CAAC;IAU/D,EAAQ;KARN,SAAS,EAAK;KACd,cAAc,EAAK;KACnB,UAAU,EAAK;KACf,MAAM,EAAK;KACX,MAAM,EAAK;KACX,UAAU,EAAK;KACf,WAAW,EAAK;IAEV,CAAK;GACf;aAYQ,CACE,kBAAC,GAAD,EAAM,WAAU,iBAAkB,CAAA,GACjC,EAAG,uBAAuB,aAAa,CAClC;IACR,EAAA,CAAA;YAGJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KAAO,kBAAC,GAAD;KAAQ,MAAK;eAAU;IAAY,CAAA;IAC3C,kBAAC,GAAD;KAAuB;KAAW;IAAM,CAAA;IACxC,kBAAC,GAAD;KAAO,OAAO,EAAG,wBAAwB,iBAAiB;eACxD,kBAAC,GAAD;MACE,MAAK;MACL,KAAI;MACJ,OAAO,EAAK;MACZ,UAAU,EAAI,MAAM;MACpB,aAAY;KACb,CAAA;IACI,CAAA;IACP,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD;MACE,MAAK;MACL,SAAS,EAAK;MACd,WAAW,MACT,GAAS,OAAa;OAAE,GAAG;OAAS,UAAU,EAAM,OAAO;MAAQ,EAAE;KAExE,CAAA,GACD,kBAAC,QAAD,EAAA,UAAO,EAAG,qBAAqB,UAAU,EAAQ,CAAA,CAC5C;;IACP,kBAAC,GAAD;KAAO,OAAO,EAAG,sBAAsB,WAAW;eAChD,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,UAAU,EAAI,WAAW;MACzB,aAAa,EAAG,sCAAsC,oBAAoB;KAC3E,CAAA;IACI,CAAA;GACJ;;CACA,CAAA;AAEX,GAEM,MAIA,EAAE,iBAAc,YAAS,gBAAa;CAC1C,IAAM,IAAK,EAAM,GACX,IAAM,EAAgB,GACtB,CAAC,GAAM,KAAW,QACtB,IAAe,EAAe,CAAY,IAAI,EAAc,CAC9D,GACM,CAAC,GAAK,KAAU,EAAwB,IAAI,GAC5C,KACH,OACA,MACC,GAAS,OAAO;EAAE,GAAG;GAAI,IAAI,EAAE,OAAO;CAAM,EAAE;CAiClD,OACE,kBAAC,GAAD;EACE,MAAA;EACS;EACT,OAAO,EAAG,oBAAoB,UAAU;EACxC,QACE,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,SAAS;GAAS,UAAU,EAAI;aACvD,EAAG,qBAAqB,QAAQ;EAC3B,CAAA,GACR,kBAAC,GAAD;GAAQ,eAzCK;IACnB,EAAO,IAAI;IACX,IAAM,KAAgB,OAAO,EAAK,KAAK,KAAK,KAAK,MAAM,OAAO,EAAK,OAAO,KAAK;IAC/E,IAAI,KAAgB,GAAG,OAAO,EAAO,EAAG,yBAAyB,uBAAuB,CAAC;IACzF,IAAM,IACJ,EAAK,YAAY,eACb,EAAE,cAAc,EAAK,aAAa,IAClC,EAAE,UAAU,EAAK,SAAS;IAChC,IAAI,EAAK,YAAY,gBAAgB,CAAC,EAAK,cACzC,OAAO,EAAO,EAAG,6BAA6B,uBAAuB,CAAC;IACxE,IAAI,EAAK,YAAY,YAAY,CAAC,EAAK,UACrC,OAAO,EAAO,EAAG,yBAAyB,kBAAkB,CAAC;IAC/D,EAAI,OACF;KACE,GAAG;KACH,MAAM,IAAI,KAAK,EAAK,IAAI,CAAC,CAAC,YAAY;KACtC,SAAS;KACT,MAAM,EAAK,OAAO,OAAO,EAAK,IAAI,IAAI;KACtC,UAAU,EAAK;KACf,WAAW,EAAK,UAAU,KAAK,KAAK;IACtC,GACA;KACE,iBAAiB;MAEf,AADA,EAAO,GACP,EAAQ;KACV;KACA,UAAU,MAAM,EAAO,aAAa,QAAQ,EAAE,UAAU,EAAG,qBAAqB,SAAS,CAAC;IAC5F,CACF;GACF;GAYiC,UAAU,EAAI;aACpC,EAAI,YACD,EAAG,oBAAoB,UAAU,IACjC,EAAG,oBAAoB,UAAU;EAC/B,CAAA,CACR,EAAA,CAAA;YAGJ,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KAAO,kBAAC,GAAD;KAAQ,MAAK;eAAU;IAAY,CAAA;IAC3C,kBAAC,GAAD;KAAuB;KAAW;IAAM,CAAA;IACxC,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAO,OAAO,EAAG,mBAAmB,MAAM;iBACxC,kBAAC,GAAD;QAAW,MAAK;QAAO,OAAO,EAAK;QAAM,UAAU,EAAI,MAAM;OAAI,CAAA;MAC5D,CAAA;MACP,kBAAC,GAAD;OAAO,OAAO,EAAG,kBAAkB,OAAO;iBACxC,kBAAC,GAAD;QACE,MAAK;QACL,KAAI;QACJ,OAAO,EAAK;QACZ,UAAU,EAAI,OAAO;QACrB,aAAY;OACb,CAAA;MACI,CAAA;MACP,kBAAC,GAAD;OAAO,OAAO,EAAG,oBAAoB,SAAS;iBAC5C,kBAAC,GAAD;QACE,MAAK;QACL,KAAI;QACJ,KAAI;QACJ,OAAO,EAAK;QACZ,UAAU,EAAI,SAAS;QACvB,aAAY;OACb,CAAA;MACI,CAAA;KACJ;;IACL,kBAAC,GAAD;KAAO,OAAO,EAAG,wBAAwB,iBAAiB;eACxD,kBAAC,GAAD;MACE,MAAK;MACL,KAAI;MACJ,OAAO,EAAK;MACZ,UAAU,EAAI,MAAM;MACpB,aAAY;KACb,CAAA;IACI,CAAA;IACP,kBAAC,SAAD;KAAO,WAAU;eAAjB,CACE,kBAAC,SAAD;MACE,MAAK;MACL,SAAS,EAAK;MACd,WAAW,MAAM,GAAS,OAAO;OAAE,GAAG;OAAG,UAAU,EAAE,OAAO;MAAQ,EAAE;KACvE,CAAA,GACD,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD,EAAQ,WAAU,uCAAwC,CAAA,GACzD,EAAG,qBAAqB,UAAU,CAC/B;OACD;;IACP,kBAAC,GAAD;KAAO,OAAO,EAAG,sBAAsB,WAAW;eAChD,kBAAC,GAAD;MACE,OAAO,EAAK;MACZ,UAAU,EAAI,WAAW;MACzB,aAAa,EACX,iCACA,oDACF;KACD,CAAA;IACI,CAAA;GACJ;;CACA,CAAA;AAEX"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"humanize.d.ts","sourceRoot":"","sources":["../../../src/proserviceworld/utils/humanize.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,KAAG,MAIX,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"humanize.js","names":[],"sources":["../../../src/proserviceworld/utils/humanize.ts"],"sourcesContent":["/** \"IN_PROGRESS\" → \"In Progress\". English-only presentation fallback for enum values. */\nexport const humanize = (v: string | null | undefined): string =>\n (v ?? '')\n .toLowerCase()\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase());\n"],"mappings":";AACA,IAAa,KAAY,OACtB,KAAK,GAAA,CACH,YAAY,CAAC,CACb,QAAQ,MAAM,GAAG,CAAC,CAClB,QAAQ,UAAU,MAAM,EAAE,YAAY,CAAC"}
|