From 2e49340663deeb3ca7aa05b8289e4586726e0ea1 Mon Sep 17 00:00:00 2001
From: HotSwapp <47397945+HotSwapp@users.noreply.github.com>
Date: Mon, 6 Oct 2025 19:21:58 -0500
Subject: [PATCH] feat(case): add GET /case/{id} detail view and Jinja
template; link from dashboard table; eager-load related data; 404 handling
and logging
---
app/main.py | 57 ++++++++++++-
app/templates/case.html | 184 +++++++++++++++++++++++++++++++++++++++-
2 files changed, 239 insertions(+), 2 deletions(-)
diff --git a/app/main.py b/app/main.py
index ac2a87c..d291338 100644
--- a/app/main.py
+++ b/app/main.py
@@ -15,7 +15,7 @@ from starlette.middleware.sessions import SessionMiddleware
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
-from sqlalchemy.orm import Session
+from sqlalchemy.orm import Session, joinedload
from sqlalchemy import or_
from dotenv import load_dotenv
from starlette.middleware.base import BaseHTTPMiddleware
@@ -314,3 +314,58 @@ async def admin_panel(request: Request, db: Session = Depends(get_db)):
"request": request,
"user": user
})
+
+
+@app.get("/case/{case_id}")
+async def case_detail(
+ request: Request,
+ case_id: int,
+ db: Session = Depends(get_db),
+):
+ """
+ Case detail view.
+
+ Displays detailed information for a single case and its related client and
+ associated records (transactions, documents, payments).
+ """
+ # Check authentication
+ user = get_current_user_from_session(request.session)
+ if not user:
+ return RedirectResponse(url="/login", status_code=302)
+
+ # Fetch case with related entities eagerly loaded to avoid lazy-load issues
+ case_obj = (
+ db.query(Case)
+ .options(
+ joinedload(Case.client),
+ joinedload(Case.transactions),
+ joinedload(Case.documents),
+ joinedload(Case.payments),
+ )
+ .filter(Case.id == case_id)
+ .first()
+ )
+
+ if not case_obj:
+ logger.warning("Case not found: id=%s", case_id)
+ return templates.TemplateResponse(
+ "case.html",
+ {
+ "request": request,
+ "user": user,
+ "case": None,
+ "error": "Case not found",
+ },
+ status_code=404,
+ )
+
+ logger.info("Rendering case detail: id=%s, file_no='%s'", case_obj.id, case_obj.file_no)
+
+ return templates.TemplateResponse(
+ "case.html",
+ {
+ "request": request,
+ "user": user,
+ "case": case_obj,
+ },
+ )
diff --git a/app/templates/case.html b/app/templates/case.html
index ec139a7..5c3b4b7 100644
--- a/app/templates/case.html
+++ b/app/templates/case.html
@@ -1 +1,183 @@
-
+{% extends "base.html" %}
+
+{% block title %}
+Case {{ case.file_no if case else '' }} ยท Delphi Database
+{% endblock %}
+
+{% block content %}
+
+
+
+ {% if error %}
+
+ {% endif %}
+
+ {% if case %}
+
+
+
+
+
+
File #
+
{{ case.file_no }}
+
+
+
Status
+
+ {% if case.status == 'active' %}
+ Active
+ {% elif case.status == 'closed' %}
+ Closed
+ {% else %}
+ {{ case.status or 'n/a' }}
+ {% endif %}
+
+
+
+
Type
+
{{ case.case_type or '' }}
+
+
+
Opened
+
{{ case.open_date.strftime('%Y-%m-%d') if case.open_date else '' }}
+
+
+
+
+ {% set client = case.client %}
+
+
Client
+
+ {% if client %}
+ {{ client.last_name }}, {{ client.first_name }}
+ {% else %}
+ Unknown
+ {% endif %}
+
+
+
+
Company
+
{{ client.company if client else '' }}
+
+
+
City/State
+
+ {% if client %}
+ {{ client.city or '' }}{% if client.state %}, {{ client.state }}{% endif %}
+ {% endif %}
+
+
+
+
+
Description
+
{{ case.description or '' }}
+
+
+
+
+
+
+
+
+
+
+
+
+ | Date |
+ Type |
+ Amount |
+
+
+
+ {% if case.transactions and case.transactions|length > 0 %}
+ {% for t in case.transactions %}
+
+ | {{ t.transaction_date.strftime('%Y-%m-%d') if t.transaction_date else '' }} |
+ {{ t.transaction_type or '' }} |
+ {{ '%.2f'|format(t.amount) if t.amount is not none else '' }} |
+
+ {% endfor %}
+ {% else %}
+ | No transactions. |
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Type |
+ File |
+ Uploaded |
+
+
+
+ {% if case.documents and case.documents|length > 0 %}
+ {% for d in case.documents %}
+
+ | {{ d.document_type or '' }} |
+ {{ d.file_name or '' }} |
+ {{ d.uploaded_date.strftime('%Y-%m-%d') if d.uploaded_date else '' }} |
+
+ {% endfor %}
+ {% else %}
+ | No documents. |
+ {% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Date |
+ Type |
+ Amount |
+
+
+
+ {% if case.payments and case.payments|length > 0 %}
+ {% for p in case.payments %}
+
+ | {{ p.payment_date.strftime('%Y-%m-%d') if p.payment_date else '' }} |
+ {{ p.payment_type or '' }} |
+ {{ '%.2f'|format(p.amount) if p.amount is not none else '' }} |
+
+ {% endfor %}
+ {% else %}
+ | No payments. |
+ {% endif %}
+
+
+
+
+
+
+ {% endif %}
+
+{% endblock %}