Systems Library / Industry Applications / How to Build a Legal Case Management Dashboard
Industry Applications legal

How to Build a Legal Case Management Dashboard

Track all cases in one dashboard with status, deadlines, and billing.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

A legal case management dashboard brings every active case, deadline, and billing metric into one view. I built this for a firm where partners were asking "what's the status?" ten times a day because information lived in three different systems. Now they open one page and see everything.

What You Need Before Starting

Step 1: Create the Unified Case Database

import sqlite3
from datetime import datetime

def init_case_db(db_path="case_management.db"):
    conn = sqlite3.connect(db_path)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS cases (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            case_number TEXT UNIQUE,
            case_name TEXT,
            client_name TEXT,
            practice_area TEXT,
            assigned_attorney TEXT,
            status TEXT DEFAULT 'active',
            opened_date TEXT,
            closed_date TEXT,
            case_value REAL,
            notes TEXT
        )
    """)
    conn.commit()
    conn.close()

Step 2: Build the Dashboard API

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/dashboard/overview")
def dashboard_overview():
    conn = sqlite3.connect("case_management.db")
    
    active_count = conn.execute("SELECT COUNT(*) FROM cases WHERE status = 'active'").fetchone()[0]
    by_attorney = conn.execute("""
        SELECT assigned_attorney, COUNT(*) as count FROM cases WHERE status = 'active'
        GROUP BY assigned_attorney ORDER BY count DESC
    """).fetchall()
    by_area = conn.execute("""
        SELECT practice_area, COUNT(*) as count FROM cases WHERE status = 'active'
        GROUP BY practice_area ORDER BY count DESC
    """).fetchall()
    
    conn.close()
    
    deadline_conn = sqlite3.connect("legal_deadlines.db")
    overdue = deadline_conn.execute(
        "SELECT COUNT(*) FROM deadlines WHERE status = 'active' AND due_date < date('now')"
    ).fetchone()[0]
    upcoming_week = deadline_conn.execute(
        "SELECT COUNT(*) FROM deadlines WHERE status = 'active' AND due_date BETWEEN date('now') AND date('now', '+7 days')"
    ).fetchone()[0]
    deadline_conn.close()
    
    return {
        "active_cases": active_count,
        "overdue_deadlines": overdue,
        "deadlines_this_week": upcoming_week,
        "by_attorney": [{"attorney": a[0], "cases": a[1]} for a in by_attorney],
        "by_practice_area": [{"area": a[0], "cases": a[1]} for a in by_area]
    }

@app.get("/dashboard/cases")
def list_cases(status: str = "active", attorney: str = None):
    conn = sqlite3.connect("case_management.db")
    sql = "SELECT * FROM cases WHERE status = ?"
    params = [status]
    
    if attorney:
        sql += " AND assigned_attorney = ?"
        params.append(attorney)
    
    sql += " ORDER BY opened_date DESC"
    rows = conn.execute(sql, params).fetchall()
    conn.close()
    
    columns = ["id", "case_number", "case_name", "client_name", "practice_area", 
               "assigned_attorney", "status", "opened_date", "closed_date", "case_value", "notes"]
    return [dict(zip(columns, row)) for row in rows]

Step 3: Add Case Detail with Timeline

@app.get("/dashboard/case/{case_number}")
def case_detail(case_number: str):
    conn = sqlite3.connect("case_management.db")
    case = conn.execute("SELECT * FROM cases WHERE case_number = ?", (case_number,)).fetchone()
    conn.close()
    
    if not case:
        return {"error": "Case not found"}
    
    columns = ["id", "case_number", "case_name", "client_name", "practice_area",
               "assigned_attorney", "status", "opened_date", "closed_date", "case_value", "notes"]
    case_dict = dict(zip(columns, case))
    
    dl_conn = sqlite3.connect("legal_deadlines.db")
    deadlines = dl_conn.execute(
        "SELECT deadline_type, description, due_date, status FROM deadlines WHERE case_id = ? ORDER BY due_date",
        (case_number,)
    ).fetchall()
    dl_conn.close()
    
    billing_conn = sqlite3.connect("legal_billing.db")
    billing = billing_conn.execute("""
        SELECT SUM(hours), SUM(amount) FROM time_entries WHERE client_id = ? AND status != 'void'
    """, (case[0],)).fetchone()
    billing_conn.close()
    
    case_dict["deadlines"] = [{"type": d[0], "description": d[1], "due": d[2], "status": d[3]} for d in deadlines]
    case_dict["total_hours"] = billing[0] or 0
    case_dict["total_billed"] = billing[1] or 0
    
    return case_dict

Step 4: Build the HTML Dashboard

@app.get("/", response_class=HTMLResponse)
def dashboard_page():
    overview = dashboard_overview()
    cases = list_cases()
    
    case_rows = ""
    for c in cases[:20]:
        case_rows += f"""<tr>
            <td><a href="/dashboard/case/{c['case_number']}">{c['case_number']}</a></td>
            <td>{c['case_name']}</td>
            <td>{c['client_name']}</td>
            <td>{c['assigned_attorney']}</td>
            <td>{c['practice_area']}</td>
            <td>{c['opened_date']}</td>
        </tr>"""
    
    return f"""<!DOCTYPE html>
<html><head><title>Case Management</title>
<style>
body {{ font-family: sans-serif; margin: 20px; }}
.stats {{ display: flex; gap: 20px; margin-bottom: 20px; }}
.stat {{ background: #f5f5f5; padding: 15px 25px; border-radius: 8px; }}
.stat h3 {{ margin: 0; font-size: 28px; }}
.stat p {{ margin: 5px 0 0; color: #666; }}
.overdue {{ background: #fee; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ padding: 8px 12px; text-align: left; border-bottom: 1px solid #eee; }}
th {{ background: #f9f9f9; }}
a {{ color: #2563eb; text-decoration: none; }}
</style></head>
<body>
<h1>Case Management Dashboard</h1>
<div class="stats">
    <div class="stat"><h3>{overview['active_cases']}</h3><p>Active Cases</p></div>
    <div class="stat overdue"><h3>{overview['overdue_deadlines']}</h3><p>Overdue Deadlines</p></div>
    <div class="stat"><h3>{overview['deadlines_this_week']}</h3><p>Due This Week</p></div>
</div>
<h2>Active Cases</h2>
<table><tr><th>Case #</th><th>Name</th><th>Client</th><th>Attorney</th><th>Area</th><th>Opened</th></tr>
{case_rows}</table>
</body></html>"""

Step 5: Run the Dashboard

pip install fastapi uvicorn
uvicorn case_dashboard:app --port 8080 --reload

What to Build Next

Add a Slack integration that sends each attorney their personal daily brief: their active cases, upcoming deadlines, and unbilled time from yesterday.

Related Reading

Want this system built for your business?

Get a free assessment. We will map every system your business needs and show you the ROI.

Get Your Free Assessment

Related Systems