@powerhousedao/contributor-billing 1.0.0-dev.6 → 1.0.0-dev.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/document-models/expense-report/gen/schema/zod.d.ts.map +1 -1
- package/dist/document-models/expense-report/gen/schema/zod.js +10 -30
- package/dist/editors/contributor-billing/components/DashboardHome.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/MonthReportCard.d.ts +4 -4
- package/dist/editors/contributor-billing/components/MonthReportCard.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/MonthReportCard.js +52 -6
- package/dist/editors/contributor-billing/components/MonthlyReportsOverview.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/MonthlyReportsOverview.js +49 -5
- package/dist/editors/contributor-billing/components/ReportingView.d.ts +0 -3
- package/dist/editors/contributor-billing/components/ReportingView.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/ReportingView.js +65 -8
- package/dist/editors/contributor-billing/hooks/useMonthlyReports.d.ts +4 -0
- package/dist/editors/contributor-billing/hooks/useMonthlyReports.d.ts.map +1 -1
- package/dist/editors/contributor-billing/hooks/useMonthlyReports.js +4 -0
- package/dist/editors/snapshot-report-editor/components/DateRangePicker.d.ts +19 -0
- package/dist/editors/snapshot-report-editor/components/DateRangePicker.d.ts.map +1 -0
- package/dist/editors/snapshot-report-editor/components/DateRangePicker.js +66 -0
- package/dist/editors/snapshot-report-editor/editor.d.ts.map +1 -1
- package/dist/editors/snapshot-report-editor/editor.js +72 -48
- package/dist/style.css +47 -0
- package/dist/subgraphs/budget-statements/resolvers.d.ts.map +1 -1
- package/dist/subgraphs/budget-statements/resolvers.js +46 -23
- package/dist/subgraphs/budget-statements/resolvers.test.js +12 -2
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod.d.ts","sourceRoot":"","sources":["../../../../../document-models/expense-report/gen/schema/zod.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,KAAK,EACV,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAGlB,WAAW,EACX,gBAAgB,EAChB,QAAQ,EACR,aAAa,EACb,aAAa,EACb,2BAA2B,EAC3B,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,MAAM,EACP,MAAM,YAAY,CAAC;AAEpB,KAAK,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC;KAC3B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAChC,CAAC,CAAC;AAEH,KAAK,iBAAiB,GAAG,EAAE,CAAC;AAE5B,eAAO,MAAM,mBAAmB,GAAI,GAAG,GAAG,KAAG,CAAC,IAAI,iBACnB,CAAC;AAEhC,eAAO,MAAM,uBAAuB,gGAEI,CAAC;AAEzC,eAAO,MAAM,yBAAyB;;;;EAAuC,CAAC;AAE9E,eAAO,MAAM,8BAA8B;;;;EAIzC,CAAC;AAEH,wBAAgB,8BAA8B,IAAI,CAAC,CAAC,SAAS,CAC3D,UAAU,CAAC,wBAAwB,CAAC,CACrC,
|
|
1
|
+
{"version":3,"file":"zod.d.ts","sourceRoot":"","sources":["../../../../../document-models/expense-report/gen/schema/zod.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,KAAK,EACV,wBAAwB,EACxB,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAGlB,WAAW,EACX,gBAAgB,EAChB,QAAQ,EACR,aAAa,EACb,aAAa,EACb,2BAA2B,EAC3B,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,cAAc,EACd,wBAAwB,EACxB,mBAAmB,EACnB,iBAAiB,EACjB,MAAM,EACP,MAAM,YAAY,CAAC;AAEpB,KAAK,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC;KAC3B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAChC,CAAC,CAAC;AAEH,KAAK,iBAAiB,GAAG,EAAE,CAAC;AAE5B,eAAO,MAAM,mBAAmB,GAAI,GAAG,GAAG,KAAG,CAAC,IAAI,iBACnB,CAAC;AAEhC,eAAO,MAAM,uBAAuB,gGAEI,CAAC;AAEzC,eAAO,MAAM,yBAAyB;;;;EAAuC,CAAC;AAE9E,eAAO,MAAM,8BAA8B;;;;EAIzC,CAAC;AAEH,wBAAgB,8BAA8B,IAAI,CAAC,CAAC,SAAS,CAC3D,UAAU,CAAC,wBAAwB,CAAC,CACrC,CAOA;AAED,wBAAgB,2BAA2B,IAAI,CAAC,CAAC,SAAS,CACxD,UAAU,CAAC,qBAAqB,CAAC,CAClC,CAMA;AAED,wBAAgB,sBAAsB,IAAI,CAAC,CAAC,SAAS,CACnD,UAAU,CAAC,gBAAgB,CAAC,CAC7B,CAOA;AAED,wBAAgB,oBAAoB,IAAI,CAAC,CAAC,SAAS,CACjD,UAAU,CAAC,cAAc,CAAC,CAC3B,CAOA;AAED,wBAAgB,wBAAwB,IAAI,CAAC,CAAC,SAAS,CACrD,UAAU,CAAC,kBAAkB,CAAC,CAC/B,CAYA;AAED,wBAAgB,iBAAiB,IAAI,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CASxE;AAED,wBAAgB,sBAAsB,IAAI,CAAC,CAAC,SAAS,CACnD,UAAU,CAAC,gBAAgB,CAAC,CAC7B,CAQA;AAED,wBAAgB,cAAc,IAAI,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAYlE;AAED,wBAAgB,mBAAmB,IAAI,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAO5E;AAED,wBAAgB,mBAAmB,IAAI,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAW5E;AAED,wBAAgB,iCAAiC,IAAI,CAAC,CAAC,SAAS,CAC9D,UAAU,CAAC,2BAA2B,CAAC,CACxC,CAOA;AAED,wBAAgB,4BAA4B,IAAI,CAAC,CAAC,SAAS,CACzD,UAAU,CAAC,sBAAsB,CAAC,CACnC,CAOA;AAED,wBAAgB,8BAA8B,IAAI,CAAC,CAAC,SAAS,CAC3D,UAAU,CAAC,wBAAwB,CAAC,CACrC,CAIA;AAED,wBAAgB,yBAAyB,IAAI,CAAC,CAAC,SAAS,CACtD,UAAU,CAAC,mBAAmB,CAAC,CAChC,CAOA;AAED,wBAAgB,uBAAuB,IAAI,CAAC,CAAC,SAAS,CACpD,UAAU,CAAC,iBAAiB,CAAC,CAC9B,CAMA;AAED,wBAAgB,yBAAyB,IAAI,CAAC,CAAC,SAAS,CACtD,UAAU,CAAC,mBAAmB,CAAC,CAChC,CAOA;AAED,wBAAgB,qBAAqB,IAAI,CAAC,CAAC,SAAS,CAClD,UAAU,CAAC,eAAe,CAAC,CAC5B,CAIA;AAED,wBAAgB,uBAAuB,IAAI,CAAC,CAAC,SAAS,CACpD,UAAU,CAAC,iBAAiB,CAAC,CAC9B,CAIA;AAED,wBAAgB,oBAAoB,IAAI,CAAC,CAAC,SAAS,CACjD,UAAU,CAAC,cAAc,CAAC,CAC3B,CAKA;AAED,wBAAgB,yBAAyB,IAAI,CAAC,CAAC,SAAS,CACtD,UAAU,CAAC,mBAAmB,CAAC,CAChC,CAIA;AAED,wBAAgB,oBAAoB,IAAI,CAAC,CAAC,SAAS,CACjD,UAAU,CAAC,cAAc,CAAC,CAC3B,CAIA;AAED,wBAAgB,8BAA8B,IAAI,CAAC,CAAC,SAAS,CAC3D,UAAU,CAAC,wBAAwB,CAAC,CACrC,CAMA;AAED,wBAAgB,yBAAyB,IAAI,CAAC,CAAC,SAAS,CACtD,UAAU,CAAC,mBAAmB,CAAC,CAChC,CAcA;AAED,wBAAgB,uBAAuB,IAAI,CAAC,CAAC,SAAS,CACpD,UAAU,CAAC,iBAAiB,CAAC,CAC9B,CASA;AAED,wBAAgB,YAAY,IAAI,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAgB9D"}
|
|
@@ -12,9 +12,7 @@ export const ExpenseReportStatusInputSchema = z.enum([
|
|
|
12
12
|
export function AddBillingStatementInputSchema() {
|
|
13
13
|
return z.object({
|
|
14
14
|
billingStatementId: z.string(),
|
|
15
|
-
wallet: z
|
|
16
|
-
.string()
|
|
17
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
15
|
+
wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
18
16
|
message: "Invalid Ethereum address format",
|
|
19
17
|
}),
|
|
20
18
|
});
|
|
@@ -29,9 +27,7 @@ export function AddLineItemGroupInputSchema() {
|
|
|
29
27
|
export function AddLineItemInputSchema() {
|
|
30
28
|
return z.object({
|
|
31
29
|
lineItem: z.lazy(() => LineItemInputSchema()),
|
|
32
|
-
wallet: z
|
|
33
|
-
.string()
|
|
34
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
30
|
+
wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
35
31
|
message: "Invalid Ethereum address format",
|
|
36
32
|
}),
|
|
37
33
|
});
|
|
@@ -39,9 +35,7 @@ export function AddLineItemInputSchema() {
|
|
|
39
35
|
export function AddWalletInputSchema() {
|
|
40
36
|
return z.object({
|
|
41
37
|
name: z.string().nullish(),
|
|
42
|
-
wallet: z
|
|
43
|
-
.string()
|
|
44
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
38
|
+
wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
45
39
|
message: "Invalid Ethereum address format",
|
|
46
40
|
}),
|
|
47
41
|
});
|
|
@@ -114,9 +108,7 @@ export function LineItemInputSchema() {
|
|
|
114
108
|
export function RemoveBillingStatementInputSchema() {
|
|
115
109
|
return z.object({
|
|
116
110
|
billingStatementId: z.string(),
|
|
117
|
-
wallet: z
|
|
118
|
-
.string()
|
|
119
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
111
|
+
wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
120
112
|
message: "Invalid Ethereum address format",
|
|
121
113
|
}),
|
|
122
114
|
});
|
|
@@ -124,9 +116,7 @@ export function RemoveBillingStatementInputSchema() {
|
|
|
124
116
|
export function RemoveGroupTotalsInputSchema() {
|
|
125
117
|
return z.object({
|
|
126
118
|
groupId: z.string(),
|
|
127
|
-
wallet: z
|
|
128
|
-
.string()
|
|
129
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
119
|
+
wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
130
120
|
message: "Invalid Ethereum address format",
|
|
131
121
|
}),
|
|
132
122
|
});
|
|
@@ -139,18 +129,14 @@ export function RemoveLineItemGroupInputSchema() {
|
|
|
139
129
|
export function RemoveLineItemInputSchema() {
|
|
140
130
|
return z.object({
|
|
141
131
|
lineItemId: z.string(),
|
|
142
|
-
wallet: z
|
|
143
|
-
.string()
|
|
144
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
132
|
+
wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
145
133
|
message: "Invalid Ethereum address format",
|
|
146
134
|
}),
|
|
147
135
|
});
|
|
148
136
|
}
|
|
149
137
|
export function RemoveWalletInputSchema() {
|
|
150
138
|
return z.object({
|
|
151
|
-
wallet: z
|
|
152
|
-
.string()
|
|
153
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
139
|
+
wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
154
140
|
message: "Invalid Ethereum address format",
|
|
155
141
|
}),
|
|
156
142
|
});
|
|
@@ -158,9 +144,7 @@ export function RemoveWalletInputSchema() {
|
|
|
158
144
|
export function SetGroupTotalsInputSchema() {
|
|
159
145
|
return z.object({
|
|
160
146
|
groupTotals: z.lazy(() => GroupTotalsInputSchema()),
|
|
161
|
-
wallet: z
|
|
162
|
-
.string()
|
|
163
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
147
|
+
wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
164
148
|
message: "Invalid Ethereum address format",
|
|
165
149
|
}),
|
|
166
150
|
});
|
|
@@ -208,9 +192,7 @@ export function UpdateLineItemInputSchema() {
|
|
|
208
192
|
label: z.string().nullish(),
|
|
209
193
|
lineItemId: z.string(),
|
|
210
194
|
payments: z.number().nullish(),
|
|
211
|
-
wallet: z
|
|
212
|
-
.string()
|
|
213
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
195
|
+
wallet: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
214
196
|
message: "Invalid Ethereum address format",
|
|
215
197
|
}),
|
|
216
198
|
});
|
|
@@ -219,9 +201,7 @@ export function UpdateWalletInputSchema() {
|
|
|
219
201
|
return z.object({
|
|
220
202
|
accountDocumentId: z.string().nullish(),
|
|
221
203
|
accountTransactionsDocumentId: z.string().nullish(),
|
|
222
|
-
address: z
|
|
223
|
-
.string()
|
|
224
|
-
.regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
204
|
+
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/, {
|
|
225
205
|
message: "Invalid Ethereum address format",
|
|
226
206
|
}),
|
|
227
207
|
name: z.string().nullish(),
|
|
@@ -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;
|
|
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":"
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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
|
|
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":"
|
|
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
|
-
|
|
17
|
-
const
|
|
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.
|
|
28
|
-
const year = date.
|
|
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
|
|
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("
|
|
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;
|
|
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":"
|
|
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
|
|
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(
|
|
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) {
|
|
@@ -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;
|
|
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",
|
|
20
|
-
"
|
|
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) =>
|
|
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
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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
|
-
? {
|
|
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
|
-
|
|
343
|
-
return
|
|
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
|
|
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.
|
|
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
|
|
492
|
+
* Get counter party name by matching the counter party address
|
|
493
|
+
* against snapshot account addresses.
|
|
465
494
|
*/
|
|
466
|
-
function getCounterPartyName(
|
|
467
|
-
if (!
|
|
495
|
+
function getCounterPartyName(counterPartyAddress, snapshotAccounts) {
|
|
496
|
+
if (!counterPartyAddress)
|
|
468
497
|
return "";
|
|
469
|
-
|
|
470
|
-
|
|
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",
|
|
121
|
-
"
|
|
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.
|
|
4
|
+
"version": "1.0.0-dev.8",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"@powerhousedao/common": "5.3.3",
|
|
75
75
|
"@powerhousedao/design-system": "5.3.3",
|
|
76
76
|
"@powerhousedao/document-engineering": "^1.40.1",
|
|
77
|
-
"@powerhousedao/service-offering": "0.0.
|
|
77
|
+
"@powerhousedao/service-offering": "0.0.3",
|
|
78
78
|
"@powerhousedao/vetra": "^5.3.3",
|
|
79
79
|
"@react-pdf/renderer": "^4.3.1",
|
|
80
80
|
"@safe-global/api-kit": "^4.0.0",
|