@powerhousedao/contributor-billing 1.0.0-dev.19 → 1.0.0-dev.20

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 +1 @@
1
- {"version":3,"file":"FolderTree.d.ts","sourceRoot":"","sources":["../../../../editors/builder-team-admin/components/FolderTree.tsx"],"names":[],"mappings":"AAkCA,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAClB,cAAc,GACd,iBAAiB,GACjB,kBAAkB,GAClB,oBAAoB,GACpB,uBAAuB,GACvB,IAAI,CAAC;AA2GT,KAAK,eAAe,GAAG;IACrB,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,kBAAkB,EAAE,EAAE,eAAe,kDA+djE"}
1
+ {"version":3,"file":"FolderTree.d.ts","sourceRoot":"","sources":["../../../../editors/builder-team-admin/components/FolderTree.tsx"],"names":[],"mappings":"AAkCA,iEAAiE;AACjE,MAAM,MAAM,UAAU,GAClB,cAAc,GACd,iBAAiB,GACjB,kBAAkB,GAClB,oBAAoB,GACpB,uBAAuB,GACvB,IAAI,CAAC;AA2GT,KAAK,eAAe,GAAG;IACrB,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,kBAAkB,EAAE,EAAE,eAAe,kDAoejE"}
@@ -271,13 +271,14 @@ export function FolderTree({ onCustomViewChange }) {
271
271
  }, [documentsInDrive]);
272
272
  // Check if builder profile document exists - don't show sidebar if it doesn't
273
273
  const hasBuilderProfile = builderProfileDocument !== null;
274
- // Get the isOperator flag from the builder profile state
275
- const isOperator = useMemo(() => {
274
+ // Get the isOperator flag and profile name from the builder profile state
275
+ const builderProfileState = useMemo(() => {
276
276
  if (!builderProfileDocument)
277
- return false;
278
- const state = builderProfileDocument.state?.global;
279
- return state?.isOperator ?? false;
277
+ return null;
278
+ return builderProfileDocument.state?.global;
280
279
  }, [builderProfileDocument]);
280
+ const isOperator = builderProfileState?.isOperator ?? false;
281
+ const builderProfileName = builderProfileState?.name || null;
281
282
  // Build navigation sections with dynamic expense reports, snapshot reports, and resources & services children
282
283
  const navigationSections = useMemo(() => {
283
284
  if (!driveDocument) {
@@ -486,7 +487,8 @@ export function FolderTree({ onCustomViewChange }) {
486
487
  showCreateDocumentModal(documentType);
487
488
  }
488
489
  };
489
- return (_jsx(SidebarProvider, { nodes: navigationSections, children: _jsx(Sidebar, { className: "pt-1", nodes: navigationSections, activeNodeId: activeNodeId, onActiveNodeChange: handleActiveNodeChange, sidebarTitle: isOperator ? "Operator Team Admin" : "Builder Team Admin", showSearchBar: false, resizable: true, allowPinning: false, showStatusFilter: false, initialWidth: 256, defaultLevel: 2, handleOnTitleClick: () => {
490
+ return (_jsx(SidebarProvider, { nodes: navigationSections, children: _jsx(Sidebar, { className: "pt-1", nodes: navigationSections, activeNodeId: activeNodeId, onActiveNodeChange: handleActiveNodeChange, sidebarTitle: builderProfileName ||
491
+ (isOperator ? "Operator Team Admin" : "Builder Team Admin"), showSearchBar: false, resizable: true, allowPinning: false, showStatusFilter: false, initialWidth: 256, defaultLevel: 2, handleOnTitleClick: () => {
490
492
  onCustomViewChange?.(null);
491
493
  setSelectedNode("");
492
494
  } }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"BillingOverview.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/BillingOverview.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,UAAU,oBAAoB;IAC5B,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;IACjE,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC9B,cAAc,EACd,oBAAoB,GACrB,EAAE,oBAAoB,2CAmLtB"}
1
+ {"version":3,"file":"BillingOverview.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/BillingOverview.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,UAAU,oBAAoB;IAC5B,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;IACjE,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC9B,cAAc,EACd,oBAAoB,GACrB,EAAE,oBAAoB,2CA+StB"}
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { CreditCard, FileText } from "lucide-react";
2
+ import { AlertTriangle, CheckCircle2, ChevronRight, CreditCard, FileText, } from "lucide-react";
3
3
  import { useBillingFolderStructure } from "../hooks/useBillingFolderStructure.js";
4
4
  import { useDocumentsInSelectedDrive, useSelectedDrive, isFileNodeKind, } from "@powerhousedao/reactor-browser";
5
5
  import { useMemo, useEffect, useCallback } from "react";
6
6
  import { MonthlyReportsOverview } from "./MonthlyReportsOverview.js";
7
+ import { useMonthlyReports } from "../hooks/useMonthlyReports.js";
7
8
  /**
8
9
  * Overview for the Billing folder showing payment stats and monthly reporting
9
10
  */
@@ -54,6 +55,64 @@ export function BillingOverview({ onFolderSelect, onActiveNodeIdChange, }) {
54
55
  paidCount,
55
56
  };
56
57
  }, [documentsInDrive, driveDocument, paymentsFolderIds]);
58
+ const { monthReportSets } = useMonthlyReports();
59
+ // Reporting completeness: count months where both snapshot + expense exist
60
+ const reportingCompleteness = useMemo(() => {
61
+ const total = monthReportSets.length;
62
+ const complete = monthReportSets.filter((rs) => rs.snapshotReport !== null && rs.expenseReports.length > 0).length;
63
+ return { complete, total };
64
+ }, [monthReportSets]);
65
+ // Action items: missing reports + pending invoices
66
+ const actionItems = useMemo(() => {
67
+ const items = [];
68
+ for (const rs of monthReportSets) {
69
+ if (!rs.snapshotReport) {
70
+ items.push({
71
+ label: `${rs.monthName} — missing snapshot report`,
72
+ type: "report",
73
+ folderInfo: rs.reportingFolderId
74
+ ? {
75
+ folderId: rs.reportingFolderId,
76
+ folderType: "reporting",
77
+ monthName: rs.monthName,
78
+ paymentsFolderId: rs.folderInfo.paymentsFolder?.id,
79
+ }
80
+ : undefined,
81
+ });
82
+ }
83
+ if (rs.expenseReports.length === 0) {
84
+ items.push({
85
+ label: `${rs.monthName} — missing expense report`,
86
+ type: "report",
87
+ folderInfo: rs.reportingFolderId
88
+ ? {
89
+ folderId: rs.reportingFolderId,
90
+ folderType: "reporting",
91
+ monthName: rs.monthName,
92
+ paymentsFolderId: rs.folderInfo.paymentsFolder?.id,
93
+ }
94
+ : undefined,
95
+ });
96
+ }
97
+ }
98
+ if (paymentStats.pendingCount > 0) {
99
+ // Navigate to the newest month's Payments folder
100
+ const newestMonth = monthReportSets[0];
101
+ items.push({
102
+ label: `${paymentStats.pendingCount} invoice${paymentStats.pendingCount === 1 ? "" : "s"} pending payment`,
103
+ type: "invoice",
104
+ folderInfo: newestMonth?.folderInfo.paymentsFolder
105
+ ? {
106
+ folderId: newestMonth.folderInfo.paymentsFolder.id,
107
+ folderType: "payments",
108
+ monthName: newestMonth.monthName,
109
+ reportingFolderId: newestMonth.folderInfo.reportingFolder?.id,
110
+ }
111
+ : undefined,
112
+ });
113
+ }
114
+ return items.slice(0, 4);
115
+ }, [monthReportSets, paymentStats.pendingCount]);
57
116
  // Auto-create billing folder if it doesn't exist
58
117
  const ensureBillingFolder = useCallback(async () => {
59
118
  if (!billingFolder) {
@@ -68,8 +127,9 @@ export function BillingOverview({ onFolderSelect, onActiveNodeIdChange, }) {
68
127
  if (!billingFolder) {
69
128
  return (_jsxs("div", { children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "Billing" }), _jsx("p", { className: "text-gray-600", children: "Manage monthly billing, payments, and reports" })] }), _jsx("div", { className: "bg-white rounded-lg border border-gray-200 p-8 text-center", children: _jsxs("div", { className: "animate-pulse", children: [_jsx("div", { className: "w-12 h-12 bg-gray-200 rounded-full mx-auto mb-4" }), _jsx("div", { className: "h-5 bg-gray-200 rounded w-32 mx-auto mb-2" }), _jsx("div", { className: "h-4 bg-gray-100 rounded w-48 mx-auto" })] }) })] }));
70
129
  }
71
- return (_jsxs("div", { children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "Billing" }), _jsx("p", { className: "text-gray-600", children: "Manage monthly billing, payments, and reports" })] }), _jsxs("div", { className: "bg-white rounded-xl border border-gray-200 p-6 mb-6", children: [_jsxs("div", { className: "flex items-center gap-3 mb-4", children: [_jsx("div", { className: "p-2 bg-blue-100 rounded-lg", children: _jsx(CreditCard, { className: "w-5 h-5 text-blue-600" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Payment Summary" }), _jsx("p", { className: "text-sm text-gray-600", children: "Overview of all invoices across billing months" })] })] }), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-4", children: [_jsxs("div", { className: "bg-gray-50 rounded-lg p-3", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx(FileText, { className: "w-4 h-4 text-gray-500" }), _jsx("span", { className: "text-sm text-gray-600", children: "Total Invoices" })] }), _jsx("p", { className: "text-xl font-bold text-gray-900", children: paymentStats.totalInvoices })] }), _jsxs("div", { className: "bg-gray-50 rounded-lg p-3", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx(CreditCard, { className: "w-4 h-4 text-gray-500" }), _jsx("span", { className: "text-sm text-gray-600", children: "Total Amount" })] }), _jsxs("p", { className: "text-xl font-bold text-gray-900", children: ["$", paymentStats.totalAmount.toLocaleString("en-US", {
130
+ return (_jsxs("div", { children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "Billing" }), _jsx("p", { className: "text-gray-600", children: "Manage monthly billing, payments, and reports" })] }), _jsxs("div", { className: "bg-white rounded-xl border border-gray-200 p-6 mb-6", children: [_jsxs("div", { className: "flex items-center gap-3 mb-4", children: [_jsx("div", { className: "p-2 bg-blue-100 rounded-lg", children: _jsx(CreditCard, { className: "w-5 h-5 text-blue-600" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Payment Summary" }), _jsx("p", { className: "text-sm text-gray-600", children: "Overview of all invoices across billing months" })] })] }), _jsxs("div", { className: "grid grid-cols-2 md:grid-cols-5 gap-4", children: [_jsxs("div", { className: "bg-gray-50 rounded-lg p-3", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx(FileText, { className: "w-4 h-4 text-gray-500" }), _jsx("span", { className: "text-sm text-gray-600", children: "Total Invoices" })] }), _jsx("p", { className: "text-xl font-bold text-gray-900", children: paymentStats.totalInvoices })] }), _jsxs("div", { className: "bg-gray-50 rounded-lg p-3", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [_jsx(CreditCard, { className: "w-4 h-4 text-gray-500" }), _jsx("span", { className: "text-sm text-gray-600", children: "Total Amount" })] }), _jsxs("p", { className: "text-xl font-bold text-gray-900", children: ["$", paymentStats.totalAmount.toLocaleString("en-US", {
72
131
  minimumFractionDigits: 2,
73
132
  maximumFractionDigits: 2,
74
- })] })] }), _jsxs("div", { className: "bg-amber-50 rounded-lg p-3", children: [_jsx("span", { className: "text-sm text-amber-600", children: "Pending" }), _jsx("p", { className: "text-xl font-bold text-amber-700", children: paymentStats.pendingCount })] }), _jsxs("div", { className: "bg-green-50 rounded-lg p-3", children: [_jsx("span", { className: "text-sm text-green-600", children: "Paid" }), _jsx("p", { className: "text-xl font-bold text-green-700", children: paymentStats.paidCount })] })] })] }), _jsx(MonthlyReportsOverview, { onFolderSelect: onFolderSelect, monthFolders: monthFolders, onCreateMonth: createMonthFolder, onActiveNodeIdChange: onActiveNodeIdChange })] }));
133
+ })] })] }), _jsxs("div", { className: "bg-amber-50 rounded-lg p-3", children: [_jsx("span", { className: "text-sm text-amber-600", children: "Pending" }), _jsx("p", { className: "text-xl font-bold text-amber-700", children: paymentStats.pendingCount })] }), _jsxs("div", { className: "bg-green-50 rounded-lg p-3", children: [_jsx("span", { className: "text-sm text-green-600", children: "Paid" }), _jsx("p", { className: "text-xl font-bold text-green-700", children: paymentStats.paidCount })] }), _jsxs("div", { className: `${reportingCompleteness.total > 0 && reportingCompleteness.complete === reportingCompleteness.total ? "bg-green-50" : "bg-amber-50"} rounded-lg p-3`, children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [reportingCompleteness.total > 0 &&
134
+ reportingCompleteness.complete === reportingCompleteness.total ? (_jsx(CheckCircle2, { className: "w-4 h-4 text-green-500" })) : (_jsx(AlertTriangle, { className: "w-4 h-4 text-amber-500" })), _jsx("span", { className: `text-sm ${reportingCompleteness.total > 0 && reportingCompleteness.complete === reportingCompleteness.total ? "text-green-600" : "text-amber-600"}`, children: "Reports Complete" })] }), _jsxs("p", { className: `text-xl font-bold ${reportingCompleteness.total > 0 && reportingCompleteness.complete === reportingCompleteness.total ? "text-green-700" : "text-amber-700"}`, children: [reportingCompleteness.complete, "/", reportingCompleteness.total] })] })] })] }), actionItems.length > 0 && (_jsxs("div", { className: "bg-amber-50 border border-amber-200 rounded-xl p-4 mb-6", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3", children: [_jsx(AlertTriangle, { className: "w-4 h-4 text-amber-600" }), _jsx("span", { className: "text-sm font-semibold text-amber-800", children: "Needs attention" })] }), _jsx("ul", { className: "space-y-1", children: actionItems.map((item) => (_jsx("li", { children: item.folderInfo && onFolderSelect ? (_jsxs("button", { type: "button", className: "w-full flex items-center justify-between text-left text-sm text-amber-900 hover:bg-amber-100 rounded px-2 py-1.5 transition-colors", onClick: () => onFolderSelect(item.folderInfo), children: [_jsxs("span", { children: ["\u2022 ", item.label] }), _jsx(ChevronRight, { className: "w-4 h-4 text-amber-400 flex-shrink-0" })] })) : (_jsxs("span", { className: "text-sm text-amber-900 px-2 py-1.5 block", children: ["\u2022 ", item.label] })) }, item.label))) })] })), _jsx(MonthlyReportsOverview, { onFolderSelect: onFolderSelect, monthFolders: monthFolders, onCreateMonth: createMonthFolder, onActiveNodeIdChange: onActiveNodeIdChange })] }));
75
135
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DocumentDropZone.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/DocumentDropZone.tsx"],"names":[],"mappings":"AAUA,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,SAAc,GACf,EAAE,qBAAqB,2CAgLvB"}
1
+ {"version":3,"file":"DocumentDropZone.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/DocumentDropZone.tsx"],"names":[],"mappings":"AAUA,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,SAAc,GACf,EAAE,qBAAqB,2CA+LvB"}
@@ -112,6 +112,15 @@ export function DocumentDropZone({ children, className = "", }) {
112
112
  if (docType === "powerhouse/expense-report") {
113
113
  cbToast(`Expense report uploaded. It will be placed in the appropriate Reporting folder based on its period.`, { type: "info" });
114
114
  }
115
+ else if (docType === "powerhouse/invoice") {
116
+ cbToast(`Invoice uploaded. It will be placed in the appropriate Payments folder based on its issue date.`, { type: "info" });
117
+ }
118
+ else if (docType === "powerhouse/billing-statement") {
119
+ cbToast(`Billing statement uploaded. It will be placed in the appropriate Payments folder based on its issue date.`, { type: "info" });
120
+ }
121
+ else if (docType === "powerhouse/snapshot-report") {
122
+ cbToast(`Snapshot report uploaded. It will be placed in the appropriate Reporting folder based on its period.`, { type: "info" });
123
+ }
115
124
  else if (docType === "powerhouse/accounts") {
116
125
  cbToast(`Accounts document uploaded. It will remain at the root level.`, { type: "info" });
117
126
  }
@@ -126,5 +135,5 @@ export function DocumentDropZone({ children, className = "", }) {
126
135
  });
127
136
  await Promise.allSettled(filePromises);
128
137
  }, [onDropFile, driveId, documentsInDrive]);
129
- return (_jsxs("div", { className: `relative ${className}`, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, children: [children, isDragging && (_jsx("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-blue-500/10 border-2 border-dashed border-blue-500 rounded-lg pointer-events-none", children: _jsx("div", { className: "bg-white rounded-lg shadow-lg p-6 border border-blue-200", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "text-4xl mb-2", children: "\uD83D\uDCC4" }), _jsx("p", { className: "text-lg font-semibold text-gray-900", children: "Drop documents here" }), _jsx("p", { className: "text-sm text-gray-600 mt-1", children: "Expense Reports and Accounts documents will be automatically organized" })] }) }) }))] }));
138
+ return (_jsxs("div", { className: `relative ${className}`, onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, children: [children, isDragging && (_jsx("div", { className: "absolute inset-0 z-50 flex items-center justify-center bg-blue-500/10 border-2 border-dashed border-blue-500 rounded-lg pointer-events-none", children: _jsx("div", { className: "bg-white rounded-lg shadow-lg p-6 border border-blue-200", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "text-4xl mb-2", children: "\uD83D\uDCC4" }), _jsx("p", { className: "text-lg font-semibold text-gray-900", children: "Drop documents here" }), _jsx("p", { className: "text-sm text-gray-600 mt-1", children: "Invoices, Expense Reports, and Accounts documents will be automatically organized" })] }) }) }))] }));
130
139
  }
@@ -1,3 +1,8 @@
1
- /** Shows a message when the selected drive or folder is empty */
2
- export declare function EmptyState(): import("react/jsx-runtime").JSX.Element | null;
1
+ interface EmptyStateProps {
2
+ title?: string;
3
+ description?: string;
4
+ }
5
+ /** Shows a contextual message when a folder or view has no content */
6
+ export declare function EmptyState({ title, description, }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
3
8
  //# sourceMappingURL=EmptyState.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/EmptyState.tsx"],"names":[],"mappings":"AAEA,iEAAiE;AACjE,wBAAgB,UAAU,mDAWzB"}
1
+ {"version":3,"file":"EmptyState.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/EmptyState.tsx"],"names":[],"mappings":"AAAA,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,EACzB,KAA8B,EAC9B,WAA2D,GAC5D,EAAE,eAAe,2CAOjB"}
@@ -1,10 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useNodesInSelectedDriveOrFolder } from "@powerhousedao/reactor-browser";
3
- /** Shows a message when the selected drive or folder is empty */
4
- export function EmptyState() {
5
- const nodes = useNodesInSelectedDriveOrFolder();
6
- const hasNodes = nodes.length > 0;
7
- if (hasNodes)
8
- return null;
9
- return (_jsxs("div", { className: "py-12 text-center text-gray-500", children: [_jsx("p", { className: "text-lg", children: "This folder is empty" }), _jsx("p", { className: "mt-2 text-sm", children: "Create your first document or folder below" })] }));
2
+ /** Shows a contextual message when a folder or view has no content */
3
+ export function EmptyState({ title = "This folder is empty", description = "Create your first document or drop one here", }) {
4
+ return (_jsxs("div", { className: "py-12 text-center text-gray-500", children: [_jsx("p", { className: "text-lg", children: title }), _jsx("p", { className: "mt-2 text-sm", children: description })] }));
10
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"HeaderStats.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/HeaderStats.tsx"],"names":[],"mappings":"AAwBA,UAAU,gBAAgB;IACxB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,GAAI,cAAc,gBAAgB,4CAyIzD,CAAC"}
1
+ {"version":3,"file":"HeaderStats.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/HeaderStats.tsx"],"names":[],"mappings":"AAyBA,UAAU,gBAAgB;IACxB,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,WAAW,GAAI,cAAc,gBAAgB,4CAkJzD,CAAC"}
@@ -4,6 +4,7 @@ import { useState, useEffect, useMemo } from "react";
4
4
  import { useDocumentsInSelectedDrive, useSelectedDrive, isFileNodeKind, } from "@powerhousedao/reactor-browser";
5
5
  import { getExchangeRate } from "../../utils/exchangeRate.js";
6
6
  import { Tooltip, TooltipProvider } from "@powerhousedao/design-system/ui";
7
+ import { cbToast } from "../cbToast.js";
7
8
  const currencyList = [
8
9
  { ticker: "USDS", crypto: true },
9
10
  { ticker: "USDC", crypto: true },
@@ -46,6 +47,7 @@ export const HeaderStats = ({ folderId }) => {
46
47
  return;
47
48
  }
48
49
  let total = 0;
50
+ let conversionFailed = false;
49
51
  for (const invoice of invoices) {
50
52
  const invoiceAmount = invoice.state.global.totalPriceTaxIncl;
51
53
  const invoiceCurrency = invoice.state.global.currency || "USD"; // Fallback to USD if currency is empty
@@ -71,10 +73,14 @@ export const HeaderStats = ({ folderId }) => {
71
73
  console.error("Error getting exchange rate:", error);
72
74
  // Fallback to original amount if exchange rate fails
73
75
  total += invoiceAmount;
76
+ conversionFailed = true;
74
77
  }
75
78
  }
76
79
  }
77
80
  setTotalExpenses(total);
81
+ if (conversionFailed) {
82
+ cbToast("Currency conversion failed for some invoices — totals shown using 1:1 fallback rate", { type: "warning" });
83
+ }
78
84
  };
79
85
  calculateTotalExpenses().catch(console.error);
80
86
  }, [invoices, selectedCurrency]);
@@ -1 +1 @@
1
- {"version":3,"file":"InvoiceTable.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/InvoiceTable.tsx"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,wBAAwB,EAC9B,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA6E3D,eAAO,MAAM,aAAa;;;GAUzB,CAAC;AAeF,UAAU,iBAAiB;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,WAAW,EAAE,CACX,QAAQ,EACJ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACvB,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAC7D,IAAI,CAAC;IACV,sBAAsB,EAAE,wBAAwB,EAAE,CAAC;IACnD,qBAAqB,EAAE,CACrB,KAAK,EAAE,wBAAwB,EAC/B,IAAI,EAAE,MAAM,KACT,IAAI,CAAC;IACV,gBAAgB,EAAE,CAChB,EAAE,EAAE,MAAM,KACP,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7D,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;IACnD,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7E,qBAAqB,EAAE,MAAM,OAAO,CAAC;IACrC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AA6BD,eAAO,MAAM,YAAY,GAAI,yLAY1B,iBAAiB,4CAkvBnB,CAAC"}
1
+ {"version":3,"file":"InvoiceTable.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/InvoiceTable.tsx"],"names":[],"mappings":"AACA,OAAO,EAML,KAAK,wBAAwB,EAC9B,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AA+E3D,eAAO,MAAM,aAAa;;;GAUzB,CAAC;AAeF,UAAU,iBAAiB;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,WAAW,EAAE,CACX,QAAQ,EACJ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACvB,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAC7D,IAAI,CAAC;IACV,sBAAsB,EAAE,wBAAwB,EAAE,CAAC;IACnD,qBAAqB,EAAE,CACrB,KAAK,EAAE,wBAAwB,EAC/B,IAAI,EAAE,MAAM,KACT,IAAI,CAAC;IACV,gBAAgB,EAAE,CAChB,EAAE,EAAE,MAAM,KACP,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7D,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,IAAI,CAAC;IACnD,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7E,qBAAqB,EAAE,MAAM,OAAO,CAAC;IACrC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AA6BD,eAAO,MAAM,YAAY,GAAI,yLAY1B,iBAAiB,4CAgxBnB,CAAC"}
@@ -8,6 +8,8 @@ import { setPeriodStart, setPeriodEnd, } from "../../../../document-models/expen
8
8
  import { mapTags } from "../../../billing-statement/lineItemTags/tagMapping.js";
9
9
  import { exportInvoicesToXeroCSV } from "../../../../scripts/contributor-billing/createXeroCsv.js";
10
10
  import { exportExpenseReportCSV } from "../../../../scripts/contributor-billing/createExpenseReportCsv.js";
11
+ import { cbToast } from "../cbToast.js";
12
+ import { EmptyState } from "../EmptyState.js";
11
13
  import { HeaderControls } from "./HeaderControls.js";
12
14
  import { InvoiceTableSection } from "./InvoiceTableSection.js";
13
15
  import { InvoiceTableRow } from "./InvoiceTableRow.js";
@@ -412,14 +414,31 @@ export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentMod
412
414
  const driveId = selectedDrive?.header.id;
413
415
  if (!driveId)
414
416
  return;
417
+ let successCount = 0;
418
+ let failCount = 0;
415
419
  for (const id of ids) {
416
420
  try {
417
421
  await dispatchActions(deleteNode({ id }), driveId);
422
+ successCount++;
418
423
  }
419
424
  catch (error) {
420
425
  console.error(`Failed to delete document ${id}:`, error);
426
+ failCount++;
421
427
  }
422
428
  }
429
+ if (failCount === 0) {
430
+ cbToast(`${successCount} document${successCount !== 1 ? "s" : ""} deleted`, { type: "success" });
431
+ }
432
+ else if (successCount === 0) {
433
+ cbToast(`Failed to delete ${failCount} document${failCount !== 1 ? "s" : ""}`, {
434
+ type: "error",
435
+ });
436
+ }
437
+ else {
438
+ cbToast(`${successCount} deleted, ${failCount} failed`, {
439
+ type: "warning",
440
+ });
441
+ }
423
442
  };
424
443
  // Check for integrations document - simple computed value
425
444
  const integrationsDoc = files.find((file) => file.documentType === "powerhouse/integrations");
@@ -518,7 +537,7 @@ export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentMod
518
537
  showBillingStatement: true,
519
538
  }), renderSection("PAYMENTISSUE", "Payment Issue", paymentIssue, {
520
539
  showBillingStatement: true,
521
- }), renderSection("PAYMENTCLOSED", "Payment Closed", paymentClosed), renderSection("REJECTED", "Rejected", rejected), renderSection("OTHER", "Other", otherInvoices), loadingFileIds.length > 0 && (_jsx(InvoiceTableSection, { title: "Loading", count: loadingFileIds.length, color: "bg-gray-100 text-gray-600", children: _jsxs("table", { className: "w-full text-sm rounded-sm border-separate border-spacing-0 border border-gray-300 overflow-hidden", children: [_jsx("thead", { children: _jsxs("tr", { className: "bg-gray-50 font-medium text-gray-500 text-xs", children: [_jsx("th", { className: "px-2 py-2 w-8 rounded-tl-sm" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Invoice" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Invoice No." }), _jsx("th", { className: "px-2 py-2 text-center", children: "Issue Date" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Due Date" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Currency" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Amount" }), _jsx("th", { className: "px-2 py-2 rounded-tr-sm text-center", children: "Status" })] }) }), _jsx("tbody", { children: loadingFileIds.map((id) => {
540
+ }), renderSection("PAYMENTCLOSED", "Payment Closed", paymentClosed), renderSection("REJECTED", "Rejected", rejected), renderSection("OTHER", "Other", otherInvoices), files.length === 0 && loadingFileIds.length === 0 && (_jsx(EmptyState, { title: "No invoices yet", description: "Create a new invoice using the Draft section above, or drop an invoice file here" })), loadingFileIds.length > 0 && (_jsx(InvoiceTableSection, { title: "Loading", count: loadingFileIds.length, color: "bg-gray-100 text-gray-600", children: _jsxs("table", { className: "w-full text-sm rounded-sm border-separate border-spacing-0 border border-gray-300 overflow-hidden", children: [_jsx("thead", { children: _jsxs("tr", { className: "bg-gray-50 font-medium text-gray-500 text-xs", children: [_jsx("th", { className: "px-2 py-2 w-8 rounded-tl-sm" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Invoice" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Invoice No." }), _jsx("th", { className: "px-2 py-2 text-center", children: "Issue Date" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Due Date" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Currency" }), _jsx("th", { className: "px-2 py-2 text-center", children: "Amount" }), _jsx("th", { className: "px-2 py-2 rounded-tr-sm text-center", children: "Status" })] }) }), _jsx("tbody", { children: loadingFileIds.map((id) => {
522
541
  const file = files.find((f) => f.id === id);
523
542
  return (_jsxs("tr", { className: "border-t border-gray-200 animate-pulse", children: [_jsx("td", { className: "px-2 py-2" }), _jsx("td", { className: "px-2 py-2 text-center text-gray-400", children: file?.name || "Loading..." }), _jsx("td", { className: "px-2 py-2 text-center", children: _jsx("div", { className: "h-4 bg-gray-200 rounded w-16 mx-auto" }) }), _jsx("td", { className: "px-2 py-2 text-center", children: _jsx("div", { className: "h-4 bg-gray-200 rounded w-20 mx-auto" }) }), _jsx("td", { className: "px-2 py-2 text-center", children: _jsx("div", { className: "h-4 bg-gray-200 rounded w-20 mx-auto" }) }), _jsx("td", { className: "px-2 py-2 text-center", children: _jsx("div", { className: "h-4 bg-gray-200 rounded w-12 mx-auto" }) }), _jsx("td", { className: "px-2 py-2 text-center", children: _jsx("div", { className: "h-4 bg-gray-200 rounded w-16 mx-auto" }) }), _jsx("td", { className: "px-2 py-2 text-center", children: _jsx("span", { className: "text-xs text-gray-400", children: "Loading..." }) })] }, id));
524
543
  }) })] }) }))] })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"useDocumentAutoPlacement.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/hooks/useDocumentAutoPlacement.ts"],"names":[],"mappings":"AAkBA,UAAU,8BAA8B;IACtC,uCAAuC;IACvC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,IAAI,8BAA8B,CAmNzE"}
1
+ {"version":3,"file":"useDocumentAutoPlacement.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/hooks/useDocumentAutoPlacement.ts"],"names":[],"mappings":"AAsBA,UAAU,8BAA8B;IACtC,uCAAuC;IACvC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,IAAI,8BAA8B,CAuezE"}
@@ -2,6 +2,7 @@ import { useEffect, useMemo } from "react";
2
2
  import { isFileNodeKind, isFolderNodeKind, useSelectedDrive, useDocumentsInSelectedDrive, useNodeActions, } from "@powerhousedao/reactor-browser";
3
3
  import { isDocumentSynced } from "../../shared/document-sync.js";
4
4
  import { useBillingFolderStructure } from "./useBillingFolderStructure.js";
5
+ import { cbToast } from "../components/cbToast.js";
5
6
  // Module-level tracking to prevent duplicate processing
6
7
  const globalProcessingState = {
7
8
  processedDocs: new Map(), // driveId -> Set of doc IDs processed
@@ -20,7 +21,7 @@ const globalProcessingState = {
20
21
  export function useDocumentAutoPlacement() {
21
22
  const [driveDocument] = useSelectedDrive();
22
23
  const documentsInDrive = useDocumentsInSelectedDrive();
23
- const { reportingFolderIds, monthFolders, billingFolder, createMonthFolder } = useBillingFolderStructure();
24
+ const { reportingFolderIds, paymentsFolderIds, monthFolders, billingFolder, createMonthFolder, } = useBillingFolderStructure();
24
25
  const { onMoveNode, onRenameNode } = useNodeActions();
25
26
  const driveId = driveDocument?.header.id;
26
27
  // Initialize module-level tracking for this drive
@@ -184,6 +185,203 @@ export function useDocumentAutoPlacement() {
184
185
  onMoveNode,
185
186
  onRenameNode,
186
187
  ]);
188
+ // Auto-place invoices into appropriate Payments folders based on dateIssued
189
+ useEffect(() => {
190
+ if (!driveId || !driveDocument || !documentsInDrive)
191
+ return;
192
+ const allNodes = driveDocument.state.global.nodes;
193
+ const processedDocs = globalProcessingState.processedDocs.get(driveId);
194
+ if (!processedDocs)
195
+ return;
196
+ // Find invoice file nodes that are NOT already in a Payments folder
197
+ const invoiceNodesToProcess = allNodes.filter((node) => isFileNodeKind(node) &&
198
+ node.documentType === "powerhouse/invoice" &&
199
+ !paymentsFolderIds.has(node.parentFolder || ""));
200
+ for (const fileNode of invoiceNodesToProcess) {
201
+ if (processedDocs.has(fileNode.id))
202
+ continue;
203
+ const doc = documentsInDrive.find((d) => d.header.documentType === "powerhouse/invoice" &&
204
+ d.header.id === fileNode.id);
205
+ if (!doc)
206
+ continue;
207
+ const dateIssued = doc.state.global.dateIssued;
208
+ const monthName = getMonthNameFromPeriod(dateIssued);
209
+ processedDocs.add(fileNode.id);
210
+ if (monthName) {
211
+ const monthInfo = monthFolders.get(monthName);
212
+ const paymentsFolder = monthInfo?.paymentsFolder;
213
+ if (paymentsFolder) {
214
+ console.log(`[DocumentAutoPlacement] Moving invoice ${fileNode.id} ("${fileNode.name}") to Payments folder for ${monthName}`);
215
+ onMoveNode(fileNode, paymentsFolder)
216
+ .then(() => {
217
+ console.log(`[DocumentAutoPlacement] Successfully moved invoice to ${monthName} > Payments`);
218
+ cbToast(`Invoice "${fileNode.name}" placed in ${monthName} > Payments`, { type: "success" });
219
+ })
220
+ .catch((error) => {
221
+ console.error(`[DocumentAutoPlacement] Failed to move invoice to Payments folder:`, error);
222
+ processedDocs.delete(fileNode.id);
223
+ });
224
+ }
225
+ else {
226
+ // Month folder doesn't exist — create it, then retry on next effect run
227
+ console.log(`[DocumentAutoPlacement] Month folder "${monthName}" doesn't exist for invoice, creating it`);
228
+ if (billingFolder && driveId) {
229
+ createMonthFolder(monthName)
230
+ .then(() => {
231
+ console.log(`[DocumentAutoPlacement] Created month folder "${monthName}" for invoice, will retry placement`);
232
+ processedDocs.delete(fileNode.id);
233
+ })
234
+ .catch((error) => {
235
+ console.error(`[DocumentAutoPlacement] Failed to create month folder "${monthName}" for invoice:`, error);
236
+ processedDocs.delete(fileNode.id);
237
+ });
238
+ }
239
+ else {
240
+ processedDocs.delete(fileNode.id);
241
+ }
242
+ }
243
+ }
244
+ else {
245
+ console.warn(`[DocumentAutoPlacement] Invoice ${fileNode.id} ("${fileNode.name}") has no dateIssued, leaving at root`);
246
+ cbToast(`Invoice "${fileNode.name}" has no issue date — could not auto-categorize. It remains at the drive root.`, { type: "warning" });
247
+ }
248
+ }
249
+ }, [
250
+ driveId,
251
+ driveDocument,
252
+ documentsInDrive,
253
+ paymentsFolderIds,
254
+ monthFolders,
255
+ billingFolder,
256
+ createMonthFolder,
257
+ onMoveNode,
258
+ ]);
259
+ // Auto-place snapshot reports into appropriate Reporting folders based on reportPeriodStart
260
+ useEffect(() => {
261
+ if (!driveId || !driveDocument || !documentsInDrive)
262
+ return;
263
+ const allNodes = driveDocument.state.global.nodes;
264
+ const processedDocs = globalProcessingState.processedDocs.get(driveId);
265
+ if (!processedDocs)
266
+ return;
267
+ const snapshotNodesToProcess = allNodes.filter((node) => isFileNodeKind(node) &&
268
+ node.documentType === "powerhouse/snapshot-report" &&
269
+ !reportingFolderIds.has(node.parentFolder || ""));
270
+ for (const fileNode of snapshotNodesToProcess) {
271
+ if (processedDocs.has(fileNode.id))
272
+ continue;
273
+ const doc = documentsInDrive.find((d) => d.header.documentType === "powerhouse/snapshot-report" &&
274
+ d.header.id === fileNode.id);
275
+ if (!doc)
276
+ continue;
277
+ const reportPeriodStart = doc.state.global.reportPeriodStart;
278
+ const monthName = getMonthNameFromPeriod(reportPeriodStart);
279
+ processedDocs.add(fileNode.id);
280
+ if (monthName) {
281
+ const monthInfo = monthFolders.get(monthName);
282
+ const reportingFolder = monthInfo?.reportingFolder;
283
+ if (reportingFolder) {
284
+ onMoveNode(fileNode, reportingFolder)
285
+ .then(() => {
286
+ cbToast(`Snapshot report "${fileNode.name}" placed in ${monthName} > Reporting`, { type: "success" });
287
+ })
288
+ .catch((error) => {
289
+ console.error(`[DocumentAutoPlacement] Failed to move snapshot report:`, error);
290
+ processedDocs.delete(fileNode.id);
291
+ });
292
+ }
293
+ else {
294
+ if (billingFolder && driveId) {
295
+ createMonthFolder(monthName)
296
+ .then(() => {
297
+ processedDocs.delete(fileNode.id);
298
+ })
299
+ .catch(() => {
300
+ processedDocs.delete(fileNode.id);
301
+ });
302
+ }
303
+ else {
304
+ processedDocs.delete(fileNode.id);
305
+ }
306
+ }
307
+ }
308
+ else {
309
+ cbToast(`Snapshot report "${fileNode.name}" has no report period — could not auto-categorize.`, { type: "warning" });
310
+ }
311
+ }
312
+ }, [
313
+ driveId,
314
+ driveDocument,
315
+ documentsInDrive,
316
+ reportingFolderIds,
317
+ monthFolders,
318
+ billingFolder,
319
+ createMonthFolder,
320
+ onMoveNode,
321
+ ]);
322
+ // Auto-place billing statements into appropriate Payments folders based on dateIssued
323
+ useEffect(() => {
324
+ if (!driveId || !driveDocument || !documentsInDrive)
325
+ return;
326
+ const allNodes = driveDocument.state.global.nodes;
327
+ const processedDocs = globalProcessingState.processedDocs.get(driveId);
328
+ if (!processedDocs)
329
+ return;
330
+ const billingStatementNodesToProcess = allNodes.filter((node) => isFileNodeKind(node) &&
331
+ node.documentType === "powerhouse/billing-statement" &&
332
+ !paymentsFolderIds.has(node.parentFolder || ""));
333
+ for (const fileNode of billingStatementNodesToProcess) {
334
+ if (processedDocs.has(fileNode.id))
335
+ continue;
336
+ const doc = documentsInDrive.find((d) => d.header.documentType === "powerhouse/billing-statement" &&
337
+ d.header.id === fileNode.id);
338
+ if (!doc)
339
+ continue;
340
+ const dateIssued = doc.state.global.dateIssued;
341
+ const monthName = getMonthNameFromPeriod(dateIssued);
342
+ processedDocs.add(fileNode.id);
343
+ if (monthName) {
344
+ const monthInfo = monthFolders.get(monthName);
345
+ const paymentsFolder = monthInfo?.paymentsFolder;
346
+ if (paymentsFolder) {
347
+ onMoveNode(fileNode, paymentsFolder)
348
+ .then(() => {
349
+ cbToast(`Billing statement "${fileNode.name}" placed in ${monthName} > Payments`, { type: "success" });
350
+ })
351
+ .catch((error) => {
352
+ console.error(`[DocumentAutoPlacement] Failed to move billing statement:`, error);
353
+ processedDocs.delete(fileNode.id);
354
+ });
355
+ }
356
+ else {
357
+ if (billingFolder && driveId) {
358
+ createMonthFolder(monthName)
359
+ .then(() => {
360
+ processedDocs.delete(fileNode.id);
361
+ })
362
+ .catch(() => {
363
+ processedDocs.delete(fileNode.id);
364
+ });
365
+ }
366
+ else {
367
+ processedDocs.delete(fileNode.id);
368
+ }
369
+ }
370
+ }
371
+ else {
372
+ cbToast(`Billing statement "${fileNode.name}" has no issue date — could not auto-categorize.`, { type: "warning" });
373
+ }
374
+ }
375
+ }, [
376
+ driveId,
377
+ driveDocument,
378
+ documentsInDrive,
379
+ paymentsFolderIds,
380
+ monthFolders,
381
+ billingFolder,
382
+ createMonthFolder,
383
+ onMoveNode,
384
+ ]);
187
385
  return {
188
386
  isActive: !!driveId,
189
387
  };
package/dist/style.css CHANGED
@@ -875,6 +875,9 @@
875
875
  .shrink-0 {
876
876
  flex-shrink: 0;
877
877
  }
878
+ .grow {
879
+ flex-grow: 1;
880
+ }
878
881
  .table-fixed {
879
882
  table-layout: fixed;
880
883
  }
@@ -1861,6 +1864,12 @@
1861
1864
  .whitespace-nowrap {
1862
1865
  white-space: nowrap;
1863
1866
  }
1867
+ .text-amber-400 {
1868
+ color: var(--color-amber-400);
1869
+ }
1870
+ .text-amber-500 {
1871
+ color: var(--color-amber-500);
1872
+ }
1864
1873
  .text-amber-600 {
1865
1874
  color: var(--color-amber-600);
1866
1875
  }
@@ -2175,6 +2184,10 @@
2175
2184
  --tw-ring-color: color-mix(in oklab, var(--color-white) 80%, transparent);
2176
2185
  }
2177
2186
  }
2187
+ .outline {
2188
+ outline-style: var(--tw-outline-style);
2189
+ outline-width: 1px;
2190
+ }
2178
2191
  .blur {
2179
2192
  --tw-blur: blur(8px);
2180
2193
  filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
@@ -3156,6 +3169,11 @@
3156
3169
  grid-template-columns: repeat(4, minmax(0, 1fr));
3157
3170
  }
3158
3171
  }
3172
+ .md\:grid-cols-5 {
3173
+ @media (width >= 48rem) {
3174
+ grid-template-columns: repeat(5, minmax(0, 1fr));
3175
+ }
3176
+ }
3159
3177
  .lg\:grid-cols-2 {
3160
3178
  @media (width >= 64rem) {
3161
3179
  grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -6820,11 +6838,6 @@
6820
6838
  }
6821
6839
  }
6822
6840
  }
6823
- @property --tw-outline-style {
6824
- syntax: "*";
6825
- inherits: false;
6826
- initial-value: solid;
6827
- }
6828
6841
  @keyframes spin {
6829
6842
  to {
6830
6843
  transform: rotate(360deg);
@@ -21466,6 +21479,11 @@ input[type='number'] {
21466
21479
  inherits: false;
21467
21480
  initial-value: 0 0 #0000;
21468
21481
  }
21482
+ @property --tw-outline-style {
21483
+ syntax: "*";
21484
+ inherits: false;
21485
+ initial-value: solid;
21486
+ }
21469
21487
  @property --tw-blur {
21470
21488
  syntax: "*";
21471
21489
  inherits: false;
@@ -21727,6 +21745,7 @@ input[type='number'] {
21727
21745
  --tw-ring-offset-width: 0px;
21728
21746
  --tw-ring-offset-color: #fff;
21729
21747
  --tw-ring-offset-shadow: 0 0 #0000;
21748
+ --tw-outline-style: solid;
21730
21749
  --tw-blur: initial;
21731
21750
  --tw-brightness: initial;
21732
21751
  --tw-contrast: initial;
@@ -21758,7 +21777,6 @@ input[type='number'] {
21758
21777
  --tw-scale-y: 1;
21759
21778
  --tw-scale-z: 1;
21760
21779
  --tw-content: "";
21761
- --tw-outline-style: solid;
21762
21780
  }
21763
21781
  }
21764
21782
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@powerhousedao/contributor-billing",
3
3
  "description": "Document models that help contributors of open organisations get paid anonymously for their work on a monthly basis.",
4
- "version": "1.0.0-dev.19",
4
+ "version": "1.0.0-dev.20",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
7
7
  "type": "git",