Skip to content

Backend

Live Revenue + Jobs Dashboard — Same Database as the Field Map, One Screen for the SLT

Materialised views + Edge Functions: real-time SLT metrics without the ETL pipeline

💰 📈

Most SaaS dashboards are built on top of a BI tool: Looker, Mode, Tableau. You point it at your data warehouse, set up a nightly batch job, and executives see reports that are 12 hours stale. Velocity X does something simpler: leadership's dashboard reads from the same Supabase that powers the field map. Revenue numbers, job counts, deal velocity—all refreshed every 5 minutes, no ETL pipeline, no separate data warehouse. This is how to ship a live SLT dashboard without the infrastructure tax.

Why Most Dashboards Are Slow

Traditional reporting uses two separate systems. The operational database powers the app; the data warehouse powers the dashboard. You sync them with ETL—usually a nightly batch—which means leadership always sees yesterday's truth. A deal closes at 4pm, and SLT doesn't see it until 8am the next morning.

For small teams, this is overkill. If you have 50–100 jobs running at any given time, materialized views refreshed every 5 minutes are faster and simpler than Postgres → Redshift → Tableau.

The Architecture: Materialized Views + Edge Functions

Velocity X uses three layers:

1. Operational layer — `jobs` and `job_assignments` tables. Reps write to these in real time as they update statuses and outcomes.

{`create table jobs (
  id uuid primary key default gen_random_uuid(),
  org_id uuid not null references organizations(id),
  title text,
  status text, -- pending, active, completed, closed
  revenue_value numeric,
  created_at timestamp default now(),
  completed_at timestamp,
  assigned_to uuid references profiles(id),
  created_by uuid references profiles(id)
);

create index on jobs(org_id, status);
create index on jobs(org_id, completed_at);`}

2. Materialized view layer — pre-computed aggregates that refresh on a schedule. Instead of asking SLT to run `COUNT(*) group by status`, we compute once and store the result.

{`create materialized view job_stats_realtime as
select
  org_id,
  date(coalesce(completed_at, created_at)) as date,
  status,
  count(*) as job_count,
  sum(revenue_value) as total_revenue,
  avg(revenue_value) as avg_deal_size
from jobs
where created_at > now() - interval '30 days'
group by org_id, date, status;

create index on job_stats_realtime(org_id, date);`}

3. Edge Function layer — a scheduled function that refreshes views every 5 minutes and computes KPIs like conversion rate and pipeline velocity.

{`// /api/refresh-slt-metrics
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.104.0'

const supabase = createClient(
  Deno.env.get('PUBLIC_SUPABASE_URL'),
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')
);

export default async (req: Request) => {
  const orgs = await supabase
    .from('organizations')
    .select('id')
    .eq('active', true);

  for (const org of orgs.data) {
    // Refresh materialized view
    await supabase.rpc('refresh_job_stats', { org_id: org.id });

    // Compute KPIs
    const { data: jobs } = await supabase
      .from('jobs')
      .select('status, revenue_value, completed_at')
      .eq('org_id', org.id)
      .gte('created_at', new Date(Date.now() - 7 * 24 * 60 * 60 * 1000));

    const completed = jobs.filter(j => j.status === 'completed').length;
    const total_revenue = jobs.reduce((sum, j) => sum + (j.revenue_value || 0), 0);

    // Write KPIs to cache table
    await supabase.from('slt_metrics_cache').upsert({
      org_id: org.id,
      period: '7d',
      completed_jobs: completed,
      total_revenue,
      conversion_rate: (completed / jobs.length * 100).toFixed(1),
      refreshed_at: new Date()
    });
  }

  return new Response(JSON.stringify({ success: true }), { status: 200 });
};`}

This function is deployed to Netlify Edge Functions and runs on a cron schedule. Every 5 minutes, it refreshes the materialised view and writes fresh KPIs to a cache table. SLT queries the cache table, not the raw jobs. The queries are instant.

The React Dashboard — Powered by Recharts

The SLT dashboard is a React component that polls the cache table every 30 seconds. When metrics update, the charts redraw. It's a single-page dashboard: revenue by day, job completion trend, conversion rate, average deal size, and a leaderboard of top-performing teams.

{`export function SltDashboard({ orgId }: { orgId: string }) {
  const [metrics, setMetrics] = useState(null);

  useEffect(() => {
    const channel = supabase
      .channel('slt_metrics')
      .on('postgres_changes', {
        event: 'UPDATE',
        schema: 'public',
        table: 'slt_metrics_cache',
        filter: \`org_id=eq.\${orgId}\`
      }, (payload) => {
        setMetrics(payload.new);
      })
      .subscribe();

    return () => supabase.removeChannel(channel);
  }, [orgId]);

  return (
    
); }`}

Supabase's realtime subscriptions mean the dashboard updates instantly when metrics refresh. No polling, no stale data. SLT opens the page, sees live numbers, and can make decisions on current facts.

How This Compares to Looker / Mode / Tableau

Those tools are better at deep exploratory BI: custom drill-downs, complex cross-tabulations, ad-hoc queries. If SLT needs to answer questions like "which team's average deal size grew fastest in Q2?", Tableau shines. But if the question is "how much revenue did we close today?" or "how many jobs are in-flight right now?", you don't need a BI tool. You need a dashboard that queries the source of truth. Velocity X is faster to ship, cheaper to run (no BI licensing), and more accurate (no stale batches).

Frequently Asked Questions

What if a job is created after the view refreshes?

It won't show up in the SLT metrics until the next refresh (max 5 minutes later). For most businesses, that's fine. If you need sub-minute reporting, use Supabase's realtime subscriptions directly instead of materialised views.

Does this scale to 100k jobs?

Materialised views stay fast if your query is simple. If you're joining 5 tables and computing complex aggregates, refresh time can balloon. Start simple: count, sum, average. Pre-compute anything complex. Test locally with supabase start before deploying.

What if SLT needs a custom metric?

Add a new column to the cache table and compute it in the Edge Function. No app redeploy needed. The dashboard reads columns dynamically, so new KPIs surface instantly.

Can SLT drill down into individual jobs?

Yes—link from the KPI card to a filtered jobs list. Use role-gated views so SLT sees aggregates but can drill into live jobs without seeing staff personal data. See the role-gated dashboards post for RLS patterns.

The Verdict

If you're shipping a SaaS with a small leadership team, skip the BI tool and build a live dashboard backed by your operational database. Materialised views + Recharts + Edge Functions will get you 80% of the way there in a fraction of the time. When you outgrow it—when SLT starts asking questions you can't pre-compute—then you migrate to Tableau. But until then, Velocity's architecture keeps the stack minimal and the data fresh.

Let us make some quick suggestions?
Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.