@burdenoff/microfe-proserviceworld 2026.709.1 → 2026.709.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.
Files changed (43) hide show
  1. package/dist/index.js +2 -1
  2. package/dist/proserviceworld/ProServiceWorldRoutes.d.ts.map +1 -1
  3. package/dist/proserviceworld/ProServiceWorldRoutes.js +95 -42
  4. package/dist/proserviceworld/ProServiceWorldRoutes.js.map +1 -1
  5. package/dist/proserviceworld/components/FeatureFlaggedPage.d.ts +8 -0
  6. package/dist/proserviceworld/components/FeatureFlaggedPage.d.ts.map +1 -0
  7. package/dist/proserviceworld/components/FeatureFlaggedPage.js +17 -0
  8. package/dist/proserviceworld/components/FeatureFlaggedPage.js.map +1 -0
  9. package/dist/proserviceworld/components/LoadMore.d.ts +12 -0
  10. package/dist/proserviceworld/components/LoadMore.d.ts.map +1 -0
  11. package/dist/proserviceworld/components/LoadMore.js +30 -0
  12. package/dist/proserviceworld/components/LoadMore.js.map +1 -0
  13. package/dist/proserviceworld/components/TagPicker.d.ts +11 -0
  14. package/dist/proserviceworld/components/TagPicker.d.ts.map +1 -0
  15. package/dist/proserviceworld/components/TagPicker.js +31 -0
  16. package/dist/proserviceworld/components/TagPicker.js.map +1 -0
  17. package/dist/proserviceworld/hooks/usePageViewAnalytics.d.ts +10 -0
  18. package/dist/proserviceworld/hooks/usePageViewAnalytics.d.ts.map +1 -0
  19. package/dist/proserviceworld/hooks/usePageViewAnalytics.js +24 -0
  20. package/dist/proserviceworld/hooks/usePageViewAnalytics.js.map +1 -0
  21. package/dist/proserviceworld/hooks/useProServiceWorldApi.js +47 -27
  22. package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -1
  23. package/dist/proserviceworld/i18n/index.d.ts +13 -0
  24. package/dist/proserviceworld/i18n/index.d.ts.map +1 -0
  25. package/dist/proserviceworld/i18n/index.js +171 -0
  26. package/dist/proserviceworld/i18n/index.js.map +1 -0
  27. package/dist/proserviceworld/index.d.ts +1 -0
  28. package/dist/proserviceworld/index.d.ts.map +1 -1
  29. package/dist/proserviceworld/index.js +1 -0
  30. package/dist/proserviceworld/pages/ClientsPage.d.ts.map +1 -1
  31. package/dist/proserviceworld/pages/ClientsPage.js +173 -121
  32. package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
  33. package/dist/proserviceworld/pages/EngagementsPage.d.ts.map +1 -1
  34. package/dist/proserviceworld/pages/EngagementsPage.js +168 -120
  35. package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
  36. package/dist/proserviceworld/pages/MattersPage.d.ts.map +1 -1
  37. package/dist/proserviceworld/pages/MattersPage.js +183 -144
  38. package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
  39. package/dist/proserviceworld/utils/native.d.ts +23 -0
  40. package/dist/proserviceworld/utils/native.d.ts.map +1 -0
  41. package/dist/proserviceworld/utils/native.js +14 -0
  42. package/dist/proserviceworld/utils/native.js.map +1 -0
  43. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ClientsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ClientsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Plus, Search, Users, Pencil, Archive, Building2, User, ChevronRight } from 'lucide-react';\nimport {\n useClients,\n useCreateClient,\n useUpdateClient,\n useArchiveClient,\n type PswClient,\n type PswClientStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { cn } from '../utils/cn';\nimport { useAnalytics } from '../hooks/useAnalytics';\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 fmtDate,\n} from '../components/ui';\n\nconst CLIENT_TYPES = ['ORGANIZATION', 'INDIVIDUAL'] as const;\nconst CLIENT_STATUSES: PswClientStatus[] = ['PROSPECT', 'ACTIVE', 'INACTIVE', 'ARCHIVED'];\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\n/** Tinted initials avatar (semantic tokens only). */\nexport const Avatar: FC<{ name: string; className?: string }> = ({ name, className }) => (\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent ring-1 ring-inset ring-accent/15',\n className\n )}\n aria-hidden\n >\n {initials(name)}\n </span>\n);\n\ninterface FormState {\n name: string;\n type: string;\n status: string;\n industry: string;\n contactName: string;\n contactEmail: string;\n contactPhone: string;\n notes: string;\n}\nconst emptyForm: FormState = {\n name: '',\n type: 'ORGANIZATION',\n status: 'PROSPECT',\n industry: '',\n contactName: '',\n contactEmail: '',\n contactPhone: '',\n notes: '',\n};\n\nexport const ClientsPage: FC = () => {\n const nav = useNav();\n const { emitIntent, emitFact } = useAnalytics();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<PswClientStatus | ''>('');\n const [editing, setEditing] = useState<PswClient | null>(null);\n const [creating, setCreating] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n\n const { data, isLoading, error, refetch } = useClients({\n search: search.trim() || undefined,\n status: statusFilter || undefined,\n });\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title=\"Clients\"\n subtitle=\"Every person or organization your firm serves — the record engagements, matters, documents and invoices hang off.\"\n icon={<Users className=\"h-6 w-6\" />}\n actions={\n <Button\n onClick={() => {\n emitIntent('psw.client.create_clicked');\n setCreating(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> New client\n </Button>\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\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 clients…\"\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: 'client',\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 PswClientStatus | '')}\n className=\"sm:w-48\"\n >\n <option value=\"\">All statuses</option>\n {enumOptions(CLIENT_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 clients yet\"\n description=\"Add your first client to start scoping engagements and matters.\"\n icon={<Users className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New client\n </Button>\n }\n />\n ) : (\n <SectionCard title={`${data.totalCount} client${data.totalCount === 1 ? '' : 's'}`}>\n <TableWrap>\n <thead>\n <tr>\n <Th>Name</Th>\n <Th>Type</Th>\n <Th>Industry</Th>\n <Th>Status</Th>\n <Th>Added</Th>\n <Th className=\"text-right\">Actions</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((c) => (\n <tr\n key={c.id}\n className=\"group cursor-pointer transition-colors hover:bg-bg-sunken/60\"\n onClick={() => nav(`clients/${c.id}`)}\n >\n <Td>\n <div className=\"flex items-center gap-3\">\n <Avatar name={c.name} />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent\">\n <span className=\"truncate\">{c.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 {c.contactName ? (\n <div className=\"truncate text-xs text-text-tertiary\">{c.contactName}</div>\n ) : null}\n </div>\n </div>\n </Td>\n <Td>\n <span className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n {c.type === 'ORGANIZATION' ? (\n <Building2 className=\"h-3.5 w-3.5 text-text-tertiary\" />\n ) : (\n <User className=\"h-3.5 w-3.5 text-text-tertiary\" />\n )}\n {c.type === 'ORGANIZATION' ? 'Organization' : 'Individual'}\n </span>\n </Td>\n <Td>{c.industry || '—'}</Td>\n <Td>\n <StatusPill status={c.status} />\n </Td>\n <Td className=\"whitespace-nowrap\">{fmtDate(c.createdAt)}</Td>\n <Td className=\"text-right\">\n <div\n className=\"flex justify-end gap-1\"\n onClick={(ev) => ev.stopPropagation()}\n role=\"presentation\"\n >\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setEditing(c)}\n aria-label=\"Edit\"\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n {c.status !== 'ARCHIVED' && (\n <ArchiveButton\n id={c.id}\n onDone={(ok) =>\n setBanner(\n ok\n ? { tone: 'success', text: `${c.name} archived.` }\n : { tone: 'danger', text: 'Could not archive client.' }\n )\n }\n />\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </SectionCard>\n )}\n\n {creating && (\n <ClientModal\n onClose={() => setCreating(false)}\n onSaved={(name) => setBanner({ tone: 'success', text: `${name} created.` })}\n />\n )}\n {editing && (\n <ClientModal\n client={editing}\n onClose={() => setEditing(null)}\n onSaved={(name) => setBanner({ tone: 'success', text: `${name} updated.` })}\n />\n )}\n </div>\n );\n};\n\nconst ArchiveButton: FC<{ id: string; onDone: (ok: boolean) => void }> = ({ id, onDone }) => {\n const archive = useArchiveClient();\n const { emitFact } = useAnalytics();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Archive\"\n disabled={archive.isPending}\n onClick={() => {\n if (!window.confirm('Archive this client? Their engagements and matters remain.')) return;\n archive.mutate(id, {\n onSuccess: (r) => {\n emitFact('psw.client.archived', { clientId: id });\n onDone(Boolean(r));\n },\n onError: () => onDone(false),\n });\n }}\n >\n <Archive className=\"h-4 w-4\" />\n </Button>\n );\n};\n\nconst ClientModal: FC<{\n client?: PswClient;\n onClose: () => void;\n onSaved: (name: string) => void;\n}> = ({ client, onClose, onSaved }) => {\n const create = useCreateClient();\n const update = useUpdateClient();\n const { emitFact } = useAnalytics();\n const [form, setForm] = useState<FormState>(\n client\n ? {\n name: client.name,\n type: client.type,\n status: client.status,\n industry: client.industry ?? '',\n contactName: client.contactName ?? '',\n contactEmail: client.contactEmail ?? '',\n contactPhone: client.contactPhone ?? '',\n notes: client.notes ?? '',\n }\n : emptyForm\n );\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof FormState) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const pending = create.isPending || update.isPending;\n\n const payload = useMemo(\n () => ({\n name: form.name.trim(),\n type: form.type,\n status: form.status,\n industry: form.industry.trim() || null,\n contactName: form.contactName.trim() || null,\n contactEmail: form.contactEmail.trim() || null,\n contactPhone: form.contactPhone.trim() || null,\n notes: form.notes.trim() || null,\n }),\n [form]\n );\n\n const submit = () => {\n setErr(null);\n if (!payload.name) return setErr('Client name is required.');\n const onError = (e: unknown) => setErr(e instanceof Error ? e.message : 'Save failed.');\n if (client) {\n update.mutate(\n { id: client.id, input: payload },\n {\n onSuccess: () => {\n emitFact('psw.client.updated', { clientId: client.id });\n onSaved(payload.name);\n onClose();\n },\n onError,\n }\n );\n } else {\n create.mutate(payload, {\n onSuccess: (created) => {\n emitFact('psw.client.created', { clientId: created.pswCreateClient.id });\n onSaved(payload.name);\n onClose();\n },\n onError,\n });\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title={client ? 'Edit client' : 'New client'}\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={pending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={pending}>\n {pending ? 'Saving…' : client ? 'Save changes' : 'Create client'}\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=\"Acme Holdings Ltd.\" />\n </Field>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Type\">\n <NativeSelect value={form.type} onChange={set('type')}>\n {enumOptions(CLIENT_TYPES)}\n </NativeSelect>\n </Field>\n <Field label=\"Status\">\n <NativeSelect value={form.status} onChange={set('status')}>\n {enumOptions(CLIENT_STATUSES)}\n </NativeSelect>\n </Field>\n </div>\n <Field label=\"Industry\">\n <TextInput\n value={form.industry}\n onChange={set('industry')}\n placeholder=\"Financial services\"\n />\n </Field>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <Field label=\"Contact name\">\n <TextInput value={form.contactName} onChange={set('contactName')} />\n </Field>\n <Field label=\"Contact email\">\n <TextInput type=\"email\" value={form.contactEmail} onChange={set('contactEmail')} />\n </Field>\n </div>\n <Field label=\"Contact phone\">\n <TextInput value={form.contactPhone} onChange={set('contactPhone')} />\n </Field>\n <Field label=\"Notes\">\n <TextArea value={form.notes} onChange={set('notes')} />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ClientsPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ClientsPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useMemo, useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n Plus,\n Search,\n Users,\n Pencil,\n Archive,\n Building2,\n User,\n ChevronRight,\n} from 'lucide-react';\nimport {\n useClients,\n useCreateClient,\n useUpdateClient,\n useArchiveClient,\n type PswClient,\n type PswClientStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { cn } from '../utils/cn';\nimport { useAnalytics } from '../hooks/useAnalytics';\nimport { t } from '../i18n';\nimport { impactFeedback } from '../utils/native';\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 fmtDate,\n} from '../components/ui';\nimport { TagPicker } from '../components/TagPicker';\nimport { LoadMore } from '../components/LoadMore';\n\nconst CLIENT_TYPES = ['ORGANIZATION', 'INDIVIDUAL'] as const;\nconst CLIENT_STATUSES: PswClientStatus[] = ['PROSPECT', 'ACTIVE', 'INACTIVE', 'ARCHIVED'];\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\n/** Tinted initials avatar (semantic tokens only). */\nexport const Avatar: FC<{ name: string; className?: string }> = ({ name, className }) => (\n <span\n className={cn(\n 'flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent/10 text-xs font-semibold text-accent ring-1 ring-inset ring-accent/15',\n className\n )}\n aria-hidden\n >\n {initials(name)}\n </span>\n);\n\ninterface FormState {\n name: string;\n type: string;\n status: string;\n industry: string;\n contactName: string;\n contactEmail: string;\n contactPhone: string;\n notes: string;\n}\nconst emptyForm: FormState = {\n name: '',\n type: 'ORGANIZATION',\n status: 'PROSPECT',\n industry: '',\n contactName: '',\n contactEmail: '',\n contactPhone: '',\n notes: '',\n};\n\nconst PAGE_SIZE = 25;\n\nexport const ClientsPage: FC = () => {\n const nav = useNav();\n const { emitIntent, emitFact } = useAnalytics();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<PswClientStatus | ''>('');\n const [editing, setEditing] = useState<PswClient | null>(null);\n const [creating, setCreating] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n const [pagination, setPagination] = useState({ limit: PAGE_SIZE, offset: 0 });\n\n const { data, isLoading, error, refetch } = useClients({\n search: search.trim() || undefined,\n status: statusFilter || undefined,\n pagination,\n });\n const update = useUpdateClient();\n\n const allItems = useMemo(() => data?.items ?? [], [data]);\n\n return (\n <div className=\"space-y-6\">\n <PageHeader\n title={t('psw.clients.title')}\n subtitle={t('psw.clients.subtitle')}\n icon={<Users className=\"h-6 w-6\" />}\n actions={\n <Button\n onClick={() => {\n void impactFeedback('light');\n emitIntent('psw.client.create_clicked');\n setCreating(true);\n }}\n >\n <Plus className=\"mr-1.5 h-4 w-4\" /> {t('psw.clients.new')}\n </Button>\n }\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\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={t('psw.clients.searchPlaceholder')}\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPagination({ limit: PAGE_SIZE, offset: 0 });\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && search.trim()) {\n emitFact('psw.search.performed', {\n entityType: 'client',\n queryLength: search.trim().length,\n });\n }\n }}\n className=\"pl-9\"\n />\n </div>\n <NativeSelect\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as PswClientStatus | '');\n setPagination({ limit: PAGE_SIZE, offset: 0 });\n }}\n className=\"sm:w-48\"\n >\n <option value=\"\">{t('psw.clients.allStatuses')}</option>\n {enumOptions(CLIENT_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={t('psw.clients.emptyTitle')}\n description={t('psw.clients.emptyDescription')}\n icon={<Users className=\"h-8 w-8\" />}\n action={\n <Button onClick={() => setCreating(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> {t('psw.clients.new')}\n </Button>\n }\n />\n ) : (\n <SectionCard title={`${data.totalCount} client${data.totalCount === 1 ? '' : 's'}`}>\n <TableWrap>\n <thead>\n <tr>\n <Th>{t('psw.common.name')}</Th>\n <Th>{t('psw.common.type')}</Th>\n <Th>Industry</Th>\n <Th>{t('psw.common.status')}</Th>\n <Th>{t('psw.common.added')}</Th>\n <Th>Tags</Th>\n <Th className=\"text-right\">{t('psw.common.actions')}</Th>\n </tr>\n </thead>\n <tbody>\n {data.items.map((c) => (\n <tr\n key={c.id}\n className=\"group cursor-pointer transition-colors hover:bg-bg-sunken/60\"\n onClick={() => nav(`clients/${c.id}`)}\n >\n <Td>\n <div className=\"flex items-center gap-3\">\n <Avatar name={c.name} />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent\">\n <span className=\"truncate\">{c.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 {c.contactName ? (\n <div className=\"truncate text-xs text-text-tertiary\">{c.contactName}</div>\n ) : null}\n </div>\n </div>\n </Td>\n <Td>\n <span className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n {c.type === 'ORGANIZATION' ? (\n <Building2 className=\"h-3.5 w-3.5 text-text-tertiary\" />\n ) : (\n <User className=\"h-3.5 w-3.5 text-text-tertiary\" />\n )}\n {c.type === 'ORGANIZATION' ? 'Organization' : 'Individual'}\n </span>\n </Td>\n <Td>{c.industry || '—'}</Td>\n <Td>\n <StatusPill status={c.status} />\n </Td>\n <Td className=\"whitespace-nowrap\">{fmtDate(c.createdAt)}</Td>\n <Td>\n <div onClick={(ev) => ev.stopPropagation()} role=\"presentation\">\n <TagPicker\n entityType=\"PswClient\"\n entityId={c.id}\n tags={c.tags ?? []}\n onChange={async (tags) => {\n await update.mutateAsync({ id: c.id, input: { tags } });\n void refetch();\n }}\n maxTags={5}\n />\n </div>\n </Td>\n <Td className=\"text-right\">\n <div\n className=\"flex justify-end gap-1\"\n onClick={(ev) => ev.stopPropagation()}\n role=\"presentation\"\n >\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setEditing(c)}\n aria-label=\"Edit\"\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n {c.status !== 'ARCHIVED' && (\n <ArchiveButton\n id={c.id}\n onDone={(ok) =>\n setBanner(\n ok\n ? { tone: 'success', text: `${c.name} archived.` }\n : { tone: 'danger', text: 'Could not archive client.' }\n )\n }\n />\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n <LoadMore\n hasMore={data?.pageInfo?.hasMore ?? false}\n loading={isLoading}\n loadedCount={allItems.length}\n totalCount={data?.totalCount ?? allItems.length}\n onLoadMore={() =>\n setPagination((p) => ({ limit: PAGE_SIZE, offset: p.offset + PAGE_SIZE }))\n }\n />\n </SectionCard>\n )}\n\n {creating && (\n <ClientModal\n onClose={() => setCreating(false)}\n onSaved={(name) => setBanner({ tone: 'success', text: `${name} created.` })}\n />\n )}\n {editing && (\n <ClientModal\n client={editing}\n onClose={() => setEditing(null)}\n onSaved={(name) => setBanner({ tone: 'success', text: `${name} updated.` })}\n />\n )}\n </div>\n );\n};\n\nconst ArchiveButton: FC<{ id: string; onDone: (ok: boolean) => void }> = ({ id, onDone }) => {\n const archive = useArchiveClient();\n const { emitFact } = useAnalytics();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Archive\"\n disabled={archive.isPending}\n onClick={() => {\n if (!window.confirm('Archive this client? Their engagements and matters remain.')) return;\n archive.mutate(id, {\n onSuccess: (r) => {\n emitFact('psw.client.archived', { clientId: id });\n onDone(Boolean(r));\n },\n onError: () => onDone(false),\n });\n }}\n >\n <Archive className=\"h-4 w-4\" />\n </Button>\n );\n};\n\nconst ClientModal: FC<{\n client?: PswClient;\n onClose: () => void;\n onSaved: (name: string) => void;\n}> = ({ client, onClose, onSaved }) => {\n const create = useCreateClient();\n const update = useUpdateClient();\n const { emitFact } = useAnalytics();\n const [form, setForm] = useState<FormState>(\n client\n ? {\n name: client.name,\n type: client.type,\n status: client.status,\n industry: client.industry ?? '',\n contactName: client.contactName ?? '',\n contactEmail: client.contactEmail ?? '',\n contactPhone: client.contactPhone ?? '',\n notes: client.notes ?? '',\n }\n : emptyForm\n );\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof FormState) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const pending = create.isPending || update.isPending;\n\n const payload = useMemo(\n () => ({\n name: form.name.trim(),\n type: form.type,\n status: form.status,\n industry: form.industry.trim() || null,\n contactName: form.contactName.trim() || null,\n contactEmail: form.contactEmail.trim() || null,\n contactPhone: form.contactPhone.trim() || null,\n notes: form.notes.trim() || null,\n }),\n [form]\n );\n\n const submit = () => {\n setErr(null);\n if (!payload.name) return setErr('Client name is required.');\n const onError = (e: unknown) => setErr(e instanceof Error ? e.message : 'Save failed.');\n if (client) {\n update.mutate(\n { id: client.id, input: payload },\n {\n onSuccess: () => {\n emitFact('psw.client.updated', { clientId: client.id });\n onSaved(payload.name);\n onClose();\n },\n onError,\n }\n );\n } else {\n create.mutate(payload, {\n onSuccess: (created) => {\n emitFact('psw.client.created', { clientId: created.pswCreateClient.id });\n onSaved(payload.name);\n onClose();\n },\n onError,\n });\n }\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title={client ? 'Edit client' : 'New client'}\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={pending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={pending}>\n {pending ? 'Saving…' : client ? 'Save changes' : 'Create client'}\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=\"Acme Holdings Ltd.\" />\n </Field>\n <div className=\"grid grid-cols-2 gap-3\">\n <Field label=\"Type\">\n <NativeSelect value={form.type} onChange={set('type')}>\n {enumOptions(CLIENT_TYPES)}\n </NativeSelect>\n </Field>\n <Field label=\"Status\">\n <NativeSelect value={form.status} onChange={set('status')}>\n {enumOptions(CLIENT_STATUSES)}\n </NativeSelect>\n </Field>\n </div>\n <Field label=\"Industry\">\n <TextInput\n value={form.industry}\n onChange={set('industry')}\n placeholder=\"Financial services\"\n />\n </Field>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <Field label=\"Contact name\">\n <TextInput value={form.contactName} onChange={set('contactName')} />\n </Field>\n <Field label=\"Contact email\">\n <TextInput type=\"email\" value={form.contactEmail} onChange={set('contactEmail')} />\n </Field>\n </div>\n <Field label=\"Contact phone\">\n <TextInput value={form.contactPhone} onChange={set('contactPhone')} />\n </Field>\n <Field label=\"Notes\">\n <TextArea value={form.notes} onChange={set('notes')} />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"EngagementsPage.d.ts","sourceRoot":"","sources":["../../../src/proserviceworld/pages/EngagementsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAiEhC,eAAO,MAAM,eAAe,EAAE,EA+J7B,CAAC"}
1
+ {"version":3,"file":"EngagementsPage.d.ts","sourceRoot":"","sources":["../../../src/proserviceworld/pages/EngagementsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAuEhC,eAAO,MAAM,eAAe,EAAE,EAsM7B,CAAC"}
@@ -1,13 +1,16 @@
1
- import { useClients as e, useCreateEngagement as t, useEngagements as n, usePracticeAreas as r } from "../hooks/useProServiceWorldApi.js";
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, humanize as C } from "../components/ui.js";
1
+ import { useClients as e, useCreateEngagement as t, useEngagements as n, usePracticeAreas as r, useUpdateEngagement as i } from "../hooks/useProServiceWorldApi.js";
2
+ import { useNav as a } from "../utils/nav.js";
3
+ import { Banner as o, Empty as s, ErrorState as c, Field as l, LoadingBlock as u, Modal as d, NativeSelect as f, PageHeader as p, SectionCard as m, StatusPill as h, TableWrap as g, Td as _, TextArea as v, TextInput as y, Th as b, enumOptions as x, fmtDate as S, fmtMoney as ee, humanize as C } from "../components/ui.js";
4
4
  import { useAnalytics as w } from "../hooks/useAnalytics.js";
5
- import { useState as T } from "react";
6
- import { Briefcase as E, ChevronRight as D, Plus as O, Search as k } from "lucide-react";
7
- import { Fragment as A, jsx as j, jsxs as M } from "react/jsx-runtime";
8
- import { Button as N } from "@burdenoff/fe-libs/ui";
5
+ import { impactFeedback as T } from "../utils/native.js";
6
+ import { TagPicker as E } from "../components/TagPicker.js";
7
+ import { LoadMore as te } from "../components/LoadMore.js";
8
+ import { useMemo as ne, useState as D } from "react";
9
+ import { Briefcase as O, ChevronRight as k, Plus as A, Search as re } from "lucide-react";
10
+ import { Fragment as j, jsx as M, jsxs as N } from "react/jsx-runtime";
11
+ import { Button as P } from "@burdenoff/fe-libs/ui";
9
12
  //#region src/proserviceworld/pages/EngagementsPage.tsx
10
- var P = [
13
+ var F = [
11
14
  "PROPOSAL",
12
15
  "ACCEPTED",
13
16
  "IN_PROGRESS",
@@ -16,148 +19,193 @@ var P = [
16
19
  "INVOICED",
17
20
  "CLOSED",
18
21
  "CANCELLED"
19
- ], F = [
22
+ ], I = [
20
23
  "FIXED",
21
24
  "HOURLY",
22
25
  "RETAINER",
23
26
  "VALUE_BASED"
24
- ], I = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpperCase() ?? "").join("") || "?", L = ({ name: e }) => /* @__PURE__ */ j("span", {
27
+ ], L = (e) => e.split(/\s+/).filter(Boolean).slice(0, 2).map((e) => e[0]?.toUpperCase() ?? "").join("") || "?", ie = ({ name: e }) => /* @__PURE__ */ M("span", {
25
28
  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",
26
29
  "aria-hidden": !0,
27
- children: I(e)
28
- }), R = () => {
29
- let t = i(), { emitIntent: r, emitFact: c } = w(), [u, _] = T(""), [A, F] = T(""), [I, R] = T(""), [B, V] = T(!1), [H, U] = T(null), W = e({ pagination: { limit: 100 } }), { data: G, isLoading: K, error: q, refetch: J } = n({
30
- search: u.trim() || void 0,
31
- status: A || void 0,
32
- clientId: I || void 0
33
- });
34
- return /* @__PURE__ */ M("div", {
30
+ children: L(e)
31
+ }), R = 25, z = () => {
32
+ let t = a(), { emitIntent: r, emitFact: l } = w(), [d, v] = D(""), [j, I] = D(""), [L, z] = D(""), [V, H] = D(!1), [U, W] = D(null), [G, K] = D({
33
+ limit: R,
34
+ offset: 0
35
+ }), q = e({ pagination: { limit: 100 } }), J = i(), { data: Y, isLoading: X, error: Z, refetch: Q } = n({
36
+ search: d.trim() || void 0,
37
+ status: j || void 0,
38
+ clientId: L || void 0,
39
+ pagination: G
40
+ }), $ = ne(() => Y?.items ?? [], [Y]);
41
+ return /* @__PURE__ */ N("div", {
35
42
  className: "space-y-6",
36
43
  children: [
37
- /* @__PURE__ */ j(f, {
44
+ /* @__PURE__ */ M(p, {
38
45
  title: "Engagements",
39
46
  subtitle: "The commercial containers of contracted work — scope, fee arrangement, delivery lifecycle, and billing.",
40
- icon: /* @__PURE__ */ j(E, { className: "h-6 w-6" }),
41
- actions: /* @__PURE__ */ M(N, {
47
+ icon: /* @__PURE__ */ M(O, { className: "h-6 w-6" }),
48
+ actions: /* @__PURE__ */ N(P, {
42
49
  onClick: () => {
43
- r("psw.engagement.create_clicked"), V(!0);
50
+ T("light"), r("psw.engagement.create_clicked"), H(!0);
44
51
  },
45
- children: [/* @__PURE__ */ j(O, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
52
+ children: [/* @__PURE__ */ M(A, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
46
53
  })
47
54
  }),
48
- H && /* @__PURE__ */ j(a, {
55
+ U && /* @__PURE__ */ M(o, {
49
56
  tone: "success",
50
- onClose: () => U(null),
51
- children: H
57
+ onClose: () => W(null),
58
+ children: U
52
59
  }),
53
- /* @__PURE__ */ M("div", {
60
+ /* @__PURE__ */ N("div", {
54
61
  className: "flex flex-col gap-2 sm:flex-row sm:items-center",
55
62
  children: [
56
- /* @__PURE__ */ M("div", {
63
+ /* @__PURE__ */ N("div", {
57
64
  className: "relative flex-1",
58
- children: [/* @__PURE__ */ j(k, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }), /* @__PURE__ */ j(v, {
65
+ children: [/* @__PURE__ */ M(re, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-tertiary" }), /* @__PURE__ */ M(y, {
59
66
  placeholder: "Search engagements…",
60
- value: u,
61
- onChange: (e) => _(e.target.value),
67
+ value: d,
68
+ onChange: (e) => {
69
+ v(e.target.value), K({
70
+ limit: R,
71
+ offset: 0
72
+ });
73
+ },
62
74
  onKeyDown: (e) => {
63
- e.key === "Enter" && u.trim() && c("psw.search.performed", {
75
+ e.key === "Enter" && d.trim() && l("psw.search.performed", {
64
76
  entityType: "engagement",
65
- queryLength: u.trim().length
77
+ queryLength: d.trim().length
66
78
  });
67
79
  },
68
80
  className: "pl-9"
69
81
  })]
70
82
  }),
71
- /* @__PURE__ */ M(d, {
72
- value: A,
73
- onChange: (e) => F(e.target.value),
83
+ /* @__PURE__ */ N(f, {
84
+ value: j,
85
+ onChange: (e) => {
86
+ I(e.target.value), K({
87
+ limit: R,
88
+ offset: 0
89
+ });
90
+ },
74
91
  className: "sm:w-44",
75
- children: [/* @__PURE__ */ j("option", {
92
+ children: [/* @__PURE__ */ M("option", {
76
93
  value: "",
77
94
  children: "All statuses"
78
- }), b(P)]
95
+ }), x(F)]
79
96
  }),
80
- /* @__PURE__ */ M(d, {
81
- value: I,
82
- onChange: (e) => R(e.target.value),
97
+ /* @__PURE__ */ N(f, {
98
+ value: L,
99
+ onChange: (e) => {
100
+ z(e.target.value), K({
101
+ limit: R,
102
+ offset: 0
103
+ });
104
+ },
83
105
  className: "sm:w-48",
84
- children: [/* @__PURE__ */ j("option", {
106
+ children: [/* @__PURE__ */ M("option", {
85
107
  value: "",
86
108
  children: "All clients"
87
- }), W.data?.items.map((e) => /* @__PURE__ */ j("option", {
109
+ }), q.data?.items.map((e) => /* @__PURE__ */ M("option", {
88
110
  value: e.id,
89
111
  children: e.name
90
112
  }, e.id))]
91
113
  })
92
114
  ]
93
115
  }),
94
- K ? /* @__PURE__ */ j(l, {}) : q ? /* @__PURE__ */ j(s, {
95
- error: q,
96
- onRetry: () => void J()
97
- }) : !G || G.items.length === 0 ? /* @__PURE__ */ j(o, {
116
+ X ? /* @__PURE__ */ M(u, {}) : Z ? /* @__PURE__ */ M(c, {
117
+ error: Z,
118
+ onRetry: () => void Q()
119
+ }) : !Y || Y.items.length === 0 ? /* @__PURE__ */ M(s, {
98
120
  title: "No engagements yet",
99
121
  description: "Scope your first engagement against a client to begin delivery and billing.",
100
- icon: /* @__PURE__ */ j(E, { className: "h-8 w-8" }),
101
- action: /* @__PURE__ */ M(N, {
102
- onClick: () => V(!0),
103
- children: [/* @__PURE__ */ j(O, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
122
+ icon: /* @__PURE__ */ M(O, { className: "h-8 w-8" }),
123
+ action: /* @__PURE__ */ N(P, {
124
+ onClick: () => H(!0),
125
+ children: [/* @__PURE__ */ M(A, { className: "mr-1.5 h-4 w-4" }), "New engagement"]
104
126
  })
105
- }) : /* @__PURE__ */ j(p, {
106
- title: `${G.totalCount} engagement${G.totalCount === 1 ? "" : "s"}`,
107
- children: /* @__PURE__ */ M(h, { children: [/* @__PURE__ */ j("thead", { children: /* @__PURE__ */ M("tr", { children: [
108
- /* @__PURE__ */ j(y, { children: "Engagement" }),
109
- /* @__PURE__ */ j(y, { children: "Client" }),
110
- /* @__PURE__ */ j(y, { children: "Fee" }),
111
- /* @__PURE__ */ j(y, { children: "Status" }),
112
- /* @__PURE__ */ j(y, { children: "Started" })
113
- ] }) }), /* @__PURE__ */ j("tbody", { children: G.items.map((e) => /* @__PURE__ */ M("tr", {
127
+ }) : /* @__PURE__ */ N(m, {
128
+ title: `${Y.totalCount} engagement${Y.totalCount === 1 ? "" : "s"}`,
129
+ children: [/* @__PURE__ */ N(g, { children: [/* @__PURE__ */ M("thead", { children: /* @__PURE__ */ N("tr", { children: [
130
+ /* @__PURE__ */ M(b, { children: "Engagement" }),
131
+ /* @__PURE__ */ M(b, { children: "Client" }),
132
+ /* @__PURE__ */ M(b, { children: "Fee" }),
133
+ /* @__PURE__ */ M(b, { children: "Status" }),
134
+ /* @__PURE__ */ M(b, { children: "Started" }),
135
+ /* @__PURE__ */ M(b, { children: "Tags" })
136
+ ] }) }), /* @__PURE__ */ M("tbody", { children: Y.items.map((e) => /* @__PURE__ */ N("tr", {
114
137
  className: "group cursor-pointer transition-colors hover:bg-bg-sunken/60",
115
138
  onClick: () => t(`engagements/${e.id}`),
116
139
  children: [
117
- /* @__PURE__ */ M(g, { children: [/* @__PURE__ */ M("div", {
140
+ /* @__PURE__ */ N(_, { children: [/* @__PURE__ */ N("div", {
118
141
  className: "flex items-center gap-1 font-medium text-text-primary transition-colors group-hover:text-accent",
119
- children: [/* @__PURE__ */ j("span", { children: e.name }), /* @__PURE__ */ j(D, { className: "h-3.5 w-3.5 shrink-0 text-text-tertiary opacity-0 transition-opacity group-hover:opacity-100" })]
120
- }), e.description ? /* @__PURE__ */ j("div", {
142
+ children: [/* @__PURE__ */ M("span", { children: e.name }), /* @__PURE__ */ M(k, { className: "h-3.5 w-3.5 shrink-0 text-text-tertiary opacity-0 transition-opacity group-hover:opacity-100" })]
143
+ }), e.description ? /* @__PURE__ */ M("div", {
121
144
  className: "mt-0.5 max-w-md truncate text-xs text-text-tertiary",
122
145
  children: e.description
123
146
  }) : null] }),
124
- /* @__PURE__ */ j(g, { children: /* @__PURE__ */ M("span", {
147
+ /* @__PURE__ */ M(_, { children: /* @__PURE__ */ N("span", {
125
148
  className: "inline-flex items-center gap-2.5",
126
- children: [/* @__PURE__ */ j(L, { name: e.client?.name ?? "—" }), /* @__PURE__ */ j("span", {
149
+ children: [/* @__PURE__ */ M(ie, { name: e.client?.name ?? "—" }), /* @__PURE__ */ M("span", {
127
150
  className: "text-text-secondary",
128
151
  children: e.client?.name ?? "—"
129
152
  })]
130
153
  }) }),
131
- /* @__PURE__ */ M(g, {
154
+ /* @__PURE__ */ N(_, {
132
155
  className: "whitespace-nowrap",
133
156
  children: [
134
- /* @__PURE__ */ j("span", {
157
+ /* @__PURE__ */ M("span", {
135
158
  className: "font-medium text-text-primary",
136
- children: S(e.feeAmount, e.currency)
159
+ children: ee(e.feeAmount, e.currency)
137
160
  }),
138
161
  " ",
139
- /* @__PURE__ */ M("span", {
162
+ /* @__PURE__ */ N("span", {
140
163
  className: "text-xs text-text-tertiary",
141
164
  children: ["/ ", C(e.feeArrangement)]
142
165
  })
143
166
  ]
144
167
  }),
145
- /* @__PURE__ */ j(g, { children: /* @__PURE__ */ j(m, { status: e.status }) }),
146
- /* @__PURE__ */ j(g, {
168
+ /* @__PURE__ */ M(_, { children: /* @__PURE__ */ M(h, { status: e.status }) }),
169
+ /* @__PURE__ */ M(_, {
147
170
  className: "whitespace-nowrap",
148
- children: x(e.startDate)
149
- })
171
+ children: S(e.startDate)
172
+ }),
173
+ /* @__PURE__ */ M(_, { children: /* @__PURE__ */ M("div", {
174
+ onClick: (e) => e.stopPropagation(),
175
+ role: "presentation",
176
+ children: /* @__PURE__ */ M(E, {
177
+ entityType: "PswEngagement",
178
+ entityId: e.id,
179
+ tags: e.tags ?? [],
180
+ onChange: async (t) => {
181
+ await J.mutateAsync({
182
+ id: e.id,
183
+ input: { tags: t }
184
+ }), Q();
185
+ },
186
+ maxTags: 5
187
+ })
188
+ }) })
150
189
  ]
151
- }, e.id)) })] })
190
+ }, e.id)) })] }), /* @__PURE__ */ M(te, {
191
+ hasMore: Y?.pageInfo?.hasMore ?? !1,
192
+ loading: X,
193
+ loadedCount: $.length,
194
+ totalCount: Y?.totalCount ?? $.length,
195
+ onLoadMore: () => K((e) => ({
196
+ limit: R,
197
+ offset: e.offset + R
198
+ }))
199
+ })]
152
200
  }),
153
- B && /* @__PURE__ */ j(z, {
154
- onClose: () => V(!1),
155
- onSaved: (e) => U(`${e} created.`)
201
+ V && /* @__PURE__ */ M(B, {
202
+ onClose: () => H(!1),
203
+ onSaved: (e) => W(`${e} created.`)
156
204
  })
157
205
  ]
158
206
  });
159
- }, z = ({ onClose: n, onSaved: i }) => {
160
- let o = t(), { emitFact: s } = w(), l = e({ pagination: { limit: 100 } }), f = r(), [p, m] = T({
207
+ }, B = ({ onClose: n, onSaved: i }) => {
208
+ let a = t(), { emitFact: s } = w(), c = e({ pagination: { limit: 100 } }), u = r(), [p, m] = D({
161
209
  clientId: "",
162
210
  practiceAreaId: "",
163
211
  name: "",
@@ -166,24 +214,24 @@ var P = [
166
214
  feeAmount: "",
167
215
  currency: "USD",
168
216
  startDate: ""
169
- }), [h, g] = T(null), y = (e) => (t) => m((n) => ({
217
+ }), [h, g] = D(null), _ = (e) => (t) => m((n) => ({
170
218
  ...n,
171
219
  [e]: t.target.value
172
220
  }));
173
- return /* @__PURE__ */ j(u, {
221
+ return /* @__PURE__ */ M(d, {
174
222
  open: !0,
175
223
  onClose: n,
176
224
  title: "New engagement",
177
- footer: /* @__PURE__ */ M(A, { children: [/* @__PURE__ */ j(N, {
225
+ footer: /* @__PURE__ */ N(j, { children: [/* @__PURE__ */ M(P, {
178
226
  variant: "outline",
179
227
  onClick: n,
180
- disabled: o.isPending,
228
+ disabled: a.isPending,
181
229
  children: "Cancel"
182
- }), /* @__PURE__ */ j(N, {
230
+ }), /* @__PURE__ */ M(P, {
183
231
  onClick: () => {
184
232
  if (g(null), !p.clientId) return g("Select a client.");
185
233
  if (!p.name.trim()) return g("Engagement name is required.");
186
- o.mutate({
234
+ a.mutate({
187
235
  clientId: p.clientId,
188
236
  practiceAreaId: p.practiceAreaId || null,
189
237
  name: p.name.trim(),
@@ -203,98 +251,98 @@ var P = [
203
251
  onError: (e) => g(e instanceof Error ? e.message : "Failed.")
204
252
  });
205
253
  },
206
- disabled: o.isPending,
207
- children: o.isPending ? "Creating…" : "Create engagement"
254
+ disabled: a.isPending,
255
+ children: a.isPending ? "Creating…" : "Create engagement"
208
256
  })] }),
209
- children: /* @__PURE__ */ M("div", {
257
+ children: /* @__PURE__ */ N("div", {
210
258
  className: "space-y-3",
211
259
  children: [
212
- h && /* @__PURE__ */ j(a, {
260
+ h && /* @__PURE__ */ M(o, {
213
261
  tone: "danger",
214
262
  children: h
215
263
  }),
216
- /* @__PURE__ */ j(c, {
264
+ /* @__PURE__ */ M(l, {
217
265
  label: "Client",
218
266
  required: !0,
219
- children: /* @__PURE__ */ M(d, {
267
+ children: /* @__PURE__ */ N(f, {
220
268
  value: p.clientId,
221
- onChange: y("clientId"),
222
- children: [/* @__PURE__ */ j("option", {
269
+ onChange: _("clientId"),
270
+ children: [/* @__PURE__ */ M("option", {
223
271
  value: "",
224
272
  children: "Select a client…"
225
- }), l.data?.items.map((e) => /* @__PURE__ */ j("option", {
273
+ }), c.data?.items.map((e) => /* @__PURE__ */ M("option", {
226
274
  value: e.id,
227
275
  children: e.name
228
276
  }, e.id))]
229
277
  })
230
278
  }),
231
- /* @__PURE__ */ j(c, {
279
+ /* @__PURE__ */ M(l, {
232
280
  label: "Name",
233
281
  required: !0,
234
- children: /* @__PURE__ */ j(v, {
282
+ children: /* @__PURE__ */ M(y, {
235
283
  value: p.name,
236
- onChange: y("name"),
284
+ onChange: _("name"),
237
285
  placeholder: "FY26 Statutory Audit"
238
286
  })
239
287
  }),
240
- /* @__PURE__ */ j(c, {
288
+ /* @__PURE__ */ M(l, {
241
289
  label: "Practice area",
242
- children: /* @__PURE__ */ M(d, {
290
+ children: /* @__PURE__ */ N(f, {
243
291
  value: p.practiceAreaId,
244
- onChange: y("practiceAreaId"),
245
- children: [/* @__PURE__ */ j("option", {
292
+ onChange: _("practiceAreaId"),
293
+ children: [/* @__PURE__ */ M("option", {
246
294
  value: "",
247
295
  children: "Unassigned"
248
- }), f.data?.map((e) => /* @__PURE__ */ j("option", {
296
+ }), u.data?.map((e) => /* @__PURE__ */ M("option", {
249
297
  value: e.id,
250
298
  children: e.name
251
299
  }, e.id))]
252
300
  })
253
301
  }),
254
- /* @__PURE__ */ M("div", {
302
+ /* @__PURE__ */ N("div", {
255
303
  className: "grid grid-cols-3 gap-3",
256
304
  children: [
257
- /* @__PURE__ */ j(c, {
305
+ /* @__PURE__ */ M(l, {
258
306
  label: "Fee type",
259
- children: /* @__PURE__ */ j(d, {
307
+ children: /* @__PURE__ */ M(f, {
260
308
  value: p.feeArrangement,
261
- onChange: y("feeArrangement"),
262
- children: b(F)
309
+ onChange: _("feeArrangement"),
310
+ children: x(I)
263
311
  })
264
312
  }),
265
- /* @__PURE__ */ j(c, {
313
+ /* @__PURE__ */ M(l, {
266
314
  label: "Fee amount",
267
- children: /* @__PURE__ */ j(v, {
315
+ children: /* @__PURE__ */ M(y, {
268
316
  type: "number",
269
317
  min: "0",
270
318
  value: p.feeAmount,
271
- onChange: y("feeAmount"),
319
+ onChange: _("feeAmount"),
272
320
  placeholder: "50000"
273
321
  })
274
322
  }),
275
- /* @__PURE__ */ j(c, {
323
+ /* @__PURE__ */ M(l, {
276
324
  label: "Currency",
277
- children: /* @__PURE__ */ j(v, {
325
+ children: /* @__PURE__ */ M(y, {
278
326
  value: p.currency,
279
- onChange: y("currency"),
327
+ onChange: _("currency"),
280
328
  maxLength: 3
281
329
  })
282
330
  })
283
331
  ]
284
332
  }),
285
- /* @__PURE__ */ j(c, {
333
+ /* @__PURE__ */ M(l, {
286
334
  label: "Start date",
287
- children: /* @__PURE__ */ j(v, {
335
+ children: /* @__PURE__ */ M(y, {
288
336
  type: "date",
289
337
  value: p.startDate,
290
- onChange: y("startDate")
338
+ onChange: _("startDate")
291
339
  })
292
340
  }),
293
- /* @__PURE__ */ j(c, {
341
+ /* @__PURE__ */ M(l, {
294
342
  label: "Description",
295
- children: /* @__PURE__ */ j(_, {
343
+ children: /* @__PURE__ */ M(v, {
296
344
  value: p.description,
297
- onChange: y("description")
345
+ onChange: _("description")
298
346
  })
299
347
  })
300
348
  ]
@@ -302,6 +350,6 @@ var P = [
302
350
  });
303
351
  };
304
352
  //#endregion
305
- export { R as EngagementsPage };
353
+ export { z as EngagementsPage };
306
354
 
307
355
  //# sourceMappingURL=EngagementsPage.js.map