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

97 lines
4.7 KiB
Markdown

# 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 -->`:
```html
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-6 mb-8">
```
Each card follows this exact template:
```html
<!-- 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:**
```js
// 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.status` ≠ `ro.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):
```js
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).