Files
hermes-config/skills/self-hosting/shop-pro-quote/references/dashboard-metric-cards.md
T
2026-07-12 10:17:17 -04:00

4.7 KiB

Dashboard Metric Cards

The ShopProQuote dashboard (dashboard.html / dashboard.js) has a set of Quick Metric cards that display live counts from the repair orders, appointments, tasks, and quotes collections.

Architecture

HTML — Card Structure

Cards live in a Tailwind grid inside <!-- Quick Metrics -->:

<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-6 mb-8">

Each card follows this exact template:

<!-- Card Label -->
<div class="metric-card rounded-xl p-6 shadow-lg">
    <div class="flex items-center justify-between mb-4">
        <div class="p-2 bg-COLOR-100 dark:bg-COLOR-900 rounded-lg">
            <!-- SVG icon matching the metric theme -->
            <svg class="w-6 h-6 text-COLOR-600 dark:text-COLOR-400" ...>...</svg>
        </div>
        <span id="UNIQUE-ID-count" class="text-3xl font-bold text-gray-900 dark:text-white">0</span>
    </div>
    <h3 class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">Card Title</h3>
    <div class="text-xs text-gray-500 dark:text-gray-500" id="UNIQUE-ID-breakdown">
        <!-- Optional sub-items with status-indicator spans -->
        <span class="status-indicator status-COLOR"></span>Status label
    </div>
</div>

Key:

  • Each card gets a unique id on its count <span> (e.g., parts-on-order-count, ready-for-pickup-count)
  • Color variants: bg-{color}-100, text-{color}-600 (light) / bg-{color}-900, text-{color}-400 (dark)
  • Status indicators use the existing class system: status-overdue (red), status-warning (amber), status-good (green), status-info (blue)
  • When adding cards, count the total. If > 4, bump lg:grid-cols-4 to lg:grid-cols-6 (or lg:grid-cols-3 for two rows of 3).

JS — Data Population (renderMetrics())

The renderMetrics() function in dashboard.js (~line 1528) populates all metric cards. It's called from renderDashboard() on every data refresh.

Pattern for adding a new card:

// 1. Compute the value from dashboardData
const myMetric = dashboardData.repairOrders.filter(ro =>
    (ro.status || '').toLowerCase() === 'my-status-value'
).length;

// 2. Log for debugging
log('My Metric count:', myMetric);

// 3. Update DOM with null-safe access
const myMetricEl = document.getElementById('my-metric-count');
if (myMetricEl) myMetricEl.textContent = myMetric;

Available data sources for filtering:

  • dashboardData.repairOrders — objects with .status (string), .writeupTime (Date), .promisedTime (Date), .customerName
  • dashboardData.appointments — objects with .status, .appointmentDateTime (Date), .customerName Available data sources for filtering:
  • dashboardData.repairOrders — objects with .workStatus (string: diagnosing/in-progress/waiting-for-approval/waiting-for-parts/waiting-for-pickup/completed), .status (string: waiter/drop-off — customer service status, NOT work progress), .writeupTime (Date), .promisedTime (Date), .customerName

CRITICAL: ro.statusro.workStatus

  • ro.status = customer service status (waiter, drop-off) — used for the red/blue service badge on RO cards
  • ro.workStatus = work progress status (diagnosing, in-progress, waiting-for-approval, waiting-for-parts, waiting-for-pickup, completed) — used for work progress badges and ALL completion/dashboard filtering logic

NEVER filter by ro.status === 'completed' — it will always match nothing because status only stores waiter/drop-off. Always use ro.workStatus === 'completed'.

Common repair order workStatus values used in filters:

  • 'diagnosing', 'in-progress', 'waiting-for-approval', 'waiting-for-parts', 'waiting-for-pickup', 'completed'

Click Handlers

Metric cards can optionally be made clickable. Follow the existing pattern in addMetricClickHandlers() (~line 1888):

const myCard = document.querySelector('#my-metric-count')?.closest('.metric-card');
if (myCard) {
    myCard.style.cursor = 'pointer';
    myCard.addEventListener('click', () => {
        window.location.href = 'target-page.html';
    });
}

Adding a New Metric Card — Full Recipe

  1. HTML: Add the card <div> inside the Quick Metrics grid, following the template above. Ensure the count <span> has a unique ID.
  2. Grid: If total cards exceed the current lg:grid-cols-N value, bump it.
  3. JS: In renderMetrics(), after the existing quote section (~line 1674), add filter logic + DOM update.
  4. Click handler (optional): If the card should navigate somewhere, add it in addMetricClickHandlers().
  5. Verify: The card will populate automatically on next data refresh (periodic auto-refresh + real-time PocketBase subscriptions).