diff --git a/src/components/KpiTiles.jsx b/src/components/KpiTiles.jsx
index 0269e86..e582ec4 100644
--- a/src/components/KpiTiles.jsx
+++ b/src/components/KpiTiles.jsx
@@ -1,14 +1,19 @@
-export default function KpiTiles({ kpis = {}, windowDays = 7 }) {
+export default function KpiTiles({ kpis = {}, previousKpis = {}, windowDays = 7 }) {
const suffix = `(${windowDays}d)`
const accText = typeof kpis.acc === 'number' ? `${kpis.acc}%` : '0%'
const volText = typeof kpis.volume === 'number' ? kpis.volume : 0
const bestText = kpis.bestZone ? `${toZoneAbbr(kpis.bestZone.label)} - ${kpis.bestZone.acc}%` : '-'
+ const hasPrevBaseline = Number(previousKpis.volume || 0) > 0
+
+ const accDelta = toDelta(kpis.acc, previousKpis.acc, '%', hasPrevBaseline)
+ const volDelta = toDelta(kpis.volume, previousKpis.volume, '', hasPrevBaseline)
+ const bestDelta = toBestZoneDelta(kpis.bestZone, previousKpis.bestZone, hasPrevBaseline)
return (
-
-
-
+
+
+
)
}
@@ -30,11 +35,36 @@ function toZoneAbbr(label) {
}
}
-function KpiCard({ title, value }) {
+function toDelta(current, previous, suffix = '', hasPrevBaseline = true) {
+ if (!hasPrevBaseline || typeof current !== 'number' || typeof previous !== 'number') {
+ return { text: 'vs prev n/a', tone: 'text-neutral-500' }
+ }
+ const diff = current - previous
+ if (diff === 0) return { text: 'vs prev 0', tone: 'text-neutral-500' }
+ const sign = diff > 0 ? '+' : ''
+ return {
+ text: `vs prev ${sign}${diff}${suffix}`,
+ tone: diff > 0 ? 'text-emerald-500' : 'text-rose-500',
+ }
+}
+
+function toBestZoneDelta(current, previous, hasPrevBaseline = true) {
+ if (!hasPrevBaseline || !previous) return { text: 'vs prev n/a', tone: 'text-neutral-500' }
+ if (!current) return { text: `prev ${toZoneAbbr(previous.label)} ${previous.acc}%`, tone: 'text-neutral-500' }
+ if (current.key !== previous.key) {
+ return { text: `prev ${toZoneAbbr(previous.label)} ${previous.acc}%`, tone: 'text-neutral-500' }
+ }
+ return toDelta(current.acc, previous.acc, 'pp')
+}
+
+function KpiCard({ title, value, delta }) {
return (
{title}
{value}
+
+ {delta?.text || 'vs prev n/a'}
+
)
}
diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx
index 3475110..96b739c 100644
--- a/src/pages/Dashboard.jsx
+++ b/src/pages/Dashboard.jsx
@@ -213,18 +213,41 @@ export default function Dashboard() {
() =>
filterSessions(rows, {
from: filters.dateFrom,
- to: addDays(filters.dateTo, 1),
+ to: filters.dateTo,
types: filters.types && filters.types.length ? filters.types : undefined,
}),
[rows, filters.dateFrom, filters.dateTo, filters.types]
)
+ const periodDays = useMemo(() => {
+ const fromTs = new Date(`${filters.dateFrom}T00:00:00Z`).getTime()
+ const toTs = new Date(`${filters.dateTo}T00:00:00Z`).getTime()
+ if (Number.isFinite(fromTs) && Number.isFinite(toTs) && toTs >= fromTs) {
+ return Math.floor((toTs - fromTs) / 86400000) + 1
+ }
+ return Number(filters.windowDays || 90)
+ }, [filters.dateFrom, filters.dateTo, filters.windowDays])
+
+ const previousTo = useMemo(() => addDays(filters.dateFrom, -1), [filters.dateFrom])
+ const previousFrom = useMemo(() => addDays(filters.dateFrom, -periodDays), [filters.dateFrom, periodDays])
+
+ const previousFiltered = useMemo(
+ () =>
+ filterSessions(rows, {
+ from: previousFrom,
+ to: previousTo,
+ types: filters.types && filters.types.length ? filters.types : undefined,
+ }),
+ [rows, previousFrom, previousTo, filters.types]
+ )
+
const aggOpts = useMemo(
() => ({ direction: normalizeDir(filters.direction), range: filters.range }),
[filters.direction, filters.range]
)
const kpis = useMemo(() => computeKpis(filtered, aggOpts), [filtered, aggOpts])
+ const previousKpis = useMemo(() => computeKpis(previousFiltered, aggOpts), [previousFiltered, aggOpts])
const byPos = useMemo(() => aggregateByPosition(filtered, aggOpts), [filtered, aggOpts])
const trend = useMemo(() => aggregateAccuracyByDate(filtered, aggOpts), [filtered, aggOpts])
const byType = useMemo(() => aggregateByType(filtered, aggOpts), [filtered, aggOpts])
@@ -285,6 +308,7 @@ export default function Dashboard() {
filters={filters}
setFilters={setFilters}
kpis={kpis}
+ previousKpis={previousKpis}
zonesForUi={zonesForUi}
trend={trend}
byType={byType}
diff --git a/src/pages/dashboard/AnalyticsSection.jsx b/src/pages/dashboard/AnalyticsSection.jsx
index 20d5e25..6ca3244 100644
--- a/src/pages/dashboard/AnalyticsSection.jsx
+++ b/src/pages/dashboard/AnalyticsSection.jsx
@@ -14,6 +14,7 @@ export default function AnalyticsSection({
filters,
setFilters,
kpis,
+ previousKpis,
zonesForUi,
trend,
byType,
@@ -54,7 +55,7 @@ export default function AnalyticsSection({
))}
) : (
-
+
)}