@powerhousedao/contributor-billing 0.1.36 → 0.1.39
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/document-models/account-transactions/gen/schema/types.d.ts +3 -3
- package/dist/document-models/account-transactions/gen/schema/types.d.ts.map +1 -1
- package/dist/document-models/accounts/gen/schema/types.d.ts +7 -7
- package/dist/document-models/accounts/gen/schema/types.d.ts.map +1 -1
- package/dist/document-models/billing-statement/gen/document-schema.d.ts +16 -16
- package/dist/document-models/billing-statement/gen/document-schema.d.ts.map +1 -1
- package/dist/document-models/billing-statement/gen/schema/types.d.ts +5 -5
- package/dist/document-models/billing-statement/gen/schema/types.d.ts.map +1 -1
- package/dist/document-models/expense-report/gen/document-model.js +1 -1
- package/dist/document-models/expense-report/gen/document-schema.d.ts +16 -16
- package/dist/document-models/expense-report/gen/ph-factories.d.ts.map +1 -1
- package/dist/document-models/expense-report/gen/ph-factories.js +5 -0
- package/dist/document-models/expense-report/gen/schema/types.d.ts +2 -2
- package/dist/document-models/expense-report/gen/schema/types.d.ts.map +1 -1
- package/dist/document-models/expense-report/gen/schema/zod.d.ts.map +1 -1
- package/dist/document-models/expense-report/gen/schema/zod.js +10 -30
- package/dist/document-models/expense-report/gen/utils.d.ts.map +1 -1
- package/dist/document-models/expense-report/gen/utils.js +5 -0
- package/dist/document-models/expense-report/src/reducers/wallet.d.ts.map +1 -1
- package/dist/document-models/expense-report/src/reducers/wallet.js +61 -0
- package/dist/document-models/invoice/gen/document-schema.d.ts +32 -32
- package/dist/document-models/invoice/gen/document-schema.d.ts.map +1 -1
- package/dist/document-models/invoice/gen/schema/types.d.ts +10 -10
- package/dist/document-models/invoice/gen/schema/types.d.ts.map +1 -1
- package/dist/document-models/invoice/gen/schema/zod.d.ts.map +1 -1
- package/dist/document-models/service-offering/gen/document-schema.d.ts +16 -16
- package/dist/document-models/service-offering/gen/document-schema.d.ts.map +1 -1
- package/dist/document-models/service-offering/gen/schema/types.d.ts +11 -11
- package/dist/document-models/service-offering/gen/schema/types.d.ts.map +1 -1
- package/dist/document-models/service-subscriptions/gen/schema/types.d.ts +6 -6
- package/dist/document-models/service-subscriptions/gen/schema/types.d.ts.map +1 -1
- package/dist/document-models/snapshot-report/gen/document-schema.d.ts.map +1 -1
- package/dist/document-models/snapshot-report/gen/schema/types.d.ts +8 -8
- package/dist/document-models/snapshot-report/gen/schema/types.d.ts.map +1 -1
- package/dist/document-models/snapshot-report/src/reducers/configuration.d.ts.map +1 -1
- package/dist/document-models/snapshot-report/src/reducers/configuration.js +1 -1
- package/dist/editors/accounts-editor/components/AccountForm.d.ts.map +1 -1
- package/dist/editors/accounts-editor/components/AccountForm.js +3 -1
- package/dist/editors/builder-team-admin/components/FolderTree.d.ts.map +1 -1
- package/dist/editors/builder-team-admin/components/FolderTree.js +2 -2
- package/dist/editors/contributor-billing/components/AddMonthButton.d.ts +5 -0
- package/dist/editors/contributor-billing/components/AddMonthButton.d.ts.map +1 -0
- package/dist/editors/contributor-billing/components/AddMonthButton.js +56 -0
- package/dist/editors/contributor-billing/components/BillingOverview.d.ts +10 -0
- package/dist/editors/contributor-billing/components/BillingOverview.d.ts.map +1 -0
- package/dist/editors/contributor-billing/components/BillingOverview.js +117 -0
- package/dist/editors/contributor-billing/components/DashboardHome.d.ts +11 -0
- package/dist/editors/contributor-billing/components/DashboardHome.d.ts.map +1 -0
- package/dist/editors/contributor-billing/components/DashboardHome.js +51 -0
- package/dist/editors/contributor-billing/components/DriveContents.d.ts +8 -2
- package/dist/editors/contributor-billing/components/DriveContents.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/DriveContents.js +24 -10
- package/dist/editors/contributor-billing/components/DriveExplorer.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/DriveExplorer.js +18 -3
- package/dist/editors/contributor-billing/components/FolderTree.d.ts +19 -9
- package/dist/editors/contributor-billing/components/FolderTree.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/FolderTree.js +200 -103
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts +8 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.js +5 -3
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.d.ts +6 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.js +14 -6
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.d.ts +6 -2
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.js +68 -19
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.d.ts +10 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.js +145 -32
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableRow.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableRow.js +6 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableSection.d.ts +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableSection.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableSection.js +33 -7
- package/dist/editors/contributor-billing/components/MonthOverview.d.ts +12 -0
- package/dist/editors/contributor-billing/components/MonthOverview.d.ts.map +1 -0
- package/dist/editors/contributor-billing/components/MonthOverview.js +35 -0
- package/dist/editors/contributor-billing/components/MonthlyReporting.d.ts +13 -0
- package/dist/editors/contributor-billing/components/MonthlyReporting.d.ts.map +1 -0
- package/dist/editors/contributor-billing/components/MonthlyReporting.js +90 -0
- package/dist/editors/contributor-billing/components/ReportingView.d.ts +10 -0
- package/dist/editors/contributor-billing/components/ReportingView.d.ts.map +1 -0
- package/dist/editors/contributor-billing/components/ReportingView.js +112 -0
- package/dist/editors/contributor-billing/config.js +1 -1
- package/dist/editors/contributor-billing/hooks/useBillingFolderStructure.d.ts +54 -0
- package/dist/editors/contributor-billing/hooks/useBillingFolderStructure.d.ts.map +1 -0
- package/dist/editors/contributor-billing/hooks/useBillingFolderStructure.js +145 -0
- package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts +3 -1
- package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts.map +1 -1
- package/dist/editors/expense-report/components/AddBillingStatementModal.js +23 -7
- package/dist/editors/expense-report/components/AggregatedExpensesTable.js +2 -2
- package/dist/editors/expense-report/components/ExpenseReportPDF.js +2 -2
- package/dist/editors/expense-report/editor.d.ts.map +1 -1
- package/dist/editors/expense-report/editor.js +70 -14
- package/dist/editors/expense-report/hooks/useSyncWallet.js +9 -9
- package/dist/editors/invoice/ingestPDF.js +1 -1
- package/dist/editors/invoice/invoiceToGnosis.js +2 -2
- package/dist/editors/invoice/requestFinance.js +2 -2
- package/dist/editors/invoice/uploadPdfChunked.js +2 -2
- package/dist/editors/snapshot-report-editor/editor.d.ts.map +1 -1
- package/dist/editors/snapshot-report-editor/editor.js +26 -5
- package/dist/scripts/invoice/pdfToClaudeAI.d.ts.map +1 -1
- package/dist/scripts/invoice/pdfToClaudeAI.js +3 -1
- package/dist/style.css +85 -0
- package/dist/subgraphs/budget-statements/index.d.ts +11 -0
- package/dist/subgraphs/budget-statements/index.d.ts.map +1 -0
- package/dist/subgraphs/budget-statements/index.js +11 -0
- package/dist/subgraphs/budget-statements/resolvers.d.ts +3 -0
- package/dist/subgraphs/budget-statements/resolvers.d.ts.map +1 -0
- package/dist/subgraphs/budget-statements/resolvers.js +335 -0
- package/dist/subgraphs/budget-statements/schema.d.ts +3 -0
- package/dist/subgraphs/budget-statements/schema.d.ts.map +1 -0
- package/dist/subgraphs/budget-statements/schema.js +131 -0
- package/dist/subgraphs/index.d.ts +1 -0
- package/dist/subgraphs/index.d.ts.map +1 -1
- package/dist/subgraphs/index.js +1 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import React, { useMemo
|
|
2
|
+
import React, { useMemo } from "react";
|
|
3
3
|
import { addDocument, dispatchActions, setSelectedNode, useSelectedDrive, useDocumentsInSelectedDrive, } from "@powerhousedao/reactor-browser";
|
|
4
4
|
import { toast } from "@powerhousedao/design-system/connect";
|
|
5
5
|
import { actions as invoiceActions } from "../../../../document-models/invoice/index.js";
|
|
@@ -48,18 +48,42 @@ const statusColors = {
|
|
|
48
48
|
};
|
|
49
49
|
// Table header component
|
|
50
50
|
const TableHeader = ({ showIssuer = true, showBillingStatement = false, }) => (_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: showIssuer ? "Issuer" : "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" }), showBillingStatement && (_jsx("th", { className: "px-2 py-2 text-center", children: "Billing Statement" })), _jsx("th", { className: "px-2 py-2 rounded-tr-sm text-center", children: "Exported" })] }) }));
|
|
51
|
-
export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentModels, onSelectDocumentModel, getDocDispatcher, selectedStatuses, onStatusChange, onRowSelection, canExportSelectedRows, }) => {
|
|
51
|
+
export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentModels, onSelectDocumentModel, getDocDispatcher, selectedStatuses, onStatusChange, onRowSelection, canExportSelectedRows, monthName, reportingFolderId, }) => {
|
|
52
52
|
const [selectedDrive] = useSelectedDrive();
|
|
53
|
-
// State to track when export actions complete, triggering page refresh
|
|
54
|
-
const [actionsCompleted, setActionsCompleted] = useState(0);
|
|
55
53
|
// Get documents directly from the hook - this will automatically update when documents change
|
|
56
|
-
const
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
54
|
+
const documentsInDrive = useDocumentsInSelectedDrive() || [];
|
|
55
|
+
// Check if an expense report already exists for the current month
|
|
56
|
+
const existingExpenseReport = useMemo(() => {
|
|
57
|
+
if (!monthName || !documentsInDrive)
|
|
58
|
+
return null;
|
|
59
|
+
const monthLower = monthName.toLowerCase();
|
|
60
|
+
return documentsInDrive.find((doc) => doc.header.documentType === "powerhouse/expense-report" &&
|
|
61
|
+
doc.header.name?.toLowerCase().includes(monthLower));
|
|
62
|
+
}, [documentsInDrive, monthName]);
|
|
63
|
+
// Build a set of file IDs from the files prop for quick lookup
|
|
64
|
+
const fileIds = useMemo(() => {
|
|
65
|
+
return new Set(files.map((f) => f.id));
|
|
66
|
+
}, [files]);
|
|
67
|
+
// Build a map of document IDs to documents for quick lookup
|
|
68
|
+
const documentsById = useMemo(() => {
|
|
69
|
+
const map = new Map();
|
|
70
|
+
for (const doc of documentsInDrive) {
|
|
71
|
+
map.set(doc.header.id, doc);
|
|
61
72
|
}
|
|
62
|
-
|
|
73
|
+
return map;
|
|
74
|
+
}, [documentsInDrive]);
|
|
75
|
+
// Filter documents to only those in the current folder (matching the files prop)
|
|
76
|
+
const allDocuments = useMemo(() => {
|
|
77
|
+
const filtered = documentsInDrive.filter((doc) => fileIds.has(doc.header.id));
|
|
78
|
+
return filtered;
|
|
79
|
+
}, [documentsInDrive, fileIds]);
|
|
80
|
+
// Find files that are in the folder but don't have document content loaded yet
|
|
81
|
+
// These are "loading" files that need to show a placeholder
|
|
82
|
+
const loadingFileIds = useMemo(() => {
|
|
83
|
+
return files
|
|
84
|
+
.filter((f) => f.documentType === "powerhouse/invoice" && !documentsById.has(f.id))
|
|
85
|
+
.map((f) => f.id);
|
|
86
|
+
}, [files, documentsById]);
|
|
63
87
|
// Helper function to map invoice document to InvoiceRowData
|
|
64
88
|
const mapInvoiceToRowData = (doc) => {
|
|
65
89
|
const state = doc.state;
|
|
@@ -212,8 +236,12 @@ export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentMod
|
|
|
212
236
|
return;
|
|
213
237
|
}
|
|
214
238
|
const invoiceState = invoiceDoc.state.global;
|
|
239
|
+
// Get the target folder (same as invoice's folder)
|
|
240
|
+
const targetFolder = invoiceFile?.parentFolder;
|
|
215
241
|
try {
|
|
216
|
-
|
|
242
|
+
// Create billing statement directly in the target folder (avoids race condition with move)
|
|
243
|
+
const createdNode = await addDocument(selectedDrive?.header.id || "", `bill-${invoiceFile?.name || id}`, "powerhouse/billing-statement", targetFolder ?? undefined, // Create directly in the payments folder
|
|
244
|
+
undefined, undefined, "powerhouse-billing-statement-editor");
|
|
217
245
|
if (!createdNode?.id) {
|
|
218
246
|
toast("Failed to create billing statement", { type: "error" });
|
|
219
247
|
return;
|
|
@@ -266,7 +294,6 @@ export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentMod
|
|
|
266
294
|
}
|
|
267
295
|
if (tagActions.length > 0) {
|
|
268
296
|
await dispatchActions(tagActions, createdNode.id);
|
|
269
|
-
window.location.reload();
|
|
270
297
|
}
|
|
271
298
|
toast("Billing statement created successfully", { type: "success" });
|
|
272
299
|
}
|
|
@@ -298,8 +325,6 @@ export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentMod
|
|
|
298
325
|
], invoice.header.id);
|
|
299
326
|
}
|
|
300
327
|
setSelected({});
|
|
301
|
-
// Trigger page refresh after all actions complete
|
|
302
|
-
setActionsCompleted((prev) => prev + 1);
|
|
303
328
|
}
|
|
304
329
|
catch (error) {
|
|
305
330
|
console.error("Error exporting invoices:", error);
|
|
@@ -342,8 +367,28 @@ export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentMod
|
|
|
342
367
|
}
|
|
343
368
|
}
|
|
344
369
|
};
|
|
345
|
-
// Check for expense report document
|
|
346
|
-
|
|
370
|
+
// Check for expense report document in the Reporting folder (not Payments folder)
|
|
371
|
+
// We need to look at documentsInDrive and check if any expense report is in the reportingFolderId
|
|
372
|
+
const expenseReportDoc = useMemo(() => {
|
|
373
|
+
if (!reportingFolderId || !selectedDrive)
|
|
374
|
+
return undefined;
|
|
375
|
+
// Get all nodes from the drive to check parent folders
|
|
376
|
+
const nodes = selectedDrive.state.global.nodes;
|
|
377
|
+
// Find expense report documents that are in the reporting folder
|
|
378
|
+
for (const doc of documentsInDrive) {
|
|
379
|
+
if (doc.header.documentType === "powerhouse/expense-report") {
|
|
380
|
+
// Find the file node to check its parent folder
|
|
381
|
+
const fileNode = nodes.find((n) => n.id === doc.header.id);
|
|
382
|
+
if (fileNode &&
|
|
383
|
+
"parentFolder" in fileNode &&
|
|
384
|
+
fileNode.parentFolder === reportingFolderId) {
|
|
385
|
+
// Return the file node (for consistency with the rest of the code)
|
|
386
|
+
return fileNode;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return undefined;
|
|
391
|
+
}, [documentsInDrive, reportingFolderId, selectedDrive]);
|
|
347
392
|
// Check if billing statements exist - memoized to update when allDocuments changes
|
|
348
393
|
const hasBillingStatements = useMemo(() => {
|
|
349
394
|
return allDocuments.some((doc) => doc.header.documentType === "powerhouse/billing-statement");
|
|
@@ -356,7 +401,8 @@ export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentMod
|
|
|
356
401
|
else {
|
|
357
402
|
const expenseReportModel = filteredDocumentModels.find((model) => model.id === "powerhouse/expense-report");
|
|
358
403
|
if (expenseReportModel) {
|
|
359
|
-
const createdNode = await addDocument(selectedDrive?.header.id || "", "expense-report", "powerhouse/expense-report",
|
|
404
|
+
const createdNode = await addDocument(selectedDrive?.header.id || "", "expense-report", "powerhouse/expense-report", reportingFolderId, // Create in the Reporting folder (sibling of Payments)
|
|
405
|
+
undefined, undefined, "powerhouse-expense-report-editor");
|
|
360
406
|
if (createdNode?.id) {
|
|
361
407
|
setSelectedNode(createdNode.id);
|
|
362
408
|
}
|
|
@@ -382,7 +428,7 @@ export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentMod
|
|
|
382
428
|
const { showIssuer = true, showBillingStatement = false, showCreateButton = false, } = options || {};
|
|
383
429
|
return (_jsx(InvoiceTableSection, { title: title, count: data.length, color: statusColors[status] || statusColors.OTHER, onSelectDocumentModel: showCreateButton ? onSelectDocumentModel : undefined, filteredDocumentModels: showCreateButton ? filteredDocumentModels : undefined, children: _jsxs("table", { className: "w-full text-sm rounded-sm border-separate border-spacing-0 border border-gray-300 overflow-hidden", children: [_jsx(TableHeader, { showIssuer: showIssuer, showBillingStatement: showBillingStatement }), _jsx("tbody", { children: data.map((row) => (_jsx(InvoiceTableRow, { files: files, row: row, isSelected: !!selected[row.id], onSelect: (checked) => onRowSelection(row.id, checked, row.status), onCreateBillingStatement: handleCreateBillingStatement, billingDocStates: billingDocStates, showIssuerColumn: showIssuer, showBillingStatementColumn: showBillingStatement }, row.id))) })] }) }));
|
|
384
430
|
};
|
|
385
|
-
return (_jsxs("div", { className: "contributor-billing-table w-full h-full bg-white rounded-lg p-4 border border-gray-200 shadow-sm mt-4 overflow-x-auto", children: [_jsx(HeaderControls, { statusOptions: statusOptions, selectedStatuses: selectedStatuses, onStatusChange: onStatusChange, onExport: handleCSVExport, onExpenseReportExport: handleExpenseReportExport, createIntegrationsDocument: createIntegrationsDocument, integrationsDoc: integrationsDoc, hasBillingStatements: hasBillingStatements, expenseReportDoc: expenseReportDoc, onCreateOrOpenExpenseReport: handleCreateOrOpenExpenseReport, selected: selected, handleCreateBillingStatement: handleCreateBillingStatement, setSelected: setSelected, invoices: invoicesDocs, billingStatements: billingStatementDocs, canExportSelectedRows: canExportSelectedRows }), renderSection("DRAFT", "Draft", draft, {
|
|
431
|
+
return (_jsxs("div", { className: "contributor-billing-table w-full h-full bg-white rounded-lg p-4 border border-gray-200 shadow-sm mt-4 overflow-x-auto", children: [_jsx(HeaderControls, { statusOptions: statusOptions, selectedStatuses: selectedStatuses, onStatusChange: onStatusChange, onExport: handleCSVExport, onExpenseReportExport: handleExpenseReportExport, createIntegrationsDocument: createIntegrationsDocument, integrationsDoc: integrationsDoc, hasBillingStatements: hasBillingStatements, expenseReportDoc: expenseReportDoc, existingExpenseReportForMonth: existingExpenseReport, onCreateOrOpenExpenseReport: handleCreateOrOpenExpenseReport, selected: selected, handleCreateBillingStatement: handleCreateBillingStatement, setSelected: setSelected, invoices: invoicesDocs, billingStatements: billingStatementDocs, canExportSelectedRows: canExportSelectedRows }), renderSection("DRAFT", "Draft", draft, {
|
|
386
432
|
showIssuer: false,
|
|
387
433
|
showCreateButton: true,
|
|
388
434
|
}), renderSection("ISSUED", "Issued", issued, {
|
|
@@ -395,5 +441,8 @@ export const InvoiceTable = ({ files, selected, setSelected, filteredDocumentMod
|
|
|
395
441
|
showBillingStatement: true,
|
|
396
442
|
}), renderSection("PAYMENTISSUE", "Payment Issue", paymentIssue, {
|
|
397
443
|
showBillingStatement: true,
|
|
398
|
-
}), renderSection("PAYMENTCLOSED", "Payment Closed", paymentClosed), renderSection("REJECTED", "Rejected", rejected), renderSection("OTHER", "Other", otherInvoices)] }))
|
|
444
|
+
}), 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) => {
|
|
445
|
+
const file = files.find((f) => f.id === id);
|
|
446
|
+
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));
|
|
447
|
+
}) })] }) }))] }));
|
|
399
448
|
};
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
interface InvoiceTableContainerProps {
|
|
2
|
+
/** The ID of the payments folder to filter invoices by */
|
|
3
|
+
folderId: string;
|
|
4
|
+
/** The month name (e.g., "January 2026") for checking existing reports */
|
|
5
|
+
monthName?: string;
|
|
6
|
+
/** The sibling Reporting folder ID where expense reports should be created */
|
|
7
|
+
reportingFolderId?: string;
|
|
8
|
+
}
|
|
1
9
|
/**
|
|
2
10
|
* Container that renders the InvoiceTable.
|
|
3
11
|
* Uses useNodesInSelectedDriveOrFolder pattern to avoid freeze issues.
|
|
4
12
|
*/
|
|
5
|
-
export declare function InvoiceTableContainer(): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare function InvoiceTableContainer({ folderId, monthName, reportingFolderId, }: InvoiceTableContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export {};
|
|
6
15
|
//# sourceMappingURL=InvoiceTableContainer.d.ts.map
|
package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoiceTableContainer.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"InvoiceTableContainer.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.tsx"],"names":[],"mappings":"AAmBA,UAAU,0BAA0B;IAClC,0DAA0D;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,EACR,SAAS,EACT,iBAAiB,GAClB,EAAE,0BAA0B,2CAiT5B"}
|
|
@@ -1,64 +1,154 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback, useRef } from "react";
|
|
3
|
-
import {
|
|
2
|
+
import { useState, useCallback, useRef, useMemo, useEffect } from "react";
|
|
3
|
+
import { isFileNodeKind, useDocumentModelModules, useDocumentsInSelectedDrive, useSelectedDrive, useOnDropFile, dispatchActions, addDocument, setSelectedNode, } from "@powerhousedao/reactor-browser";
|
|
4
|
+
import { toast } from "@powerhousedao/design-system/connect";
|
|
5
|
+
import { moveNode } from "document-drive";
|
|
4
6
|
import { InvoiceTable } from "./InvoiceTable.js";
|
|
5
7
|
/**
|
|
6
8
|
* Container that renders the InvoiceTable.
|
|
7
9
|
* Uses useNodesInSelectedDriveOrFolder pattern to avoid freeze issues.
|
|
8
10
|
*/
|
|
9
|
-
export function InvoiceTableContainer() {
|
|
11
|
+
export function InvoiceTableContainer({ folderId, monthName, reportingFolderId, }) {
|
|
10
12
|
const [selected, setSelected] = useState({});
|
|
11
13
|
const [selectedStatuses, setSelectedStatuses] = useState([]);
|
|
12
14
|
const containerRef = useRef(null);
|
|
13
15
|
const pendingFilesRef = useRef(new Set());
|
|
16
|
+
// Track file names that were dropped into THIS specific folder view
|
|
17
|
+
// Using state (not ref) so changes trigger re-renders and useEffect runs
|
|
18
|
+
const [droppedFileNames, setDroppedFileNames] = useState(() => new Map());
|
|
14
19
|
const documentModelModules = useDocumentModelModules();
|
|
15
|
-
|
|
16
|
-
const nodes = useNodesInSelectedDriveOrFolder();
|
|
17
|
-
const fileNodes = nodes.filter((n) => isFileNodeKind(n));
|
|
20
|
+
const [driveDocument] = useSelectedDrive();
|
|
18
21
|
const allDocuments = useDocumentsInSelectedDrive();
|
|
22
|
+
// Filter file nodes to only those in the specific payments folder
|
|
23
|
+
// Access drive nodes directly (same approach as HeaderStats)
|
|
24
|
+
const fileNodes = useMemo(() => {
|
|
25
|
+
if (!driveDocument)
|
|
26
|
+
return [];
|
|
27
|
+
const nodes = driveDocument.state.global.nodes;
|
|
28
|
+
const fileNodesInFolder = nodes.filter((n) => isFileNodeKind(n) && n.parentFolder === folderId);
|
|
29
|
+
return fileNodesInFolder;
|
|
30
|
+
}, [driveDocument, folderId]);
|
|
19
31
|
// Get the drop file handler
|
|
20
32
|
const onDropFile = useOnDropFile();
|
|
21
33
|
// Handle file drop
|
|
34
|
+
const driveId = driveDocument?.header.id;
|
|
35
|
+
// Watch for files that we explicitly dropped and move them if needed
|
|
36
|
+
// This handles the case where conflict resolution creates files at root
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!driveDocument || !driveId || !folderId)
|
|
39
|
+
return;
|
|
40
|
+
if (droppedFileNames.size === 0)
|
|
41
|
+
return;
|
|
42
|
+
const nodes = driveDocument.state.global.nodes;
|
|
43
|
+
// Check each file name we're tracking
|
|
44
|
+
for (const [fileName, targetFolderId] of droppedFileNames.entries()) {
|
|
45
|
+
// Only process if this is the target folder for this file
|
|
46
|
+
if (targetFolderId !== folderId)
|
|
47
|
+
continue;
|
|
48
|
+
// Find the file by name that's not in the correct folder
|
|
49
|
+
// Also match files with similar names (e.g., "freshInvoice (1)" matches "freshInvoice")
|
|
50
|
+
const fileNode = nodes.find((n) => {
|
|
51
|
+
if (!isFileNodeKind(n))
|
|
52
|
+
return false;
|
|
53
|
+
const fn = n;
|
|
54
|
+
// Only match invoice documents
|
|
55
|
+
if (fn.documentType !== "powerhouse/invoice")
|
|
56
|
+
return false;
|
|
57
|
+
// Check if file is not in the target folder (at root or elsewhere)
|
|
58
|
+
if (fn.parentFolder === targetFolderId)
|
|
59
|
+
return false;
|
|
60
|
+
// Match exact name or name with conflict suffix like "(1)"
|
|
61
|
+
const nameMatches = fn.name === fileName || fn.name.startsWith(fileName + " (");
|
|
62
|
+
return nameMatches;
|
|
63
|
+
});
|
|
64
|
+
if (fileNode) {
|
|
65
|
+
// Remove from tracking since we found it
|
|
66
|
+
setDroppedFileNames((prev) => {
|
|
67
|
+
const next = new Map(prev);
|
|
68
|
+
next.delete(fileName);
|
|
69
|
+
return next;
|
|
70
|
+
});
|
|
71
|
+
// Move the file to the correct folder
|
|
72
|
+
dispatchActions(moveNode({
|
|
73
|
+
srcFolder: fileNode.id,
|
|
74
|
+
targetParentFolder: targetFolderId,
|
|
75
|
+
}), driveId).catch((error) => {
|
|
76
|
+
console.error("[InvoiceTableContainer] Move failed:", error);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}, [driveDocument, driveId, folderId, droppedFileNames]);
|
|
22
81
|
const handleDrop = useCallback(async (event) => {
|
|
23
82
|
event.preventDefault();
|
|
24
83
|
event.stopPropagation();
|
|
25
84
|
const files = Array.from(event.dataTransfer.files);
|
|
26
85
|
if (files.length === 0)
|
|
27
86
|
return;
|
|
28
|
-
if (!onDropFile)
|
|
87
|
+
if (!onDropFile || !driveId)
|
|
88
|
+
return;
|
|
89
|
+
// Filter to only accept .phd files (Powerhouse document archives)
|
|
90
|
+
const phdFiles = files.filter((file) => file.name.endsWith(".phd"));
|
|
91
|
+
const rejectedFiles = files.filter((file) => !file.name.endsWith(".phd"));
|
|
92
|
+
// Show error for rejected files
|
|
93
|
+
if (rejectedFiles.length > 0) {
|
|
94
|
+
const rejectedNames = rejectedFiles.map((f) => f.name).join(", ");
|
|
95
|
+
toast(`Only .phd files (Powerhouse documents) can be dropped here. Rejected: ${rejectedNames}`, { type: "error" });
|
|
96
|
+
}
|
|
97
|
+
if (phdFiles.length === 0)
|
|
29
98
|
return;
|
|
30
99
|
// Track all files being processed
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
100
|
+
const fileBaseNames = [];
|
|
101
|
+
phdFiles.forEach((file) => {
|
|
102
|
+
pendingFilesRef.current.add(file);
|
|
103
|
+
const fileBaseName = file.name.replace(/\.phd$/, "");
|
|
104
|
+
fileBaseNames.push(fileBaseName);
|
|
105
|
+
});
|
|
106
|
+
// Update state to track file names -> target folder
|
|
107
|
+
// This triggers useEffect to check for misplaced files
|
|
108
|
+
setDroppedFileNames((prev) => {
|
|
109
|
+
const next = new Map(prev);
|
|
110
|
+
fileBaseNames.forEach((name) => next.set(name, folderId));
|
|
111
|
+
return next;
|
|
112
|
+
});
|
|
113
|
+
// Process all files - React state updates automatically via hooks
|
|
114
|
+
const filePromises = phdFiles.map(async (file) => {
|
|
115
|
+
const fileBaseName = file.name.replace(/\.phd$/, "");
|
|
34
116
|
try {
|
|
35
|
-
await onDropFile(file, (progress) => {
|
|
36
|
-
|
|
37
|
-
|
|
117
|
+
const fileNode = await onDropFile(file, (progress) => {
|
|
118
|
+
if (progress.stage === "complete" ||
|
|
119
|
+
progress.stage === "failed") {
|
|
38
120
|
pendingFilesRef.current.delete(file);
|
|
39
|
-
// If all files are done, reload the page
|
|
40
|
-
if (pendingFilesRef.current.size === 0) {
|
|
41
|
-
window.location.reload();
|
|
42
|
-
}
|
|
43
121
|
}
|
|
44
122
|
});
|
|
123
|
+
// Move the uploaded file to the correct folder
|
|
124
|
+
if (fileNode && folderId) {
|
|
125
|
+
try {
|
|
126
|
+
await dispatchActions(moveNode({
|
|
127
|
+
srcFolder: fileNode.id,
|
|
128
|
+
targetParentFolder: folderId,
|
|
129
|
+
}), driveId);
|
|
130
|
+
// Successfully moved, remove from tracking
|
|
131
|
+
setDroppedFileNames((prev) => {
|
|
132
|
+
const next = new Map(prev);
|
|
133
|
+
next.delete(fileBaseName);
|
|
134
|
+
return next;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch (moveError) {
|
|
138
|
+
console.error("[InvoiceTableContainer] Move failed:", moveError);
|
|
139
|
+
// Keep in tracking so useEffect can retry
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// If fileNode is null (conflict resolution), keep in tracking so useEffect can handle it
|
|
45
143
|
}
|
|
46
144
|
catch (error) {
|
|
47
145
|
console.error("Error dropping file:", error);
|
|
48
146
|
pendingFilesRef.current.delete(file);
|
|
49
|
-
//
|
|
50
|
-
if (pendingFilesRef.current.size === 0) {
|
|
51
|
-
window.location.reload();
|
|
52
|
-
}
|
|
147
|
+
// Keep in tracking - conflict resolution might still create the file
|
|
53
148
|
}
|
|
54
149
|
});
|
|
55
|
-
// Wait for all files to complete
|
|
56
150
|
await Promise.allSettled(filePromises);
|
|
57
|
-
|
|
58
|
-
if (pendingFilesRef.current.size === 0) {
|
|
59
|
-
window.location.reload();
|
|
60
|
-
}
|
|
61
|
-
}, [onDropFile]);
|
|
151
|
+
}, [onDropFile, driveId, folderId]);
|
|
62
152
|
const handleDragOver = useCallback((event) => {
|
|
63
153
|
event.preventDefault();
|
|
64
154
|
event.stopPropagation();
|
|
@@ -83,10 +173,33 @@ export function InvoiceTableContainer() {
|
|
|
83
173
|
const getDocDispatcher = useCallback((_id) => {
|
|
84
174
|
return null;
|
|
85
175
|
}, []);
|
|
86
|
-
// Handle document model selection
|
|
87
|
-
const onSelectDocumentModel = useCallback((documentModel) => {
|
|
88
|
-
|
|
89
|
-
|
|
176
|
+
// Handle document model selection - create invoice directly in the payments folder
|
|
177
|
+
const onSelectDocumentModel = useCallback(async (documentModel, name) => {
|
|
178
|
+
if (!driveId) {
|
|
179
|
+
toast("No drive selected", { type: "error" });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
// Create a new invoice document directly in the payments folder
|
|
184
|
+
const createdNode = await addDocument(driveId, name, documentModel.id, folderId, // Create directly in the payments folder
|
|
185
|
+
undefined, undefined, "powerhouse-invoice-editor");
|
|
186
|
+
if (createdNode?.id) {
|
|
187
|
+
// Small delay to allow the drive state to sync before selecting the node
|
|
188
|
+
// This prevents the Sidebar from trying to find a node that doesn't exist yet
|
|
189
|
+
setTimeout(() => {
|
|
190
|
+
setSelectedNode(createdNode.id);
|
|
191
|
+
}, 100);
|
|
192
|
+
toast("Invoice created successfully", { type: "success" });
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
toast("Failed to create invoice", { type: "error" });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.error("Error creating invoice:", error);
|
|
200
|
+
toast("Failed to create invoice", { type: "error" });
|
|
201
|
+
}
|
|
202
|
+
}, [driveId, folderId]);
|
|
90
203
|
// Determine if CSV export should be enabled based on selected rows
|
|
91
204
|
const canExportSelectedRows = useCallback(() => {
|
|
92
205
|
const allowedStatuses = [
|
|
@@ -107,5 +220,5 @@ export function InvoiceTableContainer() {
|
|
|
107
220
|
return selectedRows.every((row) => allowedStatuses.includes(row.state.global
|
|
108
221
|
.status));
|
|
109
222
|
}, [selected, allDocuments]);
|
|
110
|
-
return (_jsx("div", { ref: containerRef, className: "w-full h-full", onDrop: handleDrop, onDragOver: handleDragOver, onDragEnter: handleDragEnter, children: _jsx(InvoiceTable, { files: fileNodes, selected: selected, setSelected: setSelected, filteredDocumentModels: documentModelModules || [], onSelectDocumentModel: onSelectDocumentModel, getDocDispatcher: getDocDispatcher, selectedStatuses: selectedStatuses, onStatusChange: handleStatusChange, onRowSelection: handleRowSelection, canExportSelectedRows: canExportSelectedRows }) }));
|
|
223
|
+
return (_jsx("div", { ref: containerRef, className: "w-full h-full", onDrop: handleDrop, onDragOver: handleDragOver, onDragEnter: handleDragEnter, children: _jsx(InvoiceTable, { files: fileNodes, selected: selected, setSelected: setSelected, filteredDocumentModels: documentModelModules || [], onSelectDocumentModel: onSelectDocumentModel, getDocDispatcher: getDocDispatcher, selectedStatuses: selectedStatuses, onStatusChange: handleStatusChange, onRowSelection: handleRowSelection, canExportSelectedRows: canExportSelectedRows, monthName: monthName, reportingFolderId: reportingFolderId }) }));
|
|
111
224
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoiceTableRow.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/InvoiceTableRow.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC;KAC/B,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,oBAAoB;IAC5B,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,GAAG,EAAE,cAAc,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,wBAAwB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;
|
|
1
|
+
{"version":3,"file":"InvoiceTableRow.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/InvoiceTableRow.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,iBAAiB,CAAC,EAAE,OAAO,EAAE,CAAC;KAC/B,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,oBAAoB;IAC5B,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,GAAG,EAAE,cAAc,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACrC,wBAAwB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;AA0CD,eAAO,MAAM,eAAe,GAAI,iIAS7B,oBAAoB,4CA2GtB,CAAC"}
|
|
@@ -34,6 +34,11 @@ const formatAmount = (amount) => {
|
|
|
34
34
|
maximumFractionDigits: 2,
|
|
35
35
|
});
|
|
36
36
|
};
|
|
37
|
+
/** Format a date string without timezone conversion */
|
|
38
|
+
const formatDateUTC = (dateString) => {
|
|
39
|
+
const date = new Date(dateString);
|
|
40
|
+
return date.toLocaleDateString(undefined, { timeZone: "UTC" });
|
|
41
|
+
};
|
|
37
42
|
export const InvoiceTableRow = ({ files, row, isSelected, onSelect, onCreateBillingStatement, billingDocStates, showIssuerColumn = true, showBillingStatementColumn = false, }) => {
|
|
38
43
|
// Simple computed values like old working code
|
|
39
44
|
const billingDoc = billingDocStates?.find((doc) => doc.contributor === row.id);
|
|
@@ -51,5 +56,5 @@ export const InvoiceTableRow = ({ files, row, isSelected, onSelect, onCreateBill
|
|
|
51
56
|
const canShowBillingStatementButton = showBillingStatementColumn &&
|
|
52
57
|
allowedStatuses.includes(row.status) &&
|
|
53
58
|
!billingFile;
|
|
54
|
-
return (_jsxs("tr", { className: "hover:bg-gray-50 transition-colors", children: [_jsx("td", { className: "px-2 py-2", children: _jsx("input", { type: "checkbox", checked: isSelected, onChange: (e) => onSelect(e.target.checked), className: "w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-2 focus:ring-blue-500" }) }), _jsx("td", { className: "py-1 px-2", children: showIssuerColumn ? (invoiceFile ? (_jsx(FileItem, { fileNode: invoiceFile, className: "h-10" })) : (_jsx("span", { className: "text-gray-500", children: row.issuer || "Unknown" }))) : invoiceFile ? (_jsx(FileItem, { fileNode: invoiceFile, className: "h-10" })) : (_jsx("span", { className: "text-gray-400", children: "-" })) }), _jsx("td", { className: "px-2 py-2 text-center text-sm text-gray-700", children: row.invoiceNo || "-" }), _jsx("td", { className: "px-2 py-2 text-center text-sm text-gray-700", children: row.issueDate ?
|
|
59
|
+
return (_jsxs("tr", { className: "hover:bg-gray-50 transition-colors", children: [_jsx("td", { className: "px-2 py-2", children: _jsx("input", { type: "checkbox", checked: isSelected, onChange: (e) => onSelect(e.target.checked), className: "w-4 h-4 rounded border-gray-300 text-blue-600 focus:ring-2 focus:ring-blue-500" }) }), _jsx("td", { className: "py-1 px-2", children: showIssuerColumn ? (invoiceFile ? (_jsx(FileItem, { fileNode: invoiceFile, className: "h-10" })) : (_jsx("span", { className: "text-gray-500", children: row.issuer || "Unknown" }))) : invoiceFile ? (_jsx(FileItem, { fileNode: invoiceFile, className: "h-10" })) : (_jsx("span", { className: "text-gray-400", children: "-" })) }), _jsx("td", { className: "px-2 py-2 text-center text-sm text-gray-700", children: row.invoiceNo || "-" }), _jsx("td", { className: "px-2 py-2 text-center text-sm text-gray-700", children: row.issueDate ? formatDateUTC(row.issueDate) : "-" }), _jsx("td", { className: "px-2 py-2 text-center text-sm text-gray-700", children: row.dueDate ? formatDateUTC(row.dueDate) : "-" }), _jsx("td", { className: "px-2 py-2 text-center text-sm text-gray-700", children: row.currency || "-" }), _jsx("td", { className: "px-2 py-2 text-center text-sm font-medium text-gray-800", children: formatAmount(row.amount) }), showBillingStatementColumn && (_jsx("td", { className: "px-2 py-2 text-center", children: canShowBillingStatementButton ? (_jsx("button", { type: "button", className: "bg-white border border-gray-300 rounded px-3 py-1 text-xs font-medium hover:bg-gray-50 transition-colors", onClick: () => onCreateBillingStatement?.(row.id), children: "Generate Billing Statement" })) : billingFile ? (_jsx(FileItem, { fileNode: billingFile, className: "h-10" })) : null })), _jsx("td", { className: "px-2 py-2 text-center", children: hasExportedData ? (_jsxs("div", { className: "flex flex-col items-center", children: [_jsx("span", { className: "text-green-600 text-sm font-medium", children: "Yes" }), _jsx("span", { className: "text-green-600 text-xs", children: formatTimestamp(row.exported.timestamp) })] })) : (_jsx("span", { className: "text-red-500 text-sm", children: "No" })) })] }));
|
|
55
60
|
};
|
|
@@ -4,7 +4,7 @@ interface InvoiceTableSectionProps {
|
|
|
4
4
|
count: number;
|
|
5
5
|
children: React.ReactNode;
|
|
6
6
|
color?: string;
|
|
7
|
-
onSelectDocumentModel?: (model: VetraDocumentModelModule) => void;
|
|
7
|
+
onSelectDocumentModel?: (model: VetraDocumentModelModule, name: string) => void;
|
|
8
8
|
filteredDocumentModels?: VetraDocumentModelModule[];
|
|
9
9
|
defaultExpanded?: boolean;
|
|
10
10
|
}
|
package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableSection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InvoiceTableSection.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/InvoiceTableSection.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE/E,UAAU,wBAAwB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"InvoiceTableSection.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/InvoiceTableSection.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE/E,UAAU,wBAAwB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB,CAAC,EAAE,CACtB,KAAK,EAAE,wBAAwB,EAC/B,IAAI,EAAE,MAAM,KACT,IAAI,CAAC;IACV,sBAAsB,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,mBAAmB,GAAI,oGAQjC,wBAAwB,4CAiJ1B,CAAC"}
|
|
@@ -1,16 +1,42 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback } from "react";
|
|
3
|
-
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
2
|
+
import { useState, useCallback, useRef, useEffect } from "react";
|
|
3
|
+
import { ChevronDown, ChevronRight, X } from "lucide-react";
|
|
4
4
|
export const InvoiceTableSection = ({ title, count, children, color = "bg-blue-100 text-blue-600", onSelectDocumentModel, filteredDocumentModels, defaultExpanded = true, }) => {
|
|
5
5
|
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
6
|
+
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
7
|
+
const [invoiceName, setInvoiceName] = useState("");
|
|
8
|
+
const inputRef = useRef(null);
|
|
6
9
|
const invoiceDocModel = filteredDocumentModels?.find((model) => model.id === "powerhouse/invoice");
|
|
7
10
|
const handleToggle = useCallback(() => {
|
|
8
11
|
setIsExpanded((prev) => !prev);
|
|
9
12
|
}, []);
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
const handleOpenModal = useCallback(() => {
|
|
14
|
+
setInvoiceName("");
|
|
15
|
+
setIsModalOpen(true);
|
|
16
|
+
}, []);
|
|
17
|
+
const handleCloseModal = useCallback(() => {
|
|
18
|
+
setIsModalOpen(false);
|
|
19
|
+
setInvoiceName("");
|
|
20
|
+
}, []);
|
|
21
|
+
const handleConfirmCreate = useCallback(() => {
|
|
22
|
+
if (invoiceDocModel && invoiceName.trim()) {
|
|
23
|
+
onSelectDocumentModel?.(invoiceDocModel, invoiceName.trim());
|
|
24
|
+
handleCloseModal();
|
|
25
|
+
}
|
|
26
|
+
}, [invoiceDocModel, invoiceName, onSelectDocumentModel, handleCloseModal]);
|
|
27
|
+
const handleKeyDown = useCallback((e) => {
|
|
28
|
+
if (e.key === "Enter" && invoiceName.trim()) {
|
|
29
|
+
handleConfirmCreate();
|
|
30
|
+
}
|
|
31
|
+
else if (e.key === "Escape") {
|
|
32
|
+
handleCloseModal();
|
|
33
|
+
}
|
|
34
|
+
}, [invoiceName, handleConfirmCreate, handleCloseModal]);
|
|
35
|
+
// Focus input when modal opens
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (isModalOpen && inputRef.current) {
|
|
38
|
+
inputRef.current.focus();
|
|
13
39
|
}
|
|
14
|
-
}, [
|
|
15
|
-
return (_jsxs("div", { className: "contributor-billing-section mb-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("button", { type: "button", onClick: handleToggle, className: "flex items-center gap-2 hover:opacity-80 transition-opacity py-1", children: [_jsx("span", { className: "font-medium text-gray-800", children: title }), _jsx("span", { className: `inline-flex items-center justify-center rounded-full text-xs font-semibold px-2 py-0.5 min-w-[24px] ${color}`, children: count }), isExpanded ? (_jsx(ChevronDown, { className: "w-4 h-4 text-gray-600" })) : (_jsx(ChevronRight, { className: "w-4 h-4 text-gray-600" }))] }), title === "Draft" && invoiceDocModel && (_jsx("button", { type: "button", className: "bg-white border border-gray-300 rounded px-3 py-1 text-sm font-medium hover:bg-gray-50 transition-colors", onClick:
|
|
40
|
+
}, [isModalOpen]);
|
|
41
|
+
return (_jsxs("div", { className: "contributor-billing-section mb-3", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("button", { type: "button", onClick: handleToggle, className: "flex items-center gap-2 hover:opacity-80 transition-opacity py-1", children: [_jsx("span", { className: "font-medium text-gray-800", children: title }), _jsx("span", { className: `inline-flex items-center justify-center rounded-full text-xs font-semibold px-2 py-0.5 min-w-[24px] ${color}`, children: count }), isExpanded ? (_jsx(ChevronDown, { className: "w-4 h-4 text-gray-600" })) : (_jsx(ChevronRight, { className: "w-4 h-4 text-gray-600" }))] }), title === "Draft" && invoiceDocModel && (_jsx("button", { type: "button", className: "bg-white border border-gray-300 rounded px-3 py-1 text-sm font-medium hover:bg-gray-50 transition-colors", onClick: handleOpenModal, children: "Create Invoice" }))] }), isExpanded && _jsx("div", { className: "mt-2", children: children }), isModalOpen && (_jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [_jsx("div", { className: "absolute inset-0 bg-black/50", onClick: handleCloseModal }), _jsxs("div", { className: "relative bg-white rounded-lg shadow-xl p-6 w-full max-w-md mx-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsx("h3", { className: "text-lg font-semibold text-gray-900", children: "Create New Invoice" }), _jsx("button", { type: "button", onClick: handleCloseModal, className: "text-gray-400 hover:text-gray-600 transition-colors", children: _jsx(X, { className: "w-5 h-5" }) })] }), _jsxs("div", { className: "mb-4", children: [_jsx("label", { htmlFor: "invoice-name", className: "block text-sm font-medium text-gray-700 mb-1", children: "Invoice Name" }), _jsx("input", { ref: inputRef, id: "invoice-name", type: "text", value: invoiceName, onChange: (e) => setInvoiceName(e.target.value), onKeyDown: handleKeyDown, placeholder: "Enter invoice name...", className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" })] }), _jsxs("div", { className: "flex justify-end gap-3", children: [_jsx("button", { type: "button", onClick: handleCloseModal, className: "px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 transition-colors", children: "Cancel" }), _jsx("button", { type: "button", onClick: handleConfirmCreate, disabled: !invoiceName.trim(), className: "px-4 py-2 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed", children: "Create" })] })] })] }))] }));
|
|
16
42
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SelectedFolderInfo } from "./FolderTree.js";
|
|
2
|
+
interface MonthOverviewProps {
|
|
3
|
+
folderId: string;
|
|
4
|
+
monthName?: string;
|
|
5
|
+
onFolderSelect?: (folderInfo: SelectedFolderInfo | null) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Overview for a month folder showing links to Payments and Reporting
|
|
9
|
+
*/
|
|
10
|
+
export declare function MonthOverview({ folderId: _folderId, monthName, onFolderSelect, }: MonthOverviewProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=MonthOverview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MonthOverview.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/MonthOverview.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;CAClE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EAAE,SAAS,EACnB,SAAS,EACT,cAAc,GACf,EAAE,kBAAkB,2CAwGpB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CreditCard, BarChart3, ArrowRight } from "lucide-react";
|
|
3
|
+
import { useBillingFolderStructure, } from "../hooks/useBillingFolderStructure.js";
|
|
4
|
+
import { setSelectedNode } from "@powerhousedao/reactor-browser";
|
|
5
|
+
/**
|
|
6
|
+
* Overview for a month folder showing links to Payments and Reporting
|
|
7
|
+
*/
|
|
8
|
+
export function MonthOverview({ folderId: _folderId, monthName, onFolderSelect, }) {
|
|
9
|
+
const { monthFolders } = useBillingFolderStructure();
|
|
10
|
+
// Find the month info for this folder
|
|
11
|
+
const monthInfo = monthName
|
|
12
|
+
? monthFolders.get(monthName)
|
|
13
|
+
: undefined;
|
|
14
|
+
const handlePaymentsClick = () => {
|
|
15
|
+
if (monthInfo?.paymentsFolder) {
|
|
16
|
+
setSelectedNode(monthInfo.paymentsFolder.id);
|
|
17
|
+
onFolderSelect?.({
|
|
18
|
+
folderId: monthInfo.paymentsFolder.id,
|
|
19
|
+
folderType: "payments",
|
|
20
|
+
monthName,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const handleReportingClick = () => {
|
|
25
|
+
if (monthInfo?.reportingFolder) {
|
|
26
|
+
setSelectedNode(monthInfo.reportingFolder.id);
|
|
27
|
+
onFolderSelect?.({
|
|
28
|
+
folderId: monthInfo.reportingFolder.id,
|
|
29
|
+
folderType: "reporting",
|
|
30
|
+
monthName,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
return (_jsxs("div", { children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-2xl font-bold text-gray-900", children: monthName || "Month Overview" }), _jsx("p", { className: "text-gray-600", children: "Select a category to manage your billing for this month" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-6", children: [_jsxs("button", { onClick: handlePaymentsClick, disabled: !monthInfo?.paymentsFolder, className: "bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md hover:border-blue-300 transition-all text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed", children: [_jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "p-3 bg-blue-100 rounded-lg", children: _jsx(CreditCard, { className: "w-6 h-6 text-blue-600" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Payments" }), _jsx("p", { className: "text-sm text-gray-600", children: "Invoices and billing statements" })] })] }), _jsx(ArrowRight, { className: "w-5 h-5 text-gray-400" })] }), _jsx("div", { className: "mt-4 pt-4 border-t border-gray-100", children: _jsx("p", { className: "text-sm text-gray-500", children: "Manage contributor invoices, generate billing statements, and track payment status." }) })] }), _jsxs("button", { onClick: handleReportingClick, disabled: !monthInfo?.reportingFolder, className: "bg-white rounded-lg border border-gray-200 p-6 hover:shadow-md hover:border-purple-300 transition-all text-left cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed", children: [_jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "p-3 bg-purple-100 rounded-lg", children: _jsx(BarChart3, { className: "w-6 h-6 text-purple-600" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Reporting" }), _jsx("p", { className: "text-sm text-gray-600", children: "Expense and snapshot reports" })] })] }), _jsx(ArrowRight, { className: "w-5 h-5 text-gray-400" })] }), _jsx("div", { className: "mt-4 pt-4 border-t border-gray-100", children: _jsx("p", { className: "text-sm text-gray-500", children: "Create expense reports and snapshot reports for financial tracking and auditing." }) })] })] })] }));
|
|
35
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SelectedFolderInfo } from "./FolderTree.js";
|
|
2
|
+
interface MonthlyReportingProps {
|
|
3
|
+
onFolderSelect?: (folderInfo: SelectedFolderInfo | null) => void;
|
|
4
|
+
/** Show all months or just current/prior */
|
|
5
|
+
showAllMonths?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Reusable Monthly Reporting component
|
|
9
|
+
* Shows expense and snapshot report status for each month
|
|
10
|
+
*/
|
|
11
|
+
export declare function MonthlyReporting({ onFolderSelect, showAllMonths, }: MonthlyReportingProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=MonthlyReporting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MonthlyReporting.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/MonthlyReporting.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,UAAU,qBAAqB;IAC7B,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;IACjE,4CAA4C;IAC5C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAmCD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,cAAc,EACd,aAAqB,GACtB,EAAE,qBAAqB,2CAqKvB"}
|