> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keystoneos.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Building a Settlement Dashboard

> Build a real-time settlement monitoring page with filtering and pagination.

This guide walks through building a fully functional settlement dashboard with state filtering, color-coded badges, and pagination using `@keystoneos/react` hooks.

## Prerequisites

* `@keystoneos/react` installed
* `KeystoneProvider` configured with a valid session token (see [Getting Started](/elements/getting-started))
* A React project with TypeScript

## What you will build

A settlement list page with:

* A table showing reference, state, parties, asset/payment amounts, and creation date
* Color-coded state badges
* A dropdown filter for settlement states
* Previous/next pagination with total count
* Click-to-detail navigation

## Setting up the provider

Before building the dashboard component, make sure your app is wrapped in `KeystoneProvider` with a session token from your backend. The token needs `settlements:read` scope at minimum.

```tsx theme={null}
// app.tsx
import { useState, useEffect } from 'react';
import { KeystoneProvider } from '@keystoneos/react';
import { SettlementDashboard } from './settlement-dashboard';

function App() {
  const [token, setToken] = useState<string | null>(null);

  useEffect(() => {
    fetch('/api/keystone/session', { method: 'POST' })
      .then(r => r.json())
      .then(d => setToken(d.token));
  }, []);

  if (!token) return <div>Loading...</div>;

  return (
    <KeystoneProvider
      sessionToken={token}
      environment="production"
      onTokenExpired={async () => {
        const res = await fetch('/api/keystone/session', { method: 'POST' });
        const data = await res.json();
        return data.token;
      }}
    >
      <SettlementDashboard />
    </KeystoneProvider>
  );
}
```

## Building the state badge

The `getStateInfo` utility returns display metadata for each settlement state, including a human-readable label and a semantic category you can map to colors.

```tsx theme={null}
// state-badge.tsx
import { getStateInfo } from '@keystoneos/react';

const STATE_COLORS: Record<string, { bg: string; text: string }> = {
  active: { bg: '#dbeafe', text: '#1e40af' },
  success: { bg: '#dcfce7', text: '#166534' },
  error: { bg: '#fee2e2', text: '#991b1b' },
  warning: { bg: '#fef3c7', text: '#92400e' },
  neutral: { bg: '#f3f4f6', text: '#374151' },
};

interface StateBadgeProps {
  state: string;
}

export function StateBadge({ state }: StateBadgeProps) {
  const info = getStateInfo(state);
  const colors = STATE_COLORS[info.category] ?? STATE_COLORS.neutral;

  return (
    <span
      style={{
        display: 'inline-block',
        padding: '2px 10px',
        borderRadius: '9999px',
        fontSize: '12px',
        fontWeight: 600,
        backgroundColor: colors.bg,
        color: colors.text,
      }}
    >
      {info.label}
    </span>
  );
}
```

The `getStateInfo` function maps states to categories:

| State                 | Category  | Suggested Color |
| --------------------- | --------- | --------------- |
| `INSTRUCTED`          | `neutral` | Gray            |
| `COMPLIANCE_CHECKING` | `active`  | Blue            |
| `COMPLIANCE_CLEARED`  | `active`  | Blue            |
| `AWAITING_DEPOSITS`   | `warning` | Amber           |
| `SETTLED`             | `active`  | Blue            |
| `FINALIZED`           | `success` | Green           |
| `ROLLED_BACK`         | `error`   | Red             |
| `TIMED_OUT`           | `error`   | Red             |

## Building the state filter

Create a dropdown that filters settlements by state. Passing `undefined` fetches all states.

```tsx theme={null}
// state-filter.tsx
interface StateFilterProps {
  value: string | undefined;
  onChange: (state: string | undefined) => void;
}

const FILTER_OPTIONS = [
  { label: 'All States', value: undefined },
  { label: 'Instructed', value: 'INSTRUCTED' },
  { label: 'Compliance Checking', value: 'COMPLIANCE_CHECKING' },
  { label: 'Awaiting Deposits', value: 'AWAITING_DEPOSITS' },
  { label: 'Finalized', value: 'FINALIZED' },
  { label: 'Rolled Back', value: 'ROLLED_BACK' },
] as const;

export function StateFilter({ value, onChange }: StateFilterProps) {
  return (
    <select
      value={value ?? ''}
      onChange={e => onChange(e.target.value || undefined)}
      style={{ padding: '8px 12px', borderRadius: '6px', border: '1px solid #d1d5db' }}
    >
      {FILTER_OPTIONS.map(opt => (
        <option key={opt.label} value={opt.value ?? ''}>
          {opt.label}
        </option>
      ))}
    </select>
  );
}
```

## Building pagination controls

```tsx theme={null}
// pagination.tsx
interface PaginationProps {
  page: number;
  totalPages: number;
  total: number;
  onPageChange: (page: number) => void;
}

export function Pagination({ page, totalPages, total, onPageChange }: PaginationProps) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginTop: '16px' }}>
      <button
        onClick={() => onPageChange(page - 1)}
        disabled={page === 0}
        style={{ padding: '6px 12px', borderRadius: '6px', cursor: page === 0 ? 'not-allowed' : 'pointer' }}
      >
        Previous
      </button>
      <span style={{ fontSize: '14px', color: '#6b7280' }}>
        Page {page + 1} of {totalPages} ({total} total)
      </span>
      <button
        onClick={() => onPageChange(page + 1)}
        disabled={page >= totalPages - 1}
        style={{ padding: '6px 12px', borderRadius: '6px', cursor: page >= totalPages - 1 ? 'not-allowed' : 'pointer' }}
      >
        Next
      </button>
    </div>
  );
}
```

## The complete dashboard

This is the full working component that ties everything together.

```tsx theme={null}
// settlement-dashboard.tsx
import { useState } from 'react';
import { useSettlements, getStateInfo } from '@keystoneos/react';
import { StateBadge } from './state-badge';
import { StateFilter } from './state-filter';
import { Pagination } from './pagination';

const PAGE_SIZE = 20;

export function SettlementDashboard() {
  const [stateFilter, setStateFilter] = useState<string | undefined>(undefined);
  const [page, setPage] = useState(0);

  const { settlements, total, isLoading, error } = useSettlements({
    state: stateFilter,
    limit: PAGE_SIZE,
    offset: page * PAGE_SIZE,
  });

  const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));

  // Reset to first page when filter changes
  const handleFilterChange = (state: string | undefined) => {
    setStateFilter(state);
    setPage(0);
  };

  if (error) {
    return <div style={{ color: '#dc2626', padding: '16px' }}>Error: {error}</div>;
  }

  return (
    <div style={{ padding: '24px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
        <h1 style={{ fontSize: '24px', fontWeight: 700 }}>Settlements</h1>
        <StateFilter value={stateFilter} onChange={handleFilterChange} />
      </div>

      {isLoading ? (
        <div style={{ padding: '40px', textAlign: 'center', color: '#6b7280' }}>Loading settlements...</div>
      ) : settlements.length === 0 ? (
        <div style={{ padding: '40px', textAlign: 'center', color: '#6b7280' }}>
          No settlements found{stateFilter ? ` with state "${stateFilter}"` : ''}.
        </div>
      ) : (
        <>
          <table style={{ width: '100%', borderCollapse: 'collapse' }}>
            <thead>
              <tr style={{ borderBottom: '2px solid #e5e7eb', textAlign: 'left' }}>
                <th style={{ padding: '12px 8px' }}>Reference</th>
                <th style={{ padding: '12px 8px' }}>State</th>
                <th style={{ padding: '12px 8px' }}>Parties</th>
                <th style={{ padding: '12px 8px', textAlign: 'right' }}>Asset</th>
                <th style={{ padding: '12px 8px', textAlign: 'right' }}>Payment</th>
                <th style={{ padding: '12px 8px' }}>Created</th>
              </tr>
            </thead>
            <tbody>
              {settlements.map(settlement => {
                const assetLeg = settlement.legs.find(l => l.leg_type === 'asset_delivery');
                const paymentLeg = settlement.legs.find(l => l.leg_type === 'payment');

                return (
                  <tr
                    key={settlement.id}
                    onClick={() => window.location.href = `/settlements/${settlement.id}`}
                    style={{ borderBottom: '1px solid #f3f4f6', cursor: 'pointer' }}
                    onMouseEnter={e => (e.currentTarget.style.backgroundColor = '#f9fafb')}
                    onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
                  >
                    <td style={{ padding: '12px 8px', fontFamily: 'monospace', fontSize: '13px' }}>
                      {settlement.external_reference ?? settlement.id.slice(0, 8)}
                    </td>
                    <td style={{ padding: '12px 8px' }}>
                      <StateBadge state={settlement.state} />
                    </td>
                    <td style={{ padding: '12px 8px', fontSize: '14px' }}>
                      {settlement.parties.map(p => p.name ?? p.external_reference).join(' / ')}
                    </td>
                    <td style={{ padding: '12px 8px', textAlign: 'right', fontFamily: 'monospace', fontSize: '13px' }}>
                      {assetLeg ? `${Number(assetLeg.quantity).toLocaleString()} ${assetLeg.instrument_id}` : '-'}
                    </td>
                    <td style={{ padding: '12px 8px', textAlign: 'right', fontFamily: 'monospace', fontSize: '13px' }}>
                      {paymentLeg ? `${Number(paymentLeg.quantity).toLocaleString()} ${paymentLeg.instrument_id}` : '-'}
                    </td>
                    <td style={{ padding: '12px 8px', fontSize: '14px', color: '#6b7280' }}>
                      {new Date(settlement.created_at).toLocaleDateString()}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>

          <Pagination
            page={page}
            totalPages={totalPages}
            total={total}
            onPageChange={setPage}
          />
        </>
      )}
    </div>
  );
}
```

## What the data looks like

Here is an example of what a row renders with realistic data:

| Reference         | State                                                     | Parties                          | Asset                  | Payment         | Created      |
| ----------------- | --------------------------------------------------------- | -------------------------------- | ---------------------- | --------------- | ------------ |
| `TRADE-2026-0042` | <span style={{color: '#92400e'}}>Awaiting Deposits</span> | Securitize Fund I / Ondo Capital | 12,000,000 OUSG        | 12,120,000 USDC | Apr 1, 2026  |
| `TRADE-2026-0038` | <span style={{color: '#166534'}}>Finalized</span>         | Securitize Fund I / Ondo Capital | 5,000,000 US09311A1007 | 5,050,000 USDC  | Mar 28, 2026 |

## Adding click-to-detail navigation

The table rows are already clickable (using `window.location.href`). In a real application, use your router's navigation method:

```tsx theme={null}
// With React Router
import { useNavigate } from 'react-router-dom';

const navigate = useNavigate();
// In the row onClick:
onClick={() => navigate(`/settlements/${settlement.id}`)}

// With Next.js
import { useRouter } from 'next/navigation';

const router = useRouter();
// In the row onClick:
onClick={() => router.push(`/settlements/${settlement.id}`)}
```

## Next steps

<CardGroup cols={2}>
  <Card title="Real-time Tracking" icon="signal" href="/elements/guides/realtime-tracking">
    Build a detail view with live state updates and event timelines.
  </Card>

  <Card title="Bilateral Instructions" icon="arrows-left-right" href="/elements/guides/bilateral-instructions">
    Create settlements by submitting bilateral instructions.
  </Card>

  <Card title="useSettlements Reference" icon="code" href="/hooks/use-settlements">
    Full API reference for the useSettlements hook.
  </Card>

  <Card title="State Machine" icon="diagram-project" href="/concepts/state-machine">
    Understand the settlement state machine and transitions.
  </Card>
</CardGroup>
