'use client'; /* EngagementHoursSparkline — phase 07 (ENG-05). * Purpose: Custom inline SVG sparkline for daily hours trend over the selected period. * One series, no axes, no tooltips, no animation. DASH-04 (no recharts on mobile). * Renders the sparkline card per UI-SPEC: label row (left + right) + 48px-tall SVG. * Props: points (Plan 01 SparklinePoint[]), period (D7|D30|D90 — drives the period_label). */ import { Card, CardContent } from '@/components/ui/card'; import type { SparklinePoint } from '@/app/api/mobile/engagement/trend/route'; export interface EngagementHoursSparklineProps { points: SparklinePoint[]; period: 'D7' | 'D30' | 'D90'; } const PERIOD_LABEL: Record = { D7: '7 days', D30: '30 days', D90: '90 days', }; function formatLatestValue(points: SparklinePoint[]): string { // Find last point with hours > 0 let latest: SparklinePoint | null = null; for (let i = points.length - 1; i >= 0; i--) { if (points[i]!.hours > 0) { latest = points[i]!; break; } } if (!latest) return '—'; const todayIso = new Date().toISOString().slice(0, 10); // "YYYY-MM-DD" UTC const isToday = latest.date === todayIso; const hoursLabel = `${latest.hours.toFixed(1)}h`; if (isToday) return `${hoursLabel} today`; // shortDate: "May 2" const [y, m, d] = latest.date.split('-').map(Number); const dt = new Date(Date.UTC(y!, m! - 1, d!)); const short = dt.toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: 'UTC' }); return `${hoursLabel} ${short}`; } function buildPath(points: SparklinePoint[], svgWidth: number, svgHeight: number): string { if (points.length === 0) return ''; const maxHours = Math.max(...points.map(p => p.hours), 0); // 4px top margin + 4px bottom margin per UI-SPEC const yScale = maxHours === 0 ? 0 : (svgHeight - 8) / maxHours; const xStep = points.length === 1 ? 0 : svgWidth / (points.length - 1); return points.map((pt, i) => { const x = points.length === 1 ? svgWidth / 2 : i * xStep; const y = svgHeight - 4 - (pt.hours * yScale); // baseline 4px above bottom return `${i === 0 ? 'M' : 'L'} ${x.toFixed(2)},${y.toFixed(2)}`; }).join(' '); } export function EngagementHoursSparkline({ points, period }: EngagementHoursSparklineProps) { const periodLabel = PERIOD_LABEL[period]; const allZero = points.length === 0 || points.every(p => p.hours === 0); const SVG_W = 300; const SVG_H = 48; return (

{`Hours trend · last ${periodLabel}`}

{formatLatestValue(points)}

{allZero ? (

No activity

) : ( )}
); }