@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.
- package/dist/editors/builder-team-admin/components/FolderTree.d.ts.map +1 -1
- package/dist/editors/builder-team-admin/components/FolderTree.js +8 -6
- package/dist/editors/contributor-billing/components/BillingOverview.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/BillingOverview.js +63 -3
- package/dist/editors/contributor-billing/components/DocumentDropZone.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/DocumentDropZone.js +10 -1
- package/dist/editors/contributor-billing/components/EmptyState.d.ts +7 -2
- package/dist/editors/contributor-billing/components/EmptyState.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/EmptyState.js +3 -8
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.js +6 -0
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.js +20 -1
- package/dist/editors/contributor-billing/hooks/useDocumentAutoPlacement.d.ts.map +1 -1
- package/dist/editors/contributor-billing/hooks/useDocumentAutoPlacement.js +199 -1
- package/dist/style.css +24 -6
- package/package.json +1 -1
|
@@ -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,
|
|
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
|
|
274
|
+
// Get the isOperator flag and profile name from the builder profile state
|
|
275
|
+
const builderProfileState = useMemo(() => {
|
|
276
276
|
if (!builderProfileDocument)
|
|
277
|
-
return
|
|
278
|
-
|
|
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:
|
|
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":"
|
|
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-
|
|
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 })] })
|
|
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,
|
|
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
|
-
|
|
2
|
-
|
|
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":"
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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":"
|
|
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;
|
|
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":"
|
|
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.
|
|
4
|
+
"version": "1.0.0-dev.20",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|