@burdenoff/microfe-proserviceworld 2026.827.1 → 2026.1009.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/index.js +4 -4
  2. package/dist/proserviceworld/components/ui.js +1 -1
  3. package/dist/proserviceworld/components/ui.js.map +1 -1
  4. package/dist/proserviceworld/hooks/useProServiceWorldApi.d.ts.map +1 -1
  5. package/dist/proserviceworld/hooks/useProServiceWorldApi.js.map +1 -1
  6. package/dist/proserviceworld/hooks/useTr.d.ts +19 -0
  7. package/dist/proserviceworld/hooks/useTr.d.ts.map +1 -0
  8. package/dist/proserviceworld/hooks/useTr.js +33 -0
  9. package/dist/proserviceworld/hooks/useTr.js.map +1 -0
  10. package/dist/proserviceworld/i18n/index.d.ts +1 -1
  11. package/dist/proserviceworld/i18n/index.d.ts.map +1 -1
  12. package/dist/proserviceworld/i18n/index.js +723 -18
  13. package/dist/proserviceworld/i18n/index.js.map +1 -1
  14. package/dist/proserviceworld/index.js +4 -4
  15. package/dist/proserviceworld/pages/ClientsPage.d.ts.map +1 -1
  16. package/dist/proserviceworld/pages/ClientsPage.js +166 -164
  17. package/dist/proserviceworld/pages/ClientsPage.js.map +1 -1
  18. package/dist/proserviceworld/pages/DashboardPage.d.ts.map +1 -1
  19. package/dist/proserviceworld/pages/DashboardPage.js +152 -142
  20. package/dist/proserviceworld/pages/DashboardPage.js.map +1 -1
  21. package/dist/proserviceworld/pages/EngagementsPage.d.ts.map +1 -1
  22. package/dist/proserviceworld/pages/EngagementsPage.js +135 -135
  23. package/dist/proserviceworld/pages/EngagementsPage.js.map +1 -1
  24. package/dist/proserviceworld/pages/MattersPage.d.ts.map +1 -1
  25. package/dist/proserviceworld/pages/MattersPage.js +204 -197
  26. package/dist/proserviceworld/pages/MattersPage.js.map +1 -1
  27. 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 { 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 {/*\n Phones get a tappable card list: the desktop table carries seven\n columns behind a horizontal scroll, which hides the status and the\n row actions on a narrow viewport. Same fields, same affordances.\n */}\n <ul className=\"space-y-2.5 sm:hidden\">\n {data.items.map((c) => (\n <li\n key={c.id}\n className=\"overflow-hidden rounded-xl border border-border-seam bg-bg-canvas\"\n >\n <button\n type=\"button\"\n onClick={() => {\n void impactFeedback('light');\n nav(`clients/${c.id}`);\n }}\n className=\"flex w-full items-center gap-3 p-3.5 text-left transition-colors active:bg-bg-sunken/60\"\n >\n <Avatar name={c.name} className=\"h-11 w-11 text-sm\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-semibold text-text-primary\">\n {c.name}\n </span>\n <span className=\"mt-0.5 block truncate text-sm text-text-secondary\">\n {c.contactName || (c.type === 'ORGANIZATION' ? 'Organization' : 'Individual')}\n </span>\n <span className=\"mt-1.5 flex items-center gap-2\">\n <span className=\"truncate text-xs text-text-tertiary\">\n {c.industry || fmtDate(c.createdAt)}\n </span>\n <StatusPill status={c.status} />\n </span>\n </span>\n <ChevronRight className=\"h-4 w-4 shrink-0 text-text-tertiary\" />\n </button>\n <div className=\"flex items-center justify-between gap-2 border-t border-border-seam px-3.5 py-2\">\n <div className=\"min-w-0 flex-1\">\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 <div className=\"flex shrink-0 gap-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setEditing(c)}\n aria-label={`Edit ${c.name}`}\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 </div>\n </li>\n ))}\n </ul>\n\n <div className=\"hidden sm:block\">\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\">\n {c.contactName}\n </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 </div>\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
+ {"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 { useTr } from '../hooks/useTr';\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 tr = useTr();\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={tr('psw.clients.title', 'Clients')}\n subtitle={tr('psw.clients.subtitle', 'Every person or organization your firm serves.')}\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\" /> {tr('psw.clients.new', '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={tr('psw.clients.searchPlaceholder', 'Search clients…')}\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=\"\">{tr('psw.clients.allStatuses', '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={tr('psw.clients.emptyTitle', 'No clients yet')}\n description={tr(\n 'psw.clients.emptyDescription',\n 'Add your first client to start scoping engagements and matters.'\n )}\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\" /> {tr('psw.clients.new', 'New client')}\n </Button>\n }\n />\n ) : (\n <SectionCard\n title={\n data.totalCount === 1\n ? tr('psw.clients.count', '{{count}} client', { count: data.totalCount })\n : tr('psw.clients.countPlural', '{{count}} clients', { count: data.totalCount })\n }\n >\n {/*\n Phones get a tappable card list: the desktop table carries seven\n columns behind a horizontal scroll, which hides the status and the\n row actions on a narrow viewport. Same fields, same affordances.\n */}\n <ul className=\"space-y-2.5 sm:hidden\">\n {data.items.map((c) => (\n <li\n key={c.id}\n className=\"overflow-hidden rounded-xl border border-border-seam bg-bg-canvas\"\n >\n <button\n type=\"button\"\n onClick={() => {\n void impactFeedback('light');\n nav(`clients/${c.id}`);\n }}\n className=\"flex w-full items-center gap-3 p-3.5 text-left transition-colors active:bg-bg-sunken/60\"\n >\n <Avatar name={c.name} className=\"h-11 w-11 text-sm\" />\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-semibold text-text-primary\">\n {c.name}\n </span>\n <span className=\"mt-0.5 block truncate text-sm text-text-secondary\">\n {c.contactName ||\n (c.type === 'ORGANIZATION'\n ? tr('psw.clients.typeOrganization', 'Organization')\n : tr('psw.clients.typeIndividual', 'Individual'))}\n </span>\n <span className=\"mt-1.5 flex items-center gap-2\">\n <span className=\"truncate text-xs text-text-tertiary\">\n {c.industry || fmtDate(c.createdAt)}\n </span>\n <StatusPill status={c.status} />\n </span>\n </span>\n <ChevronRight className=\"h-4 w-4 shrink-0 text-text-tertiary\" />\n </button>\n <div className=\"flex items-center justify-between gap-2 border-t border-border-seam px-3.5 py-2\">\n <div className=\"min-w-0 flex-1\">\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 <div className=\"flex shrink-0 gap-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setEditing(c)}\n aria-label={tr('psw.common.edit', 'Edit')}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n {c.status !== 'ARCHIVED' && (\n <ArchiveButton\n id={c.id}\n name={c.name}\n onDone={(ok) =>\n setBanner(\n ok\n ? {\n tone: 'success',\n text: tr('psw.clients.archived', '{{name}} archived.', {\n name: c.name,\n }),\n }\n : {\n tone: 'danger',\n text: tr(\n 'psw.clients.archiveFailed',\n 'Could not archive client.'\n ),\n }\n )\n }\n />\n )}\n </div>\n </div>\n </li>\n ))}\n </ul>\n\n <div className=\"hidden sm:block\">\n <TableWrap>\n <thead>\n <tr>\n <Th>{tr('psw.common.name', 'Name')}</Th>\n <Th>{tr('psw.common.type', 'Type')}</Th>\n <Th>{tr('psw.clients.tableIndustry', 'Industry')}</Th>\n <Th>{tr('psw.common.status', 'Status')}</Th>\n <Th>{tr('psw.common.added', 'Added')}</Th>\n <Th>{tr('psw.clients.tableTags', 'Tags')}</Th>\n <Th className=\"text-right\">{tr('psw.common.actions', '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\">\n {c.contactName}\n </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'\n ? tr('psw.clients.typeOrganization', 'Organization')\n : tr('psw.clients.typeIndividual', 'Individual')}\n </span>\n </Td>\n <Td>{c.industry || tr('psw.common.dash', '—')}</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={tr('psw.common.edit', 'Edit')}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n {c.status !== 'ARCHIVED' && (\n <ArchiveButton\n id={c.id}\n name={c.name}\n onDone={(ok) =>\n setBanner(\n ok\n ? {\n tone: 'success',\n text: tr('psw.clients.archived', '{{name}} archived.', {\n name: c.name,\n }),\n }\n : {\n tone: 'danger',\n text: tr(\n 'psw.clients.archiveFailed',\n 'Could not archive client.'\n ),\n }\n )\n }\n />\n )}\n </div>\n </Td>\n </tr>\n ))}\n </tbody>\n </TableWrap>\n </div>\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) =>\n setBanner({\n tone: 'success',\n text: tr('psw.clients.created', '{{name}} created.', { name }),\n })\n }\n />\n )}\n {editing && (\n <ClientModal\n client={editing}\n onClose={() => setEditing(null)}\n onSaved={(name) =>\n setBanner({\n tone: 'success',\n text: tr('psw.clients.updated', '{{name}} updated.', { name }),\n })\n }\n />\n )}\n </div>\n );\n};\n\nconst ArchiveButton: FC<{ id: string; name: string; onDone: (ok: boolean) => void }> = ({\n id,\n name,\n onDone,\n}) => {\n const archive = useArchiveClient();\n const tr = useTr();\n const { emitFact } = useAnalytics();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={tr('psw.common.archive', 'Archive')}\n disabled={archive.isPending}\n onClick={() => {\n if (\n !window.confirm(\n tr(\n 'psw.clients.archiveConfirm',\n 'Archive this client? Their engagements and matters remain.'\n )\n )\n )\n 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 tr = useTr();\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(tr('psw.clients.nameRequired', 'Client name is required.'));\n const onError = (e: unknown) =>\n setErr(e instanceof Error ? e.message : tr('psw.clients.saveFailed', '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={\n client ? tr('psw.clients.edit', 'Edit client') : tr('psw.clients.newTitle', 'New client')\n }\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={pending}>\n {tr('psw.common.cancel', 'Cancel')}\n </Button>\n <Button onClick={submit} disabled={pending}>\n {pending\n ? tr('psw.common.saving', 'Saving…')\n : client\n ? tr('psw.common.save', 'Save changes')\n : tr('psw.common.create', 'Create client')}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label={tr('psw.common.name', '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={tr('psw.common.type', 'Type')}>\n <NativeSelect value={form.type} onChange={set('type')}>\n {enumOptions(CLIENT_TYPES)}\n </NativeSelect>\n </Field>\n <Field label={tr('psw.common.status', 'Status')}>\n <NativeSelect value={form.status} onChange={set('status')}>\n {enumOptions(CLIENT_STATUSES)}\n </NativeSelect>\n </Field>\n </div>\n <Field label={tr('psw.clients.industry', '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={tr('psw.clients.contactName', 'Contact name')}>\n <TextInput value={form.contactName} onChange={set('contactName')} />\n </Field>\n <Field label={tr('psw.clients.contactEmail', 'Contact email')}>\n <TextInput type=\"email\" value={form.contactEmail} onChange={set('contactEmail')} />\n </Field>\n </div>\n <Field label={tr('psw.clients.contactPhone', 'Contact phone')}>\n <TextInput value={form.contactPhone} onChange={set('contactPhone')} />\n </Field>\n <Field label={tr('psw.clients.notes', '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":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/proserviceworld/pages/DashboardPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;AA4C3C,eAAO,MAAM,aAAa,EAAE,EAiH3B,CAAC"}
1
+ {"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/proserviceworld/pages/DashboardPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAa,MAAM,OAAO,CAAC;AA6C3C,eAAO,MAAM,aAAa,EAAE,EAmI3B,CAAC"}
@@ -1,271 +1,281 @@
1
1
  import { useDashboardStats as e } from "../hooks/useProServiceWorldApi.js";
2
2
  import { useNav as t } from "../utils/nav.js";
3
3
  import { cn as n } from "../utils/cn.js";
4
- import { ErrorState as r, LoadingBlock as i, MetricCard as a, PageHeader as o, SectionCard as s, StatusPill as c, fmtHours as l, fmtMoney as u } from "../components/ui.js";
5
- import { ArrowRight as d, BarChart3 as f, Briefcase as p, Clock as m, FileText as h, FolderKanban as g, GitBranch as _, Network as v, Receipt as y, Scale as b, ShieldCheck as x, Users as S, Wallet as C } from "lucide-react";
6
- import { jsx as w, jsxs as T } from "react/jsx-runtime";
4
+ import { useTr as r } from "../hooks/useTr.js";
5
+ import { ErrorState as i, LoadingBlock as a, MetricCard as o, PageHeader as s, SectionCard as c, StatusPill as l, fmtHours as u, fmtMoney as d } from "../components/ui.js";
6
+ import { ArrowRight as f, BarChart3 as p, Briefcase as m, Clock as h, FileText as g, FolderKanban as _, GitBranch as v, Network as y, Receipt as b, Scale as x, ShieldCheck as S, Users as C, Wallet as w } from "lucide-react";
7
+ import { jsx as T, jsxs as E } from "react/jsx-runtime";
7
8
  //#region src/proserviceworld/pages/DashboardPage.tsx
8
- var E = [
9
+ var D = [
9
10
  {
10
- label: "Clients",
11
+ key: "clients",
11
12
  to: "clients",
12
- icon: /* @__PURE__ */ w(S, { className: "h-4 w-4" })
13
+ icon: /* @__PURE__ */ T(C, { className: "h-4 w-4" })
13
14
  },
14
15
  {
15
- label: "Engagements",
16
+ key: "engagements",
16
17
  to: "engagements",
17
- icon: /* @__PURE__ */ w(p, { className: "h-4 w-4" })
18
+ icon: /* @__PURE__ */ T(m, { className: "h-4 w-4" })
18
19
  },
19
20
  {
20
- label: "Matters",
21
+ key: "matters",
21
22
  to: "matters",
22
- icon: /* @__PURE__ */ w(g, { className: "h-4 w-4" })
23
+ icon: /* @__PURE__ */ T(_, { className: "h-4 w-4" })
23
24
  },
24
25
  {
25
- label: "Time",
26
+ key: "time",
26
27
  to: "time",
27
- icon: /* @__PURE__ */ w(m, { className: "h-4 w-4" })
28
+ icon: /* @__PURE__ */ T(h, { className: "h-4 w-4" })
28
29
  },
29
30
  {
30
- label: "Invoices",
31
+ key: "invoices",
31
32
  to: "invoices",
32
- icon: /* @__PURE__ */ w(y, { className: "h-4 w-4" })
33
+ icon: /* @__PURE__ */ T(b, { className: "h-4 w-4" })
33
34
  },
34
35
  {
35
- label: "Trust accounting",
36
+ key: "trust",
36
37
  to: "trust",
37
- icon: /* @__PURE__ */ w(C, { className: "h-4 w-4" })
38
+ icon: /* @__PURE__ */ T(w, { className: "h-4 w-4" })
38
39
  },
39
40
  {
40
- label: "Compliance",
41
+ key: "compliance",
41
42
  to: "compliance",
42
- icon: /* @__PURE__ */ w(x, { className: "h-4 w-4" })
43
+ icon: /* @__PURE__ */ T(S, { className: "h-4 w-4" })
43
44
  },
44
45
  {
45
- label: "Conflict checks",
46
+ key: "conflicts",
46
47
  to: "conflicts",
47
- icon: /* @__PURE__ */ w(b, { className: "h-4 w-4" })
48
+ icon: /* @__PURE__ */ T(x, { className: "h-4 w-4" })
48
49
  },
49
50
  {
50
- label: "Documents",
51
+ key: "documents",
51
52
  to: "documents",
52
- icon: /* @__PURE__ */ w(h, { className: "h-4 w-4" })
53
+ icon: /* @__PURE__ */ T(g, { className: "h-4 w-4" })
53
54
  },
54
55
  {
55
- label: "Practice areas",
56
+ key: "practice-areas",
56
57
  to: "practice-areas",
57
- icon: /* @__PURE__ */ w(v, { className: "h-4 w-4" })
58
+ icon: /* @__PURE__ */ T(y, { className: "h-4 w-4" })
58
59
  },
59
60
  {
60
- label: "Analytics",
61
+ key: "analytics",
61
62
  to: "analytics",
62
- icon: /* @__PURE__ */ w(f, { className: "h-4 w-4" })
63
+ icon: /* @__PURE__ */ T(p, { className: "h-4 w-4" })
63
64
  }
64
- ], D = () => {
65
- let c = t(), { data: f, isLoading: h, error: v, refetch: b } = e();
66
- return h ? /* @__PURE__ */ w(i, { label: "Loading practice overview…" }) : v ? /* @__PURE__ */ w(r, {
67
- error: v,
68
- onRetry: () => void b()
69
- }) : f ? /* @__PURE__ */ T("div", {
65
+ ], O = () => {
66
+ let l = t(), p = r(), { data: g, isLoading: y, error: x, refetch: O } = e();
67
+ return y ? /* @__PURE__ */ T(a, { label: p("psw.dashboard.loading", "Loading practice overview…") }) : x ? /* @__PURE__ */ T(i, {
68
+ error: x,
69
+ onRetry: () => void O()
70
+ }) : g ? /* @__PURE__ */ E("div", {
70
71
  className: "space-y-6",
71
72
  children: [
72
- /* @__PURE__ */ w(o, {
73
- title: "Practice overview",
74
- subtitle: "One governed view of clients, engagements, matters, time and billing across your firm."
73
+ /* @__PURE__ */ T(s, {
74
+ title: p("psw.dashboard.title", "Practice overview"),
75
+ subtitle: p("psw.dashboard.subtitle", "One governed view of clients, engagements, matters, time and billing across your firm.")
75
76
  }),
76
- /* @__PURE__ */ T("div", {
77
+ /* @__PURE__ */ E("div", {
77
78
  className: "grid grid-cols-2 gap-3 md:grid-cols-3 lg:grid-cols-4",
78
79
  children: [
79
- /* @__PURE__ */ w(a, {
80
- label: "Active clients",
81
- value: f.activeClients,
82
- hint: `${f.totalClients} total`,
83
- icon: /* @__PURE__ */ w(S, { className: "h-4 w-4" }),
80
+ /* @__PURE__ */ T(o, {
81
+ label: p("psw.dashboard.activeClients", "Active clients"),
82
+ value: g.activeClients,
83
+ hint: p("psw.dashboard.totalClients", "{{count}} total", { count: g.totalClients }),
84
+ icon: /* @__PURE__ */ T(C, { className: "h-4 w-4" }),
84
85
  tone: "success"
85
86
  }),
86
- /* @__PURE__ */ w(a, {
87
- label: "Active engagements",
88
- value: f.activeEngagements,
89
- icon: /* @__PURE__ */ w(p, { className: "h-4 w-4" }),
87
+ /* @__PURE__ */ T(o, {
88
+ label: p("psw.dashboard.activeEngagements", "Active engagements"),
89
+ value: g.activeEngagements,
90
+ icon: /* @__PURE__ */ T(m, { className: "h-4 w-4" }),
90
91
  tone: "accent"
91
92
  }),
92
- /* @__PURE__ */ w(a, {
93
- label: "Open matters",
94
- value: f.openMatters,
95
- icon: /* @__PURE__ */ w(g, { className: "h-4 w-4" }),
93
+ /* @__PURE__ */ T(o, {
94
+ label: p("psw.dashboard.openMatters", "Open matters"),
95
+ value: g.openMatters,
96
+ icon: /* @__PURE__ */ T(_, { className: "h-4 w-4" }),
96
97
  tone: "info"
97
98
  }),
98
- /* @__PURE__ */ w(a, {
99
- label: "Unbilled hours",
100
- value: l(f.unbilledHours),
101
- hint: `${l(f.billedHours)} billed`,
102
- icon: /* @__PURE__ */ w(m, { className: "h-4 w-4" }),
99
+ /* @__PURE__ */ T(o, {
100
+ label: p("psw.dashboard.unbilledHours", "Unbilled hours"),
101
+ value: u(g.unbilledHours),
102
+ hint: p("psw.dashboard.billedHours", "{{count}} billed", { count: u(g.billedHours) }),
103
+ icon: /* @__PURE__ */ T(h, { className: "h-4 w-4" }),
103
104
  tone: "warning"
104
105
  }),
105
- /* @__PURE__ */ w(a, {
106
- label: "Outstanding",
107
- value: u(f.outstandingInvoiceTotal),
108
- hint: "Sent + overdue",
109
- icon: /* @__PURE__ */ w(y, { className: "h-4 w-4" }),
106
+ /* @__PURE__ */ T(o, {
107
+ label: p("psw.dashboard.outstanding", "Outstanding"),
108
+ value: d(g.outstandingInvoiceTotal),
109
+ hint: p("psw.dashboard.outstandingHint", "Sent + overdue"),
110
+ icon: /* @__PURE__ */ T(b, { className: "h-4 w-4" }),
110
111
  tone: "warning"
111
112
  }),
112
- /* @__PURE__ */ w(a, {
113
- label: "Collected",
114
- value: u(f.paidInvoiceTotal),
115
- icon: /* @__PURE__ */ w(y, { className: "h-4 w-4" }),
113
+ /* @__PURE__ */ T(o, {
114
+ label: p("psw.dashboard.collected", "Collected"),
115
+ value: d(g.paidInvoiceTotal),
116
+ icon: /* @__PURE__ */ T(b, { className: "h-4 w-4" }),
116
117
  tone: "success"
117
118
  }),
118
- /* @__PURE__ */ w(a, {
119
- label: "Trust held",
120
- value: u(f.trustHeldTotal),
121
- hint: `${f.activeTrustAccounts} active account${f.activeTrustAccounts === 1 ? "" : "s"}`,
122
- icon: /* @__PURE__ */ w(C, { className: "h-4 w-4" }),
119
+ /* @__PURE__ */ T(o, {
120
+ label: p("psw.dashboard.trustHeld", "Trust held"),
121
+ value: d(g.trustHeldTotal),
122
+ hint: g.activeTrustAccounts === 1 ? p("psw.dashboard.trustAccounts", "{{count}} active account", { count: g.activeTrustAccounts }) : p("psw.dashboard.trustAccountsPlural", "{{count}} active accounts", { count: g.activeTrustAccounts }),
123
+ icon: /* @__PURE__ */ T(w, { className: "h-4 w-4" }),
123
124
  tone: "info"
124
125
  }),
125
- /* @__PURE__ */ w(a, {
126
- label: "Compliance due",
127
- value: f.pendingComplianceChecks,
128
- icon: /* @__PURE__ */ w(x, { className: "h-4 w-4" }),
129
- tone: f.pendingComplianceChecks ? "warning" : "success"
126
+ /* @__PURE__ */ T(o, {
127
+ label: p("psw.dashboard.complianceDue", "Compliance due"),
128
+ value: g.pendingComplianceChecks,
129
+ icon: /* @__PURE__ */ T(S, { className: "h-4 w-4" }),
130
+ tone: g.pendingComplianceChecks ? "warning" : "success"
130
131
  }),
131
- /* @__PURE__ */ w(a, {
132
- label: "Open conflicts",
133
- value: f.openConflictChecks,
134
- icon: /* @__PURE__ */ w(_, { className: "h-4 w-4" }),
135
- tone: f.openConflictChecks ? "danger" : "success"
132
+ /* @__PURE__ */ T(o, {
133
+ label: p("psw.dashboard.openConflicts", "Open conflicts"),
134
+ value: g.openConflictChecks,
135
+ icon: /* @__PURE__ */ T(v, { className: "h-4 w-4" }),
136
+ tone: g.openConflictChecks ? "danger" : "success"
136
137
  })
137
138
  ]
138
139
  }),
139
- /* @__PURE__ */ T("div", {
140
+ /* @__PURE__ */ E("div", {
140
141
  className: "grid grid-cols-1 gap-4 lg:grid-cols-3",
141
142
  children: [
142
- /* @__PURE__ */ w(s, {
143
- title: "Engagements by status",
144
- children: /* @__PURE__ */ w(O, { items: f.engagementsByStatus })
143
+ /* @__PURE__ */ T(c, {
144
+ title: p("psw.dashboard.engagementsByStatus", "Engagements by status"),
145
+ children: /* @__PURE__ */ T(k, {
146
+ items: g.engagementsByStatus,
147
+ tr: p
148
+ })
145
149
  }),
146
- /* @__PURE__ */ w(s, {
147
- title: "Matters by status",
148
- children: /* @__PURE__ */ w(O, { items: f.mattersByStatus })
150
+ /* @__PURE__ */ T(c, {
151
+ title: p("psw.dashboard.mattersByStatus", "Matters by status"),
152
+ children: /* @__PURE__ */ T(k, {
153
+ items: g.mattersByStatus,
154
+ tr: p
155
+ })
149
156
  }),
150
- /* @__PURE__ */ w(s, {
151
- title: "Revenue",
152
- description: "Invoiced totals, recent months.",
153
- children: /* @__PURE__ */ w(k, { data: f.revenueByMonth })
157
+ /* @__PURE__ */ T(c, {
158
+ title: p("psw.dashboard.revenue", "Revenue"),
159
+ description: p("psw.dashboard.revenueDescription", "Invoiced totals, recent months."),
160
+ children: /* @__PURE__ */ T(A, {
161
+ data: g.revenueByMonth,
162
+ tr: p
163
+ })
154
164
  })
155
165
  ]
156
166
  }),
157
- /* @__PURE__ */ w(s, {
158
- title: "Jump to",
159
- children: /* @__PURE__ */ w("div", {
167
+ /* @__PURE__ */ T(c, {
168
+ title: p("psw.dashboard.jumpTo", "Jump to"),
169
+ children: /* @__PURE__ */ T("div", {
160
170
  className: "grid grid-cols-1 gap-2.5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5",
161
- children: E.map((e) => /* @__PURE__ */ T("button", {
171
+ children: D.map((e) => /* @__PURE__ */ E("button", {
162
172
  type: "button",
163
- onClick: () => c(e.to),
173
+ onClick: () => l(e.to),
164
174
  className: n("group flex items-center gap-3 rounded-xl border border-border-seam bg-bg-canvas px-3.5 py-3 text-left", "transition hover:-translate-y-0.5 hover:border-accent hover:bg-bg-surface hover:shadow-sm"),
165
175
  children: [
166
- /* @__PURE__ */ w("span", {
176
+ /* @__PURE__ */ T("span", {
167
177
  className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-bg-sunken text-accent transition group-hover:bg-accent/15",
168
178
  children: e.icon
169
179
  }),
170
- /* @__PURE__ */ w("span", {
180
+ /* @__PURE__ */ T("span", {
171
181
  className: "flex-1 truncate text-sm font-medium text-text-primary",
172
- children: e.label
182
+ children: p(`psw.${e.key}.title`, e.key)
173
183
  }),
174
- /* @__PURE__ */ w(d, { className: "h-4 w-4 shrink-0 text-text-tertiary transition group-hover:translate-x-0.5 group-hover:text-accent" })
184
+ /* @__PURE__ */ T(f, { className: "h-4 w-4 shrink-0 text-text-tertiary transition group-hover:translate-x-0.5 group-hover:text-accent" })
175
185
  ]
176
186
  }, e.to))
177
187
  })
178
188
  })
179
189
  ]
180
190
  }) : null;
181
- }, O = ({ items: e }) => {
182
- if (!e.length) return /* @__PURE__ */ w("p", {
191
+ }, k = ({ items: e, tr: t }) => {
192
+ if (!e.length) return /* @__PURE__ */ T("p", {
183
193
  className: "text-sm text-text-tertiary",
184
- children: "No records yet."
194
+ children: t("psw.dashboard.noRecords", "No records yet.")
185
195
  });
186
- let t = Math.max(...e.map((e) => e.count), 1), r = e.reduce((e, t) => e + t.count, 0) || 1;
187
- return /* @__PURE__ */ w("ul", {
196
+ let r = Math.max(...e.map((e) => e.count), 1), i = e.reduce((e, t) => e + t.count, 0) || 1;
197
+ return /* @__PURE__ */ T("ul", {
188
198
  className: "space-y-3.5",
189
- children: e.map((e) => /* @__PURE__ */ T("li", {
199
+ children: e.map((e) => /* @__PURE__ */ E("li", {
190
200
  className: "grid grid-cols-[7.5rem_minmax(0,1fr)_auto] items-center gap-3",
191
201
  children: [
192
- /* @__PURE__ */ w(c, {
202
+ /* @__PURE__ */ T(l, {
193
203
  status: e.status,
194
204
  className: "justify-self-start"
195
205
  }),
196
- /* @__PURE__ */ w("div", {
206
+ /* @__PURE__ */ T("div", {
197
207
  className: "h-2.5 overflow-hidden rounded-full bg-bg-sunken",
198
- children: /* @__PURE__ */ w("div", {
199
- className: n("h-full rounded-full bg-gradient-to-r transition-all duration-500", M(e.status)),
200
- style: { width: `${Math.max(e.count / t * 100, 4)}%` }
208
+ children: /* @__PURE__ */ T("div", {
209
+ className: n("h-full rounded-full bg-gradient-to-r transition-all duration-500", N(e.status)),
210
+ style: { width: `${Math.max(e.count / r * 100, 4)}%` }
201
211
  })
202
212
  }),
203
- /* @__PURE__ */ T("span", {
213
+ /* @__PURE__ */ E("span", {
204
214
  className: "flex items-baseline justify-end gap-1.5 tabular-nums",
205
- children: [/* @__PURE__ */ w("span", {
215
+ children: [/* @__PURE__ */ T("span", {
206
216
  className: "text-sm font-semibold text-text-primary",
207
217
  children: e.count
208
- }), /* @__PURE__ */ T("span", {
218
+ }), /* @__PURE__ */ E("span", {
209
219
  className: "text-xs text-text-tertiary",
210
- children: [Math.round(e.count / r * 100), "%"]
220
+ children: [Math.round(e.count / i * 100), "%"]
211
221
  })]
212
222
  })
213
223
  ]
214
224
  }, e.status))
215
225
  });
216
- }, k = ({ data: e }) => {
217
- if (!e.length) return /* @__PURE__ */ w("p", {
226
+ }, A = ({ data: e, tr: t }) => {
227
+ if (!e.length) return /* @__PURE__ */ T("p", {
218
228
  className: "text-sm text-text-tertiary",
219
- children: "No invoiced revenue yet."
229
+ children: t("psw.dashboard.noRevenue", "No invoiced revenue yet.")
220
230
  });
221
- let t = N(Math.max(...e.map((e) => e.amount), 1)), r = [
231
+ let r = P(Math.max(...e.map((e) => e.amount), 1)), i = [
222
232
  1,
223
233
  .75,
224
234
  .5,
225
235
  .25,
226
236
  0
227
- ].map((e) => t * e);
228
- return /* @__PURE__ */ w("div", {
237
+ ].map((e) => r * e);
238
+ return /* @__PURE__ */ T("div", {
229
239
  className: "overflow-x-auto",
230
- children: /* @__PURE__ */ T("div", {
240
+ children: /* @__PURE__ */ E("div", {
231
241
  className: "min-w-[260px]",
232
- children: [/* @__PURE__ */ T("div", {
242
+ children: [/* @__PURE__ */ E("div", {
233
243
  className: "relative h-44",
234
- children: [r.map((e, t) => /* @__PURE__ */ T("div", {
244
+ children: [i.map((e, t) => /* @__PURE__ */ E("div", {
235
245
  className: "absolute inset-x-0 flex items-center gap-2",
236
- style: { top: `${t / (r.length - 1) * 100}%` },
237
- children: [/* @__PURE__ */ w("span", {
246
+ style: { top: `${t / (i.length - 1) * 100}%` },
247
+ children: [/* @__PURE__ */ T("span", {
238
248
  className: "w-11 shrink-0 text-right text-[10px] leading-none tabular-nums text-text-tertiary",
239
- children: P(e)
240
- }), /* @__PURE__ */ w("span", { className: n("h-px flex-1", t === r.length - 1 ? "bg-border-seam" : "bg-border-seam/50") })]
241
- }, e)), /* @__PURE__ */ w("div", {
249
+ children: F(e)
250
+ }), /* @__PURE__ */ T("span", { className: n("h-px flex-1", t === i.length - 1 ? "bg-border-seam" : "bg-border-seam/50") })]
251
+ }, e)), /* @__PURE__ */ T("div", {
242
252
  className: "absolute inset-0 flex items-end gap-1.5 pl-[3.25rem]",
243
- children: e.map((e) => /* @__PURE__ */ w("div", {
253
+ children: e.map((e) => /* @__PURE__ */ T("div", {
244
254
  className: "group flex h-full flex-1 flex-col items-center justify-end",
245
- children: /* @__PURE__ */ w("div", {
255
+ children: /* @__PURE__ */ T("div", {
246
256
  className: "w-full max-w-[2rem] rounded-t-md bg-gradient-to-t from-accent/55 to-accent shadow-sm transition-all duration-200 group-hover:-translate-y-0.5 group-hover:shadow",
247
- style: { height: `${Math.max(e.amount / t * 100, 1.5)}%` },
248
- title: u(e.amount)
257
+ style: { height: `${Math.max(e.amount / r * 100, 1.5)}%` },
258
+ title: d(e.amount)
249
259
  })
250
260
  }, e.month))
251
261
  })]
252
- }), /* @__PURE__ */ w("div", {
262
+ }), /* @__PURE__ */ T("div", {
253
263
  className: "mt-2 flex gap-1.5 pl-[3.25rem]",
254
- children: e.map((e) => /* @__PURE__ */ w("span", {
264
+ children: e.map((e) => /* @__PURE__ */ T("span", {
255
265
  className: "flex-1 text-center text-[10px] text-text-tertiary",
256
- children: F(e.month)
266
+ children: I(e.month)
257
267
  }, e.month))
258
268
  })]
259
269
  })
260
270
  });
261
- }, A = {
271
+ }, j = {
262
272
  success: "from-status-success-text/55 to-status-success-text",
263
273
  warning: "from-status-warning-text/55 to-status-warning-text",
264
274
  danger: "from-status-error-text/55 to-status-error-text",
265
275
  info: "from-text-link/55 to-text-link",
266
276
  accent: "from-accent/55 to-accent",
267
277
  neutral: "from-text-tertiary/30 to-text-tertiary/70"
268
- }, j = {
278
+ }, M = {
269
279
  ACTIVE: "success",
270
280
  DELIVERED: "success",
271
281
  INVOICED: "success",
@@ -305,24 +315,24 @@ var E = [
305
315
  DRAFT: "neutral",
306
316
  WAIVED: "neutral"
307
317
  };
308
- function M(e) {
309
- return A[j[e] ?? "accent"];
310
- }
311
318
  function N(e) {
319
+ return j[M[e] ?? "accent"];
320
+ }
321
+ function P(e) {
312
322
  if (e <= 0) return 1;
313
323
  let t = 10 ** Math.floor(Math.log10(e)), n = e / t;
314
324
  return (n <= 1 ? 1 : n <= 2 ? 2 : n <= 2.5 ? 2.5 : n <= 5 ? 5 : 10) * t;
315
325
  }
316
- function P(e) {
326
+ function F(e) {
317
327
  return e >= 1e6 ? `$${(e / 1e6).toFixed(e % 1e6 == 0 ? 0 : 1)}M` : e >= 1e3 ? `$${Math.round(e / 1e3)}k` : `$${Math.round(e)}`;
318
328
  }
319
- function F(e) {
329
+ function I(e) {
320
330
  let [t, n] = e.split("-").map(Number);
321
331
  if (!t || !n) return e;
322
332
  let r = new Date(t, n - 1, 1);
323
333
  return Number.isNaN(r.getTime()) ? e : r.toLocaleDateString(void 0, { month: "short" });
324
334
  }
325
335
  //#endregion
326
- export { D as DashboardPage };
336
+ export { O as DashboardPage };
327
337
 
328
338
  //# sourceMappingURL=DashboardPage.js.map