Display Data with DataViews

Define a filterable, sortable transaction history view that works across web and mobile without duplicating UI code.

1. Define the underlying query

First, create a query operation to fetch the data:

// lib/specs/billing/list-transactions.ts
import { defineQuery } from '@lssm/lib.contracts';

export const ListTransactions = defineQuery({
  meta: {
    name: 'billing.listTransactions',
    version: 1,
    description: 'Fetch customer transaction history',
  },
  io: {
    input: /* pagination + filters */,
    output: /* array of transactions */,
  },
  policy: { auth: 'user' },
});

2. Define the DataView spec

Wrap your query with presentation metadata:

// lib/specs/billing/transaction-history.data-view.ts
import { defineDataView } from '@lssm/lib.contracts';
import { ListTransactions } from './list-transactions';

export const TransactionHistory = defineDataView({
  meta: {
    name: 'billing.transactionHistory',
    version: 1,
    entity: 'transaction',
    description: 'Customer payment history',
    goal: 'Help customers track spending',
    context: 'Account dashboard',
    owners: ['team-billing'],
    stability: 'stable',
    tags: ['payments'],
  },
  source: {
    primary: ListTransactions,
  },
  view: {
    kind: 'list',
    fields: [
      { 
        key: 'date', 
        label: 'Date', 
        dataPath: 'createdAt', 
        format: 'date',
        sortable: true 
      },
      { 
        key: 'description', 
        label: 'Description', 
        dataPath: 'description' 
      },
      { 
        key: 'amount', 
        label: 'Amount', 
        dataPath: 'amount', 
        format: 'currency',
        sortable: true 
      },
      { 
        key: 'status', 
        label: 'Status', 
        dataPath: 'status', 
        format: 'badge' 
      },
    ],
    filters: [
      { 
        key: 'status', 
        label: 'Status', 
        field: 'status', 
        type: 'enum',
        options: [
          { value: 'succeeded', label: 'Paid' },
          { value: 'pending', label: 'Pending' },
          { value: 'failed', label: 'Failed' },
        ],
      },
      {
        key: 'dateRange',
        label: 'Date Range',
        field: 'createdAt',
        type: 'dateRange',
      },
    ],
    defaultSort: { field: 'date', direction: 'desc' },
    pagination: { pageSize: 25 },
  },
});

3. Render on the frontend

Use the runtime renderer in your React or React Native app:

// app/dashboard/transactions/page.tsx
'use client';

import { DataViewRenderer } from '@lssm/lib.design-system';
import { TransactionHistory } from '@/lib/specs/billing/transaction-history.data-view';
import { useQuery } from '@tanstack/react-query';

export default function TransactionsPage() {
  const { data, isLoading } = useQuery({
    queryKey: ['transactions'],
    queryFn: () => fetch('/api/ops/billing.listTransactions').then(r => r.json()),
  });

  return (
    <div className="container py-8">
      <h1 className="text-3xl font-bold mb-6">Payment History</h1>
      <DataViewRenderer 
        spec={TransactionHistory}
        data={data?.items ?? []}
        loading={isLoading}
        onFilterChange={(filters) => {
          // refetch with new filters
        }}
      />
    </div>
  );
}

Why DataViews?

  • ✓ Same spec renders on web (React) and mobile (React Native)
  • ✓ Filters, sorting, and pagination handled automatically
  • ✓ Format rules (currency, dates, badges) applied consistently
  • ✓ Export to CSV/PDF using the same spec
  • ✓ A/B test different layouts without touching the backend