@powerhousedao/contributor-billing 0.1.37 → 0.1.40

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.
Files changed (117) hide show
  1. package/dist/document-models/account-transactions/gen/schema/types.d.ts +3 -3
  2. package/dist/document-models/account-transactions/gen/schema/types.d.ts.map +1 -1
  3. package/dist/document-models/accounts/gen/schema/types.d.ts +7 -7
  4. package/dist/document-models/accounts/gen/schema/types.d.ts.map +1 -1
  5. package/dist/document-models/billing-statement/gen/document-schema.d.ts +16 -16
  6. package/dist/document-models/billing-statement/gen/document-schema.d.ts.map +1 -1
  7. package/dist/document-models/billing-statement/gen/schema/types.d.ts +5 -5
  8. package/dist/document-models/billing-statement/gen/schema/types.d.ts.map +1 -1
  9. package/dist/document-models/expense-report/gen/document-model.js +1 -1
  10. package/dist/document-models/expense-report/gen/document-schema.d.ts +16 -16
  11. package/dist/document-models/expense-report/gen/ph-factories.d.ts.map +1 -1
  12. package/dist/document-models/expense-report/gen/ph-factories.js +5 -0
  13. package/dist/document-models/expense-report/gen/schema/types.d.ts +2 -2
  14. package/dist/document-models/expense-report/gen/schema/types.d.ts.map +1 -1
  15. package/dist/document-models/expense-report/gen/schema/zod.d.ts.map +1 -1
  16. package/dist/document-models/expense-report/gen/schema/zod.js +10 -30
  17. package/dist/document-models/expense-report/gen/utils.d.ts.map +1 -1
  18. package/dist/document-models/expense-report/gen/utils.js +5 -0
  19. package/dist/document-models/expense-report/src/reducers/wallet.d.ts.map +1 -1
  20. package/dist/document-models/expense-report/src/reducers/wallet.js +61 -0
  21. package/dist/document-models/invoice/gen/document-schema.d.ts +32 -32
  22. package/dist/document-models/invoice/gen/document-schema.d.ts.map +1 -1
  23. package/dist/document-models/invoice/gen/schema/types.d.ts +10 -10
  24. package/dist/document-models/invoice/gen/schema/types.d.ts.map +1 -1
  25. package/dist/document-models/invoice/gen/schema/zod.d.ts.map +1 -1
  26. package/dist/document-models/service-offering/gen/document-schema.d.ts +16 -16
  27. package/dist/document-models/service-offering/gen/document-schema.d.ts.map +1 -1
  28. package/dist/document-models/service-offering/gen/schema/types.d.ts +11 -11
  29. package/dist/document-models/service-offering/gen/schema/types.d.ts.map +1 -1
  30. package/dist/document-models/service-subscriptions/gen/schema/types.d.ts +6 -6
  31. package/dist/document-models/service-subscriptions/gen/schema/types.d.ts.map +1 -1
  32. package/dist/document-models/snapshot-report/gen/document-schema.d.ts.map +1 -1
  33. package/dist/document-models/snapshot-report/gen/schema/types.d.ts +8 -8
  34. package/dist/document-models/snapshot-report/gen/schema/types.d.ts.map +1 -1
  35. package/dist/document-models/snapshot-report/src/reducers/configuration.d.ts.map +1 -1
  36. package/dist/document-models/snapshot-report/src/reducers/configuration.js +1 -1
  37. package/dist/editors/accounts-editor/components/AccountForm.d.ts.map +1 -1
  38. package/dist/editors/accounts-editor/components/AccountForm.js +3 -1
  39. package/dist/editors/builder-team-admin/components/FolderTree.d.ts.map +1 -1
  40. package/dist/editors/builder-team-admin/components/FolderTree.js +2 -2
  41. package/dist/editors/contributor-billing/components/AddMonthButton.d.ts +5 -0
  42. package/dist/editors/contributor-billing/components/AddMonthButton.d.ts.map +1 -0
  43. package/dist/editors/contributor-billing/components/AddMonthButton.js +56 -0
  44. package/dist/editors/contributor-billing/components/BillingOverview.d.ts +10 -0
  45. package/dist/editors/contributor-billing/components/BillingOverview.d.ts.map +1 -0
  46. package/dist/editors/contributor-billing/components/BillingOverview.js +117 -0
  47. package/dist/editors/contributor-billing/components/DashboardHome.d.ts +11 -0
  48. package/dist/editors/contributor-billing/components/DashboardHome.d.ts.map +1 -0
  49. package/dist/editors/contributor-billing/components/DashboardHome.js +51 -0
  50. package/dist/editors/contributor-billing/components/DriveContents.d.ts +8 -2
  51. package/dist/editors/contributor-billing/components/DriveContents.d.ts.map +1 -1
  52. package/dist/editors/contributor-billing/components/DriveContents.js +24 -10
  53. package/dist/editors/contributor-billing/components/DriveExplorer.d.ts.map +1 -1
  54. package/dist/editors/contributor-billing/components/DriveExplorer.js +18 -3
  55. package/dist/editors/contributor-billing/components/FolderTree.d.ts +19 -9
  56. package/dist/editors/contributor-billing/components/FolderTree.d.ts.map +1 -1
  57. package/dist/editors/contributor-billing/components/FolderTree.js +200 -103
  58. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts +8 -1
  59. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.d.ts.map +1 -1
  60. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderControls.js +5 -3
  61. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.d.ts +6 -1
  62. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.d.ts.map +1 -1
  63. package/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.js +14 -6
  64. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.d.ts +6 -2
  65. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.d.ts.map +1 -1
  66. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTable.js +68 -19
  67. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.d.ts +10 -1
  68. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.d.ts.map +1 -1
  69. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.js +145 -32
  70. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableRow.d.ts.map +1 -1
  71. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableRow.js +6 -1
  72. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableSection.d.ts +1 -1
  73. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableSection.d.ts.map +1 -1
  74. package/dist/editors/contributor-billing/components/InvoiceTable/InvoiceTableSection.js +33 -7
  75. package/dist/editors/contributor-billing/components/MonthOverview.d.ts +12 -0
  76. package/dist/editors/contributor-billing/components/MonthOverview.d.ts.map +1 -0
  77. package/dist/editors/contributor-billing/components/MonthOverview.js +35 -0
  78. package/dist/editors/contributor-billing/components/MonthlyReporting.d.ts +13 -0
  79. package/dist/editors/contributor-billing/components/MonthlyReporting.d.ts.map +1 -0
  80. package/dist/editors/contributor-billing/components/MonthlyReporting.js +90 -0
  81. package/dist/editors/contributor-billing/components/ReportingView.d.ts +10 -0
  82. package/dist/editors/contributor-billing/components/ReportingView.d.ts.map +1 -0
  83. package/dist/editors/contributor-billing/components/ReportingView.js +112 -0
  84. package/dist/editors/contributor-billing/config.js +1 -1
  85. package/dist/editors/contributor-billing/hooks/useBillingFolderStructure.d.ts +54 -0
  86. package/dist/editors/contributor-billing/hooks/useBillingFolderStructure.d.ts.map +1 -0
  87. package/dist/editors/contributor-billing/hooks/useBillingFolderStructure.js +145 -0
  88. package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts +3 -1
  89. package/dist/editors/expense-report/components/AddBillingStatementModal.d.ts.map +1 -1
  90. package/dist/editors/expense-report/components/AddBillingStatementModal.js +23 -7
  91. package/dist/editors/expense-report/components/AggregatedExpensesTable.js +2 -2
  92. package/dist/editors/expense-report/components/ExpenseReportPDF.js +2 -2
  93. package/dist/editors/expense-report/editor.d.ts.map +1 -1
  94. package/dist/editors/expense-report/editor.js +70 -14
  95. package/dist/editors/expense-report/hooks/useSyncWallet.js +9 -9
  96. package/dist/editors/invoice/ingestPDF.js +1 -1
  97. package/dist/editors/invoice/invoiceToGnosis.js +2 -2
  98. package/dist/editors/invoice/requestFinance.js +2 -2
  99. package/dist/editors/invoice/uploadPdfChunked.js +2 -2
  100. package/dist/editors/snapshot-report-editor/editor.d.ts.map +1 -1
  101. package/dist/editors/snapshot-report-editor/editor.js +26 -5
  102. package/dist/scripts/invoice/pdfToClaudeAI.d.ts.map +1 -1
  103. package/dist/scripts/invoice/pdfToClaudeAI.js +3 -1
  104. package/dist/style.css +85 -0
  105. package/dist/subgraphs/budget-statements/index.d.ts +11 -0
  106. package/dist/subgraphs/budget-statements/index.d.ts.map +1 -0
  107. package/dist/subgraphs/budget-statements/index.js +11 -0
  108. package/dist/subgraphs/budget-statements/resolvers.d.ts +3 -0
  109. package/dist/subgraphs/budget-statements/resolvers.d.ts.map +1 -0
  110. package/dist/subgraphs/budget-statements/resolvers.js +400 -0
  111. package/dist/subgraphs/budget-statements/schema.d.ts +3 -0
  112. package/dist/subgraphs/budget-statements/schema.d.ts.map +1 -0
  113. package/dist/subgraphs/budget-statements/schema.js +132 -0
  114. package/dist/subgraphs/index.d.ts +1 -0
  115. package/dist/subgraphs/index.d.ts.map +1 -1
  116. package/dist/subgraphs/index.js +1 -0
  117. 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, useState, useEffect } from "react";
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 allDocuments = useDocumentsInSelectedDrive() || [];
57
- // Refresh page when actions complete to ensure state is updated
58
- useEffect(() => {
59
- if (actionsCompleted > 0) {
60
- window.location.reload();
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
- }, [actionsCompleted]);
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
- const createdNode = await addDocument(selectedDrive?.header.id || "", `bill-${invoiceFile?.name || id}`, "powerhouse/billing-statement", undefined, undefined, undefined, "powerhouse-billing-statement-editor");
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 - simple computed value
346
- const expenseReportDoc = files.find((file) => file.documentType === "powerhouse/expense-report");
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", undefined, undefined, undefined, "powerhouse-expense-report-editor");
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
@@ -1 +1 @@
1
- {"version":3,"file":"InvoiceTableContainer.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/InvoiceTableContainer.tsx"],"names":[],"mappings":"AAaA;;;GAGG;AACH,wBAAgB,qBAAqB,4CAqKpC"}
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 { useNodesInSelectedDriveOrFolder, isFileNodeKind, useDocumentModelModules, showCreateDocumentModal, useDocumentsInSelectedDrive, useOnDropFile, } from "@powerhousedao/reactor-browser";
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
- // Use the simple pattern - just nodes, no document fetching
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
- files.forEach((file) => pendingFilesRef.current.add(file));
32
- // Process all files
33
- const filePromises = files.map(async (file) => {
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
- // Handle progress updates if needed
37
- if (progress.stage === "complete" || progress.stage === "failed") {
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
- // If all files are done (including failed ones), reload
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
- // Final check - reload if all files are done
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 for create modal
87
- const onSelectDocumentModel = useCallback((documentModel) => {
88
- showCreateDocumentModal(documentModel.id);
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;AAoCD,eAAO,MAAM,eAAe,GAAI,iIAS7B,oBAAoB,4CA2GtB,CAAC"}
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 ? new Date(row.issueDate).toLocaleDateString() : "-" }), _jsx("td", { className: "px-2 py-2 text-center text-sm text-gray-700", children: row.dueDate ? new Date(row.dueDate).toLocaleDateString() : "-" }), _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" })) })] }));
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
  }
@@ -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,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAC;IAClE,sBAAsB,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,mBAAmB,GAAI,oGAQjC,wBAAwB,4CAoD1B,CAAC"}
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 handleCreateInvoice = useCallback(() => {
11
- if (invoiceDocModel) {
12
- onSelectDocumentModel?.(invoiceDocModel);
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
- }, [invoiceDocModel, onSelectDocumentModel]);
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: handleCreateInvoice, children: "Create Invoice" }))] }), isExpanded && _jsx("div", { className: "mt-2", children: children })] }));
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"}