Skip to content

feat(dashboard): implement financial dashboard for receiver escrows#22

Open
KevinLatino wants to merge 2 commits intoTrustless-Work:mainfrom
KevinLatino:feature/financial-dashboard
Open

feat(dashboard): implement financial dashboard for receiver escrows#22
KevinLatino wants to merge 2 commits intoTrustless-Work:mainfrom
KevinLatino:feature/financial-dashboard

Conversation

@KevinLatino
Copy link

@KevinLatino KevinLatino commented Mar 1, 2026

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 (via useEscrowsByRoleQuery), 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 milestone status === "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 FinancialDashboardView with: 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 with formatCurrency(..., "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 at src/app/dashboard/page.tsx renders <FinancialDashboardView />; no index.ts barrel file.

2. Module structure

Location: src/components/tw-blocks/financial-dashboard/

File Responsibility
useFinancialDashboard.ts Hook that fetches escrows by receiver, filters by tab, and computes totals and chart series.
FinancialDashboardView.tsx View component: states (no wallet, loading, error, empty), tabs, KPI cards, and 3 charts.
FinancialSummaryChart.tsx Optional chart primitives (container, tooltip); the view uses @/components/ui/chart.

Same idea as the original block: 2 components + 1 hook, no index.ts, no extra components/ folder.

Captura de pantalla 2026-03-01 a la(s) 10 19 21 a  m Captura de pantalla 2026-03-01 a la(s) 10 19 26 a  m Captura de pantalla 2026-03-01 a la(s) 10 19 36 a  m Captura de pantalla 2026-03-01 a la(s) 10 19 43 a  m

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a Financial Dashboard page with KPI cards displaying total amounts, released funds, balance, and escrow information.
    • Integrated interactive charts visualizing escrow data by amount, type distribution, and creation trends.
    • Requires wallet connection to access the dashboard.
    • Added tab filters for viewing all escrows or specific types.

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

Adds 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

Cohort / File(s) Summary
Configuration
\.env.example
Only formatting: newline change at EOF; NEXT_PUBLIC_API_KEY="" unchanged (no functional change).
Dashboard Page Route
src/app/dashboard/page.tsx
New Next.js app route exporting DashboardPage that renders FinancialDashboardView.
Main Dashboard View
src/components/tw-blocks/financial-dashboard/FinancialDashboardView.tsx
Added client component requiring wallet connection; renders KPI cards, tab filtering (all/single/multi), loading/error/empty states, and three charts (Bar, Pie, Area) using chart utilities.
Chart Components & Utilities
src/components/tw-blocks/financial-dashboard/FinancialSummaryChart.tsx
New chart wrapper, tooltip, and tooltip content components plus types for Recharts integration and CSS color injection.
Dashboard Data Logic
src/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts
New hook that fetches escrows by role, computes totals (totalAmount, totalReleased, totalBalance, totalEscrows), produces time-series and donut slices, and supports tab-based filtering (all/single/multi).

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • frontend: dashboard based on financial data #18 — Implements dashboard UI and aggregation logic for escrows (single vs multi) as requested in the issue.
  • Trustless-Work/clonable-backoffice#12 — Adds the dashboard and hook that fetch and compute escrow metrics, matching the feature described.

Poem

🐰 A ledger garden springs with charts and cheer,
Escrows counted, totals tidy and clear,
Tabs flip like ears to show single or many,
Bars and pies hum tunes of funds aplenty,
Hop, wallet—let the dashboard steer! 🎋

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: implementing a financial dashboard for receiver escrows, matching the core functionality added across multiple new files.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts (2)

95-98: Unconventional memo dependency on filtered.length.

The dependency array [filtered.length] works here since the memo only returns filtered.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 between GetEscrowsFromIndexerResponse type and runtime indexer data structure.

The multiple as unknown as assertions (lines 21, 27, 33–37, 57–61) indicate the external type from @trustless-work/escrow doesn't accurately reflect the actual API response structure—specifically, _seconds on createdAt, amount, milestones, and status fields. While the defensive coding with Number.isFinite checks and type guards is necessary and well-implemented, relying on runtime assertions is fragile.

Consider:

  1. Creating a local interface that extends or overrides the external type to match the actual indexer response shape
  2. Opening an issue with the @trustless-work/escrow maintainers to update GetEscrowsFromIndexerResponse to include these fields

This 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: Unused indicator prop in ChartTooltipContent.

The indicator prop is defined in ChartTooltipContentProps (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 renaming month field to date for clarity.

The data is aggregated by date (daily granularity), but the chart data uses month as the key name. This is misleading since d.date contains ISO date strings like "2026-03-01", not month values. Renaming to date would 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 dataKey prop on XAxis components 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5ba56 and 0536bae.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • .env.example
  • src/app/dashboard/page.tsx
  • src/components/tw-blocks/financial-dashboard/FinancialDashboardView.tsx
  • src/components/tw-blocks/financial-dashboard/FinancialSummaryChart.tsx
  • src/components/tw-blocks/financial-dashboard/useFinancialDashboard.ts
💤 Files with no reviewable changes (1)
  • .env.example

Comment on lines +20 to +24
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);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 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.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0536bae and 4b360fc.

📒 Files selected for processing (1)
  • .env.example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

frontend: dashboard based on financial data

1 participant