@burdenoff/microfe-proserviceworld 2026.706.2 → 2026.706.3

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 (26) hide show
  1. package/dist/proserviceworld/components/DiscussionThread.js +1 -1
  2. package/dist/proserviceworld/components/DiscussionThread.js.map +1 -1
  3. package/dist/proserviceworld/components/ui.js +18 -18
  4. package/dist/proserviceworld/components/ui.js.map +1 -1
  5. package/dist/proserviceworld/hooks/useEntityComments.js.map +1 -1
  6. package/dist/proserviceworld/pages/AnalyticsPage.js +277 -111
  7. package/dist/proserviceworld/pages/AnalyticsPage.js.map +1 -1
  8. package/dist/proserviceworld/pages/ClientDetailPage.js +219 -151
  9. package/dist/proserviceworld/pages/ClientDetailPage.js.map +1 -1
  10. package/dist/proserviceworld/pages/ClientsPage.js +148 -127
  11. package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
  12. package/dist/proserviceworld/pages/CompliancePage.js +140 -96
  13. package/dist/proserviceworld/pages/CompliancePage.js.map +1 -1
  14. package/dist/proserviceworld/pages/DashboardPage.js +221 -104
  15. package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
  16. package/dist/proserviceworld/pages/EngagementDetailPage.js +211 -181
  17. package/dist/proserviceworld/pages/EngagementDetailPage.js.map +1 -1
  18. package/dist/proserviceworld/pages/EngagementsPage.js +144 -106
  19. package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
  20. package/dist/proserviceworld/pages/InvoicesPage.js +144 -129
  21. package/dist/proserviceworld/pages/InvoicesPage.js.map +1 -1
  22. package/dist/proserviceworld/pages/MattersPage.js +163 -122
  23. package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
  24. package/dist/proserviceworld/pages/TimePage.js +112 -92
  25. package/dist/proserviceworld/pages/TimePage.js.map +1 -1
  26. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"EngagementDetailPage.js","names":[],"sources":["../../../src/proserviceworld/pages/EngagementDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, Plus, CheckCircle2, FileText, Flag, Clock } from 'lucide-react';\nimport {\n useEngagement,\n useTransitionEngagement,\n useCreateDeliverable,\n useTransitionDeliverable,\n useCreateMilestone,\n useCompleteMilestone,\n useTimeEntries,\n type PswEngagementStatus,\n type PswDeliverableStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { DiscussionThread } from '../components/DiscussionThread';\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 fmtDate,\n fmtMoney,\n fmtMinutesAsHours,\n humanize,\n} from '../components/ui';\n\n// Mirrors the svc ENGAGEMENT_TRANSITIONS state machine so we only offer valid moves.\nconst NEXT: Record<PswEngagementStatus, PswEngagementStatus[]> = {\n PROPOSAL: ['ACCEPTED', 'CANCELLED'],\n ACCEPTED: ['IN_PROGRESS', 'CANCELLED'],\n IN_PROGRESS: ['REVIEW', 'CANCELLED'],\n REVIEW: ['DELIVERED', 'IN_PROGRESS', 'CANCELLED'],\n DELIVERED: ['INVOICED', 'CANCELLED'],\n INVOICED: ['CLOSED'],\n CLOSED: [],\n CANCELLED: [],\n};\nconst DELIVERABLE_STATUSES: PswDeliverableStatus[] = [\n 'DRAFT',\n 'INTERNAL_REVIEW',\n 'CLIENT_REVIEW',\n 'ACCEPTED',\n 'REJECTED',\n];\n\nexport const EngagementDetailPage: FC = () => {\n const { id = '' } = useParams();\n const nav = useNav();\n const { data: e, isLoading, error, refetch } = useEngagement(id);\n const time = useTimeEntries({ engagementId: id });\n const transition = useTransitionEngagement();\n const [addDeliverable, setAddDeliverable] = useState(false);\n const [addMilestone, setAddMilestone] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n\n if (isLoading) return <LoadingBlock />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!e) return <Empty title=\"Engagement not found\" />;\n\n const nextStates = NEXT[e.status] ?? [];\n const loggedMinutes = (time.data?.items ?? []).reduce((sum, t) => sum + t.minutes, 0);\n\n return (\n <div className=\"space-y-6\">\n <button\n type=\"button\"\n onClick={() => nav('engagements')}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to engagements\n </button>\n\n <PageHeader\n title={e.name}\n subtitle={\n e.client ? `${e.client.name} · ${humanize(e.feeArrangement)}` : humanize(e.feeArrangement)\n }\n actions={<StatusPill status={e.status} />}\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 lg:grid-cols-4\">\n <MetricCard\n label=\"Fee\"\n value={fmtMoney(e.feeAmount, e.currency)}\n hint={humanize(e.feeArrangement)}\n />\n <MetricCard label=\"Deliverables\" value={e.deliverables.length} />\n <MetricCard label=\"Milestones\" value={e.milestones.length} />\n <MetricCard\n label=\"Time logged\"\n value={fmtMinutesAsHours(loggedMinutes)}\n hint={`${time.data?.totalCount ?? 0} entries`}\n />\n </div>\n\n {nextStates.length > 0 && (\n <SectionCard\n title=\"Advance lifecycle\"\n description={`Current status: ${humanize(e.status)}`}\n >\n <div className=\"flex flex-wrap gap-2\">\n {nextStates.map((s) => (\n <Button\n key={s}\n size=\"sm\"\n variant={s === 'CANCELLED' ? 'outline' : 'default'}\n disabled={transition.isPending}\n onClick={() =>\n transition.mutate(\n { id: e.id, status: s },\n {\n onSuccess: () =>\n setBanner({ tone: 'success', text: `Moved to ${humanize(s)}.` }),\n onError: (err) =>\n setBanner({\n tone: 'danger',\n text: err instanceof Error ? err.message : 'Transition failed.',\n }),\n }\n )\n }\n >\n {humanize(s)}\n </Button>\n ))}\n </div>\n </SectionCard>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <SectionCard\n title=\"Deliverables\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddDeliverable(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.deliverables.length === 0 ? (\n <Empty\n title=\"No deliverables\"\n description=\"Track the tangible outputs of this engagement.\"\n icon={<FileText className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2\">\n {e.deliverables.map((d) => (\n <li key={d.id} className=\"rounded-lg border border-border-seam p-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <div className=\"text-sm font-medium text-text-primary\">\n {d.name} <span className=\"text-xs text-text-tertiary\">v{d.version}</span>\n </div>\n {d.dueDate && (\n <div className=\"text-xs text-text-tertiary\">Due {fmtDate(d.dueDate)}</div>\n )}\n </div>\n <DeliverableStatusSelect\n id={d.id}\n engagementId={e.id}\n status={d.status}\n onDone={() => void refetch()}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard\n title=\"Milestones\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddMilestone(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.milestones.length === 0 ? (\n <Empty\n title=\"No milestones\"\n description=\"Add checkpoints — some can trigger billing.\"\n icon={<Flag className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2\">\n {e.milestones.map((m) => (\n <li\n key={m.id}\n className=\"flex items-center justify-between rounded-lg border border-border-seam p-3\"\n >\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{m.name}</div>\n <div className=\"text-xs text-text-tertiary\">\n {m.triggersInvoice ? `Bills ${fmtMoney(m.triggerAmount, e.currency)} · ` : ''}\n Due {fmtDate(m.dueDate)}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusPill status={m.status} />\n {m.status !== 'COMPLETED' && m.status !== 'CANCELLED' && (\n <CompleteMilestoneButton\n id={m.id}\n engagementId={e.id}\n onDone={() => void refetch()}\n />\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n </div>\n\n <SectionCard\n title=\"Time logged\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('time')}>\n <Clock className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n >\n {time.isLoading ? (\n <LoadingBlock label=\"Loading…\" />\n ) : !time.data || time.data.items.length === 0 ? (\n <Empty\n title=\"No time logged\"\n description=\"Time logged against this engagement will appear here.\"\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {time.data.items.slice(0, 8).map((t) => (\n <li key={t.id} className=\"flex items-center justify-between py-2 text-sm\">\n <span className=\"text-text-secondary\">\n {fmtDate(t.date)} · {t.narrative || 'Time entry'}\n </span>\n <span className=\"flex items-center gap-2\">\n <span className=\"text-text-primary\">{fmtMinutesAsHours(t.minutes)}</span>\n <StatusPill status={t.status} />\n </span>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <DiscussionThread entityType=\"proserviceworld-engagement\" entityId={e.id} />\n\n {addDeliverable && (\n <DeliverableModal\n engagementId={e.id}\n onClose={() => setAddDeliverable(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Deliverable added.' });\n }}\n />\n )}\n {addMilestone && (\n <MilestoneModal\n engagementId={e.id}\n onClose={() => setAddMilestone(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Milestone added.' });\n }}\n />\n )}\n </div>\n );\n};\n\nconst DeliverableStatusSelect: FC<{\n id: string;\n engagementId: string;\n status: PswDeliverableStatus;\n onDone: () => void;\n}> = ({ id, engagementId, status, onDone }) => {\n const transition = useTransitionDeliverable();\n return (\n <NativeSelect\n value={status}\n disabled={transition.isPending}\n onChange={(ev) =>\n transition.mutate(\n { id, status: ev.target.value as PswDeliverableStatus, engagementId },\n { onSuccess: onDone }\n )\n }\n className=\"w-40\"\n >\n {DELIVERABLE_STATUSES.map((s) => (\n <option key={s} value={s}>\n {humanize(s)}\n </option>\n ))}\n </NativeSelect>\n );\n};\n\nconst CompleteMilestoneButton: FC<{ id: string; engagementId: string; onDone: () => void }> = ({\n id,\n engagementId,\n onDone,\n}) => {\n const complete = useCompleteMilestone();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Complete milestone\"\n disabled={complete.isPending}\n onClick={() => complete.mutate({ id, engagementId }, { onSuccess: onDone })}\n >\n <CheckCircle2 className=\"h-4 w-4\" />\n </Button>\n );\n};\n\nconst DeliverableModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateDeliverable();\n const [form, setForm] = useState({\n name: '',\n description: '',\n dueDate: '',\n acceptanceCriteria: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n description: form.description.trim() || null,\n acceptanceCriteria: form.acceptanceCriteria.trim() || null,\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add deliverable\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"Draft audit report\" />\n </Field>\n <Field label=\"Due date\">\n <TextInput type=\"date\" value={form.dueDate} onChange={set('dueDate')} />\n </Field>\n <Field label=\"Acceptance criteria\">\n <TextArea value={form.acceptanceCriteria} onChange={set('acceptanceCriteria')} />\n </Field>\n <Field label=\"Description\">\n <TextArea value={form.description} onChange={set('description')} />\n </Field>\n </div>\n </Modal>\n );\n};\n\nconst MilestoneModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateMilestone();\n const [form, setForm] = useState({\n name: '',\n dueDate: '',\n triggerAmount: '',\n triggersInvoice: false,\n });\n const [err, setErr] = useState<string | null>(null);\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n triggerAmount: form.triggerAmount ? Number(form.triggerAmount) : 0,\n triggersInvoice: form.triggersInvoice,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add milestone\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Phase 1 sign-off\"\n />\n </Field>\n <Field label=\"Due date\">\n <TextInput\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm((f) => ({ ...f, dueDate: e.target.value }))}\n />\n </Field>\n <Field\n label=\"Trigger amount\"\n hint=\"Amount billed when this milestone completes (if it triggers an invoice).\"\n >\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.triggerAmount}\n onChange={(e) => setForm((f) => ({ ...f, triggerAmount: e.target.value }))}\n />\n </Field>\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.triggersInvoice}\n onChange={(e) => setForm((f) => ({ ...f, triggersInvoice: e.target.checked }))}\n />\n Completing this milestone triggers an invoice\n </label>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"EngagementDetailPage.js","names":[],"sources":["../../../src/proserviceworld/pages/EngagementDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n ArrowLeft,\n ArrowRight,\n Plus,\n CheckCircle2,\n FileText,\n Flag,\n Clock,\n DollarSign,\n} from 'lucide-react';\nimport {\n useEngagement,\n useTransitionEngagement,\n useCreateDeliverable,\n useTransitionDeliverable,\n useCreateMilestone,\n useCompleteMilestone,\n useTimeEntries,\n type PswEngagementStatus,\n type PswDeliverableStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { DiscussionThread } from '../components/DiscussionThread';\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 fmtDate,\n fmtMoney,\n fmtMinutesAsHours,\n humanize,\n} from '../components/ui';\n\n// Mirrors the svc ENGAGEMENT_TRANSITIONS state machine so we only offer valid moves.\nconst NEXT: Record<PswEngagementStatus, PswEngagementStatus[]> = {\n PROPOSAL: ['ACCEPTED', 'CANCELLED'],\n ACCEPTED: ['IN_PROGRESS', 'CANCELLED'],\n IN_PROGRESS: ['REVIEW', 'CANCELLED'],\n REVIEW: ['DELIVERED', 'IN_PROGRESS', 'CANCELLED'],\n DELIVERED: ['INVOICED', 'CANCELLED'],\n INVOICED: ['CLOSED'],\n CLOSED: [],\n CANCELLED: [],\n};\nconst DELIVERABLE_STATUSES: PswDeliverableStatus[] = [\n 'DRAFT',\n 'INTERNAL_REVIEW',\n 'CLIENT_REVIEW',\n 'ACCEPTED',\n 'REJECTED',\n];\n\nexport const EngagementDetailPage: FC = () => {\n const { id = '' } = useParams();\n const nav = useNav();\n const { data: e, isLoading, error, refetch } = useEngagement(id);\n const time = useTimeEntries({ engagementId: id });\n const transition = useTransitionEngagement();\n const [addDeliverable, setAddDeliverable] = useState(false);\n const [addMilestone, setAddMilestone] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n\n if (isLoading) return <LoadingBlock />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!e) return <Empty title=\"Engagement not found\" />;\n\n const nextStates = NEXT[e.status] ?? [];\n const loggedMinutes = (time.data?.items ?? []).reduce((sum, t) => sum + t.minutes, 0);\n\n return (\n <div className=\"space-y-6\">\n <button\n type=\"button\"\n onClick={() => nav('engagements')}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary transition-colors hover:text-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to engagements\n </button>\n\n <PageHeader\n title={e.name}\n subtitle={\n e.client ? `${e.client.name} · ${humanize(e.feeArrangement)}` : humanize(e.feeArrangement)\n }\n actions={<StatusPill status={e.status} />}\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 lg:grid-cols-4\">\n <MetricCard\n label=\"Fee\"\n value={fmtMoney(e.feeAmount, e.currency)}\n hint={humanize(e.feeArrangement)}\n icon={<DollarSign className=\"h-4 w-4\" />}\n tone=\"success\"\n />\n <MetricCard\n label=\"Deliverables\"\n value={e.deliverables.length}\n icon={<FileText className=\"h-4 w-4\" />}\n tone=\"info\"\n />\n <MetricCard\n label=\"Milestones\"\n value={e.milestones.length}\n icon={<Flag className=\"h-4 w-4\" />}\n tone=\"accent\"\n />\n <MetricCard\n label=\"Time logged\"\n value={fmtMinutesAsHours(loggedMinutes)}\n hint={`${time.data?.totalCount ?? 0} entries`}\n icon={<Clock className=\"h-4 w-4\" />}\n tone=\"warning\"\n />\n </div>\n\n {nextStates.length > 0 && (\n <SectionCard title=\"Advance lifecycle\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>Currently</span>\n <StatusPill status={e.status} />\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {nextStates.map((s) => {\n const isCancel = s === 'CANCELLED';\n return (\n <Button\n key={s}\n size=\"sm\"\n variant={isCancel ? 'outline' : 'default'}\n disabled={transition.isPending}\n onClick={() =>\n transition.mutate(\n { id: e.id, status: s },\n {\n onSuccess: () =>\n setBanner({ tone: 'success', text: `Moved to ${humanize(s)}.` }),\n onError: (err) =>\n setBanner({\n tone: 'danger',\n text: err instanceof Error ? err.message : 'Transition failed.',\n }),\n }\n )\n }\n >\n {humanize(s)}\n {!isCancel && <ArrowRight className=\"ml-1.5 h-4 w-4\" />}\n </Button>\n );\n })}\n </div>\n </div>\n </SectionCard>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <SectionCard\n title=\"Deliverables\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddDeliverable(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.deliverables.length === 0 ? (\n <Empty\n title=\"No deliverables\"\n description=\"Track the tangible outputs of this engagement.\"\n icon={<FileText className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2.5\">\n {e.deliverables.map((d) => (\n <li\n key={d.id}\n className=\"rounded-xl border border-border-seam bg-bg-canvas p-3.5 transition-colors hover:border-accent/40\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2 text-sm font-medium text-text-primary\">\n <span className=\"truncate\">{d.name}</span>\n <span className=\"shrink-0 rounded-md bg-bg-sunken px-1.5 py-0.5 text-[10px] font-semibold text-text-tertiary\">\n v{d.version}\n </span>\n </div>\n {d.dueDate && (\n <div className=\"mt-0.5 text-xs text-text-tertiary\">\n Due {fmtDate(d.dueDate)}\n </div>\n )}\n </div>\n <DeliverableStatusSelect\n id={d.id}\n engagementId={e.id}\n status={d.status}\n onDone={() => void refetch()}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard\n title=\"Milestones\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddMilestone(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.milestones.length === 0 ? (\n <Empty\n title=\"No milestones\"\n description=\"Add checkpoints — some can trigger billing.\"\n icon={<Flag className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2.5\">\n {e.milestones.map((m) => (\n <li\n key={m.id}\n className=\"flex items-center justify-between gap-3 rounded-xl border border-border-seam bg-bg-canvas p-3.5 transition-colors hover:border-accent/40\"\n >\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium text-text-primary\">{m.name}</div>\n <div className=\"mt-0.5 text-xs text-text-tertiary\">\n {m.triggersInvoice ? `Bills ${fmtMoney(m.triggerAmount, e.currency)} · ` : ''}\n Due {fmtDate(m.dueDate)}\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <StatusPill status={m.status} />\n {m.status !== 'COMPLETED' && m.status !== 'CANCELLED' && (\n <CompleteMilestoneButton\n id={m.id}\n engagementId={e.id}\n onDone={() => void refetch()}\n />\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">\n <DiscussionThread entityType=\"proserviceworld-engagement\" entityId={e.id} />\n </div>\n\n <SectionCard\n title=\"Time logged\"\n description={fmtMinutesAsHours(loggedMinutes)}\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('time')}>\n <Clock className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n >\n {time.isLoading ? (\n <LoadingBlock label=\"Loading…\" />\n ) : !time.data || time.data.items.length === 0 ? (\n <Empty\n title=\"No time logged\"\n description=\"Time logged against this engagement will appear here.\"\n icon={<Clock className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2\">\n {time.data.items.slice(0, 8).map((t) => (\n <li\n key={t.id}\n className=\"rounded-lg border border-border-seam bg-bg-canvas p-3 text-sm\"\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"whitespace-nowrap text-xs text-text-tertiary\">\n {fmtDate(t.date)}\n </span>\n <span className=\"flex items-center gap-2\">\n <span className=\"font-medium text-text-primary\">\n {fmtMinutesAsHours(t.minutes)}\n </span>\n <StatusPill status={t.status} />\n </span>\n </div>\n <div className=\"mt-1 truncate text-text-secondary\">\n {t.narrative || 'Time entry'}\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n </div>\n\n {addDeliverable && (\n <DeliverableModal\n engagementId={e.id}\n onClose={() => setAddDeliverable(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Deliverable added.' });\n }}\n />\n )}\n {addMilestone && (\n <MilestoneModal\n engagementId={e.id}\n onClose={() => setAddMilestone(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Milestone added.' });\n }}\n />\n )}\n </div>\n );\n};\n\nconst DeliverableStatusSelect: FC<{\n id: string;\n engagementId: string;\n status: PswDeliverableStatus;\n onDone: () => void;\n}> = ({ id, engagementId, status, onDone }) => {\n const transition = useTransitionDeliverable();\n return (\n <NativeSelect\n value={status}\n disabled={transition.isPending}\n onChange={(ev) =>\n transition.mutate(\n { id, status: ev.target.value as PswDeliverableStatus, engagementId },\n { onSuccess: onDone }\n )\n }\n className=\"w-40 shrink-0\"\n >\n {DELIVERABLE_STATUSES.map((s) => (\n <option key={s} value={s}>\n {humanize(s)}\n </option>\n ))}\n </NativeSelect>\n );\n};\n\nconst CompleteMilestoneButton: FC<{ id: string; engagementId: string; onDone: () => void }> = ({\n id,\n engagementId,\n onDone,\n}) => {\n const complete = useCompleteMilestone();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Complete milestone\"\n disabled={complete.isPending}\n onClick={() => complete.mutate({ id, engagementId }, { onSuccess: onDone })}\n >\n <CheckCircle2 className=\"h-4 w-4\" />\n </Button>\n );\n};\n\nconst DeliverableModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateDeliverable();\n const [form, setForm] = useState({\n name: '',\n description: '',\n dueDate: '',\n acceptanceCriteria: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n description: form.description.trim() || null,\n acceptanceCriteria: form.acceptanceCriteria.trim() || null,\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add deliverable\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"Draft audit report\" />\n </Field>\n <Field label=\"Due date\">\n <TextInput type=\"date\" value={form.dueDate} onChange={set('dueDate')} />\n </Field>\n <Field label=\"Acceptance criteria\">\n <TextArea value={form.acceptanceCriteria} onChange={set('acceptanceCriteria')} />\n </Field>\n <Field label=\"Description\">\n <TextArea value={form.description} onChange={set('description')} />\n </Field>\n </div>\n </Modal>\n );\n};\n\nconst MilestoneModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateMilestone();\n const [form, setForm] = useState({\n name: '',\n dueDate: '',\n triggerAmount: '',\n triggersInvoice: false,\n });\n const [err, setErr] = useState<string | null>(null);\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n triggerAmount: form.triggerAmount ? Number(form.triggerAmount) : 0,\n triggersInvoice: form.triggersInvoice,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add milestone\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Phase 1 sign-off\"\n />\n </Field>\n <Field label=\"Due date\">\n <TextInput\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm((f) => ({ ...f, dueDate: e.target.value }))}\n />\n </Field>\n <Field\n label=\"Trigger amount\"\n hint=\"Amount billed when this milestone completes (if it triggers an invoice).\"\n >\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.triggerAmount}\n onChange={(e) => setForm((f) => ({ ...f, triggerAmount: e.target.value }))}\n />\n </Field>\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.triggersInvoice}\n onChange={(e) => setForm((f) => ({ ...f, triggersInvoice: e.target.checked }))}\n />\n Completing this milestone triggers an invoice\n </label>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
@@ -1,12 +1,12 @@
1
1
  import { useClients as e, useCreateEngagement as t, useEngagements as n, usePracticeAreas as r } from "../hooks/useProServiceWorldApi.js";
2
2
  import { useNav as i } from "../utils/nav.js";
3
- import { Banner as a, Empty as o, ErrorState as s, Field as c, LoadingBlock as l, Modal as u, NativeSelect as d, PageHeader as f, SectionCard as p, StatusPill as m, TableWrap as h, Td as g, TextArea as _, TextInput as v, Th as y, enumOptions as b, fmtDate as x, fmtMoney as S } from "../components/ui.js";
4
- import { useState as C } from "react";
5
- import { Briefcase as w, Plus as T, Search as E } from "lucide-react";
6
- import { Fragment as D, jsx as O, jsxs as k } from "react/jsx-runtime";
7
- import { Button as A } from "@burdenoff/fe-libs/ui";
3
+ import { Banner as a, Empty as o, ErrorState as s, Field as c, LoadingBlock as l, Modal as u, NativeSelect as d, PageHeader as f, SectionCard as p, StatusPill as m, TableWrap as h, Td as g, TextArea as _, TextInput as v, Th as y, enumOptions as b, fmtDate as x, fmtMoney as S, humanize as C } from "../components/ui.js";
4
+ import { useState as w } from "react";
5
+ import { Briefcase as T, ChevronRight as E, Plus as D, Search as O } from "lucide-react";
6
+ import { Fragment as k, jsx as A, jsxs as j } from "react/jsx-runtime";
7
+ import { Button as M } from "@burdenoff/fe-libs/ui";
8
8
  //#region src/proserviceworld/pages/EngagementsPage.tsx
9
- var j = [
9
+ var N = [
10
10
  "PROPOSAL",
11
11
  "ACCEPTED",
12
12
  "IN_PROGRESS",
@@ -15,102 +15,140 @@ var j = [
15
15
  "INVOICED",
16
16
  "CLOSED",
17
17
  "CANCELLED"
18
- ], M = [
18
+ ], P = [
19
19
  "FIXED",
20
20
  "HOURLY",
21
21
  "RETAINER",
22
22
  "VALUE_BASED"
23
- ], N = () => {
24
- let e = i(), [t, r] = C(""), [c, u] = C(""), [_, D] = C(!1), [M, N] = C(null), { data: F, isLoading: I, error: L, refetch: R } = n({
25
- search: t.trim() || void 0,
26
- status: c || void 0
23
+ ], F = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpperCase() ?? "").join("") || "?", I = ({ name: e }) => /* @__PURE__ */ A("span", {
24
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-[10px] font-semibold text-accent ring-1 ring-inset ring-accent/15",
25
+ "aria-hidden": !0,
26
+ children: F(e)
27
+ }), L = () => {
28
+ let t = i(), [r, c] = w(""), [u, _] = w(""), [k, P] = w(""), [F, L] = w(!1), [z, B] = w(null), V = e({ pagination: { limit: 100 } }), { data: H, isLoading: U, error: W, refetch: G } = n({
29
+ search: r.trim() || void 0,
30
+ status: u || void 0,
31
+ clientId: k || void 0
27
32
  });
28
- return /* @__PURE__ */ k("div", {
33
+ return /* @__PURE__ */ j("div", {
29
34
  className: "space-y-6",
30
35
  children: [
31
- /* @__PURE__ */ O(f, {
36
+ /* @__PURE__ */ A(f, {
32
37
  title: "Engagements",
33
38
  subtitle: "The commercial containers of contracted work — scope, fee arrangement, delivery lifecycle, and billing.",
34
- icon: /* @__PURE__ */ O(w, { className: "h-6 w-6" }),
35
- actions: /* @__PURE__ */ k(A, {
36
- onClick: () => D(!0),
37
- children: [/* @__PURE__ */ O(T, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
39
+ icon: /* @__PURE__ */ A(T, { className: "h-6 w-6" }),
40
+ actions: /* @__PURE__ */ j(M, {
41
+ onClick: () => L(!0),
42
+ children: [/* @__PURE__ */ A(D, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
38
43
  })
39
44
  }),
40
- M && /* @__PURE__ */ O(a, {
45
+ z && /* @__PURE__ */ A(a, {
41
46
  tone: "success",
42
- onClose: () => N(null),
43
- children: M
47
+ onClose: () => B(null),
48
+ children: z
44
49
  }),
45
- /* @__PURE__ */ k("div", {
50
+ /* @__PURE__ */ j("div", {
46
51
  className: "flex flex-col gap-2 sm:flex-row sm:items-center",
47
- children: [/* @__PURE__ */ k("div", {
48
- className: "relative flex-1",
49
- children: [/* @__PURE__ */ O(E, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }), /* @__PURE__ */ O(v, {
50
- placeholder: "Search engagements…",
51
- value: t,
52
- onChange: (e) => r(e.target.value),
53
- className: "pl-9"
54
- })]
55
- }), /* @__PURE__ */ k(d, {
56
- value: c,
57
- onChange: (e) => u(e.target.value),
58
- className: "sm:w-52",
59
- children: [/* @__PURE__ */ O("option", {
60
- value: "",
61
- children: "All statuses"
62
- }), b(j)]
63
- })]
52
+ children: [
53
+ /* @__PURE__ */ j("div", {
54
+ className: "relative flex-1",
55
+ children: [/* @__PURE__ */ A(O, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }), /* @__PURE__ */ A(v, {
56
+ placeholder: "Search engagements…",
57
+ value: r,
58
+ onChange: (e) => c(e.target.value),
59
+ className: "pl-9"
60
+ })]
61
+ }),
62
+ /* @__PURE__ */ j(d, {
63
+ value: u,
64
+ onChange: (e) => _(e.target.value),
65
+ className: "sm:w-44",
66
+ children: [/* @__PURE__ */ A("option", {
67
+ value: "",
68
+ children: "All statuses"
69
+ }), b(N)]
70
+ }),
71
+ /* @__PURE__ */ j(d, {
72
+ value: k,
73
+ onChange: (e) => P(e.target.value),
74
+ className: "sm:w-48",
75
+ children: [/* @__PURE__ */ A("option", {
76
+ value: "",
77
+ children: "All clients"
78
+ }), V.data?.items.map((e) => /* @__PURE__ */ A("option", {
79
+ value: e.id,
80
+ children: e.name
81
+ }, e.id))]
82
+ })
83
+ ]
64
84
  }),
65
- I ? /* @__PURE__ */ O(l, {}) : L ? /* @__PURE__ */ O(s, {
66
- error: L,
67
- onRetry: () => void R()
68
- }) : !F || F.items.length === 0 ? /* @__PURE__ */ O(o, {
85
+ U ? /* @__PURE__ */ A(l, {}) : W ? /* @__PURE__ */ A(s, {
86
+ error: W,
87
+ onRetry: () => void G()
88
+ }) : !H || H.items.length === 0 ? /* @__PURE__ */ A(o, {
69
89
  title: "No engagements yet",
70
90
  description: "Scope your first engagement against a client to begin delivery and billing.",
71
- icon: /* @__PURE__ */ O(w, { className: "h-8 w-8" }),
72
- action: /* @__PURE__ */ k(A, {
73
- onClick: () => D(!0),
74
- children: [/* @__PURE__ */ O(T, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
91
+ icon: /* @__PURE__ */ A(T, { className: "h-8 w-8" }),
92
+ action: /* @__PURE__ */ j(M, {
93
+ onClick: () => L(!0),
94
+ children: [/* @__PURE__ */ A(D, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
75
95
  })
76
- }) : /* @__PURE__ */ O(p, {
77
- title: `${F.totalCount} engagement${F.totalCount === 1 ? "" : "s"}`,
78
- children: /* @__PURE__ */ k(h, { children: [/* @__PURE__ */ O("thead", { children: /* @__PURE__ */ k("tr", { children: [
79
- /* @__PURE__ */ O(y, { children: "Engagement" }),
80
- /* @__PURE__ */ O(y, { children: "Client" }),
81
- /* @__PURE__ */ O(y, { children: "Fee" }),
82
- /* @__PURE__ */ O(y, { children: "Status" }),
83
- /* @__PURE__ */ O(y, { children: "Started" })
84
- ] }) }), /* @__PURE__ */ O("tbody", { children: F.items.map((t) => /* @__PURE__ */ k("tr", {
85
- className: "cursor-pointer hover:bg-bg-sunken/50",
86
- onClick: () => e(`engagements/${t.id}`),
96
+ }) : /* @__PURE__ */ A(p, {
97
+ title: `${H.totalCount} engagement${H.totalCount === 1 ? "" : "s"}`,
98
+ children: /* @__PURE__ */ j(h, { children: [/* @__PURE__ */ A("thead", { children: /* @__PURE__ */ j("tr", { children: [
99
+ /* @__PURE__ */ A(y, { children: "Engagement" }),
100
+ /* @__PURE__ */ A(y, { children: "Client" }),
101
+ /* @__PURE__ */ A(y, { children: "Fee" }),
102
+ /* @__PURE__ */ A(y, { children: "Status" }),
103
+ /* @__PURE__ */ A(y, { children: "Started" })
104
+ ] }) }), /* @__PURE__ */ A("tbody", { children: H.items.map((e) => /* @__PURE__ */ j("tr", {
105
+ className: "group cursor-pointer transition-colors hover:bg-bg-sunken/60",
106
+ onClick: () => t(`engagements/${e.id}`),
87
107
  children: [
88
- /* @__PURE__ */ O(g, { children: /* @__PURE__ */ O("span", {
89
- className: "font-medium text-text-primary",
90
- children: t.name
108
+ /* @__PURE__ */ j(g, { children: [/* @__PURE__ */ j("div", {
109
+ className: "flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent",
110
+ children: [/* @__PURE__ */ A("span", { children: e.name }), /* @__PURE__ */ A(E, { className: "h-3.5 w-3.5 shrink-0 text-text-tertiary opacity-0 transition-opacity group-hover:opacity-100" })]
111
+ }), e.description ? /* @__PURE__ */ A("div", {
112
+ className: "mt-0.5 max-w-md truncate text-xs text-text-tertiary",
113
+ children: e.description
114
+ }) : null] }),
115
+ /* @__PURE__ */ A(g, { children: /* @__PURE__ */ j("span", {
116
+ className: "inline-flex items-center gap-2.5",
117
+ children: [/* @__PURE__ */ A(I, { name: e.client?.name ?? "—" }), /* @__PURE__ */ A("span", {
118
+ className: "text-text-secondary",
119
+ children: e.client?.name ?? "—"
120
+ })]
91
121
  }) }),
92
- /* @__PURE__ */ O(g, { children: t.client?.name ?? "—" }),
93
- /* @__PURE__ */ k(g, { children: [
94
- S(t.feeAmount, t.currency),
95
- " ",
96
- /* @__PURE__ */ k("span", {
97
- className: "text-xs text-text-tertiary",
98
- children: ["/ ", t.feeArrangement.toLowerCase()]
99
- })
100
- ] }),
101
- /* @__PURE__ */ O(g, { children: /* @__PURE__ */ O(m, { status: t.status }) }),
102
- /* @__PURE__ */ O(g, { children: x(t.startDate) })
122
+ /* @__PURE__ */ j(g, {
123
+ className: "whitespace-nowrap",
124
+ children: [
125
+ /* @__PURE__ */ A("span", {
126
+ className: "font-medium text-text-primary",
127
+ children: S(e.feeAmount, e.currency)
128
+ }),
129
+ " ",
130
+ /* @__PURE__ */ j("span", {
131
+ className: "text-xs text-text-tertiary",
132
+ children: ["/ ", C(e.feeArrangement)]
133
+ })
134
+ ]
135
+ }),
136
+ /* @__PURE__ */ A(g, { children: /* @__PURE__ */ A(m, { status: e.status }) }),
137
+ /* @__PURE__ */ A(g, {
138
+ className: "whitespace-nowrap",
139
+ children: x(e.startDate)
140
+ })
103
141
  ]
104
- }, t.id)) })] })
142
+ }, e.id)) })] })
105
143
  }),
106
- _ && /* @__PURE__ */ O(P, {
107
- onClose: () => D(!1),
108
- onSaved: (e) => N(`${e} created.`)
144
+ F && /* @__PURE__ */ A(R, {
145
+ onClose: () => L(!1),
146
+ onSaved: (e) => B(`${e} created.`)
109
147
  })
110
148
  ]
111
149
  });
112
- }, P = ({ onClose: n, onSaved: i }) => {
113
- let o = t(), s = e({ pagination: { limit: 100 } }), l = r(), [f, p] = C({
150
+ }, R = ({ onClose: n, onSaved: i }) => {
151
+ let o = t(), s = e({ pagination: { limit: 100 } }), l = r(), [f, p] = w({
114
152
  clientId: "",
115
153
  practiceAreaId: "",
116
154
  name: "",
@@ -119,20 +157,20 @@ var j = [
119
157
  feeAmount: "",
120
158
  currency: "USD",
121
159
  startDate: ""
122
- }), [m, h] = C(null), g = (e) => (t) => p((n) => ({
160
+ }), [m, h] = w(null), g = (e) => (t) => p((n) => ({
123
161
  ...n,
124
162
  [e]: t.target.value
125
163
  }));
126
- return /* @__PURE__ */ O(u, {
164
+ return /* @__PURE__ */ A(u, {
127
165
  open: !0,
128
166
  onClose: n,
129
167
  title: "New engagement",
130
- footer: /* @__PURE__ */ k(D, { children: [/* @__PURE__ */ O(A, {
168
+ footer: /* @__PURE__ */ j(k, { children: [/* @__PURE__ */ A(M, {
131
169
  variant: "outline",
132
170
  onClick: n,
133
171
  disabled: o.isPending,
134
172
  children: "Cancel"
135
- }), /* @__PURE__ */ O(A, {
173
+ }), /* @__PURE__ */ A(M, {
136
174
  onClick: () => {
137
175
  if (h(null), !f.clientId) return h("Select a client.");
138
176
  if (!f.name.trim()) return h("Engagement name is required.");
@@ -155,65 +193,65 @@ var j = [
155
193
  disabled: o.isPending,
156
194
  children: o.isPending ? "Creating…" : "Create engagement"
157
195
  })] }),
158
- children: /* @__PURE__ */ k("div", {
196
+ children: /* @__PURE__ */ j("div", {
159
197
  className: "space-y-3",
160
198
  children: [
161
- m && /* @__PURE__ */ O(a, {
199
+ m && /* @__PURE__ */ A(a, {
162
200
  tone: "danger",
163
201
  children: m
164
202
  }),
165
- /* @__PURE__ */ O(c, {
203
+ /* @__PURE__ */ A(c, {
166
204
  label: "Client",
167
205
  required: !0,
168
- children: /* @__PURE__ */ k(d, {
206
+ children: /* @__PURE__ */ j(d, {
169
207
  value: f.clientId,
170
208
  onChange: g("clientId"),
171
- children: [/* @__PURE__ */ O("option", {
209
+ children: [/* @__PURE__ */ A("option", {
172
210
  value: "",
173
211
  children: "Select a client…"
174
- }), s.data?.items.map((e) => /* @__PURE__ */ O("option", {
212
+ }), s.data?.items.map((e) => /* @__PURE__ */ A("option", {
175
213
  value: e.id,
176
214
  children: e.name
177
215
  }, e.id))]
178
216
  })
179
217
  }),
180
- /* @__PURE__ */ O(c, {
218
+ /* @__PURE__ */ A(c, {
181
219
  label: "Name",
182
220
  required: !0,
183
- children: /* @__PURE__ */ O(v, {
221
+ children: /* @__PURE__ */ A(v, {
184
222
  value: f.name,
185
223
  onChange: g("name"),
186
224
  placeholder: "FY26 Statutory Audit"
187
225
  })
188
226
  }),
189
- /* @__PURE__ */ O(c, {
227
+ /* @__PURE__ */ A(c, {
190
228
  label: "Practice area",
191
- children: /* @__PURE__ */ k(d, {
229
+ children: /* @__PURE__ */ j(d, {
192
230
  value: f.practiceAreaId,
193
231
  onChange: g("practiceAreaId"),
194
- children: [/* @__PURE__ */ O("option", {
232
+ children: [/* @__PURE__ */ A("option", {
195
233
  value: "",
196
234
  children: "Unassigned"
197
- }), l.data?.map((e) => /* @__PURE__ */ O("option", {
235
+ }), l.data?.map((e) => /* @__PURE__ */ A("option", {
198
236
  value: e.id,
199
237
  children: e.name
200
238
  }, e.id))]
201
239
  })
202
240
  }),
203
- /* @__PURE__ */ k("div", {
241
+ /* @__PURE__ */ j("div", {
204
242
  className: "grid grid-cols-3 gap-3",
205
243
  children: [
206
- /* @__PURE__ */ O(c, {
244
+ /* @__PURE__ */ A(c, {
207
245
  label: "Fee type",
208
- children: /* @__PURE__ */ O(d, {
246
+ children: /* @__PURE__ */ A(d, {
209
247
  value: f.feeArrangement,
210
248
  onChange: g("feeArrangement"),
211
- children: b(M)
249
+ children: b(P)
212
250
  })
213
251
  }),
214
- /* @__PURE__ */ O(c, {
252
+ /* @__PURE__ */ A(c, {
215
253
  label: "Fee amount",
216
- children: /* @__PURE__ */ O(v, {
254
+ children: /* @__PURE__ */ A(v, {
217
255
  type: "number",
218
256
  min: "0",
219
257
  value: f.feeAmount,
@@ -221,9 +259,9 @@ var j = [
221
259
  placeholder: "50000"
222
260
  })
223
261
  }),
224
- /* @__PURE__ */ O(c, {
262
+ /* @__PURE__ */ A(c, {
225
263
  label: "Currency",
226
- children: /* @__PURE__ */ O(v, {
264
+ children: /* @__PURE__ */ A(v, {
227
265
  value: f.currency,
228
266
  onChange: g("currency"),
229
267
  maxLength: 3
@@ -231,17 +269,17 @@ var j = [
231
269
  })
232
270
  ]
233
271
  }),
234
- /* @__PURE__ */ O(c, {
272
+ /* @__PURE__ */ A(c, {
235
273
  label: "Start date",
236
- children: /* @__PURE__ */ O(v, {
274
+ children: /* @__PURE__ */ A(v, {
237
275
  type: "date",
238
276
  value: f.startDate,
239
277
  onChange: g("startDate")
240
278
  })
241
279
  }),
242
- /* @__PURE__ */ O(c, {
280
+ /* @__PURE__ */ A(c, {
243
281
  label: "Description",
244
- children: /* @__PURE__ */ O(_, {
282
+ children: /* @__PURE__ */ A(_, {
245
283
  value: f.description,
246
284
  onChange: g("description")
247
285
  })
@@ -251,6 +289,6 @@ var j = [
251
289
  });
252
290
  };
253
291
  //#endregion
254
- export { N as EngagementsPage };
292
+ export { L as EngagementsPage };
255
293
 
256
294
  //# sourceMappingURL=EngagementsPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EngagementsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/EngagementsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Search, Briefcase } from 'lucide-react';\nimport {\n useEngagements,\n useCreateEngagement,\n useClients,\n usePracticeAreas,\n type PswEngagementStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\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 TableWrap,\n Th,\n Td,\n enumOptions,\n fmtMoney,\n fmtDate,\n} from '../components/ui';\n\nconst STATUSES: PswEngagementStatus[] = [\n 'PROPOSAL',\n 'ACCEPTED',\n 'IN_PROGRESS',\n 'REVIEW',\n 'DELIVERED',\n 'INVOICED',\n 'CLOSED',\n 'CANCELLED',\n];\nconst FEE_ARRANGEMENTS = ['FIXED', 'HOURLY', 'RETAINER', 'VALUE_BASED'] as const;\n\nexport const EngagementsPage: FC = () => {\n const nav = useNav();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<PswEngagementStatus | ''>('');\n const [creating, setCreating] = useState(false);\n const [banner, setBanner] = useState<string | null>(null);\n const { data, isLoading, error, refetch } = useEngagements({\n search: search.trim() || undefined,\n status: statusFilter || undefined,\n });\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Engagements\"\n subtitle=\"The commercial containers of contracted work — scope, fee arrangement, delivery lifecycle, and billing.\"\n icon={<Briefcase className=\"h-6 w-6\" />}\n actions={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New engagement\n </Button>\n }\n />\n\n {banner && (\n <Banner tone=\"success\" onClose={() => setBanner(null)}>\n {banner}\n </Banner>\n )}\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 engagements…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n <NativeSelect\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as PswEngagementStatus | '')}\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 engagements yet\"\n description=\"Scope your first engagement against a client to begin delivery and billing.\"\n icon={<Briefcase className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New engagement\n </Button>\n }\n />\n ) : (\n <SectionCard title={`${data.totalCount} engagement${data.totalCount === 1 ? '' : 's'}`}>\n <TableWrap>\n <thead>\n <tr>\n <Th>Engagement</Th>\n <Th>Client</Th>\n <Th>Fee</Th>\n <Th>Status</Th>\n <Th>Started</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((e) => (\n <tr\n key={e.id}\n className=\"cursor-pointer hover:bg-bg-sunken/50\"\n onClick={() => nav(`engagements/${e.id}`)}\n >\n <Td>\n <span className=\"font-medium text-text-primary\">{e.name}</span>\n </Td>\n <Td>{e.client?.name ?? '—'}</Td>\n <Td>\n {fmtMoney(e.feeAmount, e.currency)}{' '}\n <span className=\"text-xs text-text-tertiary\">\n / {e.feeArrangement.toLowerCase()}\n </span>\n </Td>\n <Td>\n <StatusPill status={e.status} />\n </Td>\n <Td>{fmtDate(e.startDate)}</Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {creating && (\n <EngagementModal\n onClose={() => setCreating(false)}\n onSaved={(name) => setBanner(`${name} created.`)}\n />\n )}\n </div>\n );\n};\n\nconst EngagementModal: FC<{ onClose: () => void; onSaved: (name: string) => void }> = ({\n onClose,\n onSaved,\n}) => {\n const create = useCreateEngagement();\n const clients = useClients({ pagination: { limit: 100 } });\n const practiceAreas = usePracticeAreas();\n const [form, setForm] = useState({\n clientId: '',\n practiceAreaId: '',\n name: '',\n description: '',\n feeArrangement: 'FIXED',\n feeAmount: '',\n currency: 'USD',\n startDate: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n\n const submit = () => {\n setErr(null);\n if (!form.clientId) return setErr('Select a client.');\n if (!form.name.trim()) return setErr('Engagement name is required.');\n create.mutate(\n {\n clientId: form.clientId,\n practiceAreaId: form.practiceAreaId || null,\n name: form.name.trim(),\n description: form.description.trim() || null,\n feeArrangement: form.feeArrangement,\n feeAmount: form.feeAmount ? Number(form.feeAmount) : 0,\n currency: form.currency || 'USD',\n startDate: form.startDate ? new Date(form.startDate).toISOString() : null,\n },\n {\n onSuccess: () => {\n onSaved(form.name.trim());\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=\"New engagement\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Creating…' : 'Create engagement'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Client\" required>\n <NativeSelect value={form.clientId} onChange={set('clientId')}>\n <option value=\"\">Select a client…</option>\n {clients.data?.items.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"FY26 Statutory Audit\" />\n </Field>\n <Field label=\"Practice area\">\n <NativeSelect value={form.practiceAreaId} onChange={set('practiceAreaId')}>\n <option value=\"\">Unassigned</option>\n {practiceAreas.data?.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n <div className=\"grid grid-cols-3 gap-3\">\n <Field label=\"Fee type\">\n <NativeSelect value={form.feeArrangement} onChange={set('feeArrangement')}>\n {enumOptions(FEE_ARRANGEMENTS)}\n </NativeSelect>\n </Field>\n <Field label=\"Fee amount\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.feeAmount}\n onChange={set('feeAmount')}\n placeholder=\"50000\"\n />\n </Field>\n <Field label=\"Currency\">\n <TextInput value={form.currency} onChange={set('currency')} maxLength={3} />\n </Field>\n </div>\n <Field label=\"Start date\">\n <TextInput type=\"date\" value={form.startDate} onChange={set('startDate')} />\n </Field>\n <Field label=\"Description\">\n <TextArea value={form.description} onChange={set('description')} />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"EngagementsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/EngagementsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Search, Briefcase, ChevronRight } from 'lucide-react';\nimport {\n useEngagements,\n useCreateEngagement,\n useClients,\n usePracticeAreas,\n type PswEngagementStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\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 TableWrap,\n Th,\n Td,\n enumOptions,\n fmtMoney,\n fmtDate,\n humanize,\n} from '../components/ui';\n\nconst STATUSES: PswEngagementStatus[] = [\n 'PROPOSAL',\n 'ACCEPTED',\n 'IN_PROGRESS',\n 'REVIEW',\n 'DELIVERED',\n 'INVOICED',\n 'CLOSED',\n 'CANCELLED',\n];\nconst FEE_ARRANGEMENTS = ['FIXED', 'HOURLY', 'RETAINER', 'VALUE_BASED'] as const;\n\nconst initials = (name: string): string =>\n name\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .map((w) => w[0]?.toUpperCase() ?? '')\n .join('') || '?';\n\nconst ClientAvatar: FC<{ name: string }> = ({ name }) => (\n <span\n className=\"flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-accent/10 text-[10px] font-semibold text-accent ring-1 ring-inset ring-accent/15\"\n aria-hidden\n >\n {initials(name)}\n </span>\n);\n\nexport const EngagementsPage: FC = () => {\n const nav = useNav();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<PswEngagementStatus | ''>('');\n const [clientFilter, setClientFilter] = useState('');\n const [creating, setCreating] = useState(false);\n const [banner, setBanner] = useState<string | null>(null);\n const clients = useClients({ pagination: { limit: 100 } });\n const { data, isLoading, error, refetch } = useEngagements({\n search: search.trim() || undefined,\n status: statusFilter || undefined,\n clientId: clientFilter || undefined,\n });\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Engagements\"\n subtitle=\"The commercial containers of contracted work — scope, fee arrangement, delivery lifecycle, and billing.\"\n icon={<Briefcase className=\"h-6 w-6\" />}\n actions={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New engagement\n </Button>\n }\n />\n\n {banner && (\n <Banner tone=\"success\" onClose={() => setBanner(null)}>\n {banner}\n </Banner>\n )}\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 engagements…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n <NativeSelect\n value={statusFilter}\n onChange={(e) => setStatusFilter(e.target.value as PswEngagementStatus | '')}\n className=\"sm:w-44\"\n >\n <option value=\"\">All statuses</option>\n {enumOptions(STATUSES)}\n </NativeSelect>\n <NativeSelect\n value={clientFilter}\n onChange={(e) => setClientFilter(e.target.value)}\n className=\"sm:w-48\"\n >\n <option value=\"\">All clients</option>\n {clients.data?.items.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\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 engagements yet\"\n description=\"Scope your first engagement against a client to begin delivery and billing.\"\n icon={<Briefcase className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n New engagement\n </Button>\n }\n />\n ) : (\n <SectionCard title={`${data.totalCount} engagement${data.totalCount === 1 ? '' : 's'}`}>\n <TableWrap>\n <thead>\n <tr>\n <Th>Engagement</Th>\n <Th>Client</Th>\n <Th>Fee</Th>\n <Th>Status</Th>\n <Th>Started</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((e) => (\n <tr\n key={e.id}\n className=\"group cursor-pointer transition-colors hover:bg-bg-sunken/60\"\n onClick={() => nav(`engagements/${e.id}`)}\n >\n <Td>\n <div className=\"flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent\">\n <span>{e.name}</span>\n <ChevronRight className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary opacity-0 transition-opacity group-hover:opacity-100\" />\n </div>\n {e.description ? (\n <div className=\"mt-0.5 max-w-md truncate text-xs text-text-tertiary\">\n {e.description}\n </div>\n ) : null}\n </Td>\n <Td>\n <span className=\"inline-flex items-center gap-2.5\">\n <ClientAvatar name={e.client?.name ?? '—'} />\n <span className=\"text-text-secondary\">{e.client?.name ?? '—'}</span>\n </span>\n </Td>\n <Td className=\"whitespace-nowrap\">\n <span className=\"font-medium text-text-primary\">\n {fmtMoney(e.feeAmount, e.currency)}\n </span>{' '}\n <span className=\"text-xs text-text-tertiary\">\n / {humanize(e.feeArrangement)}\n </span>\n </Td>\n <Td>\n <StatusPill status={e.status} />\n </Td>\n <Td className=\"whitespace-nowrap\">{fmtDate(e.startDate)}</Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {creating && (\n <EngagementModal\n onClose={() => setCreating(false)}\n onSaved={(name) => setBanner(`${name} created.`)}\n />\n )}\n </div>\n );\n};\n\nconst EngagementModal: FC<{ onClose: () => void; onSaved: (name: string) => void }> = ({\n onClose,\n onSaved,\n}) => {\n const create = useCreateEngagement();\n const clients = useClients({ pagination: { limit: 100 } });\n const practiceAreas = usePracticeAreas();\n const [form, setForm] = useState({\n clientId: '',\n practiceAreaId: '',\n name: '',\n description: '',\n feeArrangement: 'FIXED',\n feeAmount: '',\n currency: 'USD',\n startDate: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n\n const submit = () => {\n setErr(null);\n if (!form.clientId) return setErr('Select a client.');\n if (!form.name.trim()) return setErr('Engagement name is required.');\n create.mutate(\n {\n clientId: form.clientId,\n practiceAreaId: form.practiceAreaId || null,\n name: form.name.trim(),\n description: form.description.trim() || null,\n feeArrangement: form.feeArrangement,\n feeAmount: form.feeAmount ? Number(form.feeAmount) : 0,\n currency: form.currency || 'USD',\n startDate: form.startDate ? new Date(form.startDate).toISOString() : null,\n },\n {\n onSuccess: () => {\n onSaved(form.name.trim());\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=\"New engagement\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Creating…' : 'Create engagement'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Client\" required>\n <NativeSelect value={form.clientId} onChange={set('clientId')}>\n <option value=\"\">Select a client…</option>\n {clients.data?.items.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"FY26 Statutory Audit\" />\n </Field>\n <Field label=\"Practice area\">\n <NativeSelect value={form.practiceAreaId} onChange={set('practiceAreaId')}>\n <option value=\"\">Unassigned</option>\n {practiceAreas.data?.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}\n </option>\n ))}\n </NativeSelect>\n </Field>\n <div className=\"grid grid-cols-3 gap-3\">\n <Field label=\"Fee type\">\n <NativeSelect value={form.feeArrangement} onChange={set('feeArrangement')}>\n {enumOptions(FEE_ARRANGEMENTS)}\n </NativeSelect>\n </Field>\n <Field label=\"Fee amount\">\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.feeAmount}\n onChange={set('feeAmount')}\n placeholder=\"50000\"\n />\n </Field>\n <Field label=\"Currency\">\n <TextInput value={form.currency} onChange={set('currency')} maxLength={3} />\n </Field>\n </div>\n <Field label=\"Start date\">\n <TextInput type=\"date\" value={form.startDate} onChange={set('startDate')} />\n </Field>\n <Field label=\"Description\">\n <TextArea value={form.description} onChange={set('description')} />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}