medusajs-documents-plugin 1.1.14 → 1.1.16

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.
@@ -1,8 +1,197 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { defineRouteConfig } from "@medusajs/admin-sdk";
3
- import { Container, Heading, Text, Button, Input, Select, Tooltip, IconButton, Table, Badge, Label, Switch, Textarea, FocusModal } from "@medusajs/ui";
4
- import { useState, useEffect } from "react";
5
- import { DocumentText, Plus, Sparkles, ArrowPath, ArrowDownTray, Envelope, Trash } from "@medusajs/icons";
3
+ import { Container, Heading, Text, Button, Input, Select, Tooltip, IconButton, Switch, Label, Table, Badge, DropdownMenu, Textarea, FocusModal } from "@medusajs/ui";
4
+ import { useState, useEffect, useMemo, Fragment } from "react";
5
+ import { DocumentText, Plus, Sparkles, ArrowPath, ChevronDown, ChevronRight, ArrowDownTray, Envelope, Trash } from "@medusajs/icons";
6
+ function extractBaseReference(displayId) {
7
+ if (!displayId) return "";
8
+ const cleaned = String(displayId).trim();
9
+ const withoutPrefix = cleaned.replace(/^(JOB-|RT-|EST-|RCPT-|INV-|PKG-|DOC-)+/i, "");
10
+ return withoutPrefix.trim();
11
+ }
12
+ function getDocTypePriority(type) {
13
+ switch (type == null ? void 0 : type.toLowerCase()) {
14
+ case "invoice":
15
+ return 100;
16
+ case "estimate":
17
+ return 80;
18
+ case "job_card":
19
+ return 70;
20
+ case "receipt":
21
+ return 60;
22
+ case "packing_slip":
23
+ return 50;
24
+ case "custom":
25
+ return 40;
26
+ default:
27
+ return 10;
28
+ }
29
+ }
30
+ function getDocLifecycleOrder(type) {
31
+ switch (type == null ? void 0 : type.toLowerCase()) {
32
+ case "job_card":
33
+ return 1;
34
+ case "estimate":
35
+ return 2;
36
+ case "invoice":
37
+ return 3;
38
+ case "receipt":
39
+ return 4;
40
+ case "packing_slip":
41
+ return 5;
42
+ case "custom":
43
+ return 6;
44
+ default:
45
+ return 10;
46
+ }
47
+ }
48
+ function getDocTypeLabel(type) {
49
+ switch (type == null ? void 0 : type.toLowerCase()) {
50
+ case "invoice":
51
+ return "Invoice";
52
+ case "estimate":
53
+ return "Quote";
54
+ case "job_card":
55
+ return "Job Card";
56
+ case "receipt":
57
+ return "Receipt";
58
+ case "packing_slip":
59
+ return "Packing Slip";
60
+ case "custom":
61
+ return "Certificate";
62
+ default:
63
+ return type || "Document";
64
+ }
65
+ }
66
+ function groupDocuments(documents) {
67
+ var _a, _b;
68
+ if (!documents || documents.length === 0) return [];
69
+ const parent = /* @__PURE__ */ new Map();
70
+ function find(x) {
71
+ if (!parent.has(x)) parent.set(x, x);
72
+ if (parent.get(x) !== x) {
73
+ parent.set(x, find(parent.get(x)));
74
+ }
75
+ return parent.get(x);
76
+ }
77
+ function union(x, y) {
78
+ const rootX = find(x);
79
+ const rootY = find(y);
80
+ if (rootX !== rootY) {
81
+ parent.set(rootY, rootX);
82
+ }
83
+ }
84
+ const docKeys = /* @__PURE__ */ new Map();
85
+ for (const doc of documents) {
86
+ const keys = [`doc:${doc.id}`];
87
+ if (doc.repair_ticket_id) {
88
+ keys.push(`rep:${doc.repair_ticket_id}`);
89
+ }
90
+ if (doc.order_id) {
91
+ keys.push(`ord:${doc.order_id}`);
92
+ }
93
+ const baseRef = extractBaseReference(doc.display_id);
94
+ if (baseRef && baseRef.length >= 3) {
95
+ if (baseRef.length >= 6 || /[A-Za-z]/.test(baseRef)) {
96
+ keys.push(`ref:${baseRef.toLowerCase()}`);
97
+ } else {
98
+ const custKey = (doc.customer_email || doc.customer_phone || doc.customer_name || "").toLowerCase().trim();
99
+ keys.push(`ref:${baseRef.toLowerCase()}${custKey ? `:${custKey}` : ""}`);
100
+ }
101
+ }
102
+ docKeys.set(doc.id, keys);
103
+ const primaryKey = keys[0];
104
+ for (let i = 1; i < keys.length; i++) {
105
+ union(primaryKey, keys[i]);
106
+ }
107
+ }
108
+ const keyToDocs = /* @__PURE__ */ new Map();
109
+ for (const [docId, keys] of docKeys.entries()) {
110
+ for (const k of keys) {
111
+ if (!keyToDocs.has(k)) keyToDocs.set(k, []);
112
+ keyToDocs.get(k).push(docId);
113
+ }
114
+ }
115
+ for (const [, docIds] of keyToDocs.entries()) {
116
+ if (docIds.length > 1) {
117
+ const firstRoot = `doc:${docIds[0]}`;
118
+ for (let i = 1; i < docIds.length; i++) {
119
+ union(firstRoot, `doc:${docIds[i]}`);
120
+ }
121
+ }
122
+ }
123
+ const clusters = /* @__PURE__ */ new Map();
124
+ for (const doc of documents) {
125
+ const root = find(`doc:${doc.id}`);
126
+ if (!clusters.has(root)) clusters.set(root, []);
127
+ clusters.get(root).push(doc);
128
+ }
129
+ const groups = [];
130
+ for (const [clusterId, clusterDocs] of clusters.entries()) {
131
+ clusterDocs.sort((a, b) => {
132
+ const orderA = getDocLifecycleOrder(a.type);
133
+ const orderB = getDocLifecycleOrder(b.type);
134
+ if (orderA !== orderB) return orderA - orderB;
135
+ return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
136
+ });
137
+ let primaryDoc = clusterDocs[0];
138
+ let maxPriority = -1;
139
+ for (const d of clusterDocs) {
140
+ const p = getDocTypePriority(d.type);
141
+ if (p > maxPriority) {
142
+ maxPriority = p;
143
+ primaryDoc = d;
144
+ }
145
+ }
146
+ const hasInvoice = clusterDocs.some((d) => {
147
+ var _a2;
148
+ return ((_a2 = d.type) == null ? void 0 : _a2.toLowerCase()) === "invoice";
149
+ });
150
+ const invoiceDoc = clusterDocs.find((d) => {
151
+ var _a2;
152
+ return ((_a2 = d.type) == null ? void 0 : _a2.toLowerCase()) === "invoice";
153
+ });
154
+ const estimateDoc = clusterDocs.find((d) => {
155
+ var _a2;
156
+ return ((_a2 = d.type) == null ? void 0 : _a2.toLowerCase()) === "estimate";
157
+ });
158
+ const receiptDoc = clusterDocs.find((d) => {
159
+ var _a2;
160
+ return ((_a2 = d.type) == null ? void 0 : _a2.toLowerCase()) === "receipt";
161
+ });
162
+ const totalAmount = Number(
163
+ invoiceDoc && invoiceDoc.total_amount != null ? invoiceDoc.total_amount : estimateDoc && estimateDoc.total_amount != null ? estimateDoc.total_amount : receiptDoc && receiptDoc.total_amount != null ? receiptDoc.total_amount : primaryDoc.total_amount || 0
164
+ );
165
+ const currencyCode = primaryDoc.currency_code || (invoiceDoc == null ? void 0 : invoiceDoc.currency_code) || "kes";
166
+ const uniqueTypes = Array.from(new Set(clusterDocs.map((d) => getDocTypeLabel(d.type))));
167
+ const includedTypesSummary = uniqueTypes.join(" • ");
168
+ const latestCreatedAt = clusterDocs.reduce((latest, d) => {
169
+ return new Date(d.created_at) > new Date(latest) ? d.created_at : latest;
170
+ }, clusterDocs[0].created_at);
171
+ let groupKey = primaryDoc.display_id;
172
+ if (primaryDoc.repair_ticket_id) {
173
+ groupKey = `Repair ${primaryDoc.display_id}`;
174
+ } else if (primaryDoc.order_id) {
175
+ groupKey = `Order #${primaryDoc.display_id}`;
176
+ }
177
+ groups.push({
178
+ groupId: clusterId,
179
+ groupKey,
180
+ primaryDoc,
181
+ documents: clusterDocs,
182
+ hasInvoice,
183
+ totalAmount,
184
+ currencyCode,
185
+ customerName: primaryDoc.customer_name || "Valued Customer",
186
+ customerEmail: primaryDoc.customer_email || ((_a = clusterDocs.find((d) => d.customer_email)) == null ? void 0 : _a.customer_email),
187
+ customerPhone: primaryDoc.customer_phone || ((_b = clusterDocs.find((d) => d.customer_phone)) == null ? void 0 : _b.customer_phone),
188
+ latestCreatedAt,
189
+ includedTypesSummary
190
+ });
191
+ }
192
+ groups.sort((a, b) => new Date(b.latestCreatedAt).getTime() - new Date(a.latestCreatedAt).getTime());
193
+ return groups;
194
+ }
6
195
  const CUSTOM_DOC_PRESETS = [
7
196
  {
8
197
  id: "warranty",
@@ -94,6 +283,14 @@ const DocumentsPage = () => {
94
283
  const [sourceFilter, setSourceFilter] = useState("all");
95
284
  const [zohoLiveDocs, setZohoLiveDocs] = useState([]);
96
285
  const [loadingZoho, setLoadingZoho] = useState(false);
286
+ const [groupByJob, setGroupByJob] = useState(true);
287
+ const [expandedGroups, setExpandedGroups] = useState({});
288
+ const toggleGroup = (groupId) => {
289
+ setExpandedGroups((prev) => ({
290
+ ...prev,
291
+ [groupId]: !prev[groupId]
292
+ }));
293
+ };
97
294
  const [manualModalOpen, setManualModalOpen] = useState(false);
98
295
  const [customDocModalOpen, setCustomDocModalOpen] = useState(false);
99
296
  const [sendModalOpen, setSendModalOpen] = useState(false);
@@ -473,6 +670,31 @@ const DocumentsPage = () => {
473
670
  }
474
671
  return true;
475
672
  });
673
+ const documentGroups = useMemo(() => {
674
+ const allGroups = groupDocuments(documents);
675
+ return allGroups.filter((group) => {
676
+ var _a, _b, _c, _d;
677
+ if (sourceFilter !== "all") {
678
+ const matchesSource = group.documents.some((d) => d.source === sourceFilter);
679
+ if (!matchesSource) return false;
680
+ }
681
+ if (typeFilter !== "all") {
682
+ const matchesType = group.documents.some((d) => d.type === typeFilter);
683
+ if (!matchesType) return false;
684
+ }
685
+ if (searchTerm) {
686
+ const q = searchTerm.toLowerCase();
687
+ const matchesSearch = ((_a = group.groupKey) == null ? void 0 : _a.toLowerCase().includes(q)) || ((_b = group.customerName) == null ? void 0 : _b.toLowerCase().includes(q)) || ((_c = group.customerEmail) == null ? void 0 : _c.toLowerCase().includes(q)) || ((_d = group.customerPhone) == null ? void 0 : _d.toLowerCase().includes(q)) || group.documents.some(
688
+ (d) => {
689
+ var _a2, _b2, _c2;
690
+ return ((_a2 = d.display_id) == null ? void 0 : _a2.toLowerCase().includes(q)) || ((_b2 = d.title) == null ? void 0 : _b2.toLowerCase().includes(q)) || ((_c2 = d.customer_name) == null ? void 0 : _c2.toLowerCase().includes(q));
691
+ }
692
+ );
693
+ if (!matchesSearch) return false;
694
+ }
695
+ return true;
696
+ });
697
+ }, [documents, sourceFilter, typeFilter, searchTerm]);
476
698
  return /* @__PURE__ */ jsxs(Container, { className: "p-0", children: [
477
699
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col md:flex-row md:items-center justify-between p-6 border-b border-ui-border-base gap-4 bg-ui-bg-base", children: [
478
700
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
@@ -600,10 +822,21 @@ const DocumentsPage = () => {
600
822
  title: "Refresh Live Zoho Books Invoices",
601
823
  children: /* @__PURE__ */ jsx(ArrowPath, { className: `w-4 h-4 ${loadingZoho ? "animate-spin" : ""}` })
602
824
  }
603
- ) })
825
+ ) }),
826
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pl-2 border-l border-ui-border-base", children: [
827
+ /* @__PURE__ */ jsx(
828
+ Switch,
829
+ {
830
+ id: "group-by-job",
831
+ checked: groupByJob,
832
+ onCheckedChange: setGroupByJob
833
+ }
834
+ ),
835
+ /* @__PURE__ */ jsx(Label, { htmlFor: "group-by-job", className: "text-xs cursor-pointer select-none font-medium text-ui-fg-base", children: "Pack by Job / Order" })
836
+ ] })
604
837
  ] })
605
838
  ] }),
606
- loading ? /* @__PURE__ */ jsx("div", { className: "py-12 text-center text-ui-fg-muted text-sm", children: "Loading documents..." }) : filteredDocs.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "py-12 text-center text-ui-fg-muted border border-dashed border-ui-border-base rounded-lg", children: [
839
+ loading ? /* @__PURE__ */ jsx("div", { className: "py-12 text-center text-ui-fg-muted text-sm", children: "Loading documents..." }) : (groupByJob ? documentGroups.length === 0 : filteredDocs.length === 0) ? /* @__PURE__ */ jsxs("div", { className: "py-12 text-center text-ui-fg-muted border border-dashed border-ui-border-base rounded-lg", children: [
607
840
  /* @__PURE__ */ jsx(Text, { className: "text-sm", children: "No documents found." }),
608
841
  /* @__PURE__ */ jsx(Text, { className: "text-xs text-ui-fg-subtle mt-1", children: "Create a manual invoice or process an order to generate your first document." })
609
842
  ] }) : /* @__PURE__ */ jsxs(Table, { children: [
@@ -617,7 +850,297 @@ const DocumentsPage = () => {
617
850
  /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Date" }),
618
851
  /* @__PURE__ */ jsx(Table.HeaderCell, { className: "text-right", children: "Actions" })
619
852
  ] }) }),
620
- /* @__PURE__ */ jsx(Table.Body, { children: filteredDocs.map((doc) => {
853
+ /* @__PURE__ */ jsx(Table.Body, { children: groupByJob ? documentGroups.map((group) => {
854
+ const isExpanded = !!expandedGroups[group.groupId];
855
+ const doc = group.primaryDoc;
856
+ const isPaid = doc.payment_status === "paid" || doc.status === "paid";
857
+ const isZoho = doc.source === "zoho";
858
+ const hasMultiple = group.documents.length > 1;
859
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
860
+ /* @__PURE__ */ jsxs(
861
+ Table.Row,
862
+ {
863
+ className: `transition-colors ${isExpanded ? "bg-ui-bg-subtle" : "hover:bg-ui-bg-subtle-hover"}`,
864
+ children: [
865
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
866
+ hasMultiple ? /* @__PURE__ */ jsx(
867
+ "button",
868
+ {
869
+ type: "button",
870
+ onClick: () => toggleGroup(group.groupId),
871
+ className: "p-1 rounded hover:bg-ui-bg-base text-ui-fg-subtle hover:text-ui-fg-base cursor-pointer transition-colors",
872
+ title: isExpanded ? "Collapse related documents" : `Expand ${group.documents.length} related documents`,
873
+ children: isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "w-4 h-4 text-purple-600" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "w-4 h-4 text-ui-fg-subtle" })
874
+ }
875
+ ) : /* @__PURE__ */ jsx("span", { className: "w-5 inline-block" }),
876
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 flex-wrap", children: [
877
+ /* @__PURE__ */ jsx(
878
+ "button",
879
+ {
880
+ type: "button",
881
+ onClick: () => handleViewDocument(doc.id, doc.display_id),
882
+ className: "font-medium text-ui-fg-interactive hover:underline text-left cursor-pointer bg-transparent border-0 p-0",
883
+ children: doc.display_id
884
+ }
885
+ ),
886
+ hasMultiple && /* @__PURE__ */ jsxs(
887
+ Badge,
888
+ {
889
+ size: "2xsmall",
890
+ color: "purple",
891
+ className: "cursor-pointer hover:opacity-80 transition-opacity",
892
+ onClick: () => toggleGroup(group.groupId),
893
+ children: [
894
+ group.documents.length,
895
+ " docs"
896
+ ]
897
+ }
898
+ )
899
+ ] })
900
+ ] }) }),
901
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
902
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-ui-fg-base", children: doc.title }),
903
+ hasMultiple && /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-ui-fg-subtle truncate max-w-[220px]", title: group.includedTypesSummary, children: [
904
+ "Includes: ",
905
+ group.includedTypesSummary
906
+ ] })
907
+ ] }) }),
908
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
909
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: group.customerName || "Guest" }),
910
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-ui-fg-subtle", children: group.customerEmail || group.customerPhone || "-" })
911
+ ] }) }),
912
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold", children: new Intl.NumberFormat("en-US", {
913
+ style: "currency",
914
+ currency: (group.currencyCode || "KES").toUpperCase()
915
+ }).format(Number(group.totalAmount || 0)) }) }),
916
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: isPaid ? "green" : doc.payment_status === "unpaid" ? "red" : "blue", size: "small", children: doc.payment_status || doc.status }) }),
917
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx(Badge, { color: isZoho ? "purple" : "grey", size: "small", children: isZoho ? "Zoho Books" : "PDFKit" }) }),
918
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx("span", { className: "text-xs text-ui-fg-subtle", children: new Date(group.latestCreatedAt).toLocaleDateString() }) }),
919
+ /* @__PURE__ */ jsx(Table.Cell, { className: "text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [
920
+ hasMultiple && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
921
+ /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxs(
922
+ "button",
923
+ {
924
+ type: "button",
925
+ className: "flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded bg-ui-bg-subtle hover:bg-ui-bg-base border border-ui-border-base text-ui-fg-base transition-colors",
926
+ title: "View all related documents for this job/order",
927
+ children: [
928
+ /* @__PURE__ */ jsx(DocumentText, { className: "w-3.5 h-3.5 text-purple-600" }),
929
+ /* @__PURE__ */ jsxs("span", { children: [
930
+ "Docs (",
931
+ group.documents.length,
932
+ ")"
933
+ ] }),
934
+ /* @__PURE__ */ jsx(ChevronDown, { className: "w-3 h-3 text-ui-fg-subtle" })
935
+ ]
936
+ }
937
+ ) }),
938
+ /* @__PURE__ */ jsxs(DropdownMenu.Content, { align: "end", className: "w-72 p-1.5 z-50", children: [
939
+ /* @__PURE__ */ jsxs(DropdownMenu.Label, { className: "text-xs font-semibold px-2 py-1.5 text-ui-fg-muted", children: [
940
+ "Related Documents (",
941
+ group.documents.length,
942
+ ")"
943
+ ] }),
944
+ /* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
945
+ group.documents.map((subDoc) => /* @__PURE__ */ jsxs(
946
+ DropdownMenu.Item,
947
+ {
948
+ onClick: () => handleViewDocument(subDoc.id, subDoc.display_id),
949
+ className: "flex items-center justify-between px-2 py-2 cursor-pointer hover:bg-ui-bg-subtle-hover rounded text-xs transition-colors",
950
+ children: [
951
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col text-left", children: [
952
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-ui-fg-base", children: subDoc.title }),
953
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-ui-fg-subtle", children: subDoc.display_id })
954
+ ] }),
955
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
956
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold text-ui-fg-base", children: subDoc.total_amount != null && Number(subDoc.total_amount) > 0 ? new Intl.NumberFormat("en-US", {
957
+ style: "currency",
958
+ currency: (subDoc.currency_code || "KES").toUpperCase()
959
+ }).format(Number(subDoc.total_amount)) : "—" }),
960
+ /* @__PURE__ */ jsx(ArrowDownTray, { className: "w-3.5 h-3.5 text-ui-fg-subtle" })
961
+ ] })
962
+ ]
963
+ },
964
+ subDoc.id
965
+ ))
966
+ ] })
967
+ ] }),
968
+ /* @__PURE__ */ jsx(
969
+ "button",
970
+ {
971
+ type: "button",
972
+ onClick: () => handleViewDocument(doc.id, doc.display_id),
973
+ className: "p-1.5 rounded hover:bg-ui-bg-subtle text-ui-fg-subtle hover:text-ui-fg-base cursor-pointer",
974
+ title: "View / Download PDF",
975
+ children: /* @__PURE__ */ jsx(ArrowDownTray, { className: "w-4 h-4" })
976
+ }
977
+ ),
978
+ /* @__PURE__ */ jsx(
979
+ "button",
980
+ {
981
+ onClick: () => {
982
+ setSelectedDoc(doc);
983
+ setSendForm({
984
+ channels: ["email"],
985
+ custom_message: "",
986
+ subject: `${settings.company_name}: ${doc.title} #${doc.display_id}`
987
+ });
988
+ setSendModalOpen(true);
989
+ },
990
+ className: "p-1.5 rounded hover:bg-ui-bg-subtle text-ui-fg-subtle hover:text-ui-fg-base",
991
+ title: "Send via Email / SMS / WhatsApp",
992
+ children: /* @__PURE__ */ jsx(Envelope, { className: "w-4 h-4" })
993
+ }
994
+ ),
995
+ !isZoho && settings.zoho_books_enabled && (doc.type === "invoice" || doc.type === "estimate") && Number(doc.total_amount || 0) > 0 && /* @__PURE__ */ jsx(
996
+ Button,
997
+ {
998
+ variant: "transparent",
999
+ size: "small",
1000
+ onClick: () => handleSyncToZoho(doc.id),
1001
+ className: "text-xs text-purple-600 hover:text-purple-700",
1002
+ children: "Push Zoho"
1003
+ }
1004
+ ),
1005
+ doc.short_payment_link && /* @__PURE__ */ jsx(
1006
+ "button",
1007
+ {
1008
+ onClick: () => {
1009
+ navigator.clipboard.writeText(doc.short_payment_link);
1010
+ alert("Short payment link copied to clipboard!");
1011
+ },
1012
+ className: "text-[11px] text-ui-fg-interactive hover:underline px-1.5 py-1",
1013
+ title: "Copy Short Payment Link",
1014
+ children: "Copy Link"
1015
+ }
1016
+ )
1017
+ ] }) })
1018
+ ]
1019
+ }
1020
+ ),
1021
+ isExpanded && hasMultiple && /* @__PURE__ */ jsx(Table.Row, { className: "bg-ui-bg-subtle hover:bg-ui-bg-subtle border-b border-ui-border-base", children: /* @__PURE__ */ jsx(Table.Cell, { colSpan: 8, className: "p-0", children: /* @__PURE__ */ jsxs("div", { className: "py-3 px-6 bg-ui-bg-subtle/80 border-l-4 border-l-purple-500 my-1 mx-2 rounded-r-md", children: [
1022
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
1023
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1024
+ /* @__PURE__ */ jsx(DocumentText, { className: "w-4 h-4 text-purple-600" }),
1025
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-semibold text-ui-fg-base", children: [
1026
+ "Related Documents for this Job / Order (",
1027
+ group.documents.length,
1028
+ ")"
1029
+ ] })
1030
+ ] }),
1031
+ /* @__PURE__ */ jsx(
1032
+ "button",
1033
+ {
1034
+ type: "button",
1035
+ onClick: () => toggleGroup(group.groupId),
1036
+ className: "text-[11px] text-ui-fg-interactive hover:underline",
1037
+ children: "Collapse"
1038
+ }
1039
+ )
1040
+ ] }),
1041
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded border border-ui-border-base bg-ui-bg-base", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left border-collapse", children: [
1042
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-ui-border-base bg-ui-bg-subtle text-[11px] text-ui-fg-muted font-medium", children: [
1043
+ /* @__PURE__ */ jsx("th", { className: "py-1.5 px-3", children: "Document Type" }),
1044
+ /* @__PURE__ */ jsx("th", { className: "py-1.5 px-3", children: "Reference" }),
1045
+ /* @__PURE__ */ jsx("th", { className: "py-1.5 px-3", children: "Amount" }),
1046
+ /* @__PURE__ */ jsx("th", { className: "py-1.5 px-3", children: "Status" }),
1047
+ /* @__PURE__ */ jsx("th", { className: "py-1.5 px-3", children: "Source" }),
1048
+ /* @__PURE__ */ jsx("th", { className: "py-1.5 px-3", children: "Created Date" }),
1049
+ /* @__PURE__ */ jsx("th", { className: "py-1.5 px-3 text-right", children: "Actions" })
1050
+ ] }) }),
1051
+ /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-ui-border-base text-xs", children: group.documents.map((subDoc) => {
1052
+ const subIsPaid = subDoc.payment_status === "paid" || subDoc.status === "paid";
1053
+ const subIsZoho = subDoc.source === "zoho";
1054
+ return /* @__PURE__ */ jsxs("tr", { className: "hover:bg-ui-bg-subtle-hover transition-colors", children: [
1055
+ /* @__PURE__ */ jsx("td", { className: "py-2 px-3", children: /* @__PURE__ */ jsx(
1056
+ Badge,
1057
+ {
1058
+ size: "2xsmall",
1059
+ color: subDoc.type === "invoice" ? "green" : subDoc.type === "estimate" ? "blue" : subDoc.type === "job_card" ? "orange" : subDoc.type === "receipt" ? "purple" : "grey",
1060
+ children: subDoc.title
1061
+ }
1062
+ ) }),
1063
+ /* @__PURE__ */ jsx("td", { className: "py-2 px-3 font-medium", children: /* @__PURE__ */ jsx(
1064
+ "button",
1065
+ {
1066
+ type: "button",
1067
+ onClick: () => handleViewDocument(subDoc.id, subDoc.display_id),
1068
+ className: "text-ui-fg-interactive hover:underline text-left cursor-pointer bg-transparent border-0 p-0 font-medium",
1069
+ children: subDoc.display_id
1070
+ }
1071
+ ) }),
1072
+ /* @__PURE__ */ jsx("td", { className: "py-2 px-3 font-semibold text-ui-fg-base", children: subDoc.total_amount != null && Number(subDoc.total_amount) > 0 ? new Intl.NumberFormat("en-US", {
1073
+ style: "currency",
1074
+ currency: (subDoc.currency_code || "KES").toUpperCase()
1075
+ }).format(Number(subDoc.total_amount)) : "—" }),
1076
+ /* @__PURE__ */ jsx("td", { className: "py-2 px-3", children: /* @__PURE__ */ jsx(
1077
+ Badge,
1078
+ {
1079
+ size: "2xsmall",
1080
+ color: subIsPaid ? "green" : subDoc.payment_status === "unpaid" ? "red" : "blue",
1081
+ children: subDoc.payment_status || subDoc.status
1082
+ }
1083
+ ) }),
1084
+ /* @__PURE__ */ jsx("td", { className: "py-2 px-3", children: /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: subIsZoho ? "purple" : "grey", children: subIsZoho ? "Zoho Books" : "PDFKit" }) }),
1085
+ /* @__PURE__ */ jsx("td", { className: "py-2 px-3 text-ui-fg-subtle text-[11px]", children: new Date(subDoc.created_at).toLocaleString() }),
1086
+ /* @__PURE__ */ jsx("td", { className: "py-2 px-3 text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [
1087
+ /* @__PURE__ */ jsx(
1088
+ "button",
1089
+ {
1090
+ type: "button",
1091
+ onClick: () => handleViewDocument(subDoc.id, subDoc.display_id),
1092
+ className: "p-1 rounded hover:bg-ui-bg-subtle text-ui-fg-subtle hover:text-ui-fg-base",
1093
+ title: "View / Download PDF",
1094
+ children: /* @__PURE__ */ jsx(ArrowDownTray, { className: "w-3.5 h-3.5" })
1095
+ }
1096
+ ),
1097
+ /* @__PURE__ */ jsx(
1098
+ "button",
1099
+ {
1100
+ type: "button",
1101
+ onClick: () => {
1102
+ setSelectedDoc(subDoc);
1103
+ setSendForm({
1104
+ channels: ["email"],
1105
+ custom_message: "",
1106
+ subject: `${settings.company_name}: ${subDoc.title} #${subDoc.display_id}`
1107
+ });
1108
+ setSendModalOpen(true);
1109
+ },
1110
+ className: "p-1 rounded hover:bg-ui-bg-subtle text-ui-fg-subtle hover:text-ui-fg-base",
1111
+ title: "Send Document",
1112
+ children: /* @__PURE__ */ jsx(Envelope, { className: "w-3.5 h-3.5" })
1113
+ }
1114
+ ),
1115
+ !subIsZoho && settings.zoho_books_enabled && (subDoc.type === "invoice" || subDoc.type === "estimate") && Number(subDoc.total_amount || 0) > 0 && /* @__PURE__ */ jsx(
1116
+ Button,
1117
+ {
1118
+ variant: "transparent",
1119
+ size: "small",
1120
+ onClick: () => handleSyncToZoho(subDoc.id),
1121
+ className: "text-[11px] text-purple-600 hover:text-purple-700 py-0.5 px-1.5",
1122
+ children: "Push Zoho"
1123
+ }
1124
+ ),
1125
+ subDoc.short_payment_link && /* @__PURE__ */ jsx(
1126
+ "button",
1127
+ {
1128
+ onClick: () => {
1129
+ navigator.clipboard.writeText(subDoc.short_payment_link);
1130
+ alert("Short payment link copied to clipboard!");
1131
+ },
1132
+ className: "text-[10px] text-ui-fg-interactive hover:underline px-1 py-0.5",
1133
+ title: "Copy Short Link",
1134
+ children: "Copy Link"
1135
+ }
1136
+ )
1137
+ ] }) })
1138
+ ] }, subDoc.id);
1139
+ }) })
1140
+ ] }) })
1141
+ ] }) }) })
1142
+ ] }, group.groupId);
1143
+ }) : filteredDocs.map((doc) => {
621
1144
  const isPaid = doc.payment_status === "paid" || doc.status === "paid";
622
1145
  const isZoho = doc.source === "zoho";
623
1146
  return /* @__PURE__ */ jsxs(Table.Row, { className: "hover:bg-ui-bg-subtle-hover transition-colors", children: [