@powerhousedao/contributor-billing 1.0.0-dev.7 → 1.0.0-dev.9

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 (22) hide show
  1. package/dist/editors/contributor-billing/components/DashboardHome.d.ts.map +1 -1
  2. package/dist/editors/contributor-billing/components/MonthReportCard.d.ts +4 -4
  3. package/dist/editors/contributor-billing/components/MonthReportCard.d.ts.map +1 -1
  4. package/dist/editors/contributor-billing/components/MonthReportCard.js +52 -6
  5. package/dist/editors/contributor-billing/components/MonthlyReportsOverview.d.ts.map +1 -1
  6. package/dist/editors/contributor-billing/components/MonthlyReportsOverview.js +49 -5
  7. package/dist/editors/contributor-billing/components/ReportingView.d.ts +0 -3
  8. package/dist/editors/contributor-billing/components/ReportingView.d.ts.map +1 -1
  9. package/dist/editors/contributor-billing/components/ReportingView.js +65 -8
  10. package/dist/editors/contributor-billing/hooks/useMonthlyReports.d.ts +4 -0
  11. package/dist/editors/contributor-billing/hooks/useMonthlyReports.d.ts.map +1 -1
  12. package/dist/editors/contributor-billing/hooks/useMonthlyReports.js +4 -0
  13. package/dist/editors/snapshot-report-editor/components/DateRangePicker.d.ts +19 -0
  14. package/dist/editors/snapshot-report-editor/components/DateRangePicker.d.ts.map +1 -0
  15. package/dist/editors/snapshot-report-editor/components/DateRangePicker.js +66 -0
  16. package/dist/editors/snapshot-report-editor/editor.d.ts.map +1 -1
  17. package/dist/editors/snapshot-report-editor/editor.js +72 -48
  18. package/dist/style.css +65 -0
  19. package/dist/subgraphs/budget-statements/resolvers.d.ts.map +1 -1
  20. package/dist/subgraphs/budget-statements/resolvers.js +46 -23
  21. package/dist/subgraphs/budget-statements/resolvers.test.js +12 -2
  22. package/package.json +18 -18
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardHome.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/DashboardHome.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAK1D,UAAU,kBAAkB;IAC1B,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;CAClE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,cAAc,EAAE,EAAE,kBAAkB,2CAqWnE"}
1
+ {"version":3,"file":"DashboardHome.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/DashboardHome.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAI1D,UAAU,kBAAkB;IAC1B,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;CAClE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,cAAc,EAAE,EAAE,kBAAkB,2CAoWnE"}
@@ -9,12 +9,12 @@ interface MonthReportCardProps {
9
9
  defaultExpanded?: boolean;
10
10
  onCreateExpenseReport?: (monthName: string, folderId: string) => void;
11
11
  onCreateSnapshotReport?: (monthName: string, folderId: string) => void;
12
+ onDeleteReport?: (reportId: string, reportName: string) => Promise<void>;
12
13
  onViewPayments?: (monthName: string) => void;
13
14
  paymentStats?: MonthPaymentStats;
15
+ /** Suggested start date based on previous month's snapshot period end + 1 day */
16
+ suggestedStartDate?: Date;
14
17
  }
15
- /**
16
- * Collapsible month card showing all reports for a month
17
- */
18
- export declare function MonthReportCard({ reportSet, defaultExpanded, onCreateExpenseReport, onCreateSnapshotReport, onViewPayments, paymentStats, }: MonthReportCardProps): import("react/jsx-runtime").JSX.Element;
18
+ export declare function MonthReportCard({ reportSet, defaultExpanded, onCreateExpenseReport, onCreateSnapshotReport, onDeleteReport, onViewPayments, paymentStats, suggestedStartDate, }: MonthReportCardProps): import("react/jsx-runtime").JSX.Element;
19
19
  export {};
20
20
  //# sourceMappingURL=MonthReportCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MonthReportCard.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/MonthReportCard.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,+BAA+B,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,oBAAoB;IAC5B,SAAS,EAAE,cAAc,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtE,sBAAsB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACvE,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAClC;AA6ED;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,eAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,cAAc,EACd,YAAY,GACb,EAAE,oBAAoB,2CAiJtB"}
1
+ {"version":3,"file":"MonthReportCard.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/MonthReportCard.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,+BAA+B,CAAC;AAGvC,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,oBAAoB;IAC5B,SAAS,EAAE,cAAc,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtE,sBAAsB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACvE,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,IAAI,CAAC;CAC3B;AA6HD,wBAAgB,eAAe,CAAC,EAC9B,SAAS,EACT,eAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,cAAc,EACd,cAAc,EACd,YAAY,EACZ,kBAAkB,GACnB,EAAE,oBAAoB,2CAoNtB"}
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useCallback } from "react";
3
- import { Calendar, ChevronDown, ChevronRight, Camera, CreditCard, FileText, ArrowRight, Plus, } from "lucide-react";
3
+ import { Calendar, ChevronDown, ChevronRight, Camera, CreditCard, FileText, ArrowRight, Plus, Info, Trash2, } from "lucide-react";
4
4
  import { setSelectedNode } from "@powerhousedao/reactor-browser";
5
+ import { ConfirmationModal } from "./InvoiceTable/ConfirmationModal.js";
5
6
  /**
6
7
  * Get color classes for status badges
7
8
  */
@@ -37,22 +38,64 @@ function getStatusLabel(status) {
37
38
  /**
38
39
  * Individual report row component
39
40
  */
40
- function ReportRow({ report, isSnapshot = false, }) {
41
+ function ReportRow({ report, isSnapshot = false, onDelete, }) {
41
42
  const colors = getStatusColors(report.status);
42
43
  const handleClick = useCallback(() => {
43
44
  setSelectedNode(report.id);
44
45
  }, [report.id]);
45
- return (_jsxs("button", { onClick: handleClick, className: "w-full flex items-center justify-between p-3 hover:bg-gray-50 cursor-pointer transition-colors border-b border-gray-100 last:border-b-0", children: [_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [isSnapshot ? (_jsx(Camera, { className: "w-4 h-4 text-purple-500 flex-shrink-0" })) : (_jsx(FileText, { className: "w-4 h-4 text-blue-500 flex-shrink-0" })), _jsx("span", { className: "text-sm text-gray-900 truncate", children: report.name })] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("span", { className: `px-2 py-0.5 text-xs font-medium rounded-full ${colors.bg} ${colors.text}`, children: getStatusLabel(report.status) }), _jsx(ArrowRight, { className: "w-4 h-4 text-gray-400" })] })] }));
46
+ const handleDelete = useCallback((e) => {
47
+ e.stopPropagation();
48
+ onDelete?.(report.id, report.name);
49
+ }, [onDelete, report.id, report.name]);
50
+ return (_jsxs("div", { className: "flex items-center border-b border-gray-100 last:border-b-0", children: [_jsxs("button", { onClick: handleClick, className: "flex-1 flex items-center justify-between p-3 hover:bg-gray-50 cursor-pointer transition-colors min-w-0", children: [_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [isSnapshot ? (_jsx(Camera, { className: "w-4 h-4 text-purple-500 flex-shrink-0" })) : (_jsx(FileText, { className: "w-4 h-4 text-blue-500 flex-shrink-0" })), _jsx("span", { className: "text-sm text-gray-900 truncate", children: report.name })] }), _jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [_jsx("span", { className: `px-2 py-0.5 text-xs font-medium rounded-full ${colors.bg} ${colors.text}`, children: getStatusLabel(report.status) }), _jsx(ArrowRight, { className: "w-4 h-4 text-gray-400" })] })] }), onDelete && (_jsx("button", { onClick: handleDelete, className: "p-3 text-gray-400 hover:text-red-500 transition-colors flex-shrink-0", title: "Delete report", children: _jsx(Trash2, { className: "w-4 h-4" }) }))] }));
46
51
  }
47
52
  /**
48
53
  * Collapsible month card showing all reports for a month
49
54
  */
50
- export function MonthReportCard({ reportSet, defaultExpanded = false, onCreateExpenseReport, onCreateSnapshotReport, onViewPayments, paymentStats, }) {
55
+ /**
56
+ * Format a date as "Mon DD" (e.g., "Dec 24")
57
+ */
58
+ function formatShortDate(date) {
59
+ return date.toLocaleDateString("en-US", {
60
+ month: "short",
61
+ day: "numeric",
62
+ timeZone: "UTC",
63
+ });
64
+ }
65
+ /**
66
+ * Check if a date is the first day of the month parsed from monthName
67
+ */
68
+ function isFirstOfMonth(date, monthName) {
69
+ const monthDate = new Date(monthName + " 1");
70
+ if (isNaN(monthDate.getTime()))
71
+ return false;
72
+ return (date.getUTCFullYear() === monthDate.getFullYear() &&
73
+ date.getUTCMonth() === monthDate.getMonth() &&
74
+ date.getUTCDate() === 1);
75
+ }
76
+ export function MonthReportCard({ reportSet, defaultExpanded = false, onCreateExpenseReport, onCreateSnapshotReport, onDeleteReport, onViewPayments, paymentStats, suggestedStartDate, }) {
51
77
  const [isExpanded, setIsExpanded] = useState(defaultExpanded);
78
+ const [deleteTarget, setDeleteTarget] = useState(null);
79
+ const [isDeleting, setIsDeleting] = useState(false);
52
80
  const overallColors = getStatusColors(reportSet.overallStatus);
53
81
  const toggleExpanded = useCallback(() => {
54
82
  setIsExpanded((prev) => !prev);
55
83
  }, []);
84
+ const handleDeleteRequest = useCallback((reportId, reportName) => {
85
+ setDeleteTarget({ id: reportId, name: reportName });
86
+ }, []);
87
+ const handleConfirmDelete = useCallback(async () => {
88
+ if (!deleteTarget || !onDeleteReport)
89
+ return;
90
+ setIsDeleting(true);
91
+ try {
92
+ await onDeleteReport(deleteTarget.id, deleteTarget.name);
93
+ }
94
+ finally {
95
+ setIsDeleting(false);
96
+ setDeleteTarget(null);
97
+ }
98
+ }, [deleteTarget, onDeleteReport]);
56
99
  const handleCreateExpenseReport = useCallback(() => {
57
100
  if (onCreateExpenseReport && reportSet.reportingFolderId) {
58
101
  onCreateExpenseReport(reportSet.monthName, reportSet.reportingFolderId);
@@ -73,6 +116,9 @@ export function MonthReportCard({ reportSet, defaultExpanded = false, onCreateEx
73
116
  const reportCountText = reportSet.reportCount === 1
74
117
  ? "1 Report"
75
118
  : `${reportSet.reportCount} Reports`;
76
- return (_jsxs("div", { className: "bg-white rounded-xl border border-gray-200 overflow-hidden", children: [_jsxs("button", { onClick: toggleExpanded, className: "w-full flex items-center justify-between p-4 hover:bg-gray-50 transition-colors text-left", children: [_jsxs("div", { className: "flex items-center gap-3", children: [isExpanded ? (_jsx(ChevronDown, { className: "w-5 h-5 text-gray-400" })) : (_jsx(ChevronRight, { className: "w-5 h-5 text-gray-400" })), _jsx(Calendar, { className: "w-5 h-5 text-gray-600" }), _jsx("span", { className: "font-medium text-gray-900", children: reportSet.monthName })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-sm text-gray-500", children: reportCountText }), reportSet.reportCount > 0 && (_jsx("span", { className: `px-2 py-0.5 text-xs font-medium rounded-full ${overallColors.bg} ${overallColors.text}`, children: getStatusLabel(reportSet.overallStatus) }))] })] }), isExpanded && (_jsxs("div", { className: "border-t border-gray-200", children: [onViewPayments && (_jsxs("button", { onClick: handleViewPayments, className: "w-full flex items-center justify-between p-3 hover:bg-gray-50 cursor-pointer transition-colors border-b border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsx(CreditCard, { className: "w-4 h-4 text-emerald-500 flex-shrink-0" }), _jsx("span", { className: "text-sm font-medium text-gray-900", children: "Payments" }), paymentStats && paymentStats.totalInvoices > 0 && (_jsxs("span", { className: "text-xs text-gray-500", children: [paymentStats.totalInvoices, " invoice", paymentStats.totalInvoices !== 1 ? "s" : "", paymentStats.pendingCount > 0 && (_jsxs("span", { className: "text-amber-600", children: [" ", "\u00B7 ", paymentStats.pendingCount, " pending"] })), paymentStats.paidCount > 0 && (_jsxs("span", { className: "text-green-600", children: [" ", "\u00B7 ", paymentStats.paidCount, " paid"] }))] }))] }), _jsx(ArrowRight, { className: "w-4 h-4 text-gray-400 flex-shrink-0" })] })), reportSet.snapshotReport && (_jsx(ReportRow, { report: reportSet.snapshotReport, isSnapshot: true })), reportSet.expenseReports.map((report) => (_jsx(ReportRow, { report: report }, report.id))), reportSet.reportCount === 0 && (_jsx("div", { className: "p-4 text-center text-sm text-gray-500", children: "No reports created yet" })), (onCreateExpenseReport || onCreateSnapshotReport) &&
77
- reportSet.reportingFolderId && (_jsxs("div", { className: "p-3 border-t border-gray-100 bg-gray-50 flex items-center gap-3", children: [onCreateSnapshotReport && !reportSet.snapshotReport && (_jsxs("button", { onClick: handleCreateSnapshotReport, className: "flex items-center gap-2 text-sm text-purple-600 hover:text-purple-700 font-medium transition-colors", children: [_jsx(Plus, { className: "w-4 h-4" }), "Add Snapshot Report"] })), onCreateExpenseReport && (_jsxs("button", { onClick: handleCreateExpenseReport, className: "flex items-center gap-2 text-sm text-blue-600 hover:text-blue-700 font-medium transition-colors", children: [_jsx(Plus, { className: "w-4 h-4" }), "Add Expense Report"] }))] }))] }))] }));
119
+ return (_jsxs("div", { className: "bg-white rounded-xl border border-gray-200 overflow-hidden", children: [_jsxs("button", { onClick: toggleExpanded, className: "w-full flex items-center justify-between p-4 hover:bg-gray-50 transition-colors text-left", children: [_jsxs("div", { className: "flex items-center gap-3", children: [isExpanded ? (_jsx(ChevronDown, { className: "w-5 h-5 text-gray-400" })) : (_jsx(ChevronRight, { className: "w-5 h-5 text-gray-400" })), _jsx(Calendar, { className: "w-5 h-5 text-gray-600" }), _jsx("span", { className: "font-medium text-gray-900", children: reportSet.monthName })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-sm text-gray-500", children: reportCountText }), reportSet.reportCount > 0 && (_jsx("span", { className: `px-2 py-0.5 text-xs font-medium rounded-full ${overallColors.bg} ${overallColors.text}`, children: getStatusLabel(reportSet.overallStatus) }))] })] }), isExpanded && (_jsxs("div", { className: "border-t border-gray-200", children: [onViewPayments && (_jsxs("button", { onClick: handleViewPayments, className: "w-full flex items-center justify-between p-3 hover:bg-gray-50 cursor-pointer transition-colors border-b border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsx(CreditCard, { className: "w-4 h-4 text-emerald-500 flex-shrink-0" }), _jsx("span", { className: "text-sm font-medium text-gray-900", children: "Payments" }), paymentStats && paymentStats.totalInvoices > 0 && (_jsxs("span", { className: "text-xs text-gray-500", children: [paymentStats.totalInvoices, " invoice", paymentStats.totalInvoices !== 1 ? "s" : "", paymentStats.pendingCount > 0 && (_jsxs("span", { className: "text-amber-600", children: [" ", "\u00B7 ", paymentStats.pendingCount, " pending"] })), paymentStats.paidCount > 0 && (_jsxs("span", { className: "text-green-600", children: [" ", "\u00B7 ", paymentStats.paidCount, " paid"] }))] }))] }), _jsx(ArrowRight, { className: "w-4 h-4 text-gray-400 flex-shrink-0" })] })), reportSet.snapshotReport && (_jsx(ReportRow, { report: reportSet.snapshotReport, isSnapshot: true, onDelete: onDeleteReport ? handleDeleteRequest : undefined })), reportSet.expenseReports.map((report) => (_jsx(ReportRow, { report: report, onDelete: onDeleteReport ? handleDeleteRequest : undefined }, report.id))), reportSet.reportCount === 0 && (_jsx("div", { className: "p-4 text-center text-sm text-gray-500", children: "No reports created yet" })), (onCreateExpenseReport || onCreateSnapshotReport) &&
120
+ reportSet.reportingFolderId && (_jsxs("div", { className: "p-3 border-t border-gray-100 bg-gray-50", children: [_jsxs("div", { className: "flex items-center gap-3", children: [onCreateSnapshotReport && !reportSet.snapshotReport && (_jsxs("button", { onClick: handleCreateSnapshotReport, className: "flex items-center gap-2 text-sm text-purple-600 hover:text-purple-700 font-medium transition-colors", children: [_jsx(Plus, { className: "w-4 h-4" }), "Add Snapshot Report"] })), onCreateExpenseReport && (_jsxs("button", { onClick: handleCreateExpenseReport, className: "flex items-center gap-2 text-sm text-blue-600 hover:text-blue-700 font-medium transition-colors", children: [_jsx(Plus, { className: "w-4 h-4" }), "Add Expense Report"] }))] }), onCreateSnapshotReport &&
121
+ !reportSet.snapshotReport &&
122
+ suggestedStartDate &&
123
+ !isFirstOfMonth(suggestedStartDate, reportSet.monthName) && (_jsxs("div", { className: "flex items-center gap-1.5 mt-2 text-xs text-indigo-600", children: [_jsx(Info, { className: "w-3.5 h-3.5 flex-shrink-0" }), _jsxs("span", { children: ["Transaction period will start", " ", formatShortDate(suggestedStartDate), " (day after previous snapshot period ends)"] })] }))] }))] })), _jsxs(ConfirmationModal, { open: !!deleteTarget, header: "Delete Report", onCancel: () => setDeleteTarget(null), onContinue: () => void handleConfirmDelete(), cancelLabel: "Cancel", continueLabel: isDeleting ? "Deleting..." : "Delete", continueDisabled: isDeleting, children: [_jsx("p", { className: "text-red-600 text-sm mb-2 font-medium", children: "This will permanently delete this report from the drive. This action cannot be undone." }), deleteTarget && (_jsx("p", { className: "text-gray-700 text-sm font-medium", children: deleteTarget.name }))] })] }));
78
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MonthlyReportsOverview.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/MonthlyReportsOverview.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAe7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,UAAU,2BAA2B;IACnC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;IACjE,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC5C,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAsCD;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,cAAc,EACd,YAAY,EACZ,aAAa,EACb,oBAAoB,GACrB,EAAE,2BAA2B,2CAuZ7B"}
1
+ {"version":3,"file":"MonthlyReportsOverview.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/MonthlyReportsOverview.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AAkB7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,UAAU,2BAA2B;IACnC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,CAAC;IACjE,YAAY,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAC5C,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrD,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAuED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,cAAc,EACd,YAAY,EACZ,aAAa,EACb,oBAAoB,GACrB,EAAE,2BAA2B,2CA0b7B"}
@@ -4,8 +4,8 @@ import { createPortal } from "react-dom";
4
4
  import { BarChart3, Plus, ChevronDown } from "lucide-react";
5
5
  import { useSelectedDrive, useDocumentsInSelectedDrive, addDocument, dispatchActions, setSelectedNode, isFileNodeKind, } from "@powerhousedao/reactor-browser";
6
6
  import { setName } from "document-model";
7
- import { moveNode } from "document-drive";
8
- import { useMonthlyReports } from "../hooks/useMonthlyReports.js";
7
+ import { moveNode, deleteNode } from "document-drive";
8
+ import { useMonthlyReports, } from "../hooks/useMonthlyReports.js";
9
9
  import { MonthReportCard } from "./MonthReportCard.js";
10
10
  import { actions as expenseReportActions } from "../../../document-models/expense-report/index.js";
11
11
  import { actions as snapshotReportActions } from "../../../document-models/snapshot-report/index.js";
@@ -38,6 +38,32 @@ function parseMonthDates(monthName) {
38
38
  const end = new Date(Date.UTC(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999));
39
39
  return { start, end };
40
40
  }
41
+ /**
42
+ * Get the suggested start date for a new snapshot report based on the previous
43
+ * month's snapshot period end date. Returns the day after the previous period
44
+ * ends, or the first day of the month if there's no previous snapshot.
45
+ */
46
+ function getSuggestedSnapshotStartDate(monthName, monthReportSets) {
47
+ const monthDates = parseMonthDates(monthName);
48
+ if (!monthDates)
49
+ return null;
50
+ // Find the current month's index in the sorted (descending) list
51
+ const currentIndex = monthReportSets.findIndex((s) => s.monthName === monthName);
52
+ if (currentIndex === -1)
53
+ return null;
54
+ // The previous month is the next item in the descending-sorted array
55
+ const previousMonth = monthReportSets[currentIndex + 1];
56
+ if (!previousMonth?.snapshotEndDate)
57
+ return null;
58
+ const previousEnd = new Date(previousMonth.snapshotEndDate);
59
+ if (isNaN(previousEnd.getTime()))
60
+ return null;
61
+ // Suggested start = previous period end + 1 day
62
+ const suggestedStart = new Date(previousEnd);
63
+ suggestedStart.setUTCDate(suggestedStart.getUTCDate() + 1);
64
+ suggestedStart.setUTCHours(0, 0, 0, 0);
65
+ return suggestedStart;
66
+ }
41
67
  /**
42
68
  * Monthly Reports Overview component for the billing page
43
69
  * Shows collapsible month cards with reports and status
@@ -218,9 +244,12 @@ export function MonthlyReportsOverview({ onFolderSelect, monthFolders, onCreateM
218
244
  }
219
245
  // Set the document name
220
246
  await dispatchActions(setName(reportName), createdNode.id);
221
- // Set period dates based on month
247
+ // Set reporting period to calendar month boundaries
222
248
  const dates = parseMonthDates(monthName);
223
249
  if (dates) {
250
+ const suggestedStart = getSuggestedSnapshotStartDate(monthName, monthReportSets);
251
+ // Transaction filtering start: previous period end + 1 day, or month start
252
+ const txStartDate = suggestedStart || dates.start;
224
253
  await dispatchActions([
225
254
  snapshotReportActions.setPeriodStart({
226
255
  periodStart: dates.start.toISOString(),
@@ -229,6 +258,11 @@ export function MonthlyReportsOverview({ onFolderSelect, monthFolders, onCreateM
229
258
  periodEnd: dates.end.toISOString(),
230
259
  }),
231
260
  ], createdNode.id);
261
+ // Set the transaction filtering range (snapshot period) separately
262
+ await dispatchActions(snapshotReportActions.setReportConfig({
263
+ startDate: txStartDate.toISOString(),
264
+ endDate: dates.end.toISOString(),
265
+ }), createdNode.id);
232
266
  }
233
267
  // Open the created report
234
268
  setSelectedNode(createdNode.id);
@@ -238,7 +272,17 @@ export function MonthlyReportsOverview({ onFolderSelect, monthFolders, onCreateM
238
272
  finally {
239
273
  setIsCreating(false);
240
274
  }
241
- }, [driveId, isCreating, onActiveNodeIdChange]);
275
+ }, [driveId, isCreating, monthReportSets, onActiveNodeIdChange]);
276
+ const handleDeleteReport = useCallback(async (reportId) => {
277
+ if (!driveId)
278
+ return;
279
+ try {
280
+ await dispatchActions(deleteNode({ id: reportId }), driveId);
281
+ }
282
+ catch (error) {
283
+ console.error("Failed to delete report:", error);
284
+ }
285
+ }, [driveId]);
242
286
  // Add Month button component (reused across states)
243
287
  const addMonthButton = onCreateMonth && (_jsxs("div", { className: "relative", children: [_jsxs("button", { ref: buttonRef, onClick: () => setIsDropdownOpen(!isDropdownOpen), disabled: isAddingMonth, className: "flex items-center gap-1.5 px-3 py-1.5 text-sm bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed", children: [_jsx(Plus, { className: "w-4 h-4" }), isAddingMonth ? "Adding..." : "Add Month", _jsx(ChevronDown, { className: "w-3 h-3" })] }), isDropdownOpen &&
244
288
  createPortal(_jsxs("div", { ref: dropdownRef, className: "fixed w-56 bg-white rounded-lg shadow-lg border border-gray-200 py-1 z-[9999]", style: {
@@ -253,5 +297,5 @@ export function MonthlyReportsOverview({ onFolderSelect, monthFolders, onCreateM
253
297
  if (monthReportSets.length === 0) {
254
298
  return (_jsxs("div", { className: "bg-white rounded-xl border border-gray-200 p-6 overflow-visible", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 mb-5", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "p-2 bg-indigo-100 rounded-lg", children: _jsx(BarChart3, { className: "w-5 h-5 text-indigo-600" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Monthly Reports" }), _jsx("p", { className: "text-sm text-gray-600", children: "Quick access to expense and snapshot reports" })] })] }), addMonthButton] }), _jsx("p", { className: "text-gray-500 text-sm text-center py-4", children: "No months configured yet. Click \"Add Month\" to get started." })] }));
255
299
  }
256
- return (_jsxs("div", { className: "bg-white rounded-xl border border-gray-200 p-6 overflow-visible", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 mb-5", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "p-2 bg-indigo-100 rounded-lg", children: _jsx(BarChart3, { className: "w-5 h-5 text-indigo-600" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Monthly Reports" }), _jsx("p", { className: "text-sm text-gray-600", children: "Quick access to expense and snapshot reports" })] })] }), addMonthButton] }), _jsx("div", { className: "space-y-3", children: monthReportSets.map((reportSet, index) => (_jsx(MonthReportCard, { reportSet: reportSet, defaultExpanded: index === 0, onCreateExpenseReport: handleCreateExpenseReport, onCreateSnapshotReport: handleCreateSnapshotReport, onViewPayments: onFolderSelect ? handleViewPayments : undefined, paymentStats: monthPaymentStatsMap.get(reportSet.monthName) }, reportSet.monthName))) })] }));
300
+ return (_jsxs("div", { className: "bg-white rounded-xl border border-gray-200 p-6 overflow-visible", children: [_jsxs("div", { className: "flex items-center justify-between gap-3 mb-5", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "p-2 bg-indigo-100 rounded-lg", children: _jsx(BarChart3, { className: "w-5 h-5 text-indigo-600" }) }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Monthly Reports" }), _jsx("p", { className: "text-sm text-gray-600", children: "Quick access to expense and snapshot reports" })] })] }), addMonthButton] }), _jsx("div", { className: "space-y-3", children: monthReportSets.map((reportSet, index) => (_jsx(MonthReportCard, { reportSet: reportSet, defaultExpanded: index === 0, onCreateExpenseReport: handleCreateExpenseReport, onCreateSnapshotReport: handleCreateSnapshotReport, onDeleteReport: handleDeleteReport, onViewPayments: onFolderSelect ? handleViewPayments : undefined, paymentStats: monthPaymentStatsMap.get(reportSet.monthName), suggestedStartDate: getSuggestedSnapshotStartDate(reportSet.monthName, monthReportSets) || undefined }, reportSet.monthName))) })] }));
257
301
  }
@@ -2,9 +2,6 @@ interface ReportingViewProps {
2
2
  folderId: string;
3
3
  monthName?: string;
4
4
  }
5
- /**
6
- * View for the Reporting folder showing Expense Reports and Snapshot Reports
7
- */
8
5
  export declare function ReportingView({ folderId, monthName }: ReportingViewProps): import("react/jsx-runtime").JSX.Element;
9
6
  export {};
10
7
  //# sourceMappingURL=ReportingView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReportingView.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/ReportingView.tsx"],"names":[],"mappings":"AAeA,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4BD;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CAmSxE"}
1
+ {"version":3,"file":"ReportingView.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/components/ReportingView.tsx"],"names":[],"mappings":"AAiBA,UAAU,kBAAkB;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+DD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,kBAAkB,2CA2XxE"}
@@ -1,11 +1,13 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useDocumentsInSelectedDrive, setSelectedNode, addDocument, dispatchActions, useSelectedDrive, isFileNodeKind, } from "@powerhousedao/reactor-browser";
3
3
  import { useMemo, useState } from "react";
4
- import { FileText, Camera, Plus } from "lucide-react";
4
+ import { FileText, Camera, Plus, Trash2 } from "lucide-react";
5
5
  import { setName } from "document-model";
6
- import { moveNode } from "document-drive";
6
+ import { moveNode, deleteNode } from "document-drive";
7
+ import { ConfirmationModal } from "./InvoiceTable/ConfirmationModal.js";
7
8
  import { actions as expenseReportActions } from "../../../document-models/expense-report/index.js";
8
9
  import { actions as snapshotReportActions } from "../../../document-models/snapshot-report/index.js";
10
+ import { useMonthlyReports } from "../hooks/useMonthlyReports.js";
9
11
  /**
10
12
  * Parse a month name like "January 2026" into start and end dates
11
13
  */
@@ -13,8 +15,9 @@ function parseMonthDates(monthName) {
13
15
  const date = new Date(monthName + " 1"); // e.g., "January 2026 1"
14
16
  if (isNaN(date.getTime()))
15
17
  return null;
16
- const start = new Date(date.getFullYear(), date.getMonth(), 1);
17
- const end = new Date(date.getFullYear(), date.getMonth() + 1, 0); // Last day of month
18
+ // Use UTC to avoid timezone offset being baked into .toISOString()
19
+ const start = new Date(Date.UTC(date.getFullYear(), date.getMonth(), 1));
20
+ const end = new Date(Date.UTC(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999));
18
21
  return { start, end };
19
22
  }
20
23
  /**
@@ -24,17 +27,42 @@ function formatMonthCode(monthName) {
24
27
  const date = new Date(monthName + " 1");
25
28
  if (isNaN(date.getTime()))
26
29
  return monthName;
27
- const month = String(date.getMonth() + 1).padStart(2, "0");
28
- const year = date.getFullYear();
30
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
31
+ const year = date.getUTCFullYear();
29
32
  return `${month}-${year}`;
30
33
  }
31
34
  /**
32
35
  * View for the Reporting folder showing Expense Reports and Snapshot Reports
33
36
  */
37
+ /**
38
+ * Get the suggested start date for a new snapshot report based on the previous
39
+ * month's snapshot period end date.
40
+ */
41
+ function getSuggestedSnapshotStartDate(monthName, monthReportSets) {
42
+ const monthDates = parseMonthDates(monthName);
43
+ if (!monthDates)
44
+ return null;
45
+ const currentIndex = monthReportSets.findIndex((s) => s.monthName === monthName);
46
+ if (currentIndex === -1)
47
+ return null;
48
+ const previousMonth = monthReportSets[currentIndex + 1];
49
+ if (!previousMonth?.snapshotEndDate)
50
+ return null;
51
+ const previousEnd = new Date(previousMonth.snapshotEndDate);
52
+ if (isNaN(previousEnd.getTime()))
53
+ return null;
54
+ const suggestedStart = new Date(previousEnd);
55
+ suggestedStart.setUTCDate(suggestedStart.getUTCDate() + 1);
56
+ suggestedStart.setUTCHours(0, 0, 0, 0);
57
+ return suggestedStart;
58
+ }
34
59
  export function ReportingView({ folderId, monthName }) {
35
60
  const documentsInDrive = useDocumentsInSelectedDrive();
36
61
  const [selectedDrive] = useSelectedDrive();
37
62
  const [isCreating, setIsCreating] = useState(false);
63
+ const [isDeleting, setIsDeleting] = useState(false);
64
+ const [deleteTarget, setDeleteTarget] = useState(null);
65
+ const { monthReportSets } = useMonthlyReports();
38
66
  // Find expense reports and snapshot reports in this Reporting folder
39
67
  // Also includes documents that match the month by name (for backwards compatibility)
40
68
  const { expenseReports, snapshotReports } = useMemo(() => {
@@ -83,6 +111,21 @@ export function ReportingView({ folderId, monthName }) {
83
111
  setSelectedNode(docId);
84
112
  };
85
113
  const driveId = selectedDrive?.header.id;
114
+ const handleDeleteReport = async () => {
115
+ if (!driveId || !deleteTarget)
116
+ return;
117
+ setIsDeleting(true);
118
+ try {
119
+ await dispatchActions(deleteNode({ id: deleteTarget.id }), driveId);
120
+ }
121
+ catch (error) {
122
+ console.error("Failed to delete report:", error);
123
+ }
124
+ finally {
125
+ setIsDeleting(false);
126
+ setDeleteTarget(null);
127
+ }
128
+ };
86
129
  const handleCreateExpenseReport = async () => {
87
130
  if (!driveId || !monthName || isCreating)
88
131
  return;
@@ -134,9 +177,12 @@ export function ReportingView({ folderId, monthName }) {
134
177
  }
135
178
  // Set the document name
136
179
  await dispatchActions(setName(reportName), createdNode.id);
137
- // Set period dates based on month
180
+ // Set reporting period to calendar month boundaries
138
181
  const dates = parseMonthDates(monthName);
139
182
  if (dates) {
183
+ const suggestedStart = getSuggestedSnapshotStartDate(monthName, monthReportSets);
184
+ // Transaction filtering start: previous period end + 1 day, or month start
185
+ const txStartDate = suggestedStart || dates.start;
140
186
  await dispatchActions([
141
187
  snapshotReportActions.setPeriodStart({
142
188
  periodStart: dates.start.toISOString(),
@@ -145,6 +191,11 @@ export function ReportingView({ folderId, monthName }) {
145
191
  periodEnd: dates.end.toISOString(),
146
192
  }),
147
193
  ], createdNode.id);
194
+ // Set the transaction filtering range (snapshot period) separately
195
+ await dispatchActions(snapshotReportActions.setReportConfig({
196
+ startDate: txStartDate.toISOString(),
197
+ endDate: dates.end.toISOString(),
198
+ }), createdNode.id);
148
199
  }
149
200
  // Open the created report
150
201
  setSelectedNode(createdNode.id);
@@ -153,5 +204,11 @@ export function ReportingView({ folderId, monthName }) {
153
204
  setIsCreating(false);
154
205
  }
155
206
  };
156
- return (_jsxs("div", { children: [_jsxs("div", { className: "mb-6", children: [_jsxs("h1", { className: "text-2xl font-bold text-gray-900", children: ["Reporting ", monthName ? `- ${monthName}` : ""] }), _jsxs("p", { className: "text-gray-600", children: ["Manage expense reports and snapshot reports", monthName ? ` for ${monthName}` : ""] })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-6", children: [_jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(FileText, { className: "w-5 h-5 text-blue-600" }), _jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Expense Reports" }), expenseReports.length > 0 && (_jsxs("span", { className: "text-sm text-gray-500", children: ["(", expenseReports.length, ")"] }))] }), _jsxs("button", { onClick: () => void handleCreateExpenseReport(), disabled: isCreating, className: "flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-blue-600 hover:bg-blue-50 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed", children: [_jsx(Plus, { className: "w-4 h-4" }), isCreating ? "Creating..." : "New"] })] }), expenseReports.length === 0 ? (_jsx("p", { className: "text-gray-500 text-sm", children: "No expense reports yet" })) : (_jsx("div", { className: "space-y-2", children: expenseReports.map((doc) => (_jsxs("button", { onClick: () => handleOpenDocument(doc.header.id), className: "w-full flex items-center gap-3 p-3 text-left hover:bg-gray-50 rounded-md transition-colors border border-gray-100", children: [_jsx(FileText, { className: "w-4 h-4 text-gray-400" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 truncate", children: doc.header.name || "Untitled" }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Modified:", " ", new Date(doc.header.lastModifiedAtUtcIso || Date.now()).toLocaleDateString()] })] })] }, doc.header.id))) }))] }), _jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Camera, { className: "w-5 h-5 text-purple-600" }), _jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Snapshot Reports" })] }), snapshotReports.length === 0 && (_jsxs("button", { onClick: () => void handleCreateSnapshotReport(), disabled: isCreating, className: "flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-purple-600 hover:bg-purple-50 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed", children: [_jsx(Plus, { className: "w-4 h-4" }), isCreating ? "Creating..." : "New"] }))] }), snapshotReports.length === 0 ? (_jsx("p", { className: "text-gray-500 text-sm", children: "No snapshot reports yet" })) : (_jsx("div", { className: "space-y-2", children: snapshotReports.map((doc) => (_jsxs("button", { onClick: () => handleOpenDocument(doc.header.id), className: "w-full flex items-center gap-3 p-3 text-left hover:bg-gray-50 rounded-md transition-colors border border-gray-100", children: [_jsx(Camera, { className: "w-4 h-4 text-gray-400" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 truncate", children: doc.header.name || "Untitled" }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Modified:", " ", new Date(doc.header.lastModifiedAtUtcIso || Date.now()).toLocaleDateString()] })] })] }, doc.header.id))) }))] })] })] }));
207
+ return (_jsxs("div", { children: [_jsxs("div", { className: "mb-6", children: [_jsxs("h1", { className: "text-2xl font-bold text-gray-900", children: ["Reporting ", monthName ? `- ${monthName}` : ""] }), _jsxs("p", { className: "text-gray-600", children: ["Manage expense reports and snapshot reports", monthName ? ` for ${monthName}` : ""] })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-6", children: [_jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(FileText, { className: "w-5 h-5 text-blue-600" }), _jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Expense Reports" }), expenseReports.length > 0 && (_jsxs("span", { className: "text-sm text-gray-500", children: ["(", expenseReports.length, ")"] }))] }), _jsxs("button", { onClick: () => void handleCreateExpenseReport(), disabled: isCreating, className: "flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-blue-600 hover:bg-blue-50 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed", children: [_jsx(Plus, { className: "w-4 h-4" }), isCreating ? "Creating..." : "New"] })] }), expenseReports.length === 0 ? (_jsx("p", { className: "text-gray-500 text-sm", children: "No expense reports yet" })) : (_jsx("div", { className: "space-y-2", children: expenseReports.map((doc) => (_jsxs("div", { className: "flex items-center rounded-md border border-gray-100", children: [_jsxs("button", { onClick: () => handleOpenDocument(doc.header.id), className: "flex-1 flex items-center gap-3 p-3 text-left hover:bg-gray-50 rounded-l-md transition-colors min-w-0", children: [_jsx(FileText, { className: "w-4 h-4 text-gray-400" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 truncate", children: doc.header.name || "Untitled" }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Modified:", " ", new Date(doc.header.lastModifiedAtUtcIso || Date.now()).toLocaleDateString()] })] })] }), _jsx("button", { onClick: () => setDeleteTarget({
208
+ id: doc.header.id,
209
+ name: doc.header.name || "Untitled",
210
+ }), className: "p-3 text-gray-400 hover:text-red-500 transition-colors flex-shrink-0", title: "Delete report", children: _jsx(Trash2, { className: "w-4 h-4" }) })] }, doc.header.id))) }))] }), _jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-4", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Camera, { className: "w-5 h-5 text-purple-600" }), _jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Snapshot Reports" })] }), snapshotReports.length === 0 && (_jsxs("button", { onClick: () => void handleCreateSnapshotReport(), disabled: isCreating, className: "flex items-center gap-1 px-3 py-1.5 text-sm font-medium text-purple-600 hover:bg-purple-50 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed", children: [_jsx(Plus, { className: "w-4 h-4" }), isCreating ? "Creating..." : "New"] }))] }), snapshotReports.length === 0 ? (_jsx("p", { className: "text-gray-500 text-sm", children: "No snapshot reports yet" })) : (_jsx("div", { className: "space-y-2", children: snapshotReports.map((doc) => (_jsxs("div", { className: "flex items-center rounded-md border border-gray-100", children: [_jsxs("button", { onClick: () => handleOpenDocument(doc.header.id), className: "flex-1 flex items-center gap-3 p-3 text-left hover:bg-gray-50 rounded-l-md transition-colors min-w-0", children: [_jsx(Camera, { className: "w-4 h-4 text-gray-400" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: "text-sm font-medium text-gray-900 truncate", children: doc.header.name || "Untitled" }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Modified:", " ", new Date(doc.header.lastModifiedAtUtcIso || Date.now()).toLocaleDateString()] })] })] }), _jsx("button", { onClick: () => setDeleteTarget({
211
+ id: doc.header.id,
212
+ name: doc.header.name || "Untitled",
213
+ }), className: "p-3 text-gray-400 hover:text-red-500 transition-colors flex-shrink-0", title: "Delete report", children: _jsx(Trash2, { className: "w-4 h-4" }) })] }, doc.header.id))) }))] })] }), _jsxs(ConfirmationModal, { open: !!deleteTarget, header: "Delete Report", onCancel: () => setDeleteTarget(null), onContinue: () => void handleDeleteReport(), cancelLabel: "Cancel", continueLabel: isDeleting ? "Deleting..." : "Delete", continueDisabled: isDeleting, children: [_jsx("p", { className: "text-red-600 text-sm mb-2 font-medium", children: "This will permanently delete this report from the drive. This action cannot be undone." }), deleteTarget && (_jsx("p", { className: "text-gray-700 text-sm font-medium", children: deleteTarget.name }))] })] }));
157
214
  }
@@ -24,6 +24,10 @@ export interface MonthReportSet {
24
24
  reportingFolderId: string | null;
25
25
  /** The month folder info */
26
26
  folderInfo: MonthFolderInfo;
27
+ /** The snapshot report's transaction period start date (startDate), if any */
28
+ snapshotStartDate: string | null;
29
+ /** The snapshot report's transaction period end date (endDate), if any */
30
+ snapshotEndDate: string | null;
27
31
  }
28
32
  export interface UseMonthlyReportsResult {
29
33
  /** All months with their reports, sorted by date descending */
@@ -1 +1 @@
1
- {"version":3,"file":"useMonthlyReports.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/hooks/useMonthlyReports.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,gCAAgC,CAAC;AAExC,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,2BAA2B,GAAG,4BAA4B,CAAC;IACzE,MAAM,EAAE,YAAY,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,yCAAyC;IACzC,cAAc,EAAE,cAAc,EAAE,CAAC;IACjC,2EAA2E;IAC3E,aAAa,EAAE,YAAY,CAAC;IAC5B,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,4BAA4B;IAC5B,UAAU,EAAE,eAAe,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,wCAAwC;IACxC,SAAS,EAAE,OAAO,CAAC;CACpB;AAgFD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,uBAAuB,CA0G3D"}
1
+ {"version":3,"file":"useMonthlyReports.d.ts","sourceRoot":"","sources":["../../../../editors/contributor-billing/hooks/useMonthlyReports.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,gCAAgC,CAAC;AAExC,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,2BAA2B,GAAG,4BAA4B,CAAC;IACzE,MAAM,EAAE,YAAY,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,cAAc,EAAE,cAAc,GAAG,IAAI,CAAC;IACtC,yCAAyC;IACzC,cAAc,EAAE,cAAc,EAAE,CAAC;IACjC,2EAA2E;IAC3E,aAAa,EAAE,YAAY,CAAC;IAC5B,8BAA8B;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,4BAA4B;IAC5B,UAAU,EAAE,eAAe,CAAC;IAC5B,8EAA8E;IAC9E,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,0EAA0E;IAC1E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,+DAA+D;IAC/D,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,wCAAwC;IACxC,SAAS,EAAE,OAAO,CAAC;CACpB;AAgFD;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,IAAI,uBAAuB,CAsH3D"}
@@ -139,6 +139,8 @@ export function useMonthlyReports() {
139
139
  const snapshotReport = snapshotDoc
140
140
  ? toReportDocument(snapshotDoc, "powerhouse/snapshot-report")
141
141
  : null;
142
+ // Extract snapshot transaction period dates from document state
143
+ const snapshotState = snapshotDoc?.state;
142
144
  // Combine all reports for status calculation
143
145
  const allReports = [...expenseReports];
144
146
  if (snapshotReport)
@@ -152,6 +154,8 @@ export function useMonthlyReports() {
152
154
  reportCount: allReports.length,
153
155
  reportingFolderId: folderInfo.reportingFolder?.id || null,
154
156
  folderInfo,
157
+ snapshotStartDate: snapshotState?.global?.startDate || null,
158
+ snapshotEndDate: snapshotState?.global?.endDate || null,
155
159
  });
156
160
  }
157
161
  // Sort by date descending (most recent first)
@@ -0,0 +1,19 @@
1
+ interface DateRangePickerProps {
2
+ /** ISO date string or YYYY-MM-DD for the start of the range */
3
+ fromDate: string;
4
+ /** ISO date string or YYYY-MM-DD for the end of the range */
5
+ toDate: string;
6
+ /** Called when either date changes. Both values are ISO date strings. */
7
+ onChange: (fromDate: string, toDate: string) => void;
8
+ /** Optional label for the whole range picker */
9
+ label?: string;
10
+ /** Optional className for the container */
11
+ className?: string;
12
+ }
13
+ /**
14
+ * Unified date range picker rendered as a single row with two date inputs
15
+ * joined visually. Enforces from <= to.
16
+ */
17
+ export declare function DateRangePicker({ fromDate, toDate, onChange, label, className, }: DateRangePickerProps): import("react/jsx-runtime").JSX.Element;
18
+ export {};
19
+ //# sourceMappingURL=DateRangePicker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DateRangePicker.d.ts","sourceRoot":"","sources":["../../../../editors/snapshot-report-editor/components/DateRangePicker.tsx"],"names":[],"mappings":"AAEA,UAAU,oBAAoB;IAC5B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgBD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,KAAK,EACL,SAAS,GACV,EAAE,oBAAoB,2CAsFtB"}
@@ -0,0 +1,66 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect, useCallback } from "react";
3
+ function toDateOnly(value) {
4
+ return value.split("T")[0] || "";
5
+ }
6
+ function toStartOfDayISO(dateStr) {
7
+ const d = new Date(dateStr + "T00:00:00.000Z");
8
+ return isNaN(d.getTime()) ? "" : d.toISOString();
9
+ }
10
+ function toEndOfDayISO(dateStr) {
11
+ const d = new Date(dateStr + "T23:59:59.999Z");
12
+ return isNaN(d.getTime()) ? "" : d.toISOString();
13
+ }
14
+ /**
15
+ * Unified date range picker rendered as a single row with two date inputs
16
+ * joined visually. Enforces from <= to.
17
+ */
18
+ export function DateRangePicker({ fromDate, toDate, onChange, label, className, }) {
19
+ const [localFrom, setLocalFrom] = useState(() => toDateOnly(fromDate));
20
+ const [localTo, setLocalTo] = useState(() => toDateOnly(toDate));
21
+ useEffect(() => {
22
+ const extFrom = toDateOnly(fromDate);
23
+ if (extFrom && extFrom !== localFrom) {
24
+ setLocalFrom(extFrom);
25
+ }
26
+ }, [fromDate]);
27
+ useEffect(() => {
28
+ const extTo = toDateOnly(toDate);
29
+ if (extTo && extTo !== localTo) {
30
+ setLocalTo(extTo);
31
+ }
32
+ }, [toDate]);
33
+ const handleFromChange = useCallback((e) => {
34
+ const dateStr = e.target.value;
35
+ if (!dateStr)
36
+ return;
37
+ let effectiveTo = localTo;
38
+ if (effectiveTo && dateStr > effectiveTo) {
39
+ effectiveTo = dateStr;
40
+ setLocalTo(effectiveTo);
41
+ }
42
+ setLocalFrom(dateStr);
43
+ const fromISO = toStartOfDayISO(dateStr);
44
+ const toISO = toEndOfDayISO(effectiveTo || dateStr);
45
+ if (fromISO && toISO) {
46
+ onChange(fromISO, toISO);
47
+ }
48
+ }, [localTo, onChange]);
49
+ const handleToChange = useCallback((e) => {
50
+ const dateStr = e.target.value;
51
+ if (!dateStr)
52
+ return;
53
+ let effectiveFrom = localFrom;
54
+ if (effectiveFrom && dateStr < effectiveFrom) {
55
+ effectiveFrom = dateStr;
56
+ setLocalFrom(effectiveFrom);
57
+ }
58
+ setLocalTo(dateStr);
59
+ const fromISO = toStartOfDayISO(effectiveFrom || dateStr);
60
+ const toISO = toEndOfDayISO(dateStr);
61
+ if (fromISO && toISO) {
62
+ onChange(fromISO, toISO);
63
+ }
64
+ }, [localFrom, onChange]);
65
+ return (_jsxs("div", { className: className, children: [label && (_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: label })), _jsxs("div", { className: "inline-flex items-center border border-gray-300 rounded-md overflow-hidden focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500", children: [_jsx("input", { type: "date", value: localFrom, onChange: handleFromChange, className: "px-3 py-2 text-sm border-none outline-none bg-white" }), _jsx("span", { className: "px-2 text-sm text-gray-400 bg-gray-50 self-stretch flex items-center border-x border-gray-300", children: "\u2192" }), _jsx("input", { type: "date", value: localTo, onChange: handleToChange, className: "px-3 py-2 text-sm border-none outline-none bg-white" })] })] }));
66
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/snapshot-report-editor/editor.tsx"],"names":[],"mappings":"AA2EA,MAAM,CAAC,OAAO,UAAU,MAAM,4CAuqC7B"}
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../../editors/snapshot-report-editor/editor.tsx"],"names":[],"mappings":"AAwEA,MAAM,CAAC,OAAO,UAAU,MAAM,4CAktC7B"}
@@ -1,10 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { generateId, setName } from "document-model";
3
- import { useState, useMemo, useEffect } from "react";
3
+ import { useState, useMemo, useEffect, useCallback } from "react";
4
4
  import { useDocumentsInSelectedDrive, useParentFolderForSelectedNode, setSelectedNode, dispatchActions, } from "@powerhousedao/reactor-browser";
5
5
  import { DocumentToolbar } from "@powerhousedao/design-system/connect";
6
- import { Button, Select, DatePicker, } from "@powerhousedao/document-engineering";
6
+ import { Button, Select } from "@powerhousedao/document-engineering";
7
7
  import { ChevronDown, ChevronUp, RefreshCw } from "lucide-react";
8
+ import { DateRangePicker } from "./components/DateRangePicker.js";
8
9
  import { useSelectedSnapshotReportDocument } from "../hooks/useSnapshotReportDocument.js";
9
10
  import { setReportConfig, addSnapshotAccount, addTransaction, setPeriodStart, setPeriodEnd, } from "../../document-models/snapshot-report/gen/creators.js";
10
11
  import { SetOwner } from "./components/SetOwner.js";
@@ -93,6 +94,63 @@ export default function Editor() {
93
94
  // Track if we're in editing mode
94
95
  const [isEditingPeriod, setIsEditingPeriod] = useState(!savedPeriod);
95
96
  const isPeriodChanged = selectedPeriod !== savedPeriod;
97
+ // Compute suggested start date from previous month's snapshot endDate
98
+ const suggestedStartDate = useMemo(() => {
99
+ if (!documentsInDrive || !reportPeriodStart)
100
+ return null;
101
+ const currentPeriodStart = new Date(reportPeriodStart);
102
+ if (isNaN(currentPeriodStart.getTime()))
103
+ return null;
104
+ // Find all other snapshot reports in the drive
105
+ const otherSnapshots = documentsInDrive.filter((doc) => doc.header.documentType === "powerhouse/snapshot-report" &&
106
+ doc.header.id !== document.header.id);
107
+ // Find the snapshot whose reportPeriodStart is closest before this one
108
+ let previousSnapshot = null;
109
+ let closestDistance = Infinity;
110
+ for (const snap of otherSnapshots) {
111
+ const state = snap.state;
112
+ const rps = state?.global?.reportPeriodStart;
113
+ if (!rps)
114
+ continue;
115
+ const rpDate = new Date(rps);
116
+ if (isNaN(rpDate.getTime()))
117
+ continue;
118
+ // Must be before current period
119
+ const diff = currentPeriodStart.getTime() - rpDate.getTime();
120
+ if (diff > 0 && diff < closestDistance) {
121
+ closestDistance = diff;
122
+ previousSnapshot = {
123
+ endDate: state?.global?.endDate || null,
124
+ reportPeriodStart: rps,
125
+ };
126
+ }
127
+ }
128
+ if (!previousSnapshot?.endDate)
129
+ return null;
130
+ const prevEnd = new Date(previousSnapshot.endDate);
131
+ if (isNaN(prevEnd.getTime()))
132
+ return null;
133
+ // Suggested start = previous endDate + 1 day
134
+ const suggested = new Date(prevEnd);
135
+ suggested.setUTCDate(suggested.getUTCDate() + 1);
136
+ suggested.setUTCHours(0, 0, 0, 0);
137
+ return suggested;
138
+ }, [documentsInDrive, reportPeriodStart, document.header.id]);
139
+ const handleSnapshotPeriodChange = useCallback((newFromDate, newToDate) => {
140
+ dispatch?.(setReportConfig({
141
+ startDate: newFromDate,
142
+ endDate: newToDate,
143
+ reportName: reportName || undefined,
144
+ accountsDocumentId: accountsDocumentId || undefined,
145
+ }));
146
+ }, [dispatch, reportName, accountsDocumentId]);
147
+ const handleApplySuggestedStartDate = useCallback(() => {
148
+ if (!suggestedStartDate)
149
+ return;
150
+ const fromISO = suggestedStartDate.toISOString();
151
+ const toISO = endDate || "";
152
+ handleSnapshotPeriodChange(fromISO, toISO);
153
+ }, [suggestedStartDate, endDate, handleSnapshotPeriodChange]);
96
154
  // Update selected period when document period changes externally
97
155
  useEffect(() => {
98
156
  if (savedPeriod && savedPeriod !== selectedPeriod) {
@@ -373,51 +431,6 @@ export default function Editor() {
373
431
  setIsAccountPickerOpen(false);
374
432
  setSelectedAccountIds(new Set());
375
433
  };
376
- // Handlers for the legacy startDate/endDate fields (DatePicker approach)
377
- const handleStartDateChange = (e) => {
378
- const dateValue = e.target.value;
379
- if (!dateValue)
380
- return;
381
- // DatePicker may return ISO string or date string - extract just the date part
382
- const dateString = dateValue.split("T")[0]; // Get YYYY-MM-DD part if ISO string
383
- if (!dateString)
384
- return;
385
- // Create date at start of day (00:00:00)
386
- const date = new Date(dateString + "T00:00:00.000Z");
387
- if (isNaN(date.getTime())) {
388
- console.error("Invalid date value:", dateValue);
389
- return;
390
- }
391
- const isoDateTime = date.toISOString();
392
- dispatch?.(setReportConfig({
393
- startDate: isoDateTime,
394
- endDate: endDate || undefined,
395
- reportName: reportName || undefined,
396
- accountsDocumentId: accountsDocumentId || undefined,
397
- }));
398
- };
399
- const handleEndDateChange = (e) => {
400
- const dateValue = e.target.value;
401
- if (!dateValue)
402
- return;
403
- // DatePicker may return ISO string or date string - extract just the date part
404
- const dateString = dateValue.split("T")[0]; // Get YYYY-MM-DD part if ISO string
405
- if (!dateString)
406
- return;
407
- // Create date at end of day (23:59:59.999)
408
- const endOfDay = new Date(dateString + "T23:59:59.999Z");
409
- if (isNaN(endOfDay.getTime())) {
410
- console.error("Invalid date value:", dateValue);
411
- return;
412
- }
413
- const isoDateTime = endOfDay.toISOString();
414
- dispatch?.(setReportConfig({
415
- endDate: isoDateTime,
416
- startDate: startDate || undefined,
417
- reportName: reportName || undefined,
418
- accountsDocumentId: accountsDocumentId || undefined,
419
- }));
420
- };
421
434
  // Get the parent folder node for the currently selected node
422
435
  const parentFolder = useParentFolderForSelectedNode();
423
436
  // Set the selected node to the parent folder node (close the editor)
@@ -431,7 +444,18 @@ export default function Editor() {
431
444
  endDate: endDate || undefined,
432
445
  })), className: "w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500", children: [_jsx("option", { value: "", children: "Select an Accounts document..." }), accountsDocuments.map((doc) => (_jsx("option", { value: doc.header.id, children: doc.header.name ||
433
446
  `Accounts (${doc.header.id.slice(0, 8)}...)` }, doc.header.id)))] }), selectedAccountsDoc && (_jsxs("p", { className: "text-xs text-gray-500 mt-1", children: [selectedAccountsDoc.state.global?.accounts
434
- ?.length || 0, " ", "accounts available"] }))] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Reporting Period" }), isEditingPeriod ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Select, { options: monthOptions, value: selectedPeriod, onChange: (value) => handlePeriodChange(value), className: "min-w-[180px]" }), isPeriodChanged && (_jsx(Button, { variant: "default", onClick: handleConfirmPeriod, className: "text-sm", children: "Set Period" }))] })) : (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-lg font-bold text-gray-900", children: periodDisplayLabel }), _jsx(Button, { variant: "ghost", onClick: handleEditPeriod, className: "text-sm", children: "Change" })] }))] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Snapshot Period" }), _jsxs("div", { className: "flex gap-2 items-center", children: [_jsx(DatePicker, { name: "startDate", value: startDate ? startDate.split("T")[0] : "", onChange: handleStartDateChange, dateFormat: "YYYY-MM-DD", className: "flex-1" }), _jsx("span", { className: "self-center", children: "to" }), _jsx(DatePicker, { name: "endDate", value: endDate ? endDate.split("T")[0] : "", onChange: handleEndDateChange, dateFormat: "YYYY-MM-DD", className: "flex-1" })] })] })] })] }), _jsxs("div", { className: "bg-white rounded-lg shadow p-6", children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsx("h2", { className: "text-xl font-semibold", children: "Snapshot Accounts" }), _jsxs("div", { className: "flex gap-2", children: [snapshotAccounts.length > 0 && (_jsxs("button", { onClick: handleSyncAll, disabled: isSyncingAll || !startDate || !endDate, className: "px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center gap-2", children: [_jsx(RefreshCw, { className: `w-4 h-4 ${isSyncingAll ? "animate-spin" : ""}` }), "Sync All"] })), _jsx("button", { onClick: handleOpenAccountPicker, disabled: !accountsDocumentId, className: "px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-400 disabled:cursor-not-allowed", children: "Add Account" })] })] }), snapshotAccounts.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx("p", { children: "No accounts added yet" }), _jsx("p", { className: "text-sm mt-2", children: "Click \"Add Account\" to select accounts for this snapshot" })] })) : (_jsx("div", { className: "space-y-6", children: [
447
+ ?.length || 0, " ", "accounts available"] }))] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Reporting Period" }), isEditingPeriod ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Select, { options: monthOptions, value: selectedPeriod, onChange: (value) => handlePeriodChange(value), className: "min-w-[180px]" }), isPeriodChanged && (_jsx(Button, { variant: "default", onClick: handleConfirmPeriod, className: "text-sm", children: "Set Period" }))] })) : (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "text-lg font-bold text-gray-900", children: periodDisplayLabel }), _jsx(Button, { variant: "ghost", onClick: handleEditPeriod, className: "text-sm", children: "Change" })] }))] }), _jsxs("div", { children: [_jsx(DateRangePicker, { label: "Snapshot Period", fromDate: startDate || "", toDate: endDate || "", onChange: handleSnapshotPeriodChange }), suggestedStartDate &&
448
+ (!startDate ||
449
+ startDate.split("T")[0] !==
450
+ suggestedStartDate.toISOString().split("T")[0]) && (_jsxs("div", { className: "mt-2 flex items-center gap-2 p-2 bg-indigo-50 border border-indigo-200 rounded-md", children: [_jsxs("span", { className: "text-xs text-indigo-700 flex-1", children: ["Previous snapshot period ends", " ", new Date(suggestedStartDate.getTime() - 86400000).toLocaleDateString("en-US", {
451
+ month: "short",
452
+ day: "numeric",
453
+ timeZone: "UTC",
454
+ }), ". Start from", " ", _jsx("strong", { children: suggestedStartDate.toLocaleDateString("en-US", {
455
+ month: "short",
456
+ day: "numeric",
457
+ timeZone: "UTC",
458
+ }) }), " ", "to avoid gaps."] }), _jsx("button", { onClick: handleApplySuggestedStartDate, className: "px-2 py-1 text-xs font-medium text-indigo-700 bg-indigo-100 hover:bg-indigo-200 rounded transition-colors whitespace-nowrap", children: "Apply" })] }))] })] })] }), _jsxs("div", { className: "bg-white rounded-lg shadow p-6", children: [_jsxs("div", { className: "flex justify-between items-center mb-4", children: [_jsx("h2", { className: "text-xl font-semibold", children: "Snapshot Accounts" }), _jsxs("div", { className: "flex gap-2", children: [snapshotAccounts.length > 0 && (_jsxs("button", { onClick: handleSyncAll, disabled: isSyncingAll || !startDate || !endDate, className: "px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center gap-2", children: [_jsx(RefreshCw, { className: `w-4 h-4 ${isSyncingAll ? "animate-spin" : ""}` }), "Sync All"] })), _jsx("button", { onClick: handleOpenAccountPicker, disabled: !accountsDocumentId, className: "px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-400 disabled:cursor-not-allowed", children: "Add Account" })] })] }), snapshotAccounts.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx("p", { children: "No accounts added yet" }), _jsx("p", { className: "text-sm mt-2", children: "Click \"Add Account\" to select accounts for this snapshot" })] })) : (_jsx("div", { className: "space-y-6", children: [
435
459
  {
436
460
  type: "Source",
437
461
  label: "Source",
package/dist/style.css CHANGED
@@ -546,6 +546,9 @@
546
546
  .h-3 {
547
547
  height: calc(var(--spacing) * 3);
548
548
  }
549
+ .h-3\.5 {
550
+ height: calc(var(--spacing) * 3.5);
551
+ }
549
552
  .h-4 {
550
553
  height: calc(var(--spacing) * 4);
551
554
  }
@@ -639,6 +642,9 @@
639
642
  .w-3 {
640
643
  width: calc(var(--spacing) * 3);
641
644
  }
645
+ .w-3\.5 {
646
+ width: calc(var(--spacing) * 3.5);
647
+ }
642
648
  .w-4 {
643
649
  width: calc(var(--spacing) * 4);
644
650
  }
@@ -1015,6 +1021,9 @@
1015
1021
  .self-center {
1016
1022
  align-self: center;
1017
1023
  }
1024
+ .self-stretch {
1025
+ align-self: stretch;
1026
+ }
1018
1027
  .truncate {
1019
1028
  overflow: hidden;
1020
1029
  text-overflow: ellipsis;
@@ -1062,6 +1071,10 @@
1062
1071
  .rounded-xl {
1063
1072
  border-radius: var(--radius-xl);
1064
1073
  }
1074
+ .rounded-l-md {
1075
+ border-top-left-radius: var(--radius-md);
1076
+ border-bottom-left-radius: var(--radius-md);
1077
+ }
1065
1078
  .rounded-tl-sm {
1066
1079
  border-top-left-radius: var(--radius-sm);
1067
1080
  }
@@ -1080,6 +1093,10 @@
1080
1093
  border-style: var(--tw-border-style);
1081
1094
  border-width: 2px;
1082
1095
  }
1096
+ .border-x {
1097
+ border-inline-style: var(--tw-border-style);
1098
+ border-inline-width: 1px;
1099
+ }
1083
1100
  .border-t {
1084
1101
  border-top-style: var(--tw-border-style);
1085
1102
  border-top-width: 1px;
@@ -1100,6 +1117,10 @@
1100
1117
  --tw-border-style: dashed;
1101
1118
  border-style: dashed;
1102
1119
  }
1120
+ .border-none {
1121
+ --tw-border-style: none;
1122
+ border-style: none;
1123
+ }
1103
1124
  .border-amber-200 {
1104
1125
  border-color: var(--color-amber-200);
1105
1126
  }
@@ -1151,6 +1172,9 @@
1151
1172
  .border-green-500 {
1152
1173
  border-color: var(--color-green-500);
1153
1174
  }
1175
+ .border-indigo-200 {
1176
+ border-color: var(--color-indigo-200);
1177
+ }
1154
1178
  .border-red-200 {
1155
1179
  border-color: var(--color-red-200);
1156
1180
  }
@@ -2295,6 +2319,22 @@
2295
2319
  color: var(--color-gray-800);
2296
2320
  }
2297
2321
  }
2322
+ .focus-within\:border-blue-500 {
2323
+ &:focus-within {
2324
+ border-color: var(--color-blue-500);
2325
+ }
2326
+ }
2327
+ .focus-within\:ring-2 {
2328
+ &:focus-within {
2329
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2330
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2331
+ }
2332
+ }
2333
+ .focus-within\:ring-blue-500 {
2334
+ &:focus-within {
2335
+ --tw-ring-color: var(--color-blue-500);
2336
+ }
2337
+ }
2298
2338
  .hover\:scale-105 {
2299
2339
  &:hover {
2300
2340
  @media (hover: hover) {
@@ -2497,6 +2537,13 @@
2497
2537
  }
2498
2538
  }
2499
2539
  }
2540
+ .hover\:bg-indigo-200 {
2541
+ &:hover {
2542
+ @media (hover: hover) {
2543
+ background-color: var(--color-indigo-200);
2544
+ }
2545
+ }
2546
+ }
2500
2547
  .hover\:bg-indigo-600 {
2501
2548
  &:hover {
2502
2549
  @media (hover: hover) {
@@ -4727,6 +4774,24 @@
4727
4774
  .z-50 {
4728
4775
  z-index: 50;
4729
4776
  }
4777
+ .container {
4778
+ width: 100%;
4779
+ @media (width >= 40rem) {
4780
+ max-width: 40rem;
4781
+ }
4782
+ @media (width >= 48rem) {
4783
+ max-width: 48rem;
4784
+ }
4785
+ @media (width >= 64rem) {
4786
+ max-width: 64rem;
4787
+ }
4788
+ @media (width >= 80rem) {
4789
+ max-width: 80rem;
4790
+ }
4791
+ @media (width >= 96rem) {
4792
+ max-width: 96rem;
4793
+ }
4794
+ }
4730
4795
  .mx-auto {
4731
4796
  margin-inline: auto;
4732
4797
  }
@@ -1 +1 @@
1
- {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/budget-statements/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAoB5D,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,IAGzD,CAAC;AAGF,eAAO,MAAM,YAAY,GACvB,aAAa,MAAM,GAAG,IAAI,GAAG,SAAS,EACtC,WAAW,MAAM,GAAG,IAAI,GAAG,SAAS,KACnC,MAAM,GAAG,IAMX,CAAC;AASF,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,GAAG,IAAI,KAAG,MAAM,GAAG,IAQ7D,CAAC;AA0BF,eAAO,MAAM,YAAY,GAAI,UAAU,SAAS,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAgXxE,CAAC;AA6JF,KAAK,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,KAAK,iBAAiB,GAAG;IACvB,OAAO,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,cAAc,CAAA;SAAE,CAAC,CAAC;KAC/C,CAAC,CAAC;CACJ,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,KAAK,CAAC;YAClB,SAAS,EAAE,MAAM,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,MAAM,EAAE;gBAAE,KAAK,EAAE,cAAc,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAA;aAAE,CAAC;SACjD,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,iBAAiB,EAAE,iBAAiB,GACnC,cAAc,CAQhB;AAKD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,kBAAkB,EAAE,kBAAkB,GACrC,cAAc,CAYhB"}
1
+ {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../../../subgraphs/budget-statements/resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAoB5D,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,IAGzD,CAAC;AAGF,eAAO,MAAM,YAAY,GACvB,aAAa,MAAM,GAAG,IAAI,GAAG,SAAS,EACtC,WAAW,MAAM,GAAG,IAAI,GAAG,SAAS,KACnC,MAAM,GAAG,IAMX,CAAC;AAmBF,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,GAAG,IAAI,KAAG,MAAM,GAAG,IAQ7D,CAAC;AA0BF,eAAO,MAAM,YAAY,GAAI,UAAU,SAAS,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAgYxE,CAAC;AAuJF,KAAK,cAAc,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtD,KAAK,iBAAiB,GAAG;IACvB,OAAO,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,cAAc,CAAA;SAAE,CAAC,CAAC;KAC/C,CAAC,CAAC;CACJ,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,YAAY,EAAE,KAAK,CAAC;YAClB,SAAS,EAAE,MAAM,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,MAAM,EAAE;gBAAE,KAAK,EAAE,cAAc,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAA;aAAE,CAAC;SACjD,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ,CAAC;AAEF;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,iBAAiB,EAAE,iBAAiB,GACnC,cAAc,CAQhB;AAKD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,kBAAkB,EAAE,kBAAkB,GACrC,cAAc,CAYhB"}
@@ -16,8 +16,18 @@ export const getPeriodKey = (periodStart, periodEnd) => {
16
16
  return `${start}_${end}`;
17
17
  };
18
18
  const MONTHS = [
19
- "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
20
- "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
19
+ "JAN",
20
+ "FEB",
21
+ "MAR",
22
+ "APR",
23
+ "MAY",
24
+ "JUN",
25
+ "JUL",
26
+ "AUG",
27
+ "SEP",
28
+ "OCT",
29
+ "NOV",
30
+ "DEC",
21
31
  ];
22
32
  // Helper to extract month key from an ISO date string (format: "SEP2025")
23
33
  // Parses directly from the string to avoid timezone issues
@@ -125,15 +135,24 @@ export const getResolvers = (subgraph) => {
125
135
  const drivesToScan = drives;
126
136
  for (const driveId of drivesToScan) {
127
137
  const docsIds = await reactor.getDocuments(driveId);
128
- const docs = await Promise.all(docsIds.map(async (docId) => reactor.getDocument(docId)));
138
+ const docs = await Promise.all(docsIds.map(async (docId) => {
139
+ try {
140
+ return await reactor.getDocument(docId);
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ }));
129
146
  for (const doc of docs) {
147
+ if (!doc)
148
+ continue;
130
149
  const docType = doc.header.documentType;
131
150
  if (docType === "powerhouse/snapshot-report") {
132
151
  const snapshotDoc = doc;
133
152
  const ownerId = snapshotDoc.state.global.ownerIds?.[0] ?? null;
134
- // Apply filters: teamId takes precedence, then networkSlug (via allowedBuilderPhids)
135
- if (teamId && ownerId !== teamId)
136
- continue;
153
+ // Don't filter snapshot reports by teamId here snapshots are often
154
+ // owned by the operational hub, not individual teams. The opHub sharing
155
+ // mechanism (Step 4) will associate them with the correct teams later.
137
156
  if (allowedBuilderPhids &&
138
157
  ownerId &&
139
158
  !allowedBuilderPhids.has(ownerId))
@@ -280,7 +299,7 @@ export const getResolvers = (subgraph) => {
280
299
  const month = getMonthKey(periodStartDate) || periodKey;
281
300
  // Build snapshot report data
282
301
  const snapshotReportData = snapshotReport
283
- ? buildSnapshotReportData(snapshotReport, accountTransactionsDocs)
302
+ ? buildSnapshotReportData(snapshotReport)
284
303
  : {
285
304
  startDate: "",
286
305
  endDate: "",
@@ -302,7 +321,10 @@ export const getResolvers = (subgraph) => {
302
321
  // Get operational hub member from builder profile
303
322
  const opHubMember = ownerState?.operationalHubMember ?? null;
304
323
  const operationalHubMember = opHubMember?.phid || opHubMember?.name
305
- ? { phid: opHubMember.phid || null, name: opHubMember.name || null }
324
+ ? {
325
+ phid: opHubMember.phid || null,
326
+ name: opHubMember.name || null,
327
+ }
306
328
  : null;
307
329
  budgetStatements.push({
308
330
  id: key,
@@ -338,9 +360,15 @@ export const getResolvers = (subgraph) => {
338
360
  stmt.reportedActuals = { unit: "USDS", value: String(total) };
339
361
  }
340
362
  }
363
+ // When filtering by teamId, remove entries for other owners that were
364
+ // only collected because we don't filter snapshot reports by teamId
365
+ // (snapshots are often owned by the opHub, not individual teams).
366
+ const filteredStatements = teamId
367
+ ? budgetStatements.filter((stmt) => stmt.owner.id === teamId)
368
+ : budgetStatements;
341
369
  // Sort by month (most recent first)
342
- budgetStatements.sort(sortByMonth);
343
- return budgetStatements;
370
+ filteredStatements.sort(sortByMonth);
371
+ return filteredStatements;
344
372
  },
345
373
  },
346
374
  };
@@ -348,7 +376,7 @@ export const getResolvers = (subgraph) => {
348
376
  /**
349
377
  * Build snapshot report data from a SnapshotReportDocument
350
378
  */
351
- function buildSnapshotReportData(doc, accountTransactionsDocs) {
379
+ function buildSnapshotReportData(doc) {
352
380
  const state = doc.state.global;
353
381
  return {
354
382
  startDate: state.reportPeriodStart || state.startDate || "",
@@ -375,7 +403,7 @@ function buildSnapshotReportData(doc, accountTransactionsDocs) {
375
403
  datetime: tx.datetime,
376
404
  txHash: tx.txHash,
377
405
  counterParty: tx.counterParty || "",
378
- counterPartyName: getCounterPartyName(tx.counterPartyAccountId, accountTransactionsDocs),
406
+ counterPartyName: getCounterPartyName(tx.counterParty, state.snapshotAccounts),
379
407
  amount: {
380
408
  value: tx.amount,
381
409
  unit: tx.token,
@@ -461,19 +489,14 @@ function buildExpenseReportData(doc) {
461
489
  };
462
490
  }
463
491
  /**
464
- * Get counter party name from account-transactions document
492
+ * Get counter party name by matching the counter party address
493
+ * against snapshot account addresses.
465
494
  */
466
- function getCounterPartyName(counterPartyAccountId, accountTransactionsDocs) {
467
- if (!counterPartyAccountId)
495
+ function getCounterPartyName(counterPartyAddress, snapshotAccounts) {
496
+ if (!counterPartyAddress)
468
497
  return "";
469
- // Search through all account-transactions docs to find the account name
470
- for (const txDoc of accountTransactionsDocs.values()) {
471
- const account = txDoc.state.global.account;
472
- if (account && account.id === counterPartyAccountId) {
473
- return account.name || "";
474
- }
475
- }
476
- return "";
498
+ const account = snapshotAccounts.find((acc) => acc.accountAddress.toLowerCase() === counterPartyAddress.toLowerCase());
499
+ return account?.accountName || "";
477
500
  }
478
501
  /**
479
502
  * Sum of all line item actuals from expense report wallets
@@ -117,8 +117,18 @@ describe("getMonthKey", () => {
117
117
  });
118
118
  it("handles all months", () => {
119
119
  const expected = [
120
- "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
121
- "JUL", "AUG", "SEP", "OCT", "NOV", "DEC",
120
+ "JAN",
121
+ "FEB",
122
+ "MAR",
123
+ "APR",
124
+ "MAY",
125
+ "JUN",
126
+ "JUL",
127
+ "AUG",
128
+ "SEP",
129
+ "OCT",
130
+ "NOV",
131
+ "DEC",
122
132
  ];
123
133
  for (let i = 0; i < 12; i++) {
124
134
  const month = String(i + 1).padStart(2, "0");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@powerhousedao/contributor-billing",
3
3
  "description": "Document models that help contributors of open organisations get paid anonymously for their work on a monthly basis.",
4
- "version": "1.0.0-dev.7",
4
+ "version": "1.0.0-dev.9",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
7
7
  "type": "git",
@@ -69,13 +69,13 @@
69
69
  "migrate": "ph-cli migrate"
70
70
  },
71
71
  "dependencies": {
72
- "@powerhousedao/builder-profile": "1.0.3",
73
- "@powerhousedao/builder-tools": "5.3.3",
74
- "@powerhousedao/common": "5.3.3",
75
- "@powerhousedao/design-system": "5.3.3",
72
+ "@powerhousedao/builder-profile": "^1.0.6",
73
+ "@powerhousedao/builder-tools": "5.3.4",
74
+ "@powerhousedao/common": "5.3.4",
75
+ "@powerhousedao/design-system": "5.3.4",
76
76
  "@powerhousedao/document-engineering": "^1.40.1",
77
- "@powerhousedao/service-offering": "0.0.3",
78
- "@powerhousedao/vetra": "^5.3.3",
77
+ "@powerhousedao/service-offering": "0.0.9",
78
+ "@powerhousedao/vetra": "^5.3.4",
79
79
  "@react-pdf/renderer": "^4.3.1",
80
80
  "@safe-global/api-kit": "^4.0.0",
81
81
  "@safe-global/protocol-kit": "^6.0.3",
@@ -84,7 +84,7 @@
84
84
  "@types/cors": "^2.8.17",
85
85
  "axios": "^1.9.0",
86
86
  "cors": "^2.8.5",
87
- "document-model": "5.3.3",
87
+ "document-model": "5.3.4",
88
88
  "dotenv": "^16.5.0",
89
89
  "error": "^10.4.0",
90
90
  "ethers": "^6.14.0",
@@ -99,23 +99,23 @@
99
99
  "devDependencies": {
100
100
  "@electric-sql/pglite": "^0.2.12",
101
101
  "@eslint/js": "^9.38.0",
102
- "@powerhousedao/analytics-engine-core": "^0.5.0",
103
- "@powerhousedao/codegen": "5.3.3",
104
- "@powerhousedao/config": "5.3.3",
105
- "@powerhousedao/connect": "5.3.3",
106
- "@powerhousedao/ph-cli": "5.3.3",
107
- "@powerhousedao/reactor-api": "5.3.3",
108
- "@powerhousedao/reactor-browser": "5.3.3",
109
- "@powerhousedao/reactor-local": "5.3.3",
102
+ "@powerhousedao/analytics-engine-core": "^0.6.4",
103
+ "@powerhousedao/codegen": "5.3.4",
104
+ "@powerhousedao/config": "5.3.4",
105
+ "@powerhousedao/connect": "5.3.4",
106
+ "@powerhousedao/ph-cli": "5.3.4",
107
+ "@powerhousedao/reactor-api": "5.3.4",
108
+ "@powerhousedao/reactor-browser": "5.3.4",
109
+ "@powerhousedao/reactor-local": "5.3.4",
110
110
  "@powerhousedao/scalars": "^2.0.1",
111
- "@powerhousedao/switchboard": "5.3.3",
111
+ "@powerhousedao/switchboard": "5.3.4",
112
112
  "@tailwindcss/cli": "^4.1.4",
113
113
  "@testing-library/react": "^16.3.0",
114
114
  "@types/node": "^24.9.2",
115
115
  "@types/react": "^19.2.2",
116
116
  "@types/react-dom": "^19.2.2",
117
117
  "@vitejs/plugin-react": "^5.1.0",
118
- "document-drive": "5.3.3",
118
+ "document-drive": "5.3.4",
119
119
  "eslint": "^9.38.0",
120
120
  "eslint-config-prettier": "^10.1.8",
121
121
  "eslint-plugin-prettier": "^5.5.4",