@burdenoff/microfe-proserviceworld 2026.706.1 → 2026.706.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.
@@ -0,0 +1,76 @@
1
+ import { useProServiceWorld as e } from "../providers/ProServiceWorldProvider.js";
2
+ import { Banner as t, Empty as n, LoadingBlock as r, SectionCard as i, TextArea as a, fmtDate as o } from "./ui.js";
3
+ import { useAddEntityComment as s, useEntityComments as c } from "../hooks/useEntityComments.js";
4
+ import { useState as l } from "react";
5
+ import { MessageSquare as u, Send as d } from "lucide-react";
6
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
7
+ import { Button as m } from "@burdenoff/fe-libs/ui";
8
+ //#region src/proserviceworld/components/DiscussionThread.tsx
9
+ var h = ({ entityType: h, entityId: g, title: _ = "Discussion" }) => {
10
+ let { currentUser: v } = e(), { data: y, isLoading: b, error: x } = c(h, g), S = s(h, g), [C, w] = l(""), [T, E] = l(null), D = () => {
11
+ E(null);
12
+ let e = C.trim();
13
+ e && S.mutate(e, {
14
+ onSuccess: () => w(""),
15
+ onError: (e) => E(e instanceof Error ? e.message : "Failed to post.")
16
+ });
17
+ }, O = (e) => v && e === v.id ? "You" : "Team member";
18
+ return /* @__PURE__ */ f(i, {
19
+ title: _,
20
+ description: "Notes & discussion — shared across the platform via Conversations.",
21
+ children: /* @__PURE__ */ p("div", {
22
+ className: "space-y-3",
23
+ children: [
24
+ T && /* @__PURE__ */ f(t, {
25
+ tone: "danger",
26
+ onClose: () => E(null),
27
+ children: T
28
+ }),
29
+ /* @__PURE__ */ p("div", {
30
+ className: "flex items-start gap-2",
31
+ children: [/* @__PURE__ */ f(a, {
32
+ value: C,
33
+ onChange: (e) => w(e.target.value),
34
+ placeholder: "Add a note…",
35
+ className: "min-h-[44px] flex-1",
36
+ onKeyDown: (e) => {
37
+ (e.metaKey || e.ctrlKey) && e.key === "Enter" && D();
38
+ }
39
+ }), /* @__PURE__ */ f(m, {
40
+ onClick: D,
41
+ disabled: S.isPending || !C.trim(),
42
+ "aria-label": "Post note",
43
+ children: /* @__PURE__ */ f(d, { className: "h-4 w-4" })
44
+ })]
45
+ }),
46
+ b ? /* @__PURE__ */ f(r, { label: "Loading discussion…" }) : x ? /* @__PURE__ */ f("p", {
47
+ className: "text-sm text-status-error-text",
48
+ children: "Couldn't load discussion."
49
+ }) : !y || y.items.length === 0 ? /* @__PURE__ */ f(n, {
50
+ title: "No notes yet",
51
+ description: "Start the discussion for this record.",
52
+ icon: /* @__PURE__ */ f(u, { className: "h-7 w-7" })
53
+ }) : /* @__PURE__ */ f("ul", {
54
+ className: "space-y-2.5",
55
+ children: y.items.map((e) => /* @__PURE__ */ p("li", {
56
+ className: "rounded-lg border border-border-seam bg-bg-canvas p-3",
57
+ children: [/* @__PURE__ */ p("div", {
58
+ className: "mb-1 flex items-center justify-between text-xs text-text-tertiary",
59
+ children: [/* @__PURE__ */ f("span", {
60
+ className: "font-medium text-text-secondary",
61
+ children: O(e.authorId)
62
+ }), /* @__PURE__ */ f("span", { children: o(e.createdAt) })]
63
+ }), /* @__PURE__ */ f("p", {
64
+ className: "whitespace-pre-wrap text-sm text-text-primary",
65
+ children: e.content?.text ?? ""
66
+ })]
67
+ }, e.id))
68
+ })
69
+ ]
70
+ })
71
+ });
72
+ };
73
+ //#endregion
74
+ export { h as DiscussionThread };
75
+
76
+ //# sourceMappingURL=DiscussionThread.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiscussionThread.js","names":[],"sources":["../../../src/proserviceworld/components/DiscussionThread.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { MessageSquare, Send } from 'lucide-react';\nimport { useEntityComments, useAddEntityComment } from '../hooks/useEntityComments';\nimport { useProServiceWorld } from '../providers/ProServiceWorldProvider';\nimport { SectionCard, TextArea, Banner, LoadingBlock, Empty, fmtDate } from './ui';\n\n/**\n * Discussion / notes for a Psw entity, backed entirely by wspace-conversations\n * (createEntityComment / getEntityComments) — no product-owned comments table.\n */\nexport const DiscussionThread: FC<{ entityType: string; entityId: string; title?: string }> = ({\n entityType,\n entityId,\n title = 'Discussion',\n}) => {\n const { currentUser } = useProServiceWorld();\n const { data, isLoading, error } = useEntityComments(entityType, entityId);\n const add = useAddEntityComment(entityType, entityId);\n const [text, setText] = useState('');\n const [err, setErr] = useState<string | null>(null);\n\n const submit = () => {\n setErr(null);\n const body = text.trim();\n if (!body) return;\n add.mutate(body, { onSuccess: () => setText(''), onError: (e) => setErr(e instanceof Error ? e.message : 'Failed to post.') });\n };\n\n const authorLabel = (authorId: string) =>\n currentUser && authorId === currentUser.id ? 'You' : 'Team member';\n\n return (\n <SectionCard title={title} description=\"Notes & discussion — shared across the platform via Conversations.\">\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\" onClose={() => setErr(null)}>{err}</Banner>}\n <div className=\"flex items-start gap-2\">\n <TextArea\n value={text}\n onChange={(e) => setText(e.target.value)}\n placeholder=\"Add a note…\"\n className=\"min-h-[44px] flex-1\"\n onKeyDown={(e) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') submit();\n }}\n />\n <Button onClick={submit} disabled={add.isPending || !text.trim()} aria-label=\"Post note\">\n <Send className=\"h-4 w-4\" />\n </Button>\n </div>\n\n {isLoading ? (\n <LoadingBlock label=\"Loading discussion…\" />\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">Couldn't load discussion.</p>\n ) : !data || data.items.length === 0 ? (\n <Empty title=\"No notes yet\" description=\"Start the discussion for this record.\" icon={<MessageSquare className=\"h-7 w-7\" />} />\n ) : (\n <ul className=\"space-y-2.5\">\n {data.items.map((c) => (\n <li key={c.id} className=\"rounded-lg border border-border-seam bg-bg-canvas p-3\">\n <div className=\"mb-1 flex items-center justify-between text-xs text-text-tertiary\">\n <span className=\"font-medium text-text-secondary\">{authorLabel(c.authorId)}</span>\n <span>{fmtDate(c.createdAt)}</span>\n </div>\n <p className=\"whitespace-pre-wrap text-sm text-text-primary\">{c.content?.text ?? ''}</p>\n </li>\n ))}\n </ul>\n )}\n </div>\n </SectionCard>\n );\n};\n"],"mappings":";;;;;;;;AAYA,IAAa,KAAkF,EAC7F,eACA,aACA,WAAQ,mBACJ;CACJ,IAAM,EAAE,mBAAgB,GAAoB,EACtC,EAAE,SAAM,cAAW,aAAU,EAAkB,GAAY,EAAS,EACpE,IAAM,EAAoB,GAAY,EAAS,EAC/C,CAAC,GAAM,KAAW,EAAS,GAAG,EAC9B,CAAC,GAAK,KAAU,EAAwB,KAAK,EAE7C,UAAe;AACnB,IAAO,KAAK;EACZ,IAAM,IAAO,EAAK,MAAM;AACnB,OACL,EAAI,OAAO,GAAM;GAAE,iBAAiB,EAAQ,GAAG;GAAE,UAAU,MAAM,EAAO,aAAa,QAAQ,EAAE,UAAU,kBAAkB;GAAE,CAAC;IAG1H,KAAe,MACnB,KAAe,MAAa,EAAY,KAAK,QAAQ;AAEvD,QACE,kBAAC,GAAD;EAAoB;EAAO,aAAY;YACrC,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,KAAO,kBAAC,GAAD;KAAQ,MAAK;KAAS,eAAe,EAAO,KAAK;eAAG;KAAa,CAAA;IACzE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,OAAO;MACP,WAAW,MAAM,EAAQ,EAAE,OAAO,MAAM;MACxC,aAAY;MACZ,WAAU;MACV,YAAY,MAAM;AAChB,QAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,WAAS,GAAQ;;MAE7D,CAAA,EACF,kBAAC,GAAD;MAAQ,SAAS;MAAQ,UAAU,EAAI,aAAa,CAAC,EAAK,MAAM;MAAE,cAAW;gBAC3E,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;MACrB,CAAA,CACL;;IAEL,IACC,kBAAC,GAAD,EAAc,OAAM,uBAAwB,CAAA,GAC1C,IACF,kBAAC,KAAD;KAAG,WAAU;eAAiC;KAA6B,CAAA,GACzE,CAAC,KAAQ,EAAK,MAAM,WAAW,IACjC,kBAAC,GAAD;KAAO,OAAM;KAAe,aAAY;KAAwC,MAAM,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;KAAI,CAAA,GAE/H,kBAAC,MAAD;KAAI,WAAU;eACX,EAAK,MAAM,KAAK,MACf,kBAAC,MAAD;MAAe,WAAU;gBAAzB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAmC,EAAY,EAAE,SAAS;QAAQ,CAAA,EAClF,kBAAC,QAAD,EAAA,UAAO,EAAQ,EAAE,UAAU,EAAQ,CAAA,CAC/B;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAiD,EAAE,SAAS,QAAQ;OAAO,CAAA,CACrF;QANI,EAAE,GAMN,CACL;KACC,CAAA;IAEH;;EACM,CAAA"}
@@ -0,0 +1,56 @@
1
+ import { useMutation as e, useQuery as t, useQueryClient as n } from "@tanstack/react-query";
2
+ import { graphqlFetch as r } from "@burdenoff/fe-libs/shared/graphql";
3
+ import { clearGraphqlFetchResponseCache as i } from "@burdenoff/fe-libs/shared/graphql/fetch";
4
+ //#region src/proserviceworld/hooks/useEntityComments.ts
5
+ async function a(e, t) {
6
+ let n = await r({
7
+ gateway: "workspace",
8
+ query: e,
9
+ variables: t,
10
+ workspaceToken: !0
11
+ });
12
+ if (n.errors?.length) throw Error(n.errors[0]?.message ?? "Conversations request failed");
13
+ if (!n.data) throw Error("Conversations response did not include data");
14
+ return e.trimStart().startsWith("mutation") && i(), n.data;
15
+ }
16
+ var o = "id authorId content { text } createdAt", s = "proserviceworld-comments";
17
+ function c(e, n) {
18
+ return t({
19
+ queryKey: [
20
+ s,
21
+ e,
22
+ n
23
+ ],
24
+ enabled: !!n,
25
+ queryFn: async () => (await a(`query PswGetComments($input: GetCommentsInput!) {
26
+ getEntityComments(input: $input) { items { ${o} } total }
27
+ }`, { input: {
28
+ entityType: e,
29
+ entityId: n,
30
+ take: 50,
31
+ sortBy: "createdAt",
32
+ sortOrder: "desc"
33
+ } })).getEntityComments
34
+ });
35
+ }
36
+ function l(t, r) {
37
+ let i = n();
38
+ return e({
39
+ mutationFn: (e) => a(`mutation PswAddComment($input: CreateEntityCommentInput!) {
40
+ createEntityComment(input: $input) { ${o} }
41
+ }`, { input: {
42
+ entityType: t,
43
+ entityId: r,
44
+ content: { text: e }
45
+ } }),
46
+ onSuccess: () => i.invalidateQueries({ queryKey: [
47
+ s,
48
+ t,
49
+ r
50
+ ] })
51
+ });
52
+ }
53
+ //#endregion
54
+ export { l as useAddEntityComment, c as useEntityComments };
55
+
56
+ //# sourceMappingURL=useEntityComments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useEntityComments.js","names":[],"sources":["../../../src/proserviceworld/hooks/useEntityComments.ts"],"sourcesContent":["/**\n * Entity discussion via wspace-conversations-svc (NO duplication).\n * @module proserviceworld/hooks\n *\n * ProServiceWorld does NOT own a comments table. Discussion/notes on a client,\n * engagement or matter reuse the shared `wspace-conversations` entity-comment\n * API (createEntityComment / getEntityComments) — the same service that powers\n * comments everywhere on the platform — via the MFE's proven graphqlFetch\n * transport. `entityType` namespaces threads to this product\n * (\"proserviceworld-<entity>\"); `entityId` is the Psw row id.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql/fetch';\n\nasync function convQuery<TData>(query: string, variables: Record<string, unknown>): Promise<TData> {\n const response = await graphqlFetch<TData>({ gateway: 'workspace', query, variables, workspaceToken: true });\n if (response.errors?.length) throw new Error(response.errors[0]?.message ?? 'Conversations request failed');\n if (!response.data) throw new Error('Conversations response did not include data');\n if (query.trimStart().startsWith('mutation')) clearGraphqlFetchResponseCache();\n return response.data;\n}\n\nexport interface EntityComment {\n id: string;\n authorId: string;\n content: { text: string | null } | null;\n createdAt: string;\n}\n\nconst COMMENT_F = 'id authorId content { text } createdAt';\nconst QK = 'proserviceworld-comments' as const;\n\n/** List the discussion thread for a Psw entity (most recent first). */\nexport function useEntityComments(entityType: string, entityId: string | null) {\n return useQuery({\n queryKey: [QK, entityType, entityId],\n enabled: Boolean(entityId),\n queryFn: async () => {\n const data = await convQuery<{ getEntityComments: { items: EntityComment[]; total: number } }>(\n `query PswGetComments($input: GetCommentsInput!) {\n getEntityComments(input: $input) { items { ${COMMENT_F} } total }\n }`,\n { input: { entityType, entityId, take: 50, sortBy: 'createdAt', sortOrder: 'desc' } }\n );\n return data.getEntityComments;\n },\n });\n}\n\n/** Post a plain-text note onto a Psw entity's shared discussion thread. */\nexport function useAddEntityComment(entityType: string, entityId: string) {\n const qc = useQueryClient();\n return useMutation({\n mutationFn: (text: string) =>\n convQuery(\n `mutation PswAddComment($input: CreateEntityCommentInput!) {\n createEntityComment(input: $input) { ${COMMENT_F} }\n }`,\n { input: { entityType, entityId, content: { text } } }\n ),\n onSuccess: () => qc.invalidateQueries({ queryKey: [QK, entityType, entityId] }),\n });\n}\n"],"mappings":";;;;AAiBA,eAAe,EAAiB,GAAe,GAAoD;CACjG,IAAM,IAAW,MAAM,EAAoB;EAAE,SAAS;EAAa;EAAO;EAAW,gBAAgB;EAAM,CAAC;AAC5G,KAAI,EAAS,QAAQ,OAAQ,OAAU,MAAM,EAAS,OAAO,IAAI,WAAW,+BAA+B;AAC3G,KAAI,CAAC,EAAS,KAAM,OAAU,MAAM,8CAA8C;AAElF,QADI,EAAM,WAAW,CAAC,WAAW,WAAW,IAAE,GAAgC,EACvE,EAAS;;AAUlB,IAAM,IAAY,0CACZ,IAAK;AAGX,SAAgB,EAAkB,GAAoB,GAAyB;AAC7E,QAAO,EAAS;EACd,UAAU;GAAC;GAAI;GAAY;GAAS;EACpC,SAAS,EAAQ;EACjB,SAAS,aACM,MAAM,EACjB;uDAC+C,EAAU;YAEzD,EAAE,OAAO;GAAE;GAAY;GAAU,MAAM;GAAI,QAAQ;GAAa,WAAW;GAAQ,EAAE,CACtF,EACW;EAEf,CAAC;;AAIJ,SAAgB,EAAoB,GAAoB,GAAkB;CACxE,IAAM,IAAK,GAAgB;AAC3B,QAAO,EAAY;EACjB,aAAa,MACX,EACE;iDACyC,EAAU;YAEnD,EAAE,OAAO;GAAE;GAAY;GAAU,SAAS,EAAE,SAAM;GAAE,EAAE,CACvD;EACH,iBAAiB,EAAG,kBAAkB,EAAE,UAAU;GAAC;GAAI;GAAY;GAAS,EAAE,CAAC;EAChF,CAAC"}
@@ -1,190 +1,195 @@
1
1
  import { useClient as e, useConflictChecks as t, useCreateConflictCheck as n } from "../hooks/useProServiceWorldApi.js";
2
2
  import { useNav as r } from "../utils/nav.js";
3
3
  import { Banner as i, Empty as a, ErrorState as o, Field as s, LoadingBlock as c, Modal as l, PageHeader as u, SectionCard as d, StatusPill as f, TextArea as p, TextInput as m, fmtDate as h, fmtMoney as g } from "../components/ui.js";
4
- import { useState as _ } from "react";
5
- import { ArrowLeft as v, Briefcase as y, FolderKanban as b, GitBranch as x, Mail as S, MapPin as C, Phone as w, Plus as T } from "lucide-react";
6
- import { useParams as E } from "react-router-dom";
7
- import { Fragment as D, jsx as O, jsxs as k } from "react/jsx-runtime";
8
- import { Button as A } from "@burdenoff/fe-libs/ui";
4
+ import { DiscussionThread as _ } from "../components/DiscussionThread.js";
5
+ import { useState as v } from "react";
6
+ import { ArrowLeft as y, Briefcase as b, FolderKanban as x, GitBranch as S, Mail as C, MapPin as w, Phone as T, Plus as E } from "lucide-react";
7
+ import { useParams as D } from "react-router-dom";
8
+ import { Fragment as O, jsx as k, jsxs as A } from "react/jsx-runtime";
9
+ import { Button as j } from "@burdenoff/fe-libs/ui";
9
10
  //#region src/proserviceworld/pages/ClientDetailPage.tsx
10
- var j = () => {
11
- let { id: n = "" } = E(), i = r(), { data: s, isLoading: l, error: p, refetch: m } = e(n), D = t({ clientId: n }), [j, F] = _(!1);
12
- return l ? /* @__PURE__ */ O(c, {}) : p ? /* @__PURE__ */ O(o, {
11
+ var M = () => {
12
+ let { id: n = "" } = D(), i = r(), { data: s, isLoading: l, error: p, refetch: m } = e(n), O = t({ clientId: n }), [M, I] = v(!1);
13
+ return l ? /* @__PURE__ */ k(c, {}) : p ? /* @__PURE__ */ k(o, {
13
14
  error: p,
14
15
  onRetry: () => void m()
15
- }) : s ? /* @__PURE__ */ k("div", {
16
+ }) : s ? /* @__PURE__ */ A("div", {
16
17
  className: "space-y-6",
17
18
  children: [
18
- /* @__PURE__ */ k("button", {
19
+ /* @__PURE__ */ A("button", {
19
20
  type: "button",
20
21
  onClick: () => i("clients"),
21
22
  className: "flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent",
22
- children: [/* @__PURE__ */ O(v, { className: "h-4 w-4" }), " Back to clients"]
23
+ children: [/* @__PURE__ */ k(y, { className: "h-4 w-4" }), " Back to clients"]
23
24
  }),
24
- /* @__PURE__ */ O(u, {
25
+ /* @__PURE__ */ k(u, {
25
26
  title: s.name,
26
27
  subtitle: [s.type === "ORGANIZATION" ? "Organization" : "Individual", s.industry].filter(Boolean).join(" · "),
27
- actions: /* @__PURE__ */ O(f, { status: s.status })
28
+ actions: /* @__PURE__ */ k(f, { status: s.status })
28
29
  }),
29
- /* @__PURE__ */ k("div", {
30
+ /* @__PURE__ */ A("div", {
30
31
  className: "grid grid-cols-1 gap-4 lg:grid-cols-3",
31
- children: [/* @__PURE__ */ O(d, {
32
+ children: [/* @__PURE__ */ k(d, {
32
33
  title: "Contact",
33
34
  className: "lg:col-span-1",
34
- children: /* @__PURE__ */ k("dl", {
35
+ children: /* @__PURE__ */ A("dl", {
35
36
  className: "space-y-2 text-sm",
36
37
  children: [
37
- /* @__PURE__ */ O(M, {
38
+ /* @__PURE__ */ k(N, {
38
39
  label: "Contact",
39
40
  value: s.contactName
40
41
  }),
41
- s.contactEmail && /* @__PURE__ */ O(N, {
42
- icon: /* @__PURE__ */ O(S, { className: "h-3.5 w-3.5" }),
42
+ s.contactEmail && /* @__PURE__ */ k(P, {
43
+ icon: /* @__PURE__ */ k(C, { className: "h-3.5 w-3.5" }),
43
44
  value: s.contactEmail
44
45
  }),
45
- s.contactPhone && /* @__PURE__ */ O(N, {
46
- icon: /* @__PURE__ */ O(w, { className: "h-3.5 w-3.5" }),
46
+ s.contactPhone && /* @__PURE__ */ k(P, {
47
+ icon: /* @__PURE__ */ k(T, { className: "h-3.5 w-3.5" }),
47
48
  value: s.contactPhone
48
49
  }),
49
- s.address && /* @__PURE__ */ O(N, {
50
- icon: /* @__PURE__ */ O(C, { className: "h-3.5 w-3.5" }),
50
+ s.address && /* @__PURE__ */ k(P, {
51
+ icon: /* @__PURE__ */ k(w, { className: "h-3.5 w-3.5" }),
51
52
  value: s.address
52
53
  }),
53
- s.notes && /* @__PURE__ */ O("p", {
54
+ s.notes && /* @__PURE__ */ k("p", {
54
55
  className: "pt-2 text-text-secondary",
55
56
  children: s.notes
56
57
  })
57
58
  ]
58
59
  })
59
- }), /* @__PURE__ */ O(d, {
60
+ }), /* @__PURE__ */ k(d, {
60
61
  title: "Conflict checks",
61
62
  className: "lg:col-span-2",
62
- action: /* @__PURE__ */ k(A, {
63
+ action: /* @__PURE__ */ A(j, {
63
64
  size: "sm",
64
65
  variant: "outline",
65
- onClick: () => F(!0),
66
- children: [/* @__PURE__ */ O(x, { className: "mr-1.5 h-4 w-4" }), " Run check"]
66
+ onClick: () => I(!0),
67
+ children: [/* @__PURE__ */ k(S, { className: "mr-1.5 h-4 w-4" }), " Run check"]
67
68
  }),
68
- children: D.isLoading ? /* @__PURE__ */ O(c, { label: "Loading…" }) : !D.data || D.data.length === 0 ? /* @__PURE__ */ O(a, {
69
+ children: O.isLoading ? /* @__PURE__ */ k(c, { label: "Loading…" }) : !O.data || O.data.length === 0 ? /* @__PURE__ */ k(a, {
69
70
  title: "No conflict checks",
70
71
  description: "Run a conflict check before accepting new work for this client."
71
- }) : /* @__PURE__ */ O("ul", {
72
+ }) : /* @__PURE__ */ k("ul", {
72
73
  className: "divide-y divide-border-seam",
73
- children: D.data.map((e) => /* @__PURE__ */ k("li", {
74
+ children: O.data.map((e) => /* @__PURE__ */ A("li", {
74
75
  className: "flex items-center justify-between py-2.5",
75
- children: [/* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("div", {
76
+ children: [/* @__PURE__ */ A("div", { children: [/* @__PURE__ */ k("div", {
76
77
  className: "text-sm text-text-primary",
77
78
  children: e.searchedParties.join(", ") || "Conflict search"
78
- }), /* @__PURE__ */ k("div", {
79
+ }), /* @__PURE__ */ A("div", {
79
80
  className: "text-xs text-text-tertiary",
80
81
  children: [h(e.createdAt), e.findings ? ` · ${e.findings}` : ""]
81
- })] }), /* @__PURE__ */ O(f, { status: e.status })]
82
+ })] }), /* @__PURE__ */ k(f, { status: e.status })]
82
83
  }, e.id))
83
84
  })
84
85
  })]
85
86
  }),
86
- /* @__PURE__ */ O(d, {
87
+ /* @__PURE__ */ k(d, {
87
88
  title: "Engagements",
88
- action: /* @__PURE__ */ k(A, {
89
+ action: /* @__PURE__ */ A(j, {
89
90
  size: "sm",
90
91
  variant: "outline",
91
92
  onClick: () => i("engagements"),
92
- children: [/* @__PURE__ */ O(T, { className: "mr-1.5 h-4 w-4" }), "Manage"]
93
+ children: [/* @__PURE__ */ k(E, { className: "mr-1.5 h-4 w-4" }), "Manage"]
93
94
  }),
94
- children: s.engagements.length === 0 ? /* @__PURE__ */ O(a, {
95
+ children: s.engagements.length === 0 ? /* @__PURE__ */ k(a, {
95
96
  title: "No engagements",
96
97
  description: "Scope the first engagement for this client.",
97
- icon: /* @__PURE__ */ O(y, { className: "h-7 w-7" })
98
- }) : /* @__PURE__ */ O("ul", {
98
+ icon: /* @__PURE__ */ k(b, { className: "h-7 w-7" })
99
+ }) : /* @__PURE__ */ k("ul", {
99
100
  className: "divide-y divide-border-seam",
100
- children: s.engagements.map((e) => /* @__PURE__ */ O("li", { children: /* @__PURE__ */ k("button", {
101
+ children: s.engagements.map((e) => /* @__PURE__ */ k("li", { children: /* @__PURE__ */ A("button", {
101
102
  type: "button",
102
103
  onClick: () => i(`engagements/${e.id}`),
103
104
  className: "flex w-full items-center justify-between py-2.5 text-left hover:text-accent",
104
- children: [/* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("div", {
105
+ children: [/* @__PURE__ */ A("div", { children: [/* @__PURE__ */ k("div", {
105
106
  className: "text-sm font-medium text-text-primary",
106
107
  children: e.name
107
- }), /* @__PURE__ */ k("div", {
108
+ }), /* @__PURE__ */ A("div", {
108
109
  className: "text-xs text-text-tertiary",
109
110
  children: [
110
111
  g(e.feeAmount, e.currency),
111
112
  " · ",
112
113
  e.feeArrangement.toLowerCase()
113
114
  ]
114
- })] }), /* @__PURE__ */ O(f, { status: e.status })]
115
+ })] }), /* @__PURE__ */ k(f, { status: e.status })]
115
116
  }) }, e.id))
116
117
  })
117
118
  }),
118
- /* @__PURE__ */ O(d, {
119
+ /* @__PURE__ */ k(d, {
119
120
  title: "Matters",
120
- action: /* @__PURE__ */ k(A, {
121
+ action: /* @__PURE__ */ A(j, {
121
122
  size: "sm",
122
123
  variant: "outline",
123
124
  onClick: () => i("matters"),
124
- children: [/* @__PURE__ */ O(T, { className: "mr-1.5 h-4 w-4" }), "Manage"]
125
+ children: [/* @__PURE__ */ k(E, { className: "mr-1.5 h-4 w-4" }), "Manage"]
125
126
  }),
126
- children: s.matters.length === 0 ? /* @__PURE__ */ O(a, {
127
+ children: s.matters.length === 0 ? /* @__PURE__ */ k(a, {
127
128
  title: "No matters",
128
129
  description: "Open a matter to track regulated work for this client.",
129
- icon: /* @__PURE__ */ O(b, { className: "h-7 w-7" })
130
- }) : /* @__PURE__ */ O("ul", {
130
+ icon: /* @__PURE__ */ k(x, { className: "h-7 w-7" })
131
+ }) : /* @__PURE__ */ k("ul", {
131
132
  className: "divide-y divide-border-seam",
132
- children: s.matters.map((e) => /* @__PURE__ */ k("li", {
133
+ children: s.matters.map((e) => /* @__PURE__ */ A("li", {
133
134
  className: "flex items-center justify-between py-2.5",
134
- children: [/* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("div", {
135
+ children: [/* @__PURE__ */ A("div", { children: [/* @__PURE__ */ k("div", {
135
136
  className: "text-sm font-medium text-text-primary",
136
137
  children: e.title
137
- }), /* @__PURE__ */ k("div", {
138
+ }), /* @__PURE__ */ A("div", {
138
139
  className: "text-xs text-text-tertiary",
139
140
  children: [
140
141
  e.reference || "No reference",
141
142
  " · Due ",
142
143
  h(e.dueDate)
143
144
  ]
144
- })] }), /* @__PURE__ */ k("div", {
145
+ })] }), /* @__PURE__ */ A("div", {
145
146
  className: "flex items-center gap-2",
146
- children: [/* @__PURE__ */ O(f, { status: e.priority }), /* @__PURE__ */ O(f, { status: e.status })]
147
+ children: [/* @__PURE__ */ k(f, { status: e.priority }), /* @__PURE__ */ k(f, { status: e.status })]
147
148
  })]
148
149
  }, e.id))
149
150
  })
150
151
  }),
151
- j && /* @__PURE__ */ O(P, {
152
+ /* @__PURE__ */ k(_, {
153
+ entityType: "proserviceworld-client",
154
+ entityId: n
155
+ }),
156
+ M && /* @__PURE__ */ k(F, {
152
157
  clientId: n,
153
- onClose: () => F(!1),
154
- onDone: () => void D.refetch()
158
+ onClose: () => I(!1),
159
+ onDone: () => void O.refetch()
155
160
  })
156
161
  ]
157
- }) : /* @__PURE__ */ O(a, {
162
+ }) : /* @__PURE__ */ k(a, {
158
163
  title: "Client not found",
159
164
  description: "It may have been archived or belongs to another workspace."
160
165
  });
161
- }, M = ({ label: e, value: t }) => t ? /* @__PURE__ */ k("div", {
166
+ }, N = ({ label: e, value: t }) => t ? /* @__PURE__ */ A("div", {
162
167
  className: "flex justify-between gap-4",
163
- children: [/* @__PURE__ */ O("dt", {
168
+ children: [/* @__PURE__ */ k("dt", {
164
169
  className: "text-text-tertiary",
165
170
  children: e
166
- }), /* @__PURE__ */ O("dd", {
171
+ }), /* @__PURE__ */ k("dd", {
167
172
  className: "text-right text-text-primary",
168
173
  children: t
169
174
  })]
170
- }) : null, N = ({ icon: e, value: t }) => /* @__PURE__ */ k("div", {
175
+ }) : null, P = ({ icon: e, value: t }) => /* @__PURE__ */ A("div", {
171
176
  className: "flex items-center gap-2 text-text-secondary",
172
- children: [/* @__PURE__ */ O("span", {
177
+ children: [/* @__PURE__ */ k("span", {
173
178
  className: "text-text-tertiary",
174
179
  children: e
175
180
  }), t]
176
- }), P = ({ clientId: e, onClose: t, onDone: r }) => {
177
- let a = n(), [o, c] = _(""), [u, d] = _(""), [f, h] = _(null);
178
- return /* @__PURE__ */ O(l, {
181
+ }), F = ({ clientId: e, onClose: t, onDone: r }) => {
182
+ let a = n(), [o, c] = v(""), [u, d] = v(""), [f, h] = v(null);
183
+ return /* @__PURE__ */ k(l, {
179
184
  open: !0,
180
185
  onClose: t,
181
186
  title: "Run conflict check",
182
- footer: /* @__PURE__ */ k(D, { children: [/* @__PURE__ */ O(A, {
187
+ footer: /* @__PURE__ */ A(O, { children: [/* @__PURE__ */ k(j, {
183
188
  variant: "outline",
184
189
  onClick: t,
185
190
  disabled: a.isPending,
186
191
  children: "Cancel"
187
- }), /* @__PURE__ */ O(A, {
192
+ }), /* @__PURE__ */ k(j, {
188
193
  onClick: () => {
189
194
  h(null);
190
195
  let n = o.split(",").map((e) => e.trim()).filter(Boolean);
@@ -203,26 +208,26 @@ var j = () => {
203
208
  disabled: a.isPending,
204
209
  children: a.isPending ? "Running…" : "Record check"
205
210
  })] }),
206
- children: /* @__PURE__ */ k("div", {
211
+ children: /* @__PURE__ */ A("div", {
207
212
  className: "space-y-3",
208
213
  children: [
209
- f && /* @__PURE__ */ O(i, {
214
+ f && /* @__PURE__ */ k(i, {
210
215
  tone: "danger",
211
216
  children: f
212
217
  }),
213
- /* @__PURE__ */ O(s, {
218
+ /* @__PURE__ */ k(s, {
214
219
  label: "Parties searched",
215
220
  required: !0,
216
221
  hint: "Comma-separated — counterparties, related entities, principals.",
217
- children: /* @__PURE__ */ O(m, {
222
+ children: /* @__PURE__ */ k(m, {
218
223
  value: o,
219
224
  onChange: (e) => c(e.target.value),
220
225
  placeholder: "Beta Corp, John Roe, Gamma LLP"
221
226
  })
222
227
  }),
223
- /* @__PURE__ */ O(s, {
228
+ /* @__PURE__ */ k(s, {
224
229
  label: "Findings",
225
- children: /* @__PURE__ */ O(p, {
230
+ children: /* @__PURE__ */ k(p, {
226
231
  value: u,
227
232
  onChange: (e) => d(e.target.value),
228
233
  placeholder: "Notes on any potential conflicts…"
@@ -233,6 +238,6 @@ var j = () => {
233
238
  });
234
239
  };
235
240
  //#endregion
236
- export { j as ClientDetailPage };
241
+ export { M as ClientDetailPage };
237
242
 
238
243
  //# sourceMappingURL=ClientDetailPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ClientDetailPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ClientDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n ArrowLeft,\n Briefcase,\n FolderKanban,\n GitBranch,\n Mail,\n Phone,\n MapPin,\n Plus,\n} from 'lucide-react';\nimport {\n useClient,\n useConflictChecks,\n useCreateConflictCheck,\n type PswEngagement,\n type PswMatter,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n Banner,\n fmtDate,\n fmtMoney,\n} from '../components/ui';\n\nexport const ClientDetailPage: FC = () => {\n const { id = '' } = useParams();\n const nav = useNav();\n const { data: client, isLoading, error, refetch } = useClient(id);\n const conflicts = useConflictChecks({ clientId: id });\n const [runConflict, setRunConflict] = useState(false);\n\n if (isLoading) return <LoadingBlock />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!client)\n return (\n <Empty\n title=\"Client not found\"\n description=\"It may have been archived or belongs to another workspace.\"\n />\n );\n\n return (\n <div className=\"space-y-6\">\n <button\n type=\"button\"\n onClick={() => nav('clients')}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to clients\n </button>\n\n <PageHeader\n title={client.name}\n subtitle={[client.type === 'ORGANIZATION' ? 'Organization' : 'Individual', client.industry]\n .filter(Boolean)\n .join(' · ')}\n actions={<StatusPill status={client.status} />}\n />\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <SectionCard title=\"Contact\" className=\"lg:col-span-1\">\n <dl className=\"space-y-2 text-sm\">\n <Row label=\"Contact\" value={client.contactName} />\n {client.contactEmail && (\n <IconRow icon={<Mail className=\"h-3.5 w-3.5\" />} value={client.contactEmail} />\n )}\n {client.contactPhone && (\n <IconRow icon={<Phone className=\"h-3.5 w-3.5\" />} value={client.contactPhone} />\n )}\n {client.address && (\n <IconRow icon={<MapPin className=\"h-3.5 w-3.5\" />} value={client.address} />\n )}\n {client.notes && <p className=\"pt-2 text-text-secondary\">{client.notes}</p>}\n </dl>\n </SectionCard>\n\n <SectionCard\n title=\"Conflict checks\"\n className=\"lg:col-span-2\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setRunConflict(true)}>\n <GitBranch className=\"mr-1.5 h-4 w-4\" /> Run check\n </Button>\n }\n >\n {conflicts.isLoading ? (\n <LoadingBlock label=\"Loading…\" />\n ) : !conflicts.data || conflicts.data.length === 0 ? (\n <Empty\n title=\"No conflict checks\"\n description=\"Run a conflict check before accepting new work for this client.\"\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {conflicts.data.map((c) => (\n <li key={c.id} className=\"flex items-center justify-between py-2.5\">\n <div>\n <div className=\"text-sm text-text-primary\">\n {c.searchedParties.join(', ') || 'Conflict search'}\n </div>\n <div className=\"text-xs text-text-tertiary\">\n {fmtDate(c.createdAt)}\n {c.findings ? ` · ${c.findings}` : ''}\n </div>\n </div>\n <StatusPill status={c.status} />\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n </div>\n\n <SectionCard\n title=\"Engagements\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('engagements')}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Manage\n </Button>\n }\n >\n {client.engagements.length === 0 ? (\n <Empty\n title=\"No engagements\"\n description=\"Scope the first engagement for this client.\"\n icon={<Briefcase className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {client.engagements.map((e: PswEngagement) => (\n <li key={e.id}>\n <button\n type=\"button\"\n onClick={() => nav(`engagements/${e.id}`)}\n className=\"flex w-full items-center justify-between py-2.5 text-left hover:text-accent\"\n >\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{e.name}</div>\n <div className=\"text-xs text-text-tertiary\">\n {fmtMoney(e.feeAmount, e.currency)} · {e.feeArrangement.toLowerCase()}\n </div>\n </div>\n <StatusPill status={e.status} />\n </button>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard\n title=\"Matters\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('matters')}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Manage\n </Button>\n }\n >\n {client.matters.length === 0 ? (\n <Empty\n title=\"No matters\"\n description=\"Open a matter to track regulated work for this client.\"\n icon={<FolderKanban className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {client.matters.map((m: PswMatter) => (\n <li key={m.id} className=\"flex items-center justify-between py-2.5\">\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{m.title}</div>\n <div className=\"text-xs text-text-tertiary\">\n {m.reference || 'No reference'} · Due {fmtDate(m.dueDate)}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusPill status={m.priority} />\n <StatusPill status={m.status} />\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n {runConflict && (\n <ConflictModal\n clientId={id}\n onClose={() => setRunConflict(false)}\n onDone={() => void conflicts.refetch()}\n />\n )}\n </div>\n );\n};\n\nconst Row: FC<{ label: string; value: string | null }> = ({ label, value }) =>\n value ? (\n <div className=\"flex justify-between gap-4\">\n <dt className=\"text-text-tertiary\">{label}</dt>\n <dd className=\"text-right text-text-primary\">{value}</dd>\n </div>\n ) : null;\n\nconst IconRow: FC<{ icon: React.ReactNode; value: string }> = ({ icon, value }) => (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <span className=\"text-text-tertiary\">{icon}</span>\n {value}\n </div>\n);\n\nconst ConflictModal: FC<{ clientId: string; onClose: () => void; onDone: () => void }> = ({\n clientId,\n onClose,\n onDone,\n}) => {\n const create = useCreateConflictCheck();\n const [parties, setParties] = useState('');\n const [findings, setFindings] = useState('');\n const [err, setErr] = useState<string | null>(null);\n\n const submit = () => {\n setErr(null);\n const searchedParties = parties\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean);\n if (searchedParties.length === 0) return setErr('Add at least one party to search.');\n create.mutate(\n { clientId, searchedParties, findings: findings.trim() || null },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Run conflict check\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Running…' : 'Record check'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field\n label=\"Parties searched\"\n required\n hint=\"Comma-separated — counterparties, related entities, principals.\"\n >\n <TextInput\n value={parties}\n onChange={(e) => setParties(e.target.value)}\n placeholder=\"Beta Corp, John Roe, Gamma LLP\"\n />\n </Field>\n <Field label=\"Findings\">\n <TextArea\n value={findings}\n onChange={(e) => setFindings(e.target.value)}\n placeholder=\"Notes on any potential conflicts…\"\n />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ClientDetailPage.js","names":[],"sources":["../../../src/proserviceworld/pages/ClientDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport {\n ArrowLeft,\n Briefcase,\n FolderKanban,\n GitBranch,\n Mail,\n Phone,\n MapPin,\n Plus,\n} from 'lucide-react';\nimport {\n useClient,\n useConflictChecks,\n useCreateConflictCheck,\n type PswEngagement,\n type PswMatter,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { DiscussionThread } from '../components/DiscussionThread';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n Banner,\n fmtDate,\n fmtMoney,\n} from '../components/ui';\n\nexport const ClientDetailPage: FC = () => {\n const { id = '' } = useParams();\n const nav = useNav();\n const { data: client, isLoading, error, refetch } = useClient(id);\n const conflicts = useConflictChecks({ clientId: id });\n const [runConflict, setRunConflict] = useState(false);\n\n if (isLoading) return <LoadingBlock />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!client)\n return (\n <Empty\n title=\"Client not found\"\n description=\"It may have been archived or belongs to another workspace.\"\n />\n );\n\n return (\n <div className=\"space-y-6\">\n <button\n type=\"button\"\n onClick={() => nav('clients')}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to clients\n </button>\n\n <PageHeader\n title={client.name}\n subtitle={[client.type === 'ORGANIZATION' ? 'Organization' : 'Individual', client.industry]\n .filter(Boolean)\n .join(' · ')}\n actions={<StatusPill status={client.status} />}\n />\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <SectionCard title=\"Contact\" className=\"lg:col-span-1\">\n <dl className=\"space-y-2 text-sm\">\n <Row label=\"Contact\" value={client.contactName} />\n {client.contactEmail && (\n <IconRow icon={<Mail className=\"h-3.5 w-3.5\" />} value={client.contactEmail} />\n )}\n {client.contactPhone && (\n <IconRow icon={<Phone className=\"h-3.5 w-3.5\" />} value={client.contactPhone} />\n )}\n {client.address && (\n <IconRow icon={<MapPin className=\"h-3.5 w-3.5\" />} value={client.address} />\n )}\n {client.notes && <p className=\"pt-2 text-text-secondary\">{client.notes}</p>}\n </dl>\n </SectionCard>\n\n <SectionCard\n title=\"Conflict checks\"\n className=\"lg:col-span-2\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setRunConflict(true)}>\n <GitBranch className=\"mr-1.5 h-4 w-4\" /> Run check\n </Button>\n }\n >\n {conflicts.isLoading ? (\n <LoadingBlock label=\"Loading…\" />\n ) : !conflicts.data || conflicts.data.length === 0 ? (\n <Empty\n title=\"No conflict checks\"\n description=\"Run a conflict check before accepting new work for this client.\"\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {conflicts.data.map((c) => (\n <li key={c.id} className=\"flex items-center justify-between py-2.5\">\n <div>\n <div className=\"text-sm text-text-primary\">\n {c.searchedParties.join(', ') || 'Conflict search'}\n </div>\n <div className=\"text-xs text-text-tertiary\">\n {fmtDate(c.createdAt)}\n {c.findings ? ` · ${c.findings}` : ''}\n </div>\n </div>\n <StatusPill status={c.status} />\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n </div>\n\n <SectionCard\n title=\"Engagements\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('engagements')}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Manage\n </Button>\n }\n >\n {client.engagements.length === 0 ? (\n <Empty\n title=\"No engagements\"\n description=\"Scope the first engagement for this client.\"\n icon={<Briefcase className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {client.engagements.map((e: PswEngagement) => (\n <li key={e.id}>\n <button\n type=\"button\"\n onClick={() => nav(`engagements/${e.id}`)}\n className=\"flex w-full items-center justify-between py-2.5 text-left hover:text-accent\"\n >\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{e.name}</div>\n <div className=\"text-xs text-text-tertiary\">\n {fmtMoney(e.feeAmount, e.currency)} · {e.feeArrangement.toLowerCase()}\n </div>\n </div>\n <StatusPill status={e.status} />\n </button>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard\n title=\"Matters\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('matters')}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Manage\n </Button>\n }\n >\n {client.matters.length === 0 ? (\n <Empty\n title=\"No matters\"\n description=\"Open a matter to track regulated work for this client.\"\n icon={<FolderKanban className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {client.matters.map((m: PswMatter) => (\n <li key={m.id} className=\"flex items-center justify-between py-2.5\">\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{m.title}</div>\n <div className=\"text-xs text-text-tertiary\">\n {m.reference || 'No reference'} · Due {fmtDate(m.dueDate)}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusPill status={m.priority} />\n <StatusPill status={m.status} />\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <DiscussionThread entityType=\"proserviceworld-client\" entityId={id} />\n\n {runConflict && (\n <ConflictModal\n clientId={id}\n onClose={() => setRunConflict(false)}\n onDone={() => void conflicts.refetch()}\n />\n )}\n </div>\n );\n};\n\nconst Row: FC<{ label: string; value: string | null }> = ({ label, value }) =>\n value ? (\n <div className=\"flex justify-between gap-4\">\n <dt className=\"text-text-tertiary\">{label}</dt>\n <dd className=\"text-right text-text-primary\">{value}</dd>\n </div>\n ) : null;\n\nconst IconRow: FC<{ icon: React.ReactNode; value: string }> = ({ icon, value }) => (\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <span className=\"text-text-tertiary\">{icon}</span>\n {value}\n </div>\n);\n\nconst ConflictModal: FC<{ clientId: string; onClose: () => void; onDone: () => void }> = ({\n clientId,\n onClose,\n onDone,\n}) => {\n const create = useCreateConflictCheck();\n const [parties, setParties] = useState('');\n const [findings, setFindings] = useState('');\n const [err, setErr] = useState<string | null>(null);\n\n const submit = () => {\n setErr(null);\n const searchedParties = parties\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean);\n if (searchedParties.length === 0) return setErr('Add at least one party to search.');\n create.mutate(\n { clientId, searchedParties, findings: findings.trim() || null },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Run conflict check\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Running…' : 'Record check'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field\n label=\"Parties searched\"\n required\n hint=\"Comma-separated — counterparties, related entities, principals.\"\n >\n <TextInput\n value={parties}\n onChange={(e) => setParties(e.target.value)}\n placeholder=\"Beta Corp, John Roe, Gamma LLP\"\n />\n </Field>\n <Field label=\"Findings\">\n <TextArea\n value={findings}\n onChange={(e) => setFindings(e.target.value)}\n placeholder=\"Notes on any potential conflicts…\"\n />\n </Field>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
@@ -1,13 +1,14 @@
1
1
  import { useCompleteMilestone as e, useCreateDeliverable as t, useCreateMilestone as n, useEngagement as r, useTimeEntries as i, useTransitionDeliverable as a, useTransitionEngagement as o } from "../hooks/useProServiceWorldApi.js";
2
2
  import { useNav as s } from "../utils/nav.js";
3
3
  import { Banner as c, Empty as l, ErrorState as u, Field as d, LoadingBlock as f, MetricCard as p, Modal as m, NativeSelect as h, PageHeader as g, SectionCard as _, StatusPill as v, TextArea as y, TextInput as b, fmtDate as x, fmtMinutesAsHours as S, fmtMoney as C, humanize as w } from "../components/ui.js";
4
- import { useState as T } from "react";
5
- import { ArrowLeft as E, CheckCircle2 as D, Clock as O, FileText as k, Flag as A, Plus as j } from "lucide-react";
6
- import { useParams as M } from "react-router-dom";
7
- import { Fragment as N, jsx as P, jsxs as F } from "react/jsx-runtime";
8
- import { Button as I } from "@burdenoff/fe-libs/ui";
4
+ import { DiscussionThread as T } from "../components/DiscussionThread.js";
5
+ import { useState as E } from "react";
6
+ import { ArrowLeft as D, CheckCircle2 as O, Clock as k, FileText as A, Flag as j, Plus as M } from "lucide-react";
7
+ import { useParams as N } from "react-router-dom";
8
+ import { Fragment as P, jsx as F, jsxs as I } from "react/jsx-runtime";
9
+ import { Button as L } from "@burdenoff/fe-libs/ui";
9
10
  //#region src/proserviceworld/pages/EngagementDetailPage.tsx
10
- var L = {
11
+ var R = {
11
12
  PROPOSAL: ["ACCEPTED", "CANCELLED"],
12
13
  ACCEPTED: ["IN_PROGRESS", "CANCELLED"],
13
14
  IN_PROGRESS: ["REVIEW", "CANCELLED"],
@@ -20,69 +21,69 @@ var L = {
20
21
  INVOICED: ["CLOSED"],
21
22
  CLOSED: [],
22
23
  CANCELLED: []
23
- }, R = [
24
+ }, z = [
24
25
  "DRAFT",
25
26
  "INTERNAL_REVIEW",
26
27
  "CLIENT_REVIEW",
27
28
  "ACCEPTED",
28
29
  "REJECTED"
29
- ], z = () => {
30
- let { id: e = "" } = M(), t = s(), { data: n, isLoading: a, error: d, refetch: m } = r(e), h = i({ engagementId: e }), y = o(), [b, D] = T(!1), [N, R] = T(!1), [z, W] = T(null);
31
- if (a) return /* @__PURE__ */ P(f, {});
32
- if (d) return /* @__PURE__ */ P(u, {
30
+ ], B = () => {
31
+ let { id: e = "" } = N(), t = s(), { data: n, isLoading: a, error: d, refetch: m } = r(e), h = i({ engagementId: e }), y = o(), [b, O] = E(!1), [P, z] = E(!1), [B, G] = E(null);
32
+ if (a) return /* @__PURE__ */ F(f, {});
33
+ if (d) return /* @__PURE__ */ F(u, {
33
34
  error: d,
34
35
  onRetry: () => void m()
35
36
  });
36
- if (!n) return /* @__PURE__ */ P(l, { title: "Engagement not found" });
37
- let G = L[n.status] ?? [], K = (h.data?.items ?? []).reduce((e, t) => e + t.minutes, 0);
38
- return /* @__PURE__ */ F("div", {
37
+ if (!n) return /* @__PURE__ */ F(l, { title: "Engagement not found" });
38
+ let K = R[n.status] ?? [], q = (h.data?.items ?? []).reduce((e, t) => e + t.minutes, 0);
39
+ return /* @__PURE__ */ I("div", {
39
40
  className: "space-y-6",
40
41
  children: [
41
- /* @__PURE__ */ F("button", {
42
+ /* @__PURE__ */ I("button", {
42
43
  type: "button",
43
44
  onClick: () => t("engagements"),
44
45
  className: "flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent",
45
- children: [/* @__PURE__ */ P(E, { className: "h-4 w-4" }), " Back to engagements"]
46
+ children: [/* @__PURE__ */ F(D, { className: "h-4 w-4" }), " Back to engagements"]
46
47
  }),
47
- /* @__PURE__ */ P(g, {
48
+ /* @__PURE__ */ F(g, {
48
49
  title: n.name,
49
50
  subtitle: n.client ? `${n.client.name} · ${w(n.feeArrangement)}` : w(n.feeArrangement),
50
- actions: /* @__PURE__ */ P(v, { status: n.status })
51
+ actions: /* @__PURE__ */ F(v, { status: n.status })
51
52
  }),
52
- z && /* @__PURE__ */ P(c, {
53
- tone: z.tone,
54
- onClose: () => W(null),
55
- children: z.text
53
+ B && /* @__PURE__ */ F(c, {
54
+ tone: B.tone,
55
+ onClose: () => G(null),
56
+ children: B.text
56
57
  }),
57
- /* @__PURE__ */ F("div", {
58
+ /* @__PURE__ */ I("div", {
58
59
  className: "grid grid-cols-2 gap-3 lg:grid-cols-4",
59
60
  children: [
60
- /* @__PURE__ */ P(p, {
61
+ /* @__PURE__ */ F(p, {
61
62
  label: "Fee",
62
63
  value: C(n.feeAmount, n.currency),
63
64
  hint: w(n.feeArrangement)
64
65
  }),
65
- /* @__PURE__ */ P(p, {
66
+ /* @__PURE__ */ F(p, {
66
67
  label: "Deliverables",
67
68
  value: n.deliverables.length
68
69
  }),
69
- /* @__PURE__ */ P(p, {
70
+ /* @__PURE__ */ F(p, {
70
71
  label: "Milestones",
71
72
  value: n.milestones.length
72
73
  }),
73
- /* @__PURE__ */ P(p, {
74
+ /* @__PURE__ */ F(p, {
74
75
  label: "Time logged",
75
- value: S(K),
76
+ value: S(q),
76
77
  hint: `${h.data?.totalCount ?? 0} entries`
77
78
  })
78
79
  ]
79
80
  }),
80
- G.length > 0 && /* @__PURE__ */ P(_, {
81
+ K.length > 0 && /* @__PURE__ */ F(_, {
81
82
  title: "Advance lifecycle",
82
83
  description: `Current status: ${w(n.status)}`,
83
- children: /* @__PURE__ */ P("div", {
84
+ children: /* @__PURE__ */ F("div", {
84
85
  className: "flex flex-wrap gap-2",
85
- children: G.map((e) => /* @__PURE__ */ P(I, {
86
+ children: K.map((e) => /* @__PURE__ */ F(L, {
86
87
  size: "sm",
87
88
  variant: e === "CANCELLED" ? "outline" : "default",
88
89
  disabled: y.isPending,
@@ -90,11 +91,11 @@ var L = {
90
91
  id: n.id,
91
92
  status: e
92
93
  }, {
93
- onSuccess: () => W({
94
+ onSuccess: () => G({
94
95
  tone: "success",
95
96
  text: `Moved to ${w(e)}.`
96
97
  }),
97
- onError: (e) => W({
98
+ onError: (e) => G({
98
99
  tone: "danger",
99
100
  text: e instanceof Error ? e.message : "Transition failed."
100
101
  })
@@ -103,40 +104,40 @@ var L = {
103
104
  }, e))
104
105
  })
105
106
  }),
106
- /* @__PURE__ */ F("div", {
107
+ /* @__PURE__ */ I("div", {
107
108
  className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
108
- children: [/* @__PURE__ */ P(_, {
109
+ children: [/* @__PURE__ */ F(_, {
109
110
  title: "Deliverables",
110
- action: /* @__PURE__ */ F(I, {
111
+ action: /* @__PURE__ */ I(L, {
111
112
  size: "sm",
112
113
  variant: "outline",
113
- onClick: () => D(!0),
114
- children: [/* @__PURE__ */ P(j, { className: "mr-1.5 h-4 w-4" }), "Add"]
114
+ onClick: () => O(!0),
115
+ children: [/* @__PURE__ */ F(M, { className: "mr-1.5 h-4 w-4" }), "Add"]
115
116
  }),
116
- children: n.deliverables.length === 0 ? /* @__PURE__ */ P(l, {
117
+ children: n.deliverables.length === 0 ? /* @__PURE__ */ F(l, {
117
118
  title: "No deliverables",
118
119
  description: "Track the tangible outputs of this engagement.",
119
- icon: /* @__PURE__ */ P(k, { className: "h-7 w-7" })
120
- }) : /* @__PURE__ */ P("ul", {
120
+ icon: /* @__PURE__ */ F(A, { className: "h-7 w-7" })
121
+ }) : /* @__PURE__ */ F("ul", {
121
122
  className: "space-y-2",
122
- children: n.deliverables.map((e) => /* @__PURE__ */ P("li", {
123
+ children: n.deliverables.map((e) => /* @__PURE__ */ F("li", {
123
124
  className: "rounded-lg border border-border-seam p-3",
124
- children: /* @__PURE__ */ F("div", {
125
+ children: /* @__PURE__ */ I("div", {
125
126
  className: "flex items-start justify-between gap-2",
126
- children: [/* @__PURE__ */ F("div", { children: [/* @__PURE__ */ F("div", {
127
+ children: [/* @__PURE__ */ I("div", { children: [/* @__PURE__ */ I("div", {
127
128
  className: "text-sm font-medium text-text-primary",
128
129
  children: [
129
130
  e.name,
130
131
  " ",
131
- /* @__PURE__ */ F("span", {
132
+ /* @__PURE__ */ I("span", {
132
133
  className: "text-xs text-text-tertiary",
133
134
  children: ["v", e.version]
134
135
  })
135
136
  ]
136
- }), e.dueDate && /* @__PURE__ */ F("div", {
137
+ }), e.dueDate && /* @__PURE__ */ I("div", {
137
138
  className: "text-xs text-text-tertiary",
138
139
  children: ["Due ", x(e.dueDate)]
139
- })] }), /* @__PURE__ */ P(B, {
140
+ })] }), /* @__PURE__ */ F(V, {
140
141
  id: e.id,
141
142
  engagementId: n.id,
142
143
  status: e.status,
@@ -145,35 +146,35 @@ var L = {
145
146
  })
146
147
  }, e.id))
147
148
  })
148
- }), /* @__PURE__ */ P(_, {
149
+ }), /* @__PURE__ */ F(_, {
149
150
  title: "Milestones",
150
- action: /* @__PURE__ */ F(I, {
151
+ action: /* @__PURE__ */ I(L, {
151
152
  size: "sm",
152
153
  variant: "outline",
153
- onClick: () => R(!0),
154
- children: [/* @__PURE__ */ P(j, { className: "mr-1.5 h-4 w-4" }), "Add"]
154
+ onClick: () => z(!0),
155
+ children: [/* @__PURE__ */ F(M, { className: "mr-1.5 h-4 w-4" }), "Add"]
155
156
  }),
156
- children: n.milestones.length === 0 ? /* @__PURE__ */ P(l, {
157
+ children: n.milestones.length === 0 ? /* @__PURE__ */ F(l, {
157
158
  title: "No milestones",
158
159
  description: "Add checkpoints — some can trigger billing.",
159
- icon: /* @__PURE__ */ P(A, { className: "h-7 w-7" })
160
- }) : /* @__PURE__ */ P("ul", {
160
+ icon: /* @__PURE__ */ F(j, { className: "h-7 w-7" })
161
+ }) : /* @__PURE__ */ F("ul", {
161
162
  className: "space-y-2",
162
- children: n.milestones.map((e) => /* @__PURE__ */ F("li", {
163
+ children: n.milestones.map((e) => /* @__PURE__ */ I("li", {
163
164
  className: "flex items-center justify-between rounded-lg border border-border-seam p-3",
164
- children: [/* @__PURE__ */ F("div", { children: [/* @__PURE__ */ P("div", {
165
+ children: [/* @__PURE__ */ I("div", { children: [/* @__PURE__ */ F("div", {
165
166
  className: "text-sm font-medium text-text-primary",
166
167
  children: e.name
167
- }), /* @__PURE__ */ F("div", {
168
+ }), /* @__PURE__ */ I("div", {
168
169
  className: "text-xs text-text-tertiary",
169
170
  children: [
170
171
  e.triggersInvoice ? `Bills ${C(e.triggerAmount, n.currency)} · ` : "",
171
172
  "Due ",
172
173
  x(e.dueDate)
173
174
  ]
174
- })] }), /* @__PURE__ */ F("div", {
175
+ })] }), /* @__PURE__ */ I("div", {
175
176
  className: "flex items-center gap-2",
176
- children: [/* @__PURE__ */ P(v, { status: e.status }), e.status !== "COMPLETED" && e.status !== "CANCELLED" && /* @__PURE__ */ P(V, {
177
+ children: [/* @__PURE__ */ F(v, { status: e.status }), e.status !== "COMPLETED" && e.status !== "CANCELLED" && /* @__PURE__ */ F(H, {
177
178
  id: e.id,
178
179
  engagementId: n.id,
179
180
  onDone: () => void m()
@@ -183,53 +184,57 @@ var L = {
183
184
  })
184
185
  })]
185
186
  }),
186
- /* @__PURE__ */ P(_, {
187
+ /* @__PURE__ */ F(_, {
187
188
  title: "Time logged",
188
- action: /* @__PURE__ */ F(I, {
189
+ action: /* @__PURE__ */ I(L, {
189
190
  size: "sm",
190
191
  variant: "outline",
191
192
  onClick: () => t("time"),
192
- children: [/* @__PURE__ */ P(O, { className: "mr-1.5 h-4 w-4" }), "Log time"]
193
+ children: [/* @__PURE__ */ F(k, { className: "mr-1.5 h-4 w-4" }), "Log time"]
193
194
  }),
194
- children: h.isLoading ? /* @__PURE__ */ P(f, { label: "Loading…" }) : !h.data || h.data.items.length === 0 ? /* @__PURE__ */ P(l, {
195
+ children: h.isLoading ? /* @__PURE__ */ F(f, { label: "Loading…" }) : !h.data || h.data.items.length === 0 ? /* @__PURE__ */ F(l, {
195
196
  title: "No time logged",
196
197
  description: "Time logged against this engagement will appear here."
197
- }) : /* @__PURE__ */ P("ul", {
198
+ }) : /* @__PURE__ */ F("ul", {
198
199
  className: "divide-y divide-border-seam",
199
- children: h.data.items.slice(0, 8).map((e) => /* @__PURE__ */ F("li", {
200
+ children: h.data.items.slice(0, 8).map((e) => /* @__PURE__ */ I("li", {
200
201
  className: "flex items-center justify-between py-2 text-sm",
201
- children: [/* @__PURE__ */ F("span", {
202
+ children: [/* @__PURE__ */ I("span", {
202
203
  className: "text-text-secondary",
203
204
  children: [
204
205
  x(e.date),
205
206
  " · ",
206
207
  e.narrative || "Time entry"
207
208
  ]
208
- }), /* @__PURE__ */ F("span", {
209
+ }), /* @__PURE__ */ I("span", {
209
210
  className: "flex items-center gap-2",
210
- children: [/* @__PURE__ */ P("span", {
211
+ children: [/* @__PURE__ */ F("span", {
211
212
  className: "text-text-primary",
212
213
  children: S(e.minutes)
213
- }), /* @__PURE__ */ P(v, { status: e.status })]
214
+ }), /* @__PURE__ */ F(v, { status: e.status })]
214
215
  })]
215
216
  }, e.id))
216
217
  })
217
218
  }),
218
- b && /* @__PURE__ */ P(H, {
219
+ /* @__PURE__ */ F(T, {
220
+ entityType: "proserviceworld-engagement",
221
+ entityId: n.id
222
+ }),
223
+ b && /* @__PURE__ */ F(U, {
219
224
  engagementId: n.id,
220
- onClose: () => D(!1),
225
+ onClose: () => O(!1),
221
226
  onDone: () => {
222
- m(), W({
227
+ m(), G({
223
228
  tone: "success",
224
229
  text: "Deliverable added."
225
230
  });
226
231
  }
227
232
  }),
228
- N && /* @__PURE__ */ P(U, {
233
+ P && /* @__PURE__ */ F(W, {
229
234
  engagementId: n.id,
230
- onClose: () => R(!1),
235
+ onClose: () => z(!1),
231
236
  onDone: () => {
232
- m(), W({
237
+ m(), G({
233
238
  tone: "success",
234
239
  text: "Milestone added."
235
240
  });
@@ -237,9 +242,9 @@ var L = {
237
242
  })
238
243
  ]
239
244
  });
240
- }, B = ({ id: e, engagementId: t, status: n, onDone: r }) => {
245
+ }, V = ({ id: e, engagementId: t, status: n, onDone: r }) => {
241
246
  let i = a();
242
- return /* @__PURE__ */ P(h, {
247
+ return /* @__PURE__ */ F(h, {
243
248
  value: n,
244
249
  disabled: i.isPending,
245
250
  onChange: (n) => i.mutate({
@@ -248,14 +253,14 @@ var L = {
248
253
  engagementId: t
249
254
  }, { onSuccess: r }),
250
255
  className: "w-40",
251
- children: R.map((e) => /* @__PURE__ */ P("option", {
256
+ children: z.map((e) => /* @__PURE__ */ F("option", {
252
257
  value: e,
253
258
  children: w(e)
254
259
  }, e))
255
260
  });
256
- }, V = ({ id: t, engagementId: n, onDone: r }) => {
261
+ }, H = ({ id: t, engagementId: n, onDone: r }) => {
257
262
  let i = e();
258
- return /* @__PURE__ */ P(I, {
263
+ return /* @__PURE__ */ F(L, {
259
264
  variant: "ghost",
260
265
  size: "sm",
261
266
  "aria-label": "Complete milestone",
@@ -264,28 +269,28 @@ var L = {
264
269
  id: t,
265
270
  engagementId: n
266
271
  }, { onSuccess: r }),
267
- children: /* @__PURE__ */ P(D, { className: "h-4 w-4" })
272
+ children: /* @__PURE__ */ F(O, { className: "h-4 w-4" })
268
273
  });
269
- }, H = ({ engagementId: e, onClose: n, onDone: r }) => {
270
- let i = t(), [a, o] = T({
274
+ }, U = ({ engagementId: e, onClose: n, onDone: r }) => {
275
+ let i = t(), [a, o] = E({
271
276
  name: "",
272
277
  description: "",
273
278
  dueDate: "",
274
279
  acceptanceCriteria: ""
275
- }), [s, l] = T(null), u = (e) => (t) => o((n) => ({
280
+ }), [s, l] = E(null), u = (e) => (t) => o((n) => ({
276
281
  ...n,
277
282
  [e]: t.target.value
278
283
  }));
279
- return /* @__PURE__ */ P(m, {
284
+ return /* @__PURE__ */ F(m, {
280
285
  open: !0,
281
286
  onClose: n,
282
287
  title: "Add deliverable",
283
- footer: /* @__PURE__ */ F(N, { children: [/* @__PURE__ */ P(I, {
288
+ footer: /* @__PURE__ */ I(P, { children: [/* @__PURE__ */ F(L, {
284
289
  variant: "outline",
285
290
  onClick: n,
286
291
  disabled: i.isPending,
287
292
  children: "Cancel"
288
- }), /* @__PURE__ */ P(I, {
293
+ }), /* @__PURE__ */ F(L, {
289
294
  onClick: () => {
290
295
  if (l(null), !a.name.trim()) return l("Name is required.");
291
296
  i.mutate({
@@ -304,40 +309,40 @@ var L = {
304
309
  disabled: i.isPending,
305
310
  children: i.isPending ? "Adding…" : "Add"
306
311
  })] }),
307
- children: /* @__PURE__ */ F("div", {
312
+ children: /* @__PURE__ */ I("div", {
308
313
  className: "space-y-3",
309
314
  children: [
310
- s && /* @__PURE__ */ P(c, {
315
+ s && /* @__PURE__ */ F(c, {
311
316
  tone: "danger",
312
317
  children: s
313
318
  }),
314
- /* @__PURE__ */ P(d, {
319
+ /* @__PURE__ */ F(d, {
315
320
  label: "Name",
316
321
  required: !0,
317
- children: /* @__PURE__ */ P(b, {
322
+ children: /* @__PURE__ */ F(b, {
318
323
  value: a.name,
319
324
  onChange: u("name"),
320
325
  placeholder: "Draft audit report"
321
326
  })
322
327
  }),
323
- /* @__PURE__ */ P(d, {
328
+ /* @__PURE__ */ F(d, {
324
329
  label: "Due date",
325
- children: /* @__PURE__ */ P(b, {
330
+ children: /* @__PURE__ */ F(b, {
326
331
  type: "date",
327
332
  value: a.dueDate,
328
333
  onChange: u("dueDate")
329
334
  })
330
335
  }),
331
- /* @__PURE__ */ P(d, {
336
+ /* @__PURE__ */ F(d, {
332
337
  label: "Acceptance criteria",
333
- children: /* @__PURE__ */ P(y, {
338
+ children: /* @__PURE__ */ F(y, {
334
339
  value: a.acceptanceCriteria,
335
340
  onChange: u("acceptanceCriteria")
336
341
  })
337
342
  }),
338
- /* @__PURE__ */ P(d, {
343
+ /* @__PURE__ */ F(d, {
339
344
  label: "Description",
340
- children: /* @__PURE__ */ P(y, {
345
+ children: /* @__PURE__ */ F(y, {
341
346
  value: a.description,
342
347
  onChange: u("description")
343
348
  })
@@ -345,23 +350,23 @@ var L = {
345
350
  ]
346
351
  })
347
352
  });
348
- }, U = ({ engagementId: e, onClose: t, onDone: r }) => {
349
- let i = n(), [a, o] = T({
353
+ }, W = ({ engagementId: e, onClose: t, onDone: r }) => {
354
+ let i = n(), [a, o] = E({
350
355
  name: "",
351
356
  dueDate: "",
352
357
  triggerAmount: "",
353
358
  triggersInvoice: !1
354
- }), [s, l] = T(null);
355
- return /* @__PURE__ */ P(m, {
359
+ }), [s, l] = E(null);
360
+ return /* @__PURE__ */ F(m, {
356
361
  open: !0,
357
362
  onClose: t,
358
363
  title: "Add milestone",
359
- footer: /* @__PURE__ */ F(N, { children: [/* @__PURE__ */ P(I, {
364
+ footer: /* @__PURE__ */ I(P, { children: [/* @__PURE__ */ F(L, {
360
365
  variant: "outline",
361
366
  onClick: t,
362
367
  disabled: i.isPending,
363
368
  children: "Cancel"
364
- }), /* @__PURE__ */ P(I, {
369
+ }), /* @__PURE__ */ F(L, {
365
370
  onClick: () => {
366
371
  if (l(null), !a.name.trim()) return l("Name is required.");
367
372
  i.mutate({
@@ -380,17 +385,17 @@ var L = {
380
385
  disabled: i.isPending,
381
386
  children: i.isPending ? "Adding…" : "Add"
382
387
  })] }),
383
- children: /* @__PURE__ */ F("div", {
388
+ children: /* @__PURE__ */ I("div", {
384
389
  className: "space-y-3",
385
390
  children: [
386
- s && /* @__PURE__ */ P(c, {
391
+ s && /* @__PURE__ */ F(c, {
387
392
  tone: "danger",
388
393
  children: s
389
394
  }),
390
- /* @__PURE__ */ P(d, {
395
+ /* @__PURE__ */ F(d, {
391
396
  label: "Name",
392
397
  required: !0,
393
- children: /* @__PURE__ */ P(b, {
398
+ children: /* @__PURE__ */ F(b, {
394
399
  value: a.name,
395
400
  onChange: (e) => o((t) => ({
396
401
  ...t,
@@ -399,9 +404,9 @@ var L = {
399
404
  placeholder: "Phase 1 sign-off"
400
405
  })
401
406
  }),
402
- /* @__PURE__ */ P(d, {
407
+ /* @__PURE__ */ F(d, {
403
408
  label: "Due date",
404
- children: /* @__PURE__ */ P(b, {
409
+ children: /* @__PURE__ */ F(b, {
405
410
  type: "date",
406
411
  value: a.dueDate,
407
412
  onChange: (e) => o((t) => ({
@@ -410,10 +415,10 @@ var L = {
410
415
  }))
411
416
  })
412
417
  }),
413
- /* @__PURE__ */ P(d, {
418
+ /* @__PURE__ */ F(d, {
414
419
  label: "Trigger amount",
415
420
  hint: "Amount billed when this milestone completes (if it triggers an invoice).",
416
- children: /* @__PURE__ */ P(b, {
421
+ children: /* @__PURE__ */ F(b, {
417
422
  type: "number",
418
423
  min: "0",
419
424
  value: a.triggerAmount,
@@ -423,9 +428,9 @@ var L = {
423
428
  }))
424
429
  })
425
430
  }),
426
- /* @__PURE__ */ F("label", {
431
+ /* @__PURE__ */ I("label", {
427
432
  className: "flex items-center gap-2 text-sm text-text-secondary",
428
- children: [/* @__PURE__ */ P("input", {
433
+ children: [/* @__PURE__ */ F("input", {
429
434
  type: "checkbox",
430
435
  checked: a.triggersInvoice,
431
436
  onChange: (e) => o((t) => ({
@@ -439,6 +444,6 @@ var L = {
439
444
  });
440
445
  };
441
446
  //#endregion
442
- export { z as EngagementDetailPage };
447
+ export { B as EngagementDetailPage };
443
448
 
444
449
  //# sourceMappingURL=EngagementDetailPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EngagementDetailPage.js","names":[],"sources":["../../../src/proserviceworld/pages/EngagementDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, Plus, CheckCircle2, FileText, Flag, Clock } from 'lucide-react';\nimport {\n useEngagement,\n useTransitionEngagement,\n useCreateDeliverable,\n useTransitionDeliverable,\n useCreateMilestone,\n useCompleteMilestone,\n useTimeEntries,\n type PswEngagementStatus,\n type PswDeliverableStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n MetricCard,\n fmtDate,\n fmtMoney,\n fmtMinutesAsHours,\n humanize,\n} from '../components/ui';\n\n// Mirrors the svc ENGAGEMENT_TRANSITIONS state machine so we only offer valid moves.\nconst NEXT: Record<PswEngagementStatus, PswEngagementStatus[]> = {\n PROPOSAL: ['ACCEPTED', 'CANCELLED'],\n ACCEPTED: ['IN_PROGRESS', 'CANCELLED'],\n IN_PROGRESS: ['REVIEW', 'CANCELLED'],\n REVIEW: ['DELIVERED', 'IN_PROGRESS', 'CANCELLED'],\n DELIVERED: ['INVOICED', 'CANCELLED'],\n INVOICED: ['CLOSED'],\n CLOSED: [],\n CANCELLED: [],\n};\nconst DELIVERABLE_STATUSES: PswDeliverableStatus[] = [\n 'DRAFT',\n 'INTERNAL_REVIEW',\n 'CLIENT_REVIEW',\n 'ACCEPTED',\n 'REJECTED',\n];\n\nexport const EngagementDetailPage: FC = () => {\n const { id = '' } = useParams();\n const nav = useNav();\n const { data: e, isLoading, error, refetch } = useEngagement(id);\n const time = useTimeEntries({ engagementId: id });\n const transition = useTransitionEngagement();\n const [addDeliverable, setAddDeliverable] = useState(false);\n const [addMilestone, setAddMilestone] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n\n if (isLoading) return <LoadingBlock />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!e) return <Empty title=\"Engagement not found\" />;\n\n const nextStates = NEXT[e.status] ?? [];\n const loggedMinutes = (time.data?.items ?? []).reduce((sum, t) => sum + t.minutes, 0);\n\n return (\n <div className=\"space-y-6\">\n <button\n type=\"button\"\n onClick={() => nav('engagements')}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to engagements\n </button>\n\n <PageHeader\n title={e.name}\n subtitle={\n e.client ? `${e.client.name} · ${humanize(e.feeArrangement)}` : humanize(e.feeArrangement)\n }\n actions={<StatusPill status={e.status} />}\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <MetricCard\n label=\"Fee\"\n value={fmtMoney(e.feeAmount, e.currency)}\n hint={humanize(e.feeArrangement)}\n />\n <MetricCard label=\"Deliverables\" value={e.deliverables.length} />\n <MetricCard label=\"Milestones\" value={e.milestones.length} />\n <MetricCard\n label=\"Time logged\"\n value={fmtMinutesAsHours(loggedMinutes)}\n hint={`${time.data?.totalCount ?? 0} entries`}\n />\n </div>\n\n {nextStates.length > 0 && (\n <SectionCard\n title=\"Advance lifecycle\"\n description={`Current status: ${humanize(e.status)}`}\n >\n <div className=\"flex flex-wrap gap-2\">\n {nextStates.map((s) => (\n <Button\n key={s}\n size=\"sm\"\n variant={s === 'CANCELLED' ? 'outline' : 'default'}\n disabled={transition.isPending}\n onClick={() =>\n transition.mutate(\n { id: e.id, status: s },\n {\n onSuccess: () =>\n setBanner({ tone: 'success', text: `Moved to ${humanize(s)}.` }),\n onError: (err) =>\n setBanner({\n tone: 'danger',\n text: err instanceof Error ? err.message : 'Transition failed.',\n }),\n }\n )\n }\n >\n {humanize(s)}\n </Button>\n ))}\n </div>\n </SectionCard>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <SectionCard\n title=\"Deliverables\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddDeliverable(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.deliverables.length === 0 ? (\n <Empty\n title=\"No deliverables\"\n description=\"Track the tangible outputs of this engagement.\"\n icon={<FileText className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2\">\n {e.deliverables.map((d) => (\n <li key={d.id} className=\"rounded-lg border border-border-seam p-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <div className=\"text-sm font-medium text-text-primary\">\n {d.name} <span className=\"text-xs text-text-tertiary\">v{d.version}</span>\n </div>\n {d.dueDate && (\n <div className=\"text-xs text-text-tertiary\">Due {fmtDate(d.dueDate)}</div>\n )}\n </div>\n <DeliverableStatusSelect\n id={d.id}\n engagementId={e.id}\n status={d.status}\n onDone={() => void refetch()}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard\n title=\"Milestones\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddMilestone(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.milestones.length === 0 ? (\n <Empty\n title=\"No milestones\"\n description=\"Add checkpoints — some can trigger billing.\"\n icon={<Flag className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2\">\n {e.milestones.map((m) => (\n <li\n key={m.id}\n className=\"flex items-center justify-between rounded-lg border border-border-seam p-3\"\n >\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{m.name}</div>\n <div className=\"text-xs text-text-tertiary\">\n {m.triggersInvoice ? `Bills ${fmtMoney(m.triggerAmount, e.currency)} · ` : ''}\n Due {fmtDate(m.dueDate)}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusPill status={m.status} />\n {m.status !== 'COMPLETED' && m.status !== 'CANCELLED' && (\n <CompleteMilestoneButton\n id={m.id}\n engagementId={e.id}\n onDone={() => void refetch()}\n />\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n </div>\n\n <SectionCard\n title=\"Time logged\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('time')}>\n <Clock className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n >\n {time.isLoading ? (\n <LoadingBlock label=\"Loading…\" />\n ) : !time.data || time.data.items.length === 0 ? (\n <Empty\n title=\"No time logged\"\n description=\"Time logged against this engagement will appear here.\"\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {time.data.items.slice(0, 8).map((t) => (\n <li key={t.id} className=\"flex items-center justify-between py-2 text-sm\">\n <span className=\"text-text-secondary\">\n {fmtDate(t.date)} · {t.narrative || 'Time entry'}\n </span>\n <span className=\"flex items-center gap-2\">\n <span className=\"text-text-primary\">{fmtMinutesAsHours(t.minutes)}</span>\n <StatusPill status={t.status} />\n </span>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n {addDeliverable && (\n <DeliverableModal\n engagementId={e.id}\n onClose={() => setAddDeliverable(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Deliverable added.' });\n }}\n />\n )}\n {addMilestone && (\n <MilestoneModal\n engagementId={e.id}\n onClose={() => setAddMilestone(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Milestone added.' });\n }}\n />\n )}\n </div>\n );\n};\n\nconst DeliverableStatusSelect: FC<{\n id: string;\n engagementId: string;\n status: PswDeliverableStatus;\n onDone: () => void;\n}> = ({ id, engagementId, status, onDone }) => {\n const transition = useTransitionDeliverable();\n return (\n <NativeSelect\n value={status}\n disabled={transition.isPending}\n onChange={(ev) =>\n transition.mutate(\n { id, status: ev.target.value as PswDeliverableStatus, engagementId },\n { onSuccess: onDone }\n )\n }\n className=\"w-40\"\n >\n {DELIVERABLE_STATUSES.map((s) => (\n <option key={s} value={s}>\n {humanize(s)}\n </option>\n ))}\n </NativeSelect>\n );\n};\n\nconst CompleteMilestoneButton: FC<{ id: string; engagementId: string; onDone: () => void }> = ({\n id,\n engagementId,\n onDone,\n}) => {\n const complete = useCompleteMilestone();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Complete milestone\"\n disabled={complete.isPending}\n onClick={() => complete.mutate({ id, engagementId }, { onSuccess: onDone })}\n >\n <CheckCircle2 className=\"h-4 w-4\" />\n </Button>\n );\n};\n\nconst DeliverableModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateDeliverable();\n const [form, setForm] = useState({\n name: '',\n description: '',\n dueDate: '',\n acceptanceCriteria: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n description: form.description.trim() || null,\n acceptanceCriteria: form.acceptanceCriteria.trim() || null,\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add deliverable\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"Draft audit report\" />\n </Field>\n <Field label=\"Due date\">\n <TextInput type=\"date\" value={form.dueDate} onChange={set('dueDate')} />\n </Field>\n <Field label=\"Acceptance criteria\">\n <TextArea value={form.acceptanceCriteria} onChange={set('acceptanceCriteria')} />\n </Field>\n <Field label=\"Description\">\n <TextArea value={form.description} onChange={set('description')} />\n </Field>\n </div>\n </Modal>\n );\n};\n\nconst MilestoneModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateMilestone();\n const [form, setForm] = useState({\n name: '',\n dueDate: '',\n triggerAmount: '',\n triggersInvoice: false,\n });\n const [err, setErr] = useState<string | null>(null);\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n triggerAmount: form.triggerAmount ? Number(form.triggerAmount) : 0,\n triggersInvoice: form.triggersInvoice,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add milestone\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Phase 1 sign-off\"\n />\n </Field>\n <Field label=\"Due date\">\n <TextInput\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm((f) => ({ ...f, dueDate: e.target.value }))}\n />\n </Field>\n <Field\n label=\"Trigger amount\"\n hint=\"Amount billed when this milestone completes (if it triggers an invoice).\"\n >\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.triggerAmount}\n onChange={(e) => setForm((f) => ({ ...f, triggerAmount: e.target.value }))}\n />\n </Field>\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.triggersInvoice}\n onChange={(e) => setForm((f) => ({ ...f, triggersInvoice: e.target.checked }))}\n />\n Completing this milestone triggers an invoice\n </label>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"EngagementDetailPage.js","names":[],"sources":["../../../src/proserviceworld/pages/EngagementDetailPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, Plus, CheckCircle2, FileText, Flag, Clock } from 'lucide-react';\nimport {\n useEngagement,\n useTransitionEngagement,\n useCreateDeliverable,\n useTransitionDeliverable,\n useCreateMilestone,\n useCompleteMilestone,\n useTimeEntries,\n type PswEngagementStatus,\n type PswDeliverableStatus,\n} from '../hooks/useProServiceWorldApi';\nimport { useNav } from '../utils/nav';\nimport { DiscussionThread } from '../components/DiscussionThread';\nimport {\n PageHeader,\n SectionCard,\n LoadingBlock,\n ErrorState,\n Empty,\n StatusPill,\n Modal,\n Field,\n TextInput,\n TextArea,\n NativeSelect,\n Banner,\n MetricCard,\n fmtDate,\n fmtMoney,\n fmtMinutesAsHours,\n humanize,\n} from '../components/ui';\n\n// Mirrors the svc ENGAGEMENT_TRANSITIONS state machine so we only offer valid moves.\nconst NEXT: Record<PswEngagementStatus, PswEngagementStatus[]> = {\n PROPOSAL: ['ACCEPTED', 'CANCELLED'],\n ACCEPTED: ['IN_PROGRESS', 'CANCELLED'],\n IN_PROGRESS: ['REVIEW', 'CANCELLED'],\n REVIEW: ['DELIVERED', 'IN_PROGRESS', 'CANCELLED'],\n DELIVERED: ['INVOICED', 'CANCELLED'],\n INVOICED: ['CLOSED'],\n CLOSED: [],\n CANCELLED: [],\n};\nconst DELIVERABLE_STATUSES: PswDeliverableStatus[] = [\n 'DRAFT',\n 'INTERNAL_REVIEW',\n 'CLIENT_REVIEW',\n 'ACCEPTED',\n 'REJECTED',\n];\n\nexport const EngagementDetailPage: FC = () => {\n const { id = '' } = useParams();\n const nav = useNav();\n const { data: e, isLoading, error, refetch } = useEngagement(id);\n const time = useTimeEntries({ engagementId: id });\n const transition = useTransitionEngagement();\n const [addDeliverable, setAddDeliverable] = useState(false);\n const [addMilestone, setAddMilestone] = useState(false);\n const [banner, setBanner] = useState<{ tone: 'success' | 'danger'; text: string } | null>(null);\n\n if (isLoading) return <LoadingBlock />;\n if (error) return <ErrorState error={error} onRetry={() => void refetch()} />;\n if (!e) return <Empty title=\"Engagement not found\" />;\n\n const nextStates = NEXT[e.status] ?? [];\n const loggedMinutes = (time.data?.items ?? []).reduce((sum, t) => sum + t.minutes, 0);\n\n return (\n <div className=\"space-y-6\">\n <button\n type=\"button\"\n onClick={() => nav('engagements')}\n className=\"flex items-center gap-1.5 text-sm text-text-secondary hover:text-accent\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to engagements\n </button>\n\n <PageHeader\n title={e.name}\n subtitle={\n e.client ? `${e.client.name} · ${humanize(e.feeArrangement)}` : humanize(e.feeArrangement)\n }\n actions={<StatusPill status={e.status} />}\n />\n\n {banner && (\n <Banner tone={banner.tone} onClose={() => setBanner(null)}>\n {banner.text}\n </Banner>\n )}\n\n <div className=\"grid grid-cols-2 gap-3 lg:grid-cols-4\">\n <MetricCard\n label=\"Fee\"\n value={fmtMoney(e.feeAmount, e.currency)}\n hint={humanize(e.feeArrangement)}\n />\n <MetricCard label=\"Deliverables\" value={e.deliverables.length} />\n <MetricCard label=\"Milestones\" value={e.milestones.length} />\n <MetricCard\n label=\"Time logged\"\n value={fmtMinutesAsHours(loggedMinutes)}\n hint={`${time.data?.totalCount ?? 0} entries`}\n />\n </div>\n\n {nextStates.length > 0 && (\n <SectionCard\n title=\"Advance lifecycle\"\n description={`Current status: ${humanize(e.status)}`}\n >\n <div className=\"flex flex-wrap gap-2\">\n {nextStates.map((s) => (\n <Button\n key={s}\n size=\"sm\"\n variant={s === 'CANCELLED' ? 'outline' : 'default'}\n disabled={transition.isPending}\n onClick={() =>\n transition.mutate(\n { id: e.id, status: s },\n {\n onSuccess: () =>\n setBanner({ tone: 'success', text: `Moved to ${humanize(s)}.` }),\n onError: (err) =>\n setBanner({\n tone: 'danger',\n text: err instanceof Error ? err.message : 'Transition failed.',\n }),\n }\n )\n }\n >\n {humanize(s)}\n </Button>\n ))}\n </div>\n </SectionCard>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n <SectionCard\n title=\"Deliverables\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddDeliverable(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.deliverables.length === 0 ? (\n <Empty\n title=\"No deliverables\"\n description=\"Track the tangible outputs of this engagement.\"\n icon={<FileText className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2\">\n {e.deliverables.map((d) => (\n <li key={d.id} className=\"rounded-lg border border-border-seam p-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <div className=\"text-sm font-medium text-text-primary\">\n {d.name} <span className=\"text-xs text-text-tertiary\">v{d.version}</span>\n </div>\n {d.dueDate && (\n <div className=\"text-xs text-text-tertiary\">Due {fmtDate(d.dueDate)}</div>\n )}\n </div>\n <DeliverableStatusSelect\n id={d.id}\n engagementId={e.id}\n status={d.status}\n onDone={() => void refetch()}\n />\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <SectionCard\n title=\"Milestones\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => setAddMilestone(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" />\n Add\n </Button>\n }\n >\n {e.milestones.length === 0 ? (\n <Empty\n title=\"No milestones\"\n description=\"Add checkpoints — some can trigger billing.\"\n icon={<Flag className=\"h-7 w-7\" />}\n />\n ) : (\n <ul className=\"space-y-2\">\n {e.milestones.map((m) => (\n <li\n key={m.id}\n className=\"flex items-center justify-between rounded-lg border border-border-seam p-3\"\n >\n <div>\n <div className=\"text-sm font-medium text-text-primary\">{m.name}</div>\n <div className=\"text-xs text-text-tertiary\">\n {m.triggersInvoice ? `Bills ${fmtMoney(m.triggerAmount, e.currency)} · ` : ''}\n Due {fmtDate(m.dueDate)}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <StatusPill status={m.status} />\n {m.status !== 'COMPLETED' && m.status !== 'CANCELLED' && (\n <CompleteMilestoneButton\n id={m.id}\n engagementId={e.id}\n onDone={() => void refetch()}\n />\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n </div>\n\n <SectionCard\n title=\"Time logged\"\n action={\n <Button size=\"sm\" variant=\"outline\" onClick={() => nav('time')}>\n <Clock className=\"mr-1.5 h-4 w-4\" />\n Log time\n </Button>\n }\n >\n {time.isLoading ? (\n <LoadingBlock label=\"Loading…\" />\n ) : !time.data || time.data.items.length === 0 ? (\n <Empty\n title=\"No time logged\"\n description=\"Time logged against this engagement will appear here.\"\n />\n ) : (\n <ul className=\"divide-y divide-border-seam\">\n {time.data.items.slice(0, 8).map((t) => (\n <li key={t.id} className=\"flex items-center justify-between py-2 text-sm\">\n <span className=\"text-text-secondary\">\n {fmtDate(t.date)} · {t.narrative || 'Time entry'}\n </span>\n <span className=\"flex items-center gap-2\">\n <span className=\"text-text-primary\">{fmtMinutesAsHours(t.minutes)}</span>\n <StatusPill status={t.status} />\n </span>\n </li>\n ))}\n </ul>\n )}\n </SectionCard>\n\n <DiscussionThread entityType=\"proserviceworld-engagement\" entityId={e.id} />\n\n {addDeliverable && (\n <DeliverableModal\n engagementId={e.id}\n onClose={() => setAddDeliverable(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Deliverable added.' });\n }}\n />\n )}\n {addMilestone && (\n <MilestoneModal\n engagementId={e.id}\n onClose={() => setAddMilestone(false)}\n onDone={() => {\n void refetch();\n setBanner({ tone: 'success', text: 'Milestone added.' });\n }}\n />\n )}\n </div>\n );\n};\n\nconst DeliverableStatusSelect: FC<{\n id: string;\n engagementId: string;\n status: PswDeliverableStatus;\n onDone: () => void;\n}> = ({ id, engagementId, status, onDone }) => {\n const transition = useTransitionDeliverable();\n return (\n <NativeSelect\n value={status}\n disabled={transition.isPending}\n onChange={(ev) =>\n transition.mutate(\n { id, status: ev.target.value as PswDeliverableStatus, engagementId },\n { onSuccess: onDone }\n )\n }\n className=\"w-40\"\n >\n {DELIVERABLE_STATUSES.map((s) => (\n <option key={s} value={s}>\n {humanize(s)}\n </option>\n ))}\n </NativeSelect>\n );\n};\n\nconst CompleteMilestoneButton: FC<{ id: string; engagementId: string; onDone: () => void }> = ({\n id,\n engagementId,\n onDone,\n}) => {\n const complete = useCompleteMilestone();\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label=\"Complete milestone\"\n disabled={complete.isPending}\n onClick={() => complete.mutate({ id, engagementId }, { onSuccess: onDone })}\n >\n <CheckCircle2 className=\"h-4 w-4\" />\n </Button>\n );\n};\n\nconst DeliverableModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateDeliverable();\n const [form, setForm] = useState({\n name: '',\n description: '',\n dueDate: '',\n acceptanceCriteria: '',\n });\n const [err, setErr] = useState<string | null>(null);\n const set = (k: keyof typeof form) => (e: { target: { value: string } }) =>\n setForm((f) => ({ ...f, [k]: e.target.value }));\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n description: form.description.trim() || null,\n acceptanceCriteria: form.acceptanceCriteria.trim() || null,\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add deliverable\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput value={form.name} onChange={set('name')} placeholder=\"Draft audit report\" />\n </Field>\n <Field label=\"Due date\">\n <TextInput type=\"date\" value={form.dueDate} onChange={set('dueDate')} />\n </Field>\n <Field label=\"Acceptance criteria\">\n <TextArea value={form.acceptanceCriteria} onChange={set('acceptanceCriteria')} />\n </Field>\n <Field label=\"Description\">\n <TextArea value={form.description} onChange={set('description')} />\n </Field>\n </div>\n </Modal>\n );\n};\n\nconst MilestoneModal: FC<{ engagementId: string; onClose: () => void; onDone: () => void }> = ({\n engagementId,\n onClose,\n onDone,\n}) => {\n const create = useCreateMilestone();\n const [form, setForm] = useState({\n name: '',\n dueDate: '',\n triggerAmount: '',\n triggersInvoice: false,\n });\n const [err, setErr] = useState<string | null>(null);\n const submit = () => {\n setErr(null);\n if (!form.name.trim()) return setErr('Name is required.');\n create.mutate(\n {\n engagementId,\n name: form.name.trim(),\n dueDate: form.dueDate ? new Date(form.dueDate).toISOString() : null,\n triggerAmount: form.triggerAmount ? Number(form.triggerAmount) : 0,\n triggersInvoice: form.triggersInvoice,\n },\n {\n onSuccess: () => {\n onDone();\n onClose();\n },\n onError: (e) => setErr(e instanceof Error ? e.message : 'Failed.'),\n }\n );\n };\n return (\n <Modal\n open\n onClose={onClose}\n title=\"Add milestone\"\n footer={\n <>\n <Button variant=\"outline\" onClick={onClose} disabled={create.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={create.isPending}>\n {create.isPending ? 'Adding…' : 'Add'}\n </Button>\n </>\n }\n >\n <div className=\"space-y-3\">\n {err && <Banner tone=\"danger\">{err}</Banner>}\n <Field label=\"Name\" required>\n <TextInput\n value={form.name}\n onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}\n placeholder=\"Phase 1 sign-off\"\n />\n </Field>\n <Field label=\"Due date\">\n <TextInput\n type=\"date\"\n value={form.dueDate}\n onChange={(e) => setForm((f) => ({ ...f, dueDate: e.target.value }))}\n />\n </Field>\n <Field\n label=\"Trigger amount\"\n hint=\"Amount billed when this milestone completes (if it triggers an invoice).\"\n >\n <TextInput\n type=\"number\"\n min=\"0\"\n value={form.triggerAmount}\n onChange={(e) => setForm((f) => ({ ...f, triggerAmount: e.target.value }))}\n />\n </Field>\n <label className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <input\n type=\"checkbox\"\n checked={form.triggersInvoice}\n onChange={(e) => setForm((f) => ({ ...f, triggersInvoice: e.target.checked }))}\n />\n Completing this milestone triggers an invoice\n </label>\n </div>\n </Modal>\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-proserviceworld",
3
- "version": "2026.706.1",
3
+ "version": "2026.706.2",
4
4
  "description": "Burdenoff ProServiceWorld Microfrontend",
5
5
  "type": "module",
6
6
  "files": [