feat(dashboard): implement financial dashboard for receiver escrows#22
feat(dashboard): implement financial dashboard for receiver escrows#22KevinLatino wants to merge 2 commits intoTrustless-Work:mainfrom
Conversation
📝 WalkthroughWalkthroughAdds a new financial dashboard feature: a Next.js dashboard route plus a client React dashboard component, charting utilities, and a custom hook that fetches and aggregates escrow data (with tab filters) to drive KPI cards and multiple charts. Changes
Sequence DiagramsequenceDiagram
participant User
participant DashboardPage
participant FinancialDashboardView
participant WalletContext
participant useFinancialDashboard
participant API as useEscrowsByRoleQuery
participant Charts
User->>DashboardPage: Navigate to /dashboard
DashboardPage->>FinancialDashboardView: Render component
FinancialDashboardView->>WalletContext: Check wallet connection
alt Wallet not connected
FinancialDashboardView->>User: Show connect-wallet empty state
else Wallet connected
FinancialDashboardView->>useFinancialDashboard: Init hook with active tab
useFinancialDashboard->>API: Fetch escrows for wallet
API-->>useFinancialDashboard: Return escrow records
useFinancialDashboard->>useFinancialDashboard: Aggregate totals & series
useFinancialDashboard-->>FinancialDashboardView: Return metrics, series, state
FinancialDashboardView->>Charts: Render KPI cards and Bar/Pie/Area charts
Charts-->>User: Display visualizations
User->>FinancialDashboardView: Change active tab
FinancialDashboardView->>useFinancialDashboard: Re-run with new tab
useFinancialDashboard->>useFinancialDashboard: Filter & re-aggregate
Charts->>User: Update visualizations
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
src/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts (2)
95-98: Unconventional memo dependency onfiltered.length.The dependency array
[filtered.length]works here since the memo only returnsfiltered.length, but it's unconventional. Using[filtered]would be more consistent with typical useMemo patterns and clearer for future maintainers.♻️ Suggested change
const totalEscrows = React.useMemo<number>( () => filtered.length, - [filtered.length] + [filtered] );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts` around lines 95 - 98, Replace the unconventional dependency array for the useMemo that computes totalEscrows: change the dependency from [filtered.length] to [filtered] so the memo watches the filtered array reference instead of its length; update the useMemo call for totalEscrows (the constant named totalEscrows using React.useMemo) accordingly to depend on filtered.
20-48: Type assertions reveal misalignment betweenGetEscrowsFromIndexerResponsetype and runtime indexer data structure.The multiple
as unknown asassertions (lines 21, 27, 33–37, 57–61) indicate the external type from@trustless-work/escrowdoesn't accurately reflect the actual API response structure—specifically,_secondsoncreatedAt,amount,milestones, andstatusfields. While the defensive coding withNumber.isFinitechecks and type guards is necessary and well-implemented, relying on runtime assertions is fragile.Consider:
- Creating a local interface that extends or overrides the external type to match the actual indexer response shape
- Opening an issue with the
@trustless-work/escrowmaintainers to updateGetEscrowsFromIndexerResponseto include these fieldsThis would eliminate type assertions and improve type safety across the codebase.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts` around lines 20 - 48, The code is using many unsafe casts because the external Escrow type doesn't match the indexer response; define a local interface (e.g., IndexerEscrow or LocalEscrow) that extends/overrides GetEscrowsFromIndexerResponse/Escrow to include _seconds on createdAt, amount as number|string, milestones array shape, and status, then update the signatures of getCreatedDateKey, getSingleReleaseAmount, getMultiReleaseAmount, and getEscrowAmount to accept that local interface (or convert the raw API response to that local type once on receipt), remove the ad-hoc "as unknown as" casts, and add a short comment/issue reference to open an upstream issue against `@trustless-work/escrow` to reconcile types.src/components/tw-blocks/financial-dashboard/FinancialSummaryChart.tsx (1)
46-62: Unusedindicatorprop inChartTooltipContent.The
indicatorprop is defined inChartTooltipContentProps(line 48) but is never used in the component implementation. Either implement the indicator visualization logic or remove the unused prop.♻️ Option 1: Remove unused prop
export type ChartTooltipContentProps = { hideLabel?: boolean; - indicator?: "line" | "dot"; } & RechartsTooltipContentProps; export function ChartTooltipContent({ active, label, payload, hideLabel, }: ChartTooltipContentProps) {♻️ Option 2: Implement indicator logic
If the indicator is intended to change the colored dot styling (e.g., line vs dot), implement the visual difference in the rendering logic.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/tw-blocks/financial-dashboard/FinancialSummaryChart.tsx` around lines 46 - 62, ChartTooltipContentProps declares an unused "indicator" prop; update ChartTooltipContent to either remove the prop from ChartTooltipContentProps if it's not needed, or implement its behavior inside ChartTooltipContent by reading the indicator value and using it to vary the rendered marker (e.g., render a line-style indicator when indicator === "line" and a dot when indicator === "dot") in the payload mapping; modify the ChartTooltipContentProps type and ChartTooltipContent function together so the prop signature and rendering logic remain consistent.src/components/tw-blocks/financial-dashboard/FinancialDashboardView.tsx (3)
123-129: Consider extracting duplicate Tabs component.The identical Tabs JSX is repeated three times (loading state, empty state, and main content). Extract it into a reusable variable or component to reduce duplication and ease future maintenance.
♻️ Suggested approach
+const TabsFilter = ( + <Tabs value={activeTab} onValueChange={(v) => setActiveTab(v as FinancialDashboardTab)}> + <TabsList className="h-12 w-fit rounded-lg p-1.5 text-sm"> + <TabsTrigger value="all" className="h-9 px-5">All</TabsTrigger> + <TabsTrigger value="single" className="h-9 px-5">Single Release</TabsTrigger> + <TabsTrigger value="multi" className="h-9 px-5">Multi Release</TabsTrigger> + </TabsList> + </Tabs> +);Then use
{TabsFilter}in each branch instead of duplicating the JSX.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/tw-blocks/financial-dashboard/FinancialDashboardView.tsx` around lines 123 - 129, The repeated Tabs JSX (Tabs, TabsList, TabsTrigger) controlled by activeTab and setActiveTab (cast to FinancialDashboardTab) should be extracted into a reusable piece—either a small component (e.g., TabsFilter) or a memoized variable—to avoid duplication across the loading, empty and main branches; implement TabsFilter that renders the Tabs/TabsList/TabsTrigger trio and accepts activeTab and onValueChange (or uses setActiveTab) and then replace the three duplicated blocks with a single {TabsFilter} usage in each branch.
403-404: Remove trailing semicolon after function declaration.Function declarations in JavaScript/TypeScript don't require a trailing semicolon. This is inconsistent with standard style.
🔧 Fix
</div> ); -}; +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/tw-blocks/financial-dashboard/FinancialDashboardView.tsx` around lines 403 - 404, Remove the trailing semicolon after the FinancialDashboardView function declaration; locate the component/function named FinancialDashboardView in FinancialDashboardView.tsx and delete the extraneous semicolon following its closing brace so the declaration ends with "}" only (no semicolon) to match standard TypeScript/React style.
73-93: Consider renamingmonthfield todatefor clarity.The data is aggregated by date (daily granularity), but the chart data uses
monthas the key name. This is misleading sinced.datecontains ISO date strings like "2026-03-01", not month values. Renaming todatewould improve readability.♻️ Suggested improvement
const amountByDateChartData = React.useMemo( - () => financialData.amountsByDate.map((d) => ({ month: d.date, desktop: d.amount })), + () => financialData.amountsByDate.map((d) => ({ date: d.date, desktop: d.amount })), [financialData.amountsByDate] );Note: This would also require updating the
dataKeyprop onXAxiscomponents from"month"to"date".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/tw-blocks/financial-dashboard/FinancialDashboardView.tsx` around lines 73 - 93, Rename the chart data fields from "month" to "date" in the memoized arrays so keys reflect the actual ISO date values: update amountByDateChartData mapping ({ month: d.date, ... } -> { date: d.date, ... }) and createdByDateChartData mapping similarly, and adjust escrowTypeDonutData if it relies on the name; then update any XAxis components' dataKey props from "month" to "date" to match (search for amountByDateChartData, createdByDateChartData, escrowTypeDonutData and XAxis dataKey="month"). Ensure the useMemo dependencies remain the same and no other references to "month" remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts`:
- Around line 20-24: getCreatedDateKey silently falls back to the current date
when createdAt._seconds is missing which can hide malformed timestamps; update
getCreatedDateKey to explicitly detect a missing or invalid _seconds on the
Escrow.createdAt value, and handle it instead of returning today's date — e.g.,
log a warning (console.warn or your app logger) including the escrow
identifier/context, and return a clearly invalid sentinel (null, undefined, or
'invalid-date') or throw so callers (charting code) can filter/handle the bad
record; ensure the change is made where getCreatedDateKey is defined and that
callers handle the sentinel accordingly.
---
Nitpick comments:
In `@src/components/tw-blocks/financial-dashboard/FinancialDashboardView.tsx`:
- Around line 123-129: The repeated Tabs JSX (Tabs, TabsList, TabsTrigger)
controlled by activeTab and setActiveTab (cast to FinancialDashboardTab) should
be extracted into a reusable piece—either a small component (e.g., TabsFilter)
or a memoized variable—to avoid duplication across the loading, empty and main
branches; implement TabsFilter that renders the Tabs/TabsList/TabsTrigger trio
and accepts activeTab and onValueChange (or uses setActiveTab) and then replace
the three duplicated blocks with a single {TabsFilter} usage in each branch.
- Around line 403-404: Remove the trailing semicolon after the
FinancialDashboardView function declaration; locate the component/function named
FinancialDashboardView in FinancialDashboardView.tsx and delete the extraneous
semicolon following its closing brace so the declaration ends with "}" only (no
semicolon) to match standard TypeScript/React style.
- Around line 73-93: Rename the chart data fields from "month" to "date" in the
memoized arrays so keys reflect the actual ISO date values: update
amountByDateChartData mapping ({ month: d.date, ... } -> { date: d.date, ... })
and createdByDateChartData mapping similarly, and adjust escrowTypeDonutData if
it relies on the name; then update any XAxis components' dataKey props from
"month" to "date" to match (search for amountByDateChartData,
createdByDateChartData, escrowTypeDonutData and XAxis dataKey="month"). Ensure
the useMemo dependencies remain the same and no other references to "month"
remain.
In `@src/components/tw-blocks/financial-dashboard/FinancialSummaryChart.tsx`:
- Around line 46-62: ChartTooltipContentProps declares an unused "indicator"
prop; update ChartTooltipContent to either remove the prop from
ChartTooltipContentProps if it's not needed, or implement its behavior inside
ChartTooltipContent by reading the indicator value and using it to vary the
rendered marker (e.g., render a line-style indicator when indicator === "line"
and a dot when indicator === "dot") in the payload mapping; modify the
ChartTooltipContentProps type and ChartTooltipContent function together so the
prop signature and rendering logic remain consistent.
In `@src/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts`:
- Around line 95-98: Replace the unconventional dependency array for the useMemo
that computes totalEscrows: change the dependency from [filtered.length] to
[filtered] so the memo watches the filtered array reference instead of its
length; update the useMemo call for totalEscrows (the constant named
totalEscrows using React.useMemo) accordingly to depend on filtered.
- Around line 20-48: The code is using many unsafe casts because the external
Escrow type doesn't match the indexer response; define a local interface (e.g.,
IndexerEscrow or LocalEscrow) that extends/overrides
GetEscrowsFromIndexerResponse/Escrow to include _seconds on createdAt, amount as
number|string, milestones array shape, and status, then update the signatures of
getCreatedDateKey, getSingleReleaseAmount, getMultiReleaseAmount, and
getEscrowAmount to accept that local interface (or convert the raw API response
to that local type once on receipt), remove the ad-hoc "as unknown as" casts,
and add a short comment/issue reference to open an upstream issue against
`@trustless-work/escrow` to reconcile types.
ℹ️ Review info
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
.env.examplesrc/app/dashboard/page.tsxsrc/components/tw-blocks/financial-dashboard/FinancialDashboardView.tsxsrc/components/tw-blocks/financial-dashboard/FinancialSummaryChart.tsxsrc/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts
💤 Files with no reviewable changes (1)
- .env.example
| function getCreatedDateKey(createdAt: Escrow["createdAt"]): string { | ||
| const seconds = (createdAt as unknown as { _seconds?: number })?._seconds; | ||
| const d = seconds ? new Date(seconds * 1000) : new Date(); | ||
| return d.toISOString().slice(0, 10); | ||
| } |
There was a problem hiding this comment.
Silent fallback to current date may mask data issues.
When createdAt._seconds is missing, the function defaults to new Date(), causing the escrow to appear as created "today". This could distort chart data if any escrow records have malformed timestamps. Consider logging a warning or handling this edge case explicitly.
🛡️ Suggested defensive approach
function getCreatedDateKey(createdAt: Escrow["createdAt"]): string {
const seconds = (createdAt as unknown as { _seconds?: number })?._seconds;
- const d = seconds ? new Date(seconds * 1000) : new Date();
+ if (!seconds) {
+ console.warn("Escrow missing createdAt timestamp, using fallback date");
+ }
+ const d = seconds ? new Date(seconds * 1000) : new Date(0); // or throw
return d.toISOString().slice(0, 10);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function getCreatedDateKey(createdAt: Escrow["createdAt"]): string { | |
| const seconds = (createdAt as unknown as { _seconds?: number })?._seconds; | |
| const d = seconds ? new Date(seconds * 1000) : new Date(); | |
| return d.toISOString().slice(0, 10); | |
| } | |
| function getCreatedDateKey(createdAt: Escrow["createdAt"]): string { | |
| const seconds = (createdAt as unknown as { _seconds?: number })?._seconds; | |
| if (!seconds) { | |
| console.warn("Escrow missing createdAt timestamp, using fallback date"); | |
| } | |
| const d = seconds ? new Date(seconds * 1000) : new Date(0); // or throw | |
| return d.toISOString().slice(0, 10); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts` around
lines 20 - 24, getCreatedDateKey silently falls back to the current date when
createdAt._seconds is missing which can hide malformed timestamps; update
getCreatedDateKey to explicitly detect a missing or invalid _seconds on the
Escrow.createdAt value, and handle it instead of returning today's date — e.g.,
log a warning (console.warn or your app logger) including the escrow
identifier/context, and return a clearly invalid sentinel (null, undefined, or
'invalid-date') or throw so callers (charting code) can filter/handle the bad
record; ensure the change is made where getCreatedDateKey is defined and that
callers handle the sentinel accordingly.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.env.example (1)
2-2: Formatting change looks good.This appears to be a newline-at-end-of-file formatting improvement with no functional impact.
Note: The static analysis tool flagged the quotes around the empty string as a style issue. In .env files, empty values can optionally be written without quotes (
NEXT_PUBLIC_API_KEY=), though both forms are valid and this is purely a stylistic preference.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.env.example at line 2, The change is a newline fix but the linter flagged quotes around the empty env value; update the .env example by removing the surrounding quotes for the empty NEXT_PUBLIC_API_KEY (i.e., change NEXT_PUBLIC_API_KEY="" to NEXT_PUBLIC_API_KEY=) to satisfy style rules while preserving behavior; locate the NEXT_PUBLIC_API_KEY entry in the .env.example and remove the quotes only—no other functional changes are needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In @.env.example:
- Line 2: The change is a newline fix but the linter flagged quotes around the
empty env value; update the .env example by removing the surrounding quotes for
the empty NEXT_PUBLIC_API_KEY (i.e., change NEXT_PUBLIC_API_KEY="" to
NEXT_PUBLIC_API_KEY=) to satisfy style rules while preserving behavior; locate
the NEXT_PUBLIC_API_KEY entry in the .env.example and remove the quotes only—no
other functional changes are needed.
Financial Dashboard (receiver)
closes #18
Summary of everything implemented for the financial dashboard by receiver, based on the dashboard-dashboard-01 block and the task spec (Trustless Work Indexer, receiver role, Total Amount / Total Released / Total Balance).
1. Changes description
New financial dashboard by receiver
Added a dashboard view that shows financial data for escrows where the connected wallet is the receiver. Data is loaded with
useGetEscrowsFromIndexerByRole(viauseEscrowsByRoleQuery), filtered by receiver role.Metrics and calculations
The dashboard displays Total Amount (sum of escrow/milestone amounts), Total Released (released to the receiver: single-release via
flags.released, multi-release via milestonestatus === "released"), and Total Balance (Total Amount − Total Released). Escrow count is also shown. All values are derived from indexer data; single-release uses escrow-level fields, multi-release uses milestone-based aggregation.UI and states
Implemented
FinancialDashboardViewwith: connect-wallet prompt, loading state, error state with retry, empty state when there are no receiver escrows, and a main view with tabs (All / Single Release / Multi Release), four KPI cards, and three charts (Escrow Amounts bar, Escrow Types donut, Escrow Created area). Charts use an explicit Y-axis domain where needed to avoid incorrect rendering with few data points. Styling and layout follow the reference block and existing repo patterns; currency is formatted withformatCurrency(..., "USDC").Module layout
New module under
src/components/tw-blocks/financial-dashboard/:useFinancialDashboard.ts(hook + types),FinancialDashboardView.tsx(view component),FinancialSummaryChart.tsx(optional chart primitives). The dashboard page atsrc/app/dashboard/page.tsxrenders<FinancialDashboardView />; noindex.tsbarrel file.2. Module structure
Location:
src/components/tw-blocks/financial-dashboard/useFinancialDashboard.tsFinancialDashboardView.tsxFinancialSummaryChart.tsx@/components/ui/chart.Same idea as the original block: 2 components + 1 hook, no
index.ts, no extracomponents/folder.Summary by CodeRabbit
Release Notes