Please update my dashboard app and HTML template to make it look and feel like a premium Audi/Loyalty manager console, and add stubs for Data Upload and Campaigns.
Overall goals
Keep using the existing FastAPI app (dashboard_app.py) and Supabase views – do not change any SQL or database schema.
Upgrade the UI layout + styling in templates/index.html:
Premium, minimal, Audi-ish feel:
Dark header bar
Clean white content area
Simple accent red for important numbers / badges
Make the layout feel like a real dealer dashboard, not a generic demo.
Add top-level navigation:
“Overview” (current dashboard – default)
“Campaigns”
“Data Upload”
Use the existing API endpoints:
/api/kpis
/api/contacts
/api/tcard/{agreement_number}
Please follow these steps:
STEP 1 – Confirm backend endpoints (dashboard_app.py)
Open dashboard_app.py.
Ensure the following endpoints exist and keep their behaviour (just tidy if needed, but don’t break them):
GET / – renders index.html
GET /api/kpis
Returns JSON with:
month_uk
new_contacts
responses_48h
reply_rate_pct
inbound_late
followups_created
out_sent
in_recv
total_msgs
generated_at_uk
GET /api/contacts
Returns JSON rows from v_manager_monthly_contacts, with at least:
agreement_id
agreement_number
customer_name
out_count_month
in_count_month
last_at_month
last_dir_month
last_msg_preview_month
has_dealt_month
has_appt_month
last_reason_category
last_reason_label
contact_status
status_colour
month_uk
GET /api/tcard/{agreement_number}
Returns JSON with:
summary from v_tcard_summary
appointments from v_tcard_appointments
messages from v_tcard_contact_history
Add two simple placeholder routes for now:
GET /campaigns → returns index.html but with a variable like active_tab="campaigns".
GET /upload → returns index.html with active_tab="upload".
We’ll use a single HTML template and switch content based on active_tab.
In the root / route, pass active_tab="overview" to the template.
STEP 2 – Replace templates/index.html with a premium layout
Replace the existing contents of templates/index.html with a modern, responsive layout using Tailwind CDN. Requirements:
2.1. Page skeleton
Full-height flex layout:
Dark header (top)
Light background main content
Header content:
Left:
“Lia | Loyalty Dashboard”
Smaller text: “Lincoln Audi”
Right:
Today’s date (rendered client-side with JS is fine, or just text)
A small subtle “Live” indicator dot if you like (purely cosmetic).
2.2. Top navigation tabs
Below the header, add three tabs (styled as buttons):
“Overview”
“Campaigns”
“Data Upload”
Behaviour:
Highlight the active tab based on a Jinja variable active_tab.
Each tab is just a link:
Overview → /
Campaigns → /campaigns
Data Upload → /upload
Use Tailwind classes to make them look like pill buttons (bordered, with active one filled).
2.3. Overview tab layout
When active_tab == "overview":
Top row: KPI cards
Create a responsive grid of cards (e.g. 3 or 4 across on desktop, stacked on mobile) showing:
Card 1 – “New Contacts”
Big number from new_contacts
Sub text: “New customers messaged this month”
Card 2 – “Response Rate”
Big text: reply_rate_pct + “%”
Sub text: “% of contacted customers who replied”
If reply_rate_pct >= 60 → green accent
30–59 → amber
<30 → red
Card 3 – “Messages”
Main: total_msgs
Sub: “↑ {out_sent} sent • ↓ {in_recv} received”
Card 4 – “Follow-Ups & Late Replies”
Main: {followups_created}
Sub: “Late replies: {inbound_late}”
Use a bit of Tailwind shadow and rounded corners to make them feel like proper tiles.
Middle row: Contact status summary
Under the KPI cards, add a small horizontal strip with summary chips for contact status (using /api/contacts data aggregated client-side):
“Active this month: X”
Color chips with counts:
Green: contact_status == 'dealt' or 'engaged'
Yellow: contact_status == 'appointment'
Red: contact_status == 'declined'
Blue: contact_status == 'engaged' (if you distinguish)
Grey: contact_status == 'no_response'
Dark grey: contact_status == 'paused'
You can do this aggregation in JS after fetching /api/contacts.
Bottom: Contacts table
A full-width card containing:
Top bar:
Title: “This month’s customer conversations”
Right: text input for search (filters by customer_name or agreement_number on the client side).
Table columns:
Customer
Agreement
Last activity (date/time)
Out / In (e.g. “42 / 39”)
Status (with coloured pill using status_colour)
Last note (truncated from last_msg_preview_month)
Each row:
Add a left border coloured using status_colour:
green → border-l-green-500
yellow → border-l-yellow-500
red → border-l-red-500
blue → border-l-sky-500
grey → border-l-gray-300
dark_grey → border-l-slate-500
On row click, call a JS function that fetches /api/tcard/{agreement_number} and opens the T-card drawer.
2.4. T-card drawer
On the right, create a sliding drawer (fixed on desktop, or overlay on mobile) that appears when a row is clicked.
Content layout:
Header:
Customer name + reg, e.g. “Jonathan Jordan – FY72UBT”
Smaller: “Q5 DIESEL ESTATE · Ends: 15 Oct 2026”
Badge for asset type:
“New” if asset_type == 'N'
“Used” if asset_type == 'U' or null.
Key stats block:
Monthly payment
True equity (with green/red indicator)
Last lost reason (if present) – e.g. “Last reason: Monthly payment too high”
Next follow-up date / appointment (if any)
Appointments section:
A small table listing:
Date
Time
Status
Reminder sent / channel
Thanks sent boolean
Messages section:
Scrollable list of last ~20 messages:
Timestamp
Arrow icon (→ for outbound, ← for inbound)
Short text (truncate after 120 chars)
Keep it simple; no reply box yet.
Add a close button (X) and ESC key handler to hide the drawer.
STEP 3 – Campaigns tab (stub)
When active_tab == "campaigns":
Use the same header and nav.
Show a centered panel with:
Title: “Campaigns”
Short explainer text:
“This section will show monthly end-of-term campaigns and overlay campaigns (key-for-key, sales events, new model launches). For now, campaigns are created via CLI scripts. This screen will later show live performance per campaign tag.”
Below that, a simple table pulling from /api/kpis or a new /api/campaigns endpoint if it already exists:
campaign_tag
outreached
replied_48h
attended
reply_rate_pct
attended_rate_pct
If /api/campaigns doesn’t exist yet, just put placeholder static rows and a note:
“Backend campaign performance API to be wired in later.”
STEP 4 – Data Upload tab (stub)
When active_tab == "upload":
Show a panel titled “Data Upload”.
Inside, do not actually implement file upload yet; just:
A short paragraph explaining how data is currently loaded (via CLI scripts like run_import_now.py, import_campaign_subset.py, etc.).
A 2–3 step bullet list for “Monthly data refresh”:
“1. Export latest VWFS agreement data”
“2. Run python run_import_now.py”
“3. Refresh this page to see updated KPIs and contact lists”
Add a disabled and button labelled “Coming soon – in-browser CSV upload” to show intent but not functional yet.
STEP 5 – JavaScript wiring
Inside index.html:
On load of Overview tab:
Fetch /api/kpis once and populate the KPI cards.
Fetch /api/contacts and:
Populate the contacts table.
Aggregate counts into the contact summary strip.
Add search filter that hides rows that don’t match the search text.
When a row is clicked:
Get data-agreement-number from the row.
Fetch /api/tcard/{agreement_number}.
Populate the drawer sections (summary, appointments, messages).
Add a loading state while fetching.
Keep JavaScript inlined at the bottom of index.html for now (no need for separate JS file).
Important constraints
Do not modify any database objects or SQL.
Do not break the existing FastAPI endpoints – only extend / polish them.
It is fine to:
Replace the entire contents of templates/index.html.
Tidy dashboard_app.py as long as all existing behaviour still works.
Focus on:
Premium look and feel,
Clear layout for a manager,
Using the data we already expose from Supabase.
When you’re done, please:
Run the dashboard server (or reload the Dashboard workflow),
Confirm the root page loads without errors,
Confirm:
KPIs fetch,
Contacts table loads with correct colours,
Clicking a row opens a populated T-card drawer.
Then summarise for me what you changed and show example screenshots in the console output if possible.