@trustless-work/blocks 1.0.2 → 1.0.3
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/bin/index.js
CHANGED
|
@@ -1580,7 +1580,7 @@ if (args[0] === "init") {
|
|
|
1580
1580
|
}
|
|
1581
1581
|
|
|
1582
1582
|
const addShadcn = await promptYesNo(
|
|
1583
|
-
"Add shadcn components (button, input, form, card, sonner, checkbox, dialog, textarea, sonner, select, table, calendar, popover, separator, calendar-05, badge, sheet, tabs, avatar, tooltip, progress)?",
|
|
1583
|
+
"Add shadcn components (button, input, form, card, sonner, checkbox, dialog, textarea, sonner, select, table, calendar, popover, separator, calendar-05, badge, sheet, tabs, avatar, tooltip, progress, chart)?",
|
|
1584
1584
|
true
|
|
1585
1585
|
);
|
|
1586
1586
|
if (addShadcn) {
|
|
@@ -1609,6 +1609,7 @@ if (args[0] === "init") {
|
|
|
1609
1609
|
"avatar",
|
|
1610
1610
|
"tooltip",
|
|
1611
1611
|
"progress",
|
|
1612
|
+
"chart",
|
|
1612
1613
|
]);
|
|
1613
1614
|
});
|
|
1614
1615
|
} else {
|
|
@@ -1621,7 +1622,7 @@ if (args[0] === "init") {
|
|
|
1621
1622
|
}
|
|
1622
1623
|
const meta = JSON.parse(fs.readFileSync(GLOBAL_DEPS_FILE, "utf8"));
|
|
1623
1624
|
const installLibs = await promptYesNo(
|
|
1624
|
-
"Install (react-hook-form, @tanstack/react-query, @tanstack/react-query-devtools, @trustless-work/escrow, @hookform/resolvers, axios, @creit.tech/stellar-wallets-kit, react-day-picker & zod) dependencies now?",
|
|
1625
|
+
"Install (react-hook-form, @tanstack/react-query, @tanstack/react-query-devtools, @trustless-work/escrow, @hookform/resolvers, axios, @creit.tech/stellar-wallets-kit, react-day-picker, recharts & zod) dependencies now?",
|
|
1625
1626
|
true
|
|
1626
1627
|
);
|
|
1627
1628
|
if (installLibs) {
|
|
@@ -1807,6 +1808,9 @@ if (args[0] === "init") {
|
|
|
1807
1808
|
--- Escrows ---
|
|
1808
1809
|
trustless-work add escrows
|
|
1809
1810
|
|
|
1811
|
+
--- Dashboard ---
|
|
1812
|
+
trustless-work add dashboard
|
|
1813
|
+
|
|
1810
1814
|
--- Indicators ---
|
|
1811
1815
|
trustless-work add escrows/indicators/balance-progress
|
|
1812
1816
|
trustless-work add escrows/indicators/balance-progress/bar
|
package/package.json
CHANGED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import {
|
|
5
|
+
Card,
|
|
6
|
+
CardContent,
|
|
7
|
+
CardDescription,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
} from "__UI_BASE__/card";
|
|
11
|
+
import { Separator } from "__UI_BASE__/separator";
|
|
12
|
+
import { useDashboard } from "./useDashboard";
|
|
13
|
+
import { formatCurrency } from "../helpers/format.helper";
|
|
14
|
+
import { Activity, Layers3, PiggyBank } from "lucide-react";
|
|
15
|
+
import {
|
|
16
|
+
AreaChart,
|
|
17
|
+
Area,
|
|
18
|
+
XAxis,
|
|
19
|
+
CartesianGrid,
|
|
20
|
+
BarChart,
|
|
21
|
+
Bar,
|
|
22
|
+
PieChart,
|
|
23
|
+
Pie,
|
|
24
|
+
Cell,
|
|
25
|
+
} from "recharts";
|
|
26
|
+
import {
|
|
27
|
+
ChartContainer,
|
|
28
|
+
ChartTooltip,
|
|
29
|
+
ChartTooltipContent,
|
|
30
|
+
type ChartConfig,
|
|
31
|
+
} from "__UI_BASE__/chart";
|
|
32
|
+
|
|
33
|
+
const chartConfigBar: ChartConfig = {
|
|
34
|
+
desktop: {
|
|
35
|
+
label: "Amount",
|
|
36
|
+
color: "var(--chart-1)",
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const chartConfigDonut: ChartConfig = {
|
|
41
|
+
visitors: { label: "Count" },
|
|
42
|
+
single: { label: "Single", color: "var(--chart-1)" },
|
|
43
|
+
multi: { label: "Multi", color: "var(--chart-2)" },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const chartConfigArea: ChartConfig = {
|
|
47
|
+
desktop: {
|
|
48
|
+
label: "Created",
|
|
49
|
+
color: "var(--chart-1)",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export function Dashboard() {
|
|
54
|
+
const {
|
|
55
|
+
isLoading,
|
|
56
|
+
totalEscrows,
|
|
57
|
+
totalAmount,
|
|
58
|
+
totalBalance,
|
|
59
|
+
typeSlices,
|
|
60
|
+
amountsByDate,
|
|
61
|
+
createdByDate,
|
|
62
|
+
} = useDashboard();
|
|
63
|
+
|
|
64
|
+
const barData = React.useMemo(
|
|
65
|
+
() => amountsByDate.map((d) => ({ month: d.date, desktop: d.amount })),
|
|
66
|
+
[amountsByDate]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const donutData = React.useMemo(
|
|
70
|
+
() =>
|
|
71
|
+
typeSlices.map((s) => ({
|
|
72
|
+
browser: s.type === "single" ? "single" : "multi",
|
|
73
|
+
visitors: s.value,
|
|
74
|
+
fill:
|
|
75
|
+
s.type === "single" ? "var(--color-single)" : "var(--color-multi)",
|
|
76
|
+
})),
|
|
77
|
+
[typeSlices]
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const areaData = React.useMemo(
|
|
81
|
+
() => createdByDate.map((d) => ({ month: d.date, desktop: d.count })),
|
|
82
|
+
[createdByDate]
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="grid gap-6">
|
|
87
|
+
{/* KPI Cards */}
|
|
88
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
89
|
+
<Card>
|
|
90
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
91
|
+
<CardTitle className="text-sm font-medium">Escrows</CardTitle>
|
|
92
|
+
<Layers3 className="h-4 w-4 text-muted-foreground" />
|
|
93
|
+
</CardHeader>
|
|
94
|
+
<CardContent>
|
|
95
|
+
<div className="text-2xl font-bold">
|
|
96
|
+
{isLoading ? "-" : totalEscrows}
|
|
97
|
+
</div>
|
|
98
|
+
<p className="text-xs text-muted-foreground">
|
|
99
|
+
Total number of escrows
|
|
100
|
+
</p>
|
|
101
|
+
</CardContent>
|
|
102
|
+
</Card>
|
|
103
|
+
|
|
104
|
+
<Card>
|
|
105
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
106
|
+
<CardTitle className="text-sm font-medium">Total Amount</CardTitle>
|
|
107
|
+
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
108
|
+
</CardHeader>
|
|
109
|
+
<CardContent>
|
|
110
|
+
<div className="text-2xl font-bold">
|
|
111
|
+
{isLoading ? "-" : formatCurrency(totalAmount, "USDC")}
|
|
112
|
+
</div>
|
|
113
|
+
<p className="text-xs text-muted-foreground">
|
|
114
|
+
Sum of amounts (SR + MR)
|
|
115
|
+
</p>
|
|
116
|
+
</CardContent>
|
|
117
|
+
</Card>
|
|
118
|
+
|
|
119
|
+
<Card>
|
|
120
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
121
|
+
<CardTitle className="text-sm font-medium">Total Balance</CardTitle>
|
|
122
|
+
<PiggyBank className="h-4 w-4 text-muted-foreground" />
|
|
123
|
+
</CardHeader>
|
|
124
|
+
<CardContent>
|
|
125
|
+
<div className="text-2xl font-bold">
|
|
126
|
+
{isLoading ? "-" : formatCurrency(totalBalance, "USDC")}
|
|
127
|
+
</div>
|
|
128
|
+
<p className="text-xs text-muted-foreground">
|
|
129
|
+
Total balance across all escrows
|
|
130
|
+
</p>
|
|
131
|
+
</CardContent>
|
|
132
|
+
</Card>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<Separator />
|
|
136
|
+
|
|
137
|
+
{/* Charts */}
|
|
138
|
+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
139
|
+
{/* Bar chart: Amounts by date (shadcn pattern) */}
|
|
140
|
+
<Card>
|
|
141
|
+
<CardHeader>
|
|
142
|
+
<CardTitle>Escrow Amounts</CardTitle>
|
|
143
|
+
<CardDescription>Amounts by date</CardDescription>
|
|
144
|
+
</CardHeader>
|
|
145
|
+
<CardContent>
|
|
146
|
+
<ChartContainer
|
|
147
|
+
className="w-full h-56 sm:h-64 lg:h-72"
|
|
148
|
+
config={chartConfigBar}
|
|
149
|
+
>
|
|
150
|
+
<BarChart accessibilityLayer data={barData}>
|
|
151
|
+
<CartesianGrid vertical={false} />
|
|
152
|
+
<XAxis
|
|
153
|
+
dataKey="month"
|
|
154
|
+
tickLine={false}
|
|
155
|
+
tickMargin={10}
|
|
156
|
+
axisLine={false}
|
|
157
|
+
tickFormatter={(value) =>
|
|
158
|
+
new Date(String(value)).toLocaleDateString("en-US", {
|
|
159
|
+
month: "short",
|
|
160
|
+
day: "numeric",
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
/>
|
|
164
|
+
<ChartTooltip
|
|
165
|
+
cursor={false}
|
|
166
|
+
content={<ChartTooltipContent hideLabel />}
|
|
167
|
+
/>
|
|
168
|
+
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={8} />
|
|
169
|
+
</BarChart>
|
|
170
|
+
</ChartContainer>
|
|
171
|
+
</CardContent>
|
|
172
|
+
</Card>
|
|
173
|
+
|
|
174
|
+
{/* Donut chart: Escrow types (shadcn pattern) */}
|
|
175
|
+
<Card className="flex flex-col">
|
|
176
|
+
<CardHeader className="items-center pb-0">
|
|
177
|
+
<CardTitle>Escrow Types</CardTitle>
|
|
178
|
+
<CardDescription>Escrow types</CardDescription>
|
|
179
|
+
</CardHeader>
|
|
180
|
+
<CardContent className="flex-1 pb-0">
|
|
181
|
+
<ChartContainer
|
|
182
|
+
config={chartConfigDonut}
|
|
183
|
+
className="w-full h-56 sm:h-64 lg:h-72"
|
|
184
|
+
>
|
|
185
|
+
<PieChart>
|
|
186
|
+
<ChartTooltip
|
|
187
|
+
cursor={false}
|
|
188
|
+
content={<ChartTooltipContent hideLabel />}
|
|
189
|
+
/>
|
|
190
|
+
<Pie
|
|
191
|
+
data={donutData}
|
|
192
|
+
dataKey="visitors"
|
|
193
|
+
nameKey="browser"
|
|
194
|
+
innerRadius={60}
|
|
195
|
+
>
|
|
196
|
+
{donutData.map((slice, idx) => (
|
|
197
|
+
<Cell key={`cell-${idx}`} fill={slice.fill} />
|
|
198
|
+
))}
|
|
199
|
+
</Pie>
|
|
200
|
+
</PieChart>
|
|
201
|
+
</ChartContainer>
|
|
202
|
+
<div className="mt-4 flex items-center justify-center gap-6">
|
|
203
|
+
<div className="flex items-center gap-2">
|
|
204
|
+
<span
|
|
205
|
+
className="h-2 w-2 rounded-full"
|
|
206
|
+
style={{ background: "var(--chart-1)" }}
|
|
207
|
+
/>
|
|
208
|
+
<span className="text-xs text-muted-foreground">Single</span>
|
|
209
|
+
</div>
|
|
210
|
+
<div className="flex items-center gap-2">
|
|
211
|
+
<span
|
|
212
|
+
className="h-2 w-2 rounded-full"
|
|
213
|
+
style={{ background: "var(--chart-2)" }}
|
|
214
|
+
/>
|
|
215
|
+
<span className="text-xs text-muted-foreground">Multi</span>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</CardContent>
|
|
219
|
+
</Card>
|
|
220
|
+
|
|
221
|
+
{/* Area chart: Created escrows (shadcn pattern) */}
|
|
222
|
+
<Card className="lg:col-span-2">
|
|
223
|
+
<CardHeader>
|
|
224
|
+
<CardTitle>Escrow Created</CardTitle>
|
|
225
|
+
<CardDescription>Created escrows by date</CardDescription>
|
|
226
|
+
</CardHeader>
|
|
227
|
+
<CardContent>
|
|
228
|
+
<ChartContainer
|
|
229
|
+
className="w-full h-56 sm:h-64 lg:h-72"
|
|
230
|
+
config={chartConfigArea}
|
|
231
|
+
>
|
|
232
|
+
<AreaChart
|
|
233
|
+
accessibilityLayer
|
|
234
|
+
data={areaData}
|
|
235
|
+
margin={{ left: 12, right: 12 }}
|
|
236
|
+
>
|
|
237
|
+
<CartesianGrid vertical={false} />
|
|
238
|
+
<XAxis
|
|
239
|
+
dataKey="month"
|
|
240
|
+
tickLine={false}
|
|
241
|
+
axisLine={false}
|
|
242
|
+
tickMargin={8}
|
|
243
|
+
tickFormatter={(value) =>
|
|
244
|
+
new Date(String(value)).toLocaleDateString("en-US", {
|
|
245
|
+
month: "short",
|
|
246
|
+
day: "numeric",
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
/>
|
|
250
|
+
<ChartTooltip
|
|
251
|
+
cursor={false}
|
|
252
|
+
content={<ChartTooltipContent indicator="line" />}
|
|
253
|
+
/>
|
|
254
|
+
<Area
|
|
255
|
+
dataKey="desktop"
|
|
256
|
+
type="natural"
|
|
257
|
+
fill="var(--color-desktop)"
|
|
258
|
+
fillOpacity={0.4}
|
|
259
|
+
stroke="var(--color-desktop)"
|
|
260
|
+
/>
|
|
261
|
+
</AreaChart>
|
|
262
|
+
</ChartContainer>
|
|
263
|
+
</CardContent>
|
|
264
|
+
</Card>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Tooltip as RechartsTooltip } from "recharts";
|
|
5
|
+
|
|
6
|
+
export type ChartConfig = Record<string, { label: string; color?: string }>;
|
|
7
|
+
|
|
8
|
+
interface ChartContainerProps {
|
|
9
|
+
config: ChartConfig;
|
|
10
|
+
className?: string;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ChartContainer({
|
|
15
|
+
config,
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
}: ChartContainerProps) {
|
|
19
|
+
const style: React.CSSProperties = {};
|
|
20
|
+
for (const [key, value] of Object.entries(config)) {
|
|
21
|
+
const varName = `--color-${key}` as const;
|
|
22
|
+
if (value.color) (style as any)[varName] = value.color;
|
|
23
|
+
}
|
|
24
|
+
return (
|
|
25
|
+
<div className={className} style={style}>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type RechartsPayloadItem = {
|
|
32
|
+
value?: number | string;
|
|
33
|
+
dataKey?: string;
|
|
34
|
+
color?: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
type RechartsTooltipContentProps = {
|
|
39
|
+
active?: boolean;
|
|
40
|
+
label?: string | number;
|
|
41
|
+
payload?: RechartsPayloadItem[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type ChartTooltipContentProps = {
|
|
45
|
+
hideLabel?: boolean;
|
|
46
|
+
indicator?: "line" | "dot";
|
|
47
|
+
} & RechartsTooltipContentProps;
|
|
48
|
+
|
|
49
|
+
export function ChartTooltip(
|
|
50
|
+
props: React.ComponentProps<typeof RechartsTooltip>
|
|
51
|
+
) {
|
|
52
|
+
return <RechartsTooltip {...props} />;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function ChartTooltipContent({
|
|
56
|
+
active,
|
|
57
|
+
label,
|
|
58
|
+
payload,
|
|
59
|
+
hideLabel,
|
|
60
|
+
}: ChartTooltipContentProps) {
|
|
61
|
+
if (!active || !payload || payload.length === 0) return null;
|
|
62
|
+
return (
|
|
63
|
+
<div className="rounded-md border bg-background p-2 text-sm shadow-sm">
|
|
64
|
+
{!hideLabel ? <div className="mb-1 font-medium">{label}</div> : null}
|
|
65
|
+
<div className="space-y-1">
|
|
66
|
+
{payload.map((item, idx) => (
|
|
67
|
+
<div key={idx} className="flex items-center gap-2">
|
|
68
|
+
<span
|
|
69
|
+
className="inline-block h-2 w-2 rounded-full"
|
|
70
|
+
style={{ backgroundColor: item.color }}
|
|
71
|
+
aria-hidden
|
|
72
|
+
/>
|
|
73
|
+
<span className="text-muted-foreground">
|
|
74
|
+
{item.name ?? String(item.dataKey)}
|
|
75
|
+
</span>
|
|
76
|
+
<span className="font-medium ml-auto">
|
|
77
|
+
{item.value as React.ReactNode}
|
|
78
|
+
</span>
|
|
79
|
+
</div>
|
|
80
|
+
))}
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useWalletContext } from "../wallet-kit/WalletProvider";
|
|
5
|
+
import { useEscrowsBySignerQuery } from "../tanstack/useEscrowsBySignerQuery";
|
|
6
|
+
import type { GetEscrowsFromIndexerResponse as Escrow } from "@trustless-work/escrow/types";
|
|
7
|
+
|
|
8
|
+
type AmountsByDatePoint = { date: string; amount: number };
|
|
9
|
+
type CreatedByDatePoint = { date: string; count: number };
|
|
10
|
+
type DonutSlice = { type: "single" | "multi"; value: number; fill: string };
|
|
11
|
+
|
|
12
|
+
function getCreatedDateKey(createdAt: Escrow["createdAt"]): string {
|
|
13
|
+
// createdAt is a Firestore-like timestamp: { _seconds, _nanoseconds }
|
|
14
|
+
const seconds = (createdAt as unknown as { _seconds?: number })?._seconds;
|
|
15
|
+
const d = seconds ? new Date(seconds * 1000) : new Date();
|
|
16
|
+
// YYYY-MM-DD
|
|
17
|
+
return d.toISOString().slice(0, 10);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getSingleReleaseAmount(escrow: Escrow): number {
|
|
21
|
+
// Single release stores total in .amount
|
|
22
|
+
const raw = (escrow as unknown as { amount?: number | string }).amount;
|
|
23
|
+
const n = Number(raw ?? 0);
|
|
24
|
+
return Number.isFinite(n) ? n : 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getMultiReleaseAmount(escrow: Escrow): number {
|
|
28
|
+
// Multi release accumulates across milestones
|
|
29
|
+
const milestones = (
|
|
30
|
+
escrow as unknown as {
|
|
31
|
+
milestones?: Array<{ amount?: number | string }>;
|
|
32
|
+
}
|
|
33
|
+
).milestones;
|
|
34
|
+
if (!Array.isArray(milestones)) return 0;
|
|
35
|
+
return milestones.reduce((acc: number, m) => {
|
|
36
|
+
const n = Number(m?.amount ?? 0);
|
|
37
|
+
return acc + (Number.isFinite(n) ? n : 0);
|
|
38
|
+
}, 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getEscrowAmount(escrow: Escrow): number {
|
|
42
|
+
if (escrow.type === "single-release") return getSingleReleaseAmount(escrow);
|
|
43
|
+
if (escrow.type === "multi-release") return getMultiReleaseAmount(escrow);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useDashboard() {
|
|
48
|
+
const { walletAddress } = useWalletContext();
|
|
49
|
+
|
|
50
|
+
const {
|
|
51
|
+
data = [],
|
|
52
|
+
isLoading,
|
|
53
|
+
isFetching,
|
|
54
|
+
isError,
|
|
55
|
+
refetch,
|
|
56
|
+
} = useEscrowsBySignerQuery({
|
|
57
|
+
signer: walletAddress ?? "",
|
|
58
|
+
enabled: !!walletAddress,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const totalEscrows = React.useMemo<number>(() => data.length, [data.length]);
|
|
62
|
+
|
|
63
|
+
const totalAmount = React.useMemo<number>(() => {
|
|
64
|
+
return data.reduce((acc: number, e) => acc + getEscrowAmount(e), 0);
|
|
65
|
+
}, [data]);
|
|
66
|
+
|
|
67
|
+
const totalBalance = React.useMemo<number>(() => {
|
|
68
|
+
return data.reduce((acc: number, e) => acc + Number(e?.balance ?? 0), 0);
|
|
69
|
+
}, [data]);
|
|
70
|
+
|
|
71
|
+
const typeSlices = React.useMemo<DonutSlice[]>(() => {
|
|
72
|
+
let single = 0;
|
|
73
|
+
let multi = 0;
|
|
74
|
+
for (const e of data) {
|
|
75
|
+
if (e.type === "single-release") single += 1;
|
|
76
|
+
else if (e.type === "multi-release") multi += 1;
|
|
77
|
+
}
|
|
78
|
+
return [
|
|
79
|
+
{ type: "single", value: single, fill: "var(--color-single)" },
|
|
80
|
+
{ type: "multi", value: multi, fill: "var(--color-multi)" },
|
|
81
|
+
];
|
|
82
|
+
}, [data]);
|
|
83
|
+
|
|
84
|
+
const amountsByDate = React.useMemo<AmountsByDatePoint[]>(() => {
|
|
85
|
+
const map = new Map<string, number>();
|
|
86
|
+
for (const e of data) {
|
|
87
|
+
const key = getCreatedDateKey(e.createdAt);
|
|
88
|
+
const current = map.get(key) ?? 0;
|
|
89
|
+
map.set(key, current + getEscrowAmount(e));
|
|
90
|
+
}
|
|
91
|
+
return Array.from(map.entries())
|
|
92
|
+
.map(([date, amount]) => ({ date, amount }))
|
|
93
|
+
.sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
|
|
94
|
+
}, [data]);
|
|
95
|
+
|
|
96
|
+
const createdByDate = React.useMemo<CreatedByDatePoint[]>(() => {
|
|
97
|
+
const map = new Map<string, number>();
|
|
98
|
+
for (const e of data) {
|
|
99
|
+
const key = getCreatedDateKey(e.createdAt);
|
|
100
|
+
map.set(key, (map.get(key) ?? 0) + 1);
|
|
101
|
+
}
|
|
102
|
+
return Array.from(map.entries())
|
|
103
|
+
.map(([date, count]) => ({ date, count }))
|
|
104
|
+
.sort((a, b) => (a.date < b.date ? -1 : a.date > b.date ? 1 : 0));
|
|
105
|
+
}, [data]);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
isLoading,
|
|
109
|
+
isFetching,
|
|
110
|
+
isError,
|
|
111
|
+
refetch,
|
|
112
|
+
totalEscrows,
|
|
113
|
+
totalAmount,
|
|
114
|
+
totalBalance,
|
|
115
|
+
typeSlices,
|
|
116
|
+
amountsByDate,
|
|
117
|
+
createdByDate,
|
|
118
|
+
} as const;
|
|
119
|
+
}
|
package/templates/deps.json
CHANGED