Add metric sub-team intermediate drill-down view

Clicking a metric now shows a sub-team breakdown page with totals per team
(compliant, non-compliant, total, %) instead of jumping directly to a flat
device list. Clicking a sub-team then shows the device list filtered to
that team only.

Navigation flow: Overview → Vertical → Metric (sub-team totals) → Team (devices)

Backend: added optional ?team= query param to the device list endpoint for
filtered queries.

Frontend: added MetricSubTeamView component with metric-level stats bar and
clickable sub-team table. Updated navigation state to include selectedTeam.

Also updated design brief to reflect the new drill-down hierarchy.
This commit is contained in:
Jordan Ramos
2026-05-14 14:53:41 -06:00
parent 682ee9417f
commit a2bc1ff564
3 changed files with 185 additions and 29 deletions

View File

@@ -796,16 +796,19 @@ function createVCLMultiVerticalRouter(upload) {
/**
* GET /vertical/:code/metric/:metricId/devices
* Returns the list of non-compliant devices for a specific vertical + metric.
* Optionally filters by team when the query parameter is provided.
*
* @method GET
* @route /vertical/:code/metric/:metricId/devices
* @param {string} code — vertical code (e.g., "NTS_AEO")
* @param {string} metricId — metric identifier (e.g., "VM-001")
* @query {string} [team] — optional team name to filter devices (e.g., "STEAM", "ACCESS-ENG")
*
* @response 200
* {
* vertical: string,
* metric_id: string,
* team: string|null,
* devices: Array<{
* hostname: string,
* ip_address: string,
@@ -828,19 +831,26 @@ function createVCLMultiVerticalRouter(upload) {
if (!metricId || metricId.length > 50) return res.status(400).json({ error: 'Invalid metric ID' });
try {
const { rows } = await pool.query(
`SELECT ci.hostname, ci.ip_address, ci.device_type, ci.team, ci.seen_count,
const team = req.query.team || null;
let query = `SELECT ci.hostname, ci.ip_address, ci.device_type, ci.team, ci.seen_count,
ci.resolution_date, ci.remediation_plan,
fu.report_date AS first_seen, lu.report_date AS last_seen
FROM compliance_items ci
LEFT JOIN compliance_uploads fu ON ci.first_seen_upload_id = fu.id
LEFT JOIN compliance_uploads lu ON ci.upload_id = lu.id
WHERE ci.vertical = $1 AND ci.metric_id = $2 AND ci.status = 'active'
ORDER BY ci.hostname`,
[vertical, metricId]
);
WHERE ci.vertical = $1 AND ci.metric_id = $2 AND ci.status = 'active'`;
const params = [vertical, metricId];
res.json({ vertical, metric_id: metricId, devices: rows });
if (team) {
query += ` AND ci.team = $3`;
params.push(team);
}
query += ` ORDER BY ci.hostname`;
const { rows } = await pool.query(query, params);
res.json({ vertical, metric_id: metricId, team: team || null, devices: rows });
} catch (err) {
console.error('[VCL Multi] GET /vertical/:code/metric/:metricId/devices error:', err.message);
res.status(500).json({ error: 'Database error' });