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.