@sanity/ailf-studio 1.12.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1155 -222
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3534,18 +3534,18 @@ import { route } from "sanity/router";
|
|
|
3534
3534
|
// src/components/Dashboard.tsx
|
|
3535
3535
|
import { HelpCircleIcon as HelpCircleIcon8 } from "@sanity/icons";
|
|
3536
3536
|
import {
|
|
3537
|
-
Box as
|
|
3538
|
-
Button as
|
|
3537
|
+
Box as Box37,
|
|
3538
|
+
Button as Button15,
|
|
3539
3539
|
Container,
|
|
3540
|
-
Flex as
|
|
3541
|
-
Stack as
|
|
3540
|
+
Flex as Flex39,
|
|
3541
|
+
Stack as Stack42,
|
|
3542
3542
|
Tab as Tab3,
|
|
3543
3543
|
TabList as TabList3,
|
|
3544
3544
|
TabPanel as TabPanel3,
|
|
3545
|
-
Text as
|
|
3545
|
+
Text as Text53
|
|
3546
3546
|
} from "@sanity/ui";
|
|
3547
|
-
import { useCallback as
|
|
3548
|
-
import { useRouter as
|
|
3547
|
+
import { useCallback as useCallback42, useEffect as useEffect18 } from "react";
|
|
3548
|
+
import { useRouter as useRouter5 } from "sanity/router";
|
|
3549
3549
|
|
|
3550
3550
|
// src/lib/help-context.ts
|
|
3551
3551
|
import {
|
|
@@ -4225,9 +4225,12 @@ var scoreTimelineQuery = (
|
|
|
4225
4225
|
&& completedAt >= $startDate
|
|
4226
4226
|
${filterSourceClause("$source")}
|
|
4227
4227
|
${filterModeClause("$mode")}
|
|
4228
|
+
${filterOwnerTeamClause("$ownerTeam")}
|
|
4228
4229
|
] | order(completedAt asc) {
|
|
4229
4230
|
_id,
|
|
4231
|
+
reportId,
|
|
4230
4232
|
completedAt,
|
|
4233
|
+
durationMs,
|
|
4231
4234
|
"overall": summary.overall.avgScore,
|
|
4232
4235
|
"scores": summary.scores[] {
|
|
4233
4236
|
feature,
|
|
@@ -4237,6 +4240,10 @@ var scoreTimelineQuery = (
|
|
|
4237
4240
|
"actualScore": summary.overall.avgActualScore,
|
|
4238
4241
|
"mode": provenance.mode,
|
|
4239
4242
|
"source": provenance.source.name,
|
|
4243
|
+
"trigger": provenance.trigger.type,
|
|
4244
|
+
"areas": provenance.areas,
|
|
4245
|
+
"models": provenance.models[].label,
|
|
4246
|
+
"ownerTeam": provenance.owner.team,
|
|
4240
4247
|
tag,
|
|
4241
4248
|
title
|
|
4242
4249
|
}
|
|
@@ -12766,6 +12773,18 @@ import React4 from "react";
|
|
|
12766
12773
|
import { Card as Card19, Stack as Stack34, Text as Text45 } from "@sanity/ui";
|
|
12767
12774
|
|
|
12768
12775
|
// src/lib/scoring.ts
|
|
12776
|
+
var TONE_MAP = {
|
|
12777
|
+
good: "positive",
|
|
12778
|
+
warning: "caution",
|
|
12779
|
+
"needs-work": "default",
|
|
12780
|
+
critical: "critical"
|
|
12781
|
+
};
|
|
12782
|
+
function gradeTone(grade) {
|
|
12783
|
+
return TONE_MAP[grade];
|
|
12784
|
+
}
|
|
12785
|
+
function scoreTone(score) {
|
|
12786
|
+
return gradeTone(scoreGrade(Math.round(score)));
|
|
12787
|
+
}
|
|
12769
12788
|
var HEX_MAP = {
|
|
12770
12789
|
good: "#2ecc71",
|
|
12771
12790
|
warning: "#f1c40f",
|
|
@@ -13358,244 +13377,1158 @@ function JudgmentDetailDrawerOutlet({
|
|
|
13358
13377
|
}
|
|
13359
13378
|
|
|
13360
13379
|
// src/components/ScoreTimeline.tsx
|
|
13361
|
-
import { Card as
|
|
13362
|
-
import { useCallback as
|
|
13363
|
-
import {
|
|
13364
|
-
|
|
13365
|
-
|
|
13366
|
-
|
|
13367
|
-
|
|
13368
|
-
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
{ days:
|
|
13378
|
-
{ days:
|
|
13379
|
-
{ days:
|
|
13380
|
+
import { Card as Card25, Flex as Flex38, Spinner, Stack as Stack41, Text as Text52 } from "@sanity/ui";
|
|
13381
|
+
import { useCallback as useCallback41, useMemo as useMemo19, useState as useState32 } from "react";
|
|
13382
|
+
import { useRouter as useRouter4 } from "sanity/router";
|
|
13383
|
+
|
|
13384
|
+
// src/components/timeline/TimelineChart.tsx
|
|
13385
|
+
import { Box as Box34, Card as Card23, Flex as Flex36, Stack as Stack39, Text as Text50 } from "@sanity/ui";
|
|
13386
|
+
import {
|
|
13387
|
+
useCallback as useCallback38,
|
|
13388
|
+
useEffect as useEffect16,
|
|
13389
|
+
useMemo as useMemo17,
|
|
13390
|
+
useRef as useRef9,
|
|
13391
|
+
useState as useState30
|
|
13392
|
+
} from "react";
|
|
13393
|
+
|
|
13394
|
+
// src/components/timeline/lib.ts
|
|
13395
|
+
var RANGE_OPTIONS = [
|
|
13396
|
+
{ days: 7, label: "Last 7 days", value: "7d" },
|
|
13397
|
+
{ days: 30, label: "Last 30 days", value: "30d" },
|
|
13398
|
+
{ days: 90, label: "Last 90 days", value: "90d" },
|
|
13399
|
+
{ days: 180, label: "Last 180 days", value: "180d" },
|
|
13400
|
+
{ days: 365, label: "Last year", value: "365d" },
|
|
13401
|
+
{ days: null, label: "All time", value: "all" }
|
|
13402
|
+
];
|
|
13403
|
+
var DEFAULT_RANGE = "30d";
|
|
13404
|
+
function rangePresetToDays(preset) {
|
|
13405
|
+
const opt = RANGE_OPTIONS.find((r) => r.value === preset);
|
|
13406
|
+
if (!opt) throw new Error(`Unknown range preset: ${preset}`);
|
|
13407
|
+
return opt.days;
|
|
13408
|
+
}
|
|
13409
|
+
function isRangePreset(value) {
|
|
13410
|
+
return RANGE_OPTIONS.some((r) => r.value === value);
|
|
13411
|
+
}
|
|
13412
|
+
var GRANULARITY_OPTIONS = [
|
|
13413
|
+
{ label: "All", value: "raw" },
|
|
13414
|
+
{ label: "Daily average", value: "day" },
|
|
13415
|
+
{ label: "Weekly average", value: "week" }
|
|
13380
13416
|
];
|
|
13417
|
+
var DEFAULT_GRANULARITY = "raw";
|
|
13418
|
+
function isGranularity(value) {
|
|
13419
|
+
return value === "raw" || value === "day" || value === "week";
|
|
13420
|
+
}
|
|
13381
13421
|
function daysAgo(n) {
|
|
13382
13422
|
const d = /* @__PURE__ */ new Date();
|
|
13383
13423
|
d.setDate(d.getDate() - n);
|
|
13384
13424
|
return d.toISOString();
|
|
13385
13425
|
}
|
|
13386
|
-
function
|
|
13426
|
+
function formatDateShort(iso) {
|
|
13387
13427
|
return new Date(iso).toLocaleDateString(void 0, {
|
|
13388
13428
|
day: "numeric",
|
|
13389
13429
|
month: "short"
|
|
13390
13430
|
});
|
|
13391
13431
|
}
|
|
13432
|
+
function formatDateTime(iso) {
|
|
13433
|
+
return new Date(iso).toLocaleString(void 0, {
|
|
13434
|
+
day: "numeric",
|
|
13435
|
+
hour: "2-digit",
|
|
13436
|
+
minute: "2-digit",
|
|
13437
|
+
month: "short",
|
|
13438
|
+
year: "numeric"
|
|
13439
|
+
});
|
|
13440
|
+
}
|
|
13441
|
+
function startOfDay(iso) {
|
|
13442
|
+
const d = new Date(iso);
|
|
13443
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
13444
|
+
return d.toISOString();
|
|
13445
|
+
}
|
|
13446
|
+
function startOfWeek(iso) {
|
|
13447
|
+
const d = new Date(iso);
|
|
13448
|
+
d.setUTCHours(0, 0, 0, 0);
|
|
13449
|
+
const day = d.getUTCDay();
|
|
13450
|
+
const diff = (day + 6) % 7;
|
|
13451
|
+
d.setUTCDate(d.getUTCDate() - diff);
|
|
13452
|
+
return d.toISOString();
|
|
13453
|
+
}
|
|
13392
13454
|
function scoreForPoint(point, area) {
|
|
13393
13455
|
if (!area) return point.overall;
|
|
13394
13456
|
const match = point.scores.find((s) => s.feature === area);
|
|
13395
13457
|
return match?.totalScore ?? null;
|
|
13396
13458
|
}
|
|
13397
|
-
function
|
|
13398
|
-
const
|
|
13399
|
-
const
|
|
13400
|
-
|
|
13401
|
-
|
|
13402
|
-
|
|
13403
|
-
|
|
13404
|
-
|
|
13405
|
-
|
|
13406
|
-
|
|
13407
|
-
|
|
13408
|
-
|
|
13459
|
+
function aggregate(points, area, granularity) {
|
|
13460
|
+
const scored = [];
|
|
13461
|
+
for (const dp of points) {
|
|
13462
|
+
const score = scoreForPoint(dp, area);
|
|
13463
|
+
if (score === null) continue;
|
|
13464
|
+
scored.push({ count: 1, date: dp.completedAt, score, source: dp });
|
|
13465
|
+
}
|
|
13466
|
+
if (granularity === "raw" || scored.length === 0) return scored;
|
|
13467
|
+
const bucketKey = granularity === "day" ? startOfDay : startOfWeek;
|
|
13468
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
13469
|
+
for (const d of scored) {
|
|
13470
|
+
const key = bucketKey(d.date);
|
|
13471
|
+
const cur = buckets.get(key);
|
|
13472
|
+
if (cur) {
|
|
13473
|
+
cur.sum += d.score;
|
|
13474
|
+
cur.count += 1;
|
|
13475
|
+
if (d.date > cur.source.completedAt) cur.source = d.source;
|
|
13476
|
+
} else {
|
|
13477
|
+
buckets.set(key, { count: 1, source: d.source, sum: d.score });
|
|
13409
13478
|
}
|
|
13410
|
-
|
|
13411
|
-
|
|
13412
|
-
|
|
13413
|
-
|
|
13414
|
-
|
|
13415
|
-
|
|
13416
|
-
|
|
13417
|
-
|
|
13418
|
-
|
|
13419
|
-
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
} finally {
|
|
13424
|
-
setLoading(false);
|
|
13479
|
+
}
|
|
13480
|
+
return Array.from(buckets.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([date, { count, source, sum }]) => ({
|
|
13481
|
+
count,
|
|
13482
|
+
date,
|
|
13483
|
+
score: sum / count,
|
|
13484
|
+
source
|
|
13485
|
+
}));
|
|
13486
|
+
}
|
|
13487
|
+
function uniqueSorted(items, extract) {
|
|
13488
|
+
const set2 = /* @__PURE__ */ new Set();
|
|
13489
|
+
for (const item of items) {
|
|
13490
|
+
for (const v of extract(item)) {
|
|
13491
|
+
if (v) set2.add(v);
|
|
13425
13492
|
}
|
|
13426
|
-
}
|
|
13493
|
+
}
|
|
13494
|
+
return Array.from(set2).sort();
|
|
13495
|
+
}
|
|
13496
|
+
function computeStats(points, window2 = 5) {
|
|
13497
|
+
if (points.length === 0) {
|
|
13498
|
+
return {
|
|
13499
|
+
count: 0,
|
|
13500
|
+
delta: null,
|
|
13501
|
+
first: null,
|
|
13502
|
+
last: null,
|
|
13503
|
+
max: null,
|
|
13504
|
+
mean: null,
|
|
13505
|
+
median: null,
|
|
13506
|
+
min: null,
|
|
13507
|
+
movingAverage: [],
|
|
13508
|
+
stdev: null,
|
|
13509
|
+
trend: "flat"
|
|
13510
|
+
};
|
|
13511
|
+
}
|
|
13512
|
+
const scores = points.map((p) => p.score);
|
|
13513
|
+
const sum = scores.reduce((a, b) => a + b, 0);
|
|
13514
|
+
const mean = sum / scores.length;
|
|
13515
|
+
const sorted = [...scores].sort((a, b) => a - b);
|
|
13516
|
+
const median2 = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[(sorted.length - 1) / 2];
|
|
13517
|
+
let minIdx = 0;
|
|
13518
|
+
let maxIdx = 0;
|
|
13519
|
+
for (let i = 1; i < points.length; i++) {
|
|
13520
|
+
if (points[i].score < points[minIdx].score) minIdx = i;
|
|
13521
|
+
if (points[i].score > points[maxIdx].score) maxIdx = i;
|
|
13522
|
+
}
|
|
13523
|
+
const variance = scores.reduce((acc, x) => acc + (x - mean) ** 2, 0) / scores.length;
|
|
13524
|
+
const stdev = Math.sqrt(variance);
|
|
13525
|
+
const first = points[0];
|
|
13526
|
+
const last = points[points.length - 1];
|
|
13527
|
+
const delta = points.length > 1 ? last.score - first.score : 0;
|
|
13528
|
+
const trend = classifyTrend(delta);
|
|
13529
|
+
return {
|
|
13530
|
+
count: points.length,
|
|
13531
|
+
delta,
|
|
13532
|
+
first,
|
|
13533
|
+
last,
|
|
13534
|
+
max: points[maxIdx],
|
|
13535
|
+
mean,
|
|
13536
|
+
median: median2,
|
|
13537
|
+
min: points[minIdx],
|
|
13538
|
+
movingAverage: rollingMean(scores, window2),
|
|
13539
|
+
stdev,
|
|
13540
|
+
trend
|
|
13541
|
+
};
|
|
13542
|
+
}
|
|
13543
|
+
function classifyTrend(delta) {
|
|
13544
|
+
if (Math.abs(delta) < 1.5) return "flat";
|
|
13545
|
+
return delta > 0 ? "up" : "down";
|
|
13546
|
+
}
|
|
13547
|
+
function rollingMean(values, window2) {
|
|
13548
|
+
if (values.length === 0) return [];
|
|
13549
|
+
const w = Math.max(1, Math.min(window2, values.length));
|
|
13550
|
+
const out = [];
|
|
13551
|
+
let runningSum = 0;
|
|
13552
|
+
const queue = [];
|
|
13553
|
+
for (const v of values) {
|
|
13554
|
+
queue.push(v);
|
|
13555
|
+
runningSum += v;
|
|
13556
|
+
if (queue.length > w) {
|
|
13557
|
+
runningSum -= queue.shift();
|
|
13558
|
+
}
|
|
13559
|
+
out.push(runningSum / queue.length);
|
|
13560
|
+
}
|
|
13561
|
+
return out;
|
|
13562
|
+
}
|
|
13563
|
+
var CSV_HEADER = [
|
|
13564
|
+
"completedAt",
|
|
13565
|
+
"reportId",
|
|
13566
|
+
"title",
|
|
13567
|
+
"tag",
|
|
13568
|
+
"mode",
|
|
13569
|
+
"source",
|
|
13570
|
+
"trigger",
|
|
13571
|
+
"overall",
|
|
13572
|
+
"selectedAreaScore",
|
|
13573
|
+
"actualScore",
|
|
13574
|
+
"durationMs"
|
|
13575
|
+
];
|
|
13576
|
+
function csvEscape(value) {
|
|
13577
|
+
if (value == null) return "";
|
|
13578
|
+
const s = String(value);
|
|
13579
|
+
if (/[",\n\r]/.test(s)) return `"${s.replaceAll('"', '""')}"`;
|
|
13580
|
+
return s;
|
|
13581
|
+
}
|
|
13582
|
+
function toCsv(points, selectedArea) {
|
|
13583
|
+
const rows = [CSV_HEADER.join(",")];
|
|
13584
|
+
for (const p of points) {
|
|
13585
|
+
const areaScore = scoreForPoint(p, selectedArea);
|
|
13586
|
+
rows.push(
|
|
13587
|
+
[
|
|
13588
|
+
p.completedAt,
|
|
13589
|
+
p.reportId ?? p._id,
|
|
13590
|
+
p.title ?? "",
|
|
13591
|
+
p.tag ?? "",
|
|
13592
|
+
p.mode,
|
|
13593
|
+
p.source,
|
|
13594
|
+
p.trigger ?? "",
|
|
13595
|
+
p.overall ?? "",
|
|
13596
|
+
areaScore ?? "",
|
|
13597
|
+
p.actualScore ?? "",
|
|
13598
|
+
p.durationMs ?? ""
|
|
13599
|
+
].map(csvEscape).join(",")
|
|
13600
|
+
);
|
|
13601
|
+
}
|
|
13602
|
+
return rows.join("\n") + "\n";
|
|
13603
|
+
}
|
|
13604
|
+
function downloadCsv(content, filename) {
|
|
13605
|
+
if (typeof document === "undefined") return;
|
|
13606
|
+
const blob = new Blob([content], { type: "text/csv;charset=utf-8" });
|
|
13607
|
+
const url = URL.createObjectURL(blob);
|
|
13608
|
+
const link = document.createElement("a");
|
|
13609
|
+
link.href = url;
|
|
13610
|
+
link.download = filename;
|
|
13611
|
+
document.body.append(link);
|
|
13612
|
+
link.click();
|
|
13613
|
+
link.remove();
|
|
13614
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
13615
|
+
}
|
|
13616
|
+
async function copyTextToClipboard(text) {
|
|
13617
|
+
if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
|
|
13618
|
+
return false;
|
|
13619
|
+
}
|
|
13620
|
+
try {
|
|
13621
|
+
await navigator.clipboard.writeText(text);
|
|
13622
|
+
return true;
|
|
13623
|
+
} catch {
|
|
13624
|
+
return false;
|
|
13625
|
+
}
|
|
13626
|
+
}
|
|
13627
|
+
var TIMELINE_DATA_START_DATE = "2026-04-15T00:00:00Z";
|
|
13628
|
+
function effectiveStartDate(rangeDays, floor = TIMELINE_DATA_START_DATE) {
|
|
13629
|
+
const rangeStart = rangeDays ? daysAgo(rangeDays) : "1970-01-01T00:00:00Z";
|
|
13630
|
+
if (!floor) return rangeStart;
|
|
13631
|
+
return rangeStart > floor ? rangeStart : floor;
|
|
13632
|
+
}
|
|
13633
|
+
|
|
13634
|
+
// src/components/timeline/TimelineChart.tsx
|
|
13635
|
+
import { jsx as jsx66, jsxs as jsxs50 } from "react/jsx-runtime";
|
|
13636
|
+
var CHART_HEIGHT = 280;
|
|
13637
|
+
var PAD_BOTTOM = 36;
|
|
13638
|
+
var PAD_LEFT = 44;
|
|
13639
|
+
var PAD_RIGHT = 16;
|
|
13640
|
+
var PAD_TOP = 16;
|
|
13641
|
+
var Y_MAX = 100;
|
|
13642
|
+
var Y_TICKS = [0, 25, 50, 75, 100];
|
|
13643
|
+
var NOISE_BAND = 3;
|
|
13644
|
+
var X_LABEL_TARGET = 6;
|
|
13645
|
+
function TimelineChart({
|
|
13646
|
+
movingAverage,
|
|
13647
|
+
onSelectPoint,
|
|
13648
|
+
points,
|
|
13649
|
+
showMovingAverage
|
|
13650
|
+
}) {
|
|
13651
|
+
const containerRef = useRef9(null);
|
|
13652
|
+
const [width, setWidth] = useState30(800);
|
|
13653
|
+
const [hoverIdx, setHoverIdx] = useState30(null);
|
|
13654
|
+
const [focusIdx, setFocusIdx] = useState30(null);
|
|
13427
13655
|
useEffect16(() => {
|
|
13428
|
-
|
|
13429
|
-
|
|
13430
|
-
|
|
13431
|
-
|
|
13432
|
-
|
|
13433
|
-
|
|
13434
|
-
|
|
13435
|
-
})).filter((d) => d.score !== null);
|
|
13436
|
-
if (scored.length === 0) return pts;
|
|
13437
|
-
scored.forEach((d, i) => {
|
|
13438
|
-
const x = PAD_LEFT + (scored.length === 1 ? PLOT_WIDTH / 2 : i / (scored.length - 1) * PLOT_WIDTH);
|
|
13439
|
-
const y = PAD_TOP + PLOT_HEIGHT - d.score / Y_MAX * PLOT_HEIGHT;
|
|
13440
|
-
pts.push({ date: d.date, score: d.score, x, y });
|
|
13656
|
+
const node = containerRef.current;
|
|
13657
|
+
if (!node || typeof ResizeObserver === "undefined") return;
|
|
13658
|
+
const ro = new ResizeObserver((entries) => {
|
|
13659
|
+
const entry = entries[0];
|
|
13660
|
+
if (!entry) return;
|
|
13661
|
+
const w = Math.max(320, Math.floor(entry.contentRect.width));
|
|
13662
|
+
setWidth(w);
|
|
13441
13663
|
});
|
|
13442
|
-
|
|
13443
|
-
|
|
13664
|
+
ro.observe(node);
|
|
13665
|
+
return () => ro.disconnect();
|
|
13666
|
+
}, []);
|
|
13667
|
+
const plotWidth = width - PAD_LEFT - PAD_RIGHT;
|
|
13668
|
+
const plotHeight = CHART_HEIGHT - PAD_TOP - PAD_BOTTOM;
|
|
13669
|
+
const xFor = useCallback38(
|
|
13670
|
+
(i, n) => PAD_LEFT + (n === 1 ? plotWidth / 2 : i / (n - 1) * plotWidth),
|
|
13671
|
+
[plotWidth]
|
|
13672
|
+
);
|
|
13673
|
+
const yFor = useCallback38(
|
|
13674
|
+
(score) => PAD_TOP + plotHeight - score / Y_MAX * plotHeight,
|
|
13675
|
+
[plotHeight]
|
|
13676
|
+
);
|
|
13677
|
+
const computed = useMemo17(
|
|
13678
|
+
() => points.map((p, i) => ({
|
|
13679
|
+
...p,
|
|
13680
|
+
x: xFor(i, points.length),
|
|
13681
|
+
y: yFor(p.score)
|
|
13682
|
+
})),
|
|
13683
|
+
[points, xFor, yFor]
|
|
13684
|
+
);
|
|
13685
|
+
const polylinePoints = useMemo17(
|
|
13686
|
+
() => computed.map((p) => `${p.x},${p.y}`).join(" "),
|
|
13687
|
+
[computed]
|
|
13688
|
+
);
|
|
13689
|
+
const maPoints = useMemo17(() => {
|
|
13690
|
+
if (!showMovingAverage || movingAverage.length !== computed.length)
|
|
13691
|
+
return "";
|
|
13692
|
+
return computed.map((p, i) => `${p.x},${yFor(movingAverage[i] ?? p.score)}`).join(" ");
|
|
13693
|
+
}, [computed, movingAverage, showMovingAverage, yFor]);
|
|
13444
13694
|
const avgScore = useMemo17(() => {
|
|
13445
|
-
if (
|
|
13446
|
-
return
|
|
13447
|
-
}, [
|
|
13448
|
-
const
|
|
13695
|
+
if (computed.length === 0) return 0;
|
|
13696
|
+
return computed.reduce((acc, p) => acc + p.score, 0) / computed.length;
|
|
13697
|
+
}, [computed]);
|
|
13698
|
+
const xLabelIndexes = useMemo17(() => {
|
|
13699
|
+
if (computed.length === 0) return [];
|
|
13700
|
+
if (computed.length <= X_LABEL_TARGET) return computed.map((_, i) => i);
|
|
13701
|
+
const step = (computed.length - 1) / (X_LABEL_TARGET - 1);
|
|
13702
|
+
const out = [];
|
|
13703
|
+
for (let k = 0; k < X_LABEL_TARGET; k++) {
|
|
13704
|
+
out.push(Math.round(k * step));
|
|
13705
|
+
}
|
|
13706
|
+
return Array.from(new Set(out));
|
|
13707
|
+
}, [computed]);
|
|
13708
|
+
const findNearestIndex = useCallback38(
|
|
13709
|
+
(clientX, rect) => {
|
|
13710
|
+
if (computed.length === 0) return null;
|
|
13711
|
+
const localX = (clientX - rect.left) * width / rect.width;
|
|
13712
|
+
let best = 0;
|
|
13713
|
+
let bestDist = Math.abs(computed[0].x - localX);
|
|
13714
|
+
for (let i = 1; i < computed.length; i++) {
|
|
13715
|
+
const d = Math.abs(computed[i].x - localX);
|
|
13716
|
+
if (d < bestDist) {
|
|
13717
|
+
best = i;
|
|
13718
|
+
bestDist = d;
|
|
13719
|
+
}
|
|
13720
|
+
}
|
|
13721
|
+
return best;
|
|
13722
|
+
},
|
|
13723
|
+
[computed, width]
|
|
13724
|
+
);
|
|
13725
|
+
const handleMouseMove = useCallback38(
|
|
13449
13726
|
(e) => {
|
|
13450
|
-
const
|
|
13451
|
-
|
|
13727
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
13728
|
+
const idx = findNearestIndex(e.clientX, rect);
|
|
13729
|
+
setHoverIdx(idx);
|
|
13452
13730
|
},
|
|
13453
|
-
[]
|
|
13731
|
+
[findNearestIndex]
|
|
13732
|
+
);
|
|
13733
|
+
const handleMouseLeave = useCallback38(() => {
|
|
13734
|
+
setHoverIdx(null);
|
|
13735
|
+
}, []);
|
|
13736
|
+
const handlePointClick = useCallback38(
|
|
13737
|
+
(idx) => {
|
|
13738
|
+
const datum = computed[idx];
|
|
13739
|
+
if (!datum) return;
|
|
13740
|
+
onSelectPoint(datum.source);
|
|
13741
|
+
},
|
|
13742
|
+
[computed, onSelectPoint]
|
|
13454
13743
|
);
|
|
13455
|
-
const
|
|
13744
|
+
const handleFocus = useCallback38(() => {
|
|
13745
|
+
setFocusIdx((prev) => prev ?? 0);
|
|
13746
|
+
}, []);
|
|
13747
|
+
const moveTo = useCallback38((idx) => {
|
|
13748
|
+
setFocusIdx(idx);
|
|
13749
|
+
setHoverIdx(idx);
|
|
13750
|
+
}, []);
|
|
13751
|
+
const handleKey = useCallback38(
|
|
13456
13752
|
(e) => {
|
|
13457
|
-
|
|
13458
|
-
|
|
13753
|
+
if (computed.length === 0) return;
|
|
13754
|
+
const last = computed.length - 1;
|
|
13755
|
+
const cur = focusIdx ?? 0;
|
|
13756
|
+
switch (e.key) {
|
|
13757
|
+
case "ArrowRight":
|
|
13758
|
+
e.preventDefault();
|
|
13759
|
+
moveTo(Math.min(last, cur + 1));
|
|
13760
|
+
return;
|
|
13761
|
+
case "ArrowLeft":
|
|
13762
|
+
e.preventDefault();
|
|
13763
|
+
moveTo(Math.max(0, cur - 1));
|
|
13764
|
+
return;
|
|
13765
|
+
case "Home":
|
|
13766
|
+
e.preventDefault();
|
|
13767
|
+
moveTo(0);
|
|
13768
|
+
return;
|
|
13769
|
+
case "End":
|
|
13770
|
+
e.preventDefault();
|
|
13771
|
+
moveTo(last);
|
|
13772
|
+
return;
|
|
13773
|
+
case "Enter":
|
|
13774
|
+
case " ":
|
|
13775
|
+
e.preventDefault();
|
|
13776
|
+
handlePointClick(cur);
|
|
13777
|
+
}
|
|
13459
13778
|
},
|
|
13460
|
-
[]
|
|
13779
|
+
[computed, focusIdx, handlePointClick, moveTo]
|
|
13461
13780
|
);
|
|
13462
|
-
const
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13781
|
+
const activeIdx = hoverIdx ?? focusIdx;
|
|
13782
|
+
const active = activeIdx !== null ? computed[activeIdx] : null;
|
|
13783
|
+
if (computed.length === 0) {
|
|
13784
|
+
return /* @__PURE__ */ jsx66(Card23, { padding: 4, radius: 2, shadow: 1, children: /* @__PURE__ */ jsx66(Flex36, { align: "center", justify: "center", style: { height: 220 }, children: /* @__PURE__ */ jsx66(Text50, { muted: true, size: 2, children: "No reports found in this window. Adjust the filters or extend the time range." }) }) });
|
|
13785
|
+
}
|
|
13786
|
+
return /* @__PURE__ */ jsx66(Card23, { padding: 3, radius: 2, shadow: 1, children: /* @__PURE__ */ jsxs50(Stack39, { space: 2, children: [
|
|
13787
|
+
/* @__PURE__ */ jsxs50(Box34, { ref: containerRef, style: { position: "relative", width: "100%" }, children: [
|
|
13788
|
+
/* @__PURE__ */ jsxs50(
|
|
13789
|
+
"svg",
|
|
13467
13790
|
{
|
|
13468
|
-
|
|
13469
|
-
|
|
13470
|
-
|
|
13791
|
+
"aria-label": "Score timeline chart",
|
|
13792
|
+
onFocus: handleFocus,
|
|
13793
|
+
onKeyDown: handleKey,
|
|
13794
|
+
onMouseLeave: handleMouseLeave,
|
|
13795
|
+
onMouseMove: handleMouseMove,
|
|
13796
|
+
role: "img",
|
|
13797
|
+
style: { display: "block", width: "100%" },
|
|
13798
|
+
tabIndex: 0,
|
|
13799
|
+
viewBox: `0 0 ${width} ${CHART_HEIGHT}`,
|
|
13800
|
+
children: [
|
|
13801
|
+
Y_TICKS.map((tick) => {
|
|
13802
|
+
const y = yFor(tick);
|
|
13803
|
+
return /* @__PURE__ */ jsxs50("g", { children: [
|
|
13804
|
+
/* @__PURE__ */ jsx66(
|
|
13805
|
+
"line",
|
|
13806
|
+
{
|
|
13807
|
+
stroke: "var(--card-border-color, #ddd)",
|
|
13808
|
+
strokeDasharray: "3 4",
|
|
13809
|
+
x1: PAD_LEFT,
|
|
13810
|
+
x2: width - PAD_RIGHT,
|
|
13811
|
+
y1: y,
|
|
13812
|
+
y2: y
|
|
13813
|
+
}
|
|
13814
|
+
),
|
|
13815
|
+
/* @__PURE__ */ jsx66(
|
|
13816
|
+
"text",
|
|
13817
|
+
{
|
|
13818
|
+
dominantBaseline: "middle",
|
|
13819
|
+
fill: "var(--card-muted-fg-color, #999)",
|
|
13820
|
+
fontSize: 11,
|
|
13821
|
+
textAnchor: "end",
|
|
13822
|
+
x: PAD_LEFT - 6,
|
|
13823
|
+
y,
|
|
13824
|
+
children: tick
|
|
13825
|
+
}
|
|
13826
|
+
)
|
|
13827
|
+
] }, tick);
|
|
13828
|
+
}),
|
|
13829
|
+
avgScore > 0 ? /* @__PURE__ */ jsx66(
|
|
13830
|
+
"rect",
|
|
13831
|
+
{
|
|
13832
|
+
fill: scoreHex(avgScore),
|
|
13833
|
+
fillOpacity: 0.07,
|
|
13834
|
+
height: Math.max(
|
|
13835
|
+
0,
|
|
13836
|
+
yFor(avgScore - NOISE_BAND) - yFor(avgScore + NOISE_BAND)
|
|
13837
|
+
),
|
|
13838
|
+
width: width - PAD_LEFT - PAD_RIGHT,
|
|
13839
|
+
x: PAD_LEFT,
|
|
13840
|
+
y: yFor(avgScore + NOISE_BAND)
|
|
13841
|
+
}
|
|
13842
|
+
) : null,
|
|
13843
|
+
xLabelIndexes.map((i) => {
|
|
13844
|
+
const p = computed[i];
|
|
13845
|
+
if (!p) return null;
|
|
13846
|
+
return /* @__PURE__ */ jsx66(
|
|
13847
|
+
"text",
|
|
13848
|
+
{
|
|
13849
|
+
fill: "var(--card-muted-fg-color, #999)",
|
|
13850
|
+
fontSize: 10,
|
|
13851
|
+
textAnchor: i === 0 ? "start" : i === computed.length - 1 ? "end" : "middle",
|
|
13852
|
+
x: p.x,
|
|
13853
|
+
y: CHART_HEIGHT - 10,
|
|
13854
|
+
children: formatDateShort(p.date)
|
|
13855
|
+
},
|
|
13856
|
+
i
|
|
13857
|
+
);
|
|
13858
|
+
}),
|
|
13859
|
+
showMovingAverage && maPoints ? /* @__PURE__ */ jsx66(
|
|
13860
|
+
"polyline",
|
|
13861
|
+
{
|
|
13862
|
+
fill: "none",
|
|
13863
|
+
points: maPoints,
|
|
13864
|
+
stroke: "var(--card-muted-fg-color, #888)",
|
|
13865
|
+
strokeDasharray: "6 4",
|
|
13866
|
+
strokeLinejoin: "round",
|
|
13867
|
+
strokeWidth: 1.5
|
|
13868
|
+
}
|
|
13869
|
+
) : null,
|
|
13870
|
+
/* @__PURE__ */ jsx66(
|
|
13871
|
+
"polyline",
|
|
13872
|
+
{
|
|
13873
|
+
fill: "none",
|
|
13874
|
+
points: polylinePoints,
|
|
13875
|
+
stroke: scoreHex(avgScore),
|
|
13876
|
+
strokeLinejoin: "round",
|
|
13877
|
+
strokeWidth: 2.5
|
|
13878
|
+
}
|
|
13879
|
+
),
|
|
13880
|
+
active ? /* @__PURE__ */ jsx66(
|
|
13881
|
+
"line",
|
|
13882
|
+
{
|
|
13883
|
+
stroke: "var(--card-border-color, #bbb)",
|
|
13884
|
+
strokeWidth: 1,
|
|
13885
|
+
x1: active.x,
|
|
13886
|
+
x2: active.x,
|
|
13887
|
+
y1: PAD_TOP,
|
|
13888
|
+
y2: CHART_HEIGHT - PAD_BOTTOM
|
|
13889
|
+
}
|
|
13890
|
+
) : null,
|
|
13891
|
+
computed.map((p, idx) => {
|
|
13892
|
+
const isActive = idx === activeIdx;
|
|
13893
|
+
return /* @__PURE__ */ jsx66(
|
|
13894
|
+
"circle",
|
|
13895
|
+
{
|
|
13896
|
+
cx: p.x,
|
|
13897
|
+
cy: p.y,
|
|
13898
|
+
fill: scoreHex(p.score),
|
|
13899
|
+
onClick: () => handlePointClick(idx),
|
|
13900
|
+
r: isActive ? 6 : 4,
|
|
13901
|
+
stroke: "var(--card-bg-color, #fff)",
|
|
13902
|
+
strokeWidth: isActive ? 2 : 1.5,
|
|
13903
|
+
style: { cursor: "pointer" }
|
|
13904
|
+
},
|
|
13905
|
+
idx
|
|
13906
|
+
);
|
|
13907
|
+
})
|
|
13908
|
+
]
|
|
13471
13909
|
}
|
|
13472
13910
|
),
|
|
13473
|
-
/* @__PURE__ */
|
|
13474
|
-
/* @__PURE__ */ jsx66("option", { value: "", children: "Overall" }),
|
|
13475
|
-
areaNames.map((name) => /* @__PURE__ */ jsx66("option", { value: name, children: name }, name))
|
|
13476
|
-
] })
|
|
13911
|
+
active ? /* @__PURE__ */ jsx66(Tooltip13, { active, chartWidth: width }) : null
|
|
13477
13912
|
] }),
|
|
13478
|
-
/* @__PURE__ */
|
|
13479
|
-
"
|
|
13913
|
+
/* @__PURE__ */ jsxs50(Flex36, { gap: 3, align: "center", children: [
|
|
13914
|
+
/* @__PURE__ */ jsx66(LegendDot, { color: scoreHex(avgScore), label: "Score" }),
|
|
13915
|
+
showMovingAverage ? /* @__PURE__ */ jsx66(
|
|
13916
|
+
LegendDot,
|
|
13917
|
+
{
|
|
13918
|
+
color: "var(--card-muted-fg-color, #888)",
|
|
13919
|
+
dashed: true,
|
|
13920
|
+
label: "5-point moving average"
|
|
13921
|
+
}
|
|
13922
|
+
) : null,
|
|
13923
|
+
/* @__PURE__ */ jsx66(Text50, { muted: true, size: 1, children: "Click a point to open the report" })
|
|
13924
|
+
] })
|
|
13925
|
+
] }) });
|
|
13926
|
+
}
|
|
13927
|
+
function LegendDot({
|
|
13928
|
+
color,
|
|
13929
|
+
dashed = false,
|
|
13930
|
+
label
|
|
13931
|
+
}) {
|
|
13932
|
+
return /* @__PURE__ */ jsxs50(Flex36, { align: "center", gap: 2, children: [
|
|
13933
|
+
/* @__PURE__ */ jsx66("svg", { height: "10", width: "22", children: /* @__PURE__ */ jsx66(
|
|
13934
|
+
"line",
|
|
13480
13935
|
{
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13487
|
-
|
|
13488
|
-
|
|
13489
|
-
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
|
|
13497
|
-
|
|
13498
|
-
|
|
13499
|
-
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
|
|
13503
|
-
|
|
13504
|
-
|
|
13505
|
-
|
|
13506
|
-
|
|
13507
|
-
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
].map((idx) => {
|
|
13517
|
-
const p = chartPoints[idx];
|
|
13518
|
-
return /* @__PURE__ */ jsx66(
|
|
13519
|
-
"text",
|
|
13520
|
-
{
|
|
13521
|
-
fill: "#999",
|
|
13522
|
-
fontSize: 10,
|
|
13523
|
-
textAnchor: "middle",
|
|
13524
|
-
x: p.x,
|
|
13525
|
-
y: CHART_HEIGHT - 6,
|
|
13526
|
-
children: formatDate(p.date)
|
|
13527
|
-
},
|
|
13528
|
-
idx
|
|
13529
|
-
);
|
|
13530
|
-
}) : chartPoints.map((p, idx) => /* @__PURE__ */ jsx66(
|
|
13531
|
-
"text",
|
|
13532
|
-
{
|
|
13533
|
-
fill: "#999",
|
|
13534
|
-
fontSize: 10,
|
|
13535
|
-
textAnchor: "middle",
|
|
13536
|
-
x: p.x,
|
|
13537
|
-
y: CHART_HEIGHT - 6,
|
|
13538
|
-
children: formatDate(p.date)
|
|
13539
|
-
},
|
|
13540
|
-
idx
|
|
13541
|
-
)),
|
|
13936
|
+
stroke: color,
|
|
13937
|
+
strokeDasharray: dashed ? "4 3" : void 0,
|
|
13938
|
+
strokeWidth: 2.5,
|
|
13939
|
+
x1: "0",
|
|
13940
|
+
x2: "22",
|
|
13941
|
+
y1: "5",
|
|
13942
|
+
y2: "5"
|
|
13943
|
+
}
|
|
13944
|
+
) }),
|
|
13945
|
+
/* @__PURE__ */ jsx66(Text50, { muted: true, size: 1, children: label })
|
|
13946
|
+
] });
|
|
13947
|
+
}
|
|
13948
|
+
function Tooltip13({ active, chartWidth }) {
|
|
13949
|
+
const cssX = active.x;
|
|
13950
|
+
const isRightEdge = cssX > chartWidth - 220;
|
|
13951
|
+
const left = isRightEdge ? cssX - 220 : cssX + 12;
|
|
13952
|
+
return /* @__PURE__ */ jsx66(
|
|
13953
|
+
Box34,
|
|
13954
|
+
{
|
|
13955
|
+
style: {
|
|
13956
|
+
background: "var(--card-bg-color, #fff)",
|
|
13957
|
+
border: "1px solid var(--card-border-color, #ddd)",
|
|
13958
|
+
borderRadius: 4,
|
|
13959
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.08)",
|
|
13960
|
+
left,
|
|
13961
|
+
maxWidth: 240,
|
|
13962
|
+
minWidth: 180,
|
|
13963
|
+
padding: 8,
|
|
13964
|
+
pointerEvents: "none",
|
|
13965
|
+
position: "absolute",
|
|
13966
|
+
top: 8
|
|
13967
|
+
},
|
|
13968
|
+
children: /* @__PURE__ */ jsxs50(Stack39, { space: 2, children: [
|
|
13969
|
+
/* @__PURE__ */ jsx66(Text50, { size: 1, weight: "semibold", children: formatDateTime(active.date) }),
|
|
13970
|
+
/* @__PURE__ */ jsxs50(Flex36, { gap: 2, align: "center", children: [
|
|
13542
13971
|
/* @__PURE__ */ jsx66(
|
|
13543
|
-
|
|
13972
|
+
Box34,
|
|
13544
13973
|
{
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13974
|
+
style: {
|
|
13975
|
+
background: scoreHex(active.score),
|
|
13976
|
+
borderRadius: "50%",
|
|
13977
|
+
height: 10,
|
|
13978
|
+
width: 10
|
|
13979
|
+
}
|
|
13550
13980
|
}
|
|
13551
13981
|
),
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13982
|
+
/* @__PURE__ */ jsx66(Text50, { size: 2, weight: "bold", children: Math.round(active.score) }),
|
|
13983
|
+
active.count > 1 ? /* @__PURE__ */ jsxs50(Text50, { muted: true, size: 1, children: [
|
|
13984
|
+
"avg of ",
|
|
13985
|
+
active.count,
|
|
13986
|
+
" reports"
|
|
13987
|
+
] }) : null
|
|
13988
|
+
] }),
|
|
13989
|
+
active.source.title ? /* @__PURE__ */ jsx66(Text50, { size: 1, children: active.source.title }) : null,
|
|
13990
|
+
/* @__PURE__ */ jsxs50(Text50, { muted: true, size: 1, children: [
|
|
13991
|
+
active.source.mode,
|
|
13992
|
+
active.source.source ? ` \xB7 ${active.source.source}` : ""
|
|
13993
|
+
] }),
|
|
13994
|
+
active.source.tag ? /* @__PURE__ */ jsxs50(Text50, { muted: true, size: 1, children: [
|
|
13995
|
+
"tag: ",
|
|
13996
|
+
active.source.tag
|
|
13997
|
+
] }) : null
|
|
13998
|
+
] })
|
|
13999
|
+
}
|
|
14000
|
+
);
|
|
14001
|
+
}
|
|
14002
|
+
|
|
14003
|
+
// src/components/timeline/TimelineFilters.tsx
|
|
14004
|
+
import {
|
|
14005
|
+
ClipboardIcon as ClipboardIcon2,
|
|
14006
|
+
DownloadIcon as DownloadIcon2,
|
|
14007
|
+
LinkIcon as LinkIcon4,
|
|
14008
|
+
RefreshIcon
|
|
14009
|
+
} from "@sanity/icons";
|
|
14010
|
+
import { Box as Box35, Button as Button14, Inline as Inline3, MenuDivider as MenuDivider4, MenuItem as MenuItem10 } from "@sanity/ui";
|
|
14011
|
+
import { useCallback as useCallback39 } from "react";
|
|
14012
|
+
|
|
14013
|
+
// src/components/timeline/SelectChip.tsx
|
|
14014
|
+
import { ChevronDownIcon as ChevronDownIcon5 } from "@sanity/icons";
|
|
14015
|
+
import { Button as Button13, Menu as Menu3, MenuButton as MenuButton3, MenuItem as MenuItem9 } from "@sanity/ui";
|
|
14016
|
+
import { useId as useId2 } from "react";
|
|
14017
|
+
import { jsx as jsx67 } from "react/jsx-runtime";
|
|
14018
|
+
function SelectChip({
|
|
14019
|
+
defaultValue,
|
|
14020
|
+
label,
|
|
14021
|
+
onChange,
|
|
14022
|
+
options,
|
|
14023
|
+
value
|
|
14024
|
+
}) {
|
|
14025
|
+
const menuId = useId2();
|
|
14026
|
+
const current = options.find((o) => o.value === value);
|
|
14027
|
+
const displayValue = current?.label ?? value;
|
|
14028
|
+
const isActive = defaultValue !== void 0 && value !== defaultValue;
|
|
14029
|
+
return /* @__PURE__ */ jsx67(
|
|
14030
|
+
MenuButton3,
|
|
14031
|
+
{
|
|
14032
|
+
button: /* @__PURE__ */ jsx67(
|
|
14033
|
+
Button13,
|
|
14034
|
+
{
|
|
14035
|
+
fontSize: 1,
|
|
14036
|
+
iconRight: ChevronDownIcon5,
|
|
14037
|
+
mode: isActive ? "default" : "ghost",
|
|
14038
|
+
padding: 2,
|
|
14039
|
+
radius: 6,
|
|
14040
|
+
text: `${label}: ${displayValue}`,
|
|
14041
|
+
tone: isActive ? "primary" : "default"
|
|
14042
|
+
}
|
|
14043
|
+
),
|
|
14044
|
+
id: menuId,
|
|
14045
|
+
menu: /* @__PURE__ */ jsx67(Menu3, { style: { minWidth: 160 }, children: options.map((opt) => /* @__PURE__ */ jsx67(
|
|
14046
|
+
MenuItem9,
|
|
14047
|
+
{
|
|
14048
|
+
onClick: () => onChange(opt.value),
|
|
14049
|
+
selected: value === opt.value,
|
|
14050
|
+
text: opt.label
|
|
14051
|
+
},
|
|
14052
|
+
opt.value
|
|
14053
|
+
)) }),
|
|
14054
|
+
popover: {
|
|
14055
|
+
animate: true,
|
|
14056
|
+
constrainSize: true,
|
|
14057
|
+
placement: "bottom-start",
|
|
14058
|
+
portal: true
|
|
13570
14059
|
}
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
14060
|
+
}
|
|
14061
|
+
);
|
|
14062
|
+
}
|
|
14063
|
+
|
|
14064
|
+
// src/components/timeline/TimelineFilters.tsx
|
|
14065
|
+
import { Fragment as Fragment15, jsx as jsx68, jsxs as jsxs51 } from "react/jsx-runtime";
|
|
14066
|
+
var BAR_STYLE2 = {
|
|
14067
|
+
alignItems: "center",
|
|
14068
|
+
background: "var(--card-bg-color)",
|
|
14069
|
+
border: "1px solid var(--card-border-color, transparent)",
|
|
14070
|
+
borderRadius: 4,
|
|
14071
|
+
display: "flex",
|
|
14072
|
+
flexWrap: "wrap",
|
|
14073
|
+
gap: 8,
|
|
14074
|
+
padding: "8px 12px"
|
|
14075
|
+
};
|
|
14076
|
+
function TimelineFilters(props) {
|
|
14077
|
+
const {
|
|
14078
|
+
area,
|
|
14079
|
+
areaOptions,
|
|
14080
|
+
granularity,
|
|
14081
|
+
loading,
|
|
14082
|
+
mode,
|
|
14083
|
+
modeOptions,
|
|
14084
|
+
onAreaChange,
|
|
14085
|
+
onCopyCsv,
|
|
14086
|
+
onCopyUrl,
|
|
14087
|
+
onExportCsv,
|
|
14088
|
+
onGranularityChange,
|
|
14089
|
+
onModeChange,
|
|
14090
|
+
onOwnerTeamChange,
|
|
14091
|
+
onRangeChange,
|
|
14092
|
+
onRefresh,
|
|
14093
|
+
onShowMovingAverageChange,
|
|
14094
|
+
onSourceChange,
|
|
14095
|
+
ownerTeam,
|
|
14096
|
+
ownerTeamOptions,
|
|
14097
|
+
range,
|
|
14098
|
+
showMovingAverage,
|
|
14099
|
+
source,
|
|
14100
|
+
sourceOptions
|
|
14101
|
+
} = props;
|
|
14102
|
+
const handleMaToggle = useCallback39(() => {
|
|
14103
|
+
onShowMovingAverageChange(!showMovingAverage);
|
|
14104
|
+
}, [onShowMovingAverageChange, showMovingAverage]);
|
|
14105
|
+
return /* @__PURE__ */ jsxs51("div", { style: BAR_STYLE2, children: [
|
|
14106
|
+
/* @__PURE__ */ jsx68(
|
|
14107
|
+
SelectChip,
|
|
14108
|
+
{
|
|
14109
|
+
defaultValue: DEFAULT_RANGE,
|
|
14110
|
+
label: "Range",
|
|
14111
|
+
onChange: onRangeChange,
|
|
14112
|
+
options: RANGE_OPTIONS,
|
|
14113
|
+
value: range
|
|
14114
|
+
}
|
|
14115
|
+
),
|
|
14116
|
+
/* @__PURE__ */ jsx68(
|
|
14117
|
+
SelectChip,
|
|
14118
|
+
{
|
|
14119
|
+
defaultValue: DEFAULT_GRANULARITY,
|
|
14120
|
+
label: "Aggregate",
|
|
14121
|
+
onChange: onGranularityChange,
|
|
14122
|
+
options: GRANULARITY_OPTIONS,
|
|
14123
|
+
value: granularity
|
|
14124
|
+
}
|
|
14125
|
+
),
|
|
14126
|
+
areaOptions.length > 0 && /* @__PURE__ */ jsx68(
|
|
14127
|
+
FilterChip,
|
|
14128
|
+
{
|
|
14129
|
+
label: "Area",
|
|
14130
|
+
onChange: onAreaChange,
|
|
14131
|
+
options: areaOptions,
|
|
14132
|
+
searchable: true,
|
|
14133
|
+
value: area
|
|
14134
|
+
}
|
|
14135
|
+
),
|
|
14136
|
+
modeOptions.length > 0 && /* @__PURE__ */ jsx68(
|
|
14137
|
+
FilterChip,
|
|
14138
|
+
{
|
|
14139
|
+
label: "Mode",
|
|
14140
|
+
onChange: onModeChange,
|
|
14141
|
+
options: modeOptions,
|
|
14142
|
+
value: mode
|
|
14143
|
+
}
|
|
14144
|
+
),
|
|
14145
|
+
sourceOptions.length > 0 && /* @__PURE__ */ jsx68(
|
|
14146
|
+
FilterChip,
|
|
14147
|
+
{
|
|
14148
|
+
label: "Source",
|
|
14149
|
+
onChange: onSourceChange,
|
|
14150
|
+
options: sourceOptions,
|
|
14151
|
+
value: source
|
|
14152
|
+
}
|
|
14153
|
+
),
|
|
14154
|
+
ownerTeamOptions.length > 0 && /* @__PURE__ */ jsx68(
|
|
14155
|
+
FilterChip,
|
|
14156
|
+
{
|
|
14157
|
+
label: "Team",
|
|
14158
|
+
onChange: onOwnerTeamChange,
|
|
14159
|
+
options: ownerTeamOptions,
|
|
14160
|
+
searchable: true,
|
|
14161
|
+
value: ownerTeam
|
|
14162
|
+
}
|
|
14163
|
+
),
|
|
14164
|
+
/* @__PURE__ */ jsx68(Box35, { style: { flex: "1 0 0px" } }),
|
|
14165
|
+
/* @__PURE__ */ jsxs51(Inline3, { space: 1, children: [
|
|
14166
|
+
/* @__PURE__ */ jsx68(
|
|
14167
|
+
Button14,
|
|
14168
|
+
{
|
|
14169
|
+
fontSize: 1,
|
|
14170
|
+
mode: showMovingAverage ? "default" : "ghost",
|
|
14171
|
+
onClick: handleMaToggle,
|
|
14172
|
+
padding: 2,
|
|
14173
|
+
radius: 6,
|
|
14174
|
+
text: "5-pt MA",
|
|
14175
|
+
tone: showMovingAverage ? "primary" : "default"
|
|
14176
|
+
}
|
|
14177
|
+
),
|
|
14178
|
+
/* @__PURE__ */ jsx68(
|
|
14179
|
+
Button14,
|
|
14180
|
+
{
|
|
14181
|
+
fontSize: 1,
|
|
14182
|
+
icon: RefreshIcon,
|
|
14183
|
+
mode: "ghost",
|
|
14184
|
+
onClick: onRefresh,
|
|
14185
|
+
disabled: loading,
|
|
14186
|
+
padding: 2,
|
|
14187
|
+
radius: 6,
|
|
14188
|
+
text: "Refresh"
|
|
14189
|
+
}
|
|
14190
|
+
),
|
|
14191
|
+
/* @__PURE__ */ jsx68(
|
|
14192
|
+
SplitActionButton,
|
|
14193
|
+
{
|
|
14194
|
+
menuId: "timeline-share-actions",
|
|
14195
|
+
menu: /* @__PURE__ */ jsxs51(Fragment15, { children: [
|
|
14196
|
+
/* @__PURE__ */ jsx68(
|
|
14197
|
+
MenuItem10,
|
|
14198
|
+
{
|
|
14199
|
+
icon: ClipboardIcon2,
|
|
14200
|
+
onClick: onCopyCsv,
|
|
14201
|
+
text: "Copy CSV to clipboard"
|
|
14202
|
+
}
|
|
14203
|
+
),
|
|
14204
|
+
/* @__PURE__ */ jsx68(MenuDivider4, {}),
|
|
14205
|
+
/* @__PURE__ */ jsx68(
|
|
14206
|
+
MenuItem10,
|
|
14207
|
+
{
|
|
14208
|
+
icon: DownloadIcon2,
|
|
14209
|
+
onClick: onExportCsv,
|
|
14210
|
+
text: "Export CSV"
|
|
14211
|
+
}
|
|
14212
|
+
)
|
|
14213
|
+
] }),
|
|
14214
|
+
primary: {
|
|
14215
|
+
fontSize: 1,
|
|
14216
|
+
icon: LinkIcon4,
|
|
14217
|
+
onClick: onCopyUrl,
|
|
14218
|
+
padding: 2,
|
|
14219
|
+
text: "Copy URL"
|
|
14220
|
+
}
|
|
14221
|
+
}
|
|
14222
|
+
)
|
|
14223
|
+
] })
|
|
14224
|
+
] });
|
|
14225
|
+
}
|
|
14226
|
+
|
|
14227
|
+
// src/components/timeline/TimelineSummary.tsx
|
|
14228
|
+
import { Badge as Badge13, Box as Box36, Card as Card24, Flex as Flex37, Grid as Grid7, Stack as Stack40, Text as Text51 } from "@sanity/ui";
|
|
14229
|
+
import { jsx as jsx69, jsxs as jsxs52 } from "react/jsx-runtime";
|
|
14230
|
+
function TimelineSummary({ area, stats }) {
|
|
14231
|
+
if (stats.count === 0) return null;
|
|
14232
|
+
return /* @__PURE__ */ jsx69(Card24, { padding: 3, radius: 2, shadow: 1, children: /* @__PURE__ */ jsxs52(Stack40, { space: 3, children: [
|
|
14233
|
+
/* @__PURE__ */ jsxs52(Flex37, { align: "center", gap: 2, children: [
|
|
14234
|
+
/* @__PURE__ */ jsx69(Text51, { size: 1, weight: "semibold", children: "Summary" }),
|
|
14235
|
+
/* @__PURE__ */ jsxs52(Text51, { muted: true, size: 1, children: [
|
|
14236
|
+
area ? `Area: ${area}` : "Overall score",
|
|
14237
|
+
" \xB7 ",
|
|
14238
|
+
stats.count,
|
|
14239
|
+
" report",
|
|
14240
|
+
stats.count === 1 ? "" : "s"
|
|
14241
|
+
] }),
|
|
14242
|
+
/* @__PURE__ */ jsx69(Box36, { flex: 1 }),
|
|
14243
|
+
/* @__PURE__ */ jsx69(TrendPill, { stats })
|
|
14244
|
+
] }),
|
|
14245
|
+
/* @__PURE__ */ jsxs52(Grid7, { columns: [2, 2, 4], gap: 3, children: [
|
|
14246
|
+
/* @__PURE__ */ jsx69(
|
|
14247
|
+
StatTile,
|
|
14248
|
+
{
|
|
14249
|
+
label: "Mean",
|
|
14250
|
+
score: stats.mean,
|
|
14251
|
+
sub: stats.stdev !== null ? `\xB1 ${stats.stdev.toFixed(1)} stdev` : null
|
|
14252
|
+
}
|
|
14253
|
+
),
|
|
14254
|
+
/* @__PURE__ */ jsx69(StatTile, { label: "Median", score: stats.median, sub: null }),
|
|
14255
|
+
/* @__PURE__ */ jsx69(
|
|
14256
|
+
StatTile,
|
|
14257
|
+
{
|
|
14258
|
+
label: "Best",
|
|
14259
|
+
score: stats.max?.score ?? null,
|
|
14260
|
+
sub: stats.max ? formatDateShort(stats.max.date) : null
|
|
14261
|
+
}
|
|
14262
|
+
),
|
|
14263
|
+
/* @__PURE__ */ jsx69(
|
|
14264
|
+
StatTile,
|
|
14265
|
+
{
|
|
14266
|
+
label: "Worst",
|
|
14267
|
+
score: stats.min?.score ?? null,
|
|
14268
|
+
sub: stats.min ? formatDateShort(stats.min.date) : null
|
|
14269
|
+
}
|
|
14270
|
+
)
|
|
14271
|
+
] })
|
|
14272
|
+
] }) });
|
|
14273
|
+
}
|
|
14274
|
+
function TrendPill({ stats }) {
|
|
14275
|
+
if (stats.delta === null || stats.count < 2) {
|
|
14276
|
+
return /* @__PURE__ */ jsx69(Badge13, { fontSize: 1, mode: "outline", tone: "default", children: "Single data point" });
|
|
14277
|
+
}
|
|
14278
|
+
const { label, tone } = trendBadgeContent(stats.trend, stats.delta);
|
|
14279
|
+
return /* @__PURE__ */ jsx69(Badge13, { fontSize: 1, mode: "default", tone, children: label });
|
|
14280
|
+
}
|
|
14281
|
+
function trendBadgeContent(trend, delta) {
|
|
14282
|
+
switch (trend) {
|
|
14283
|
+
case "up":
|
|
14284
|
+
return { label: `\u2191 ${formatDelta(delta)} pts vs first`, tone: "positive" };
|
|
14285
|
+
case "down":
|
|
14286
|
+
return { label: `\u2193 ${formatDelta(delta)} pts vs first`, tone: "critical" };
|
|
14287
|
+
case "flat":
|
|
14288
|
+
return { label: "Flat (within \xB11.5)", tone: "default" };
|
|
14289
|
+
}
|
|
14290
|
+
}
|
|
14291
|
+
function StatTile({
|
|
14292
|
+
label,
|
|
14293
|
+
score,
|
|
14294
|
+
sub
|
|
14295
|
+
}) {
|
|
14296
|
+
return /* @__PURE__ */ jsxs52(Stack40, { space: 2, children: [
|
|
14297
|
+
/* @__PURE__ */ jsx69(Text51, { muted: true, size: 1, weight: "medium", children: label }),
|
|
14298
|
+
score === null ? /* @__PURE__ */ jsx69(Text51, { muted: true, size: 3, children: "\u2014" }) : /* @__PURE__ */ jsx69(Badge13, { fontSize: 2, mode: "default", tone: scoreTone(score), children: score.toFixed(1) }),
|
|
14299
|
+
score !== null && sub ? /* @__PURE__ */ jsx69(Text51, { muted: true, size: 1, children: sub }) : null
|
|
14300
|
+
] });
|
|
14301
|
+
}
|
|
14302
|
+
|
|
14303
|
+
// src/components/timeline/useTimelineData.ts
|
|
14304
|
+
import { useCallback as useCallback40, useEffect as useEffect17, useMemo as useMemo18, useState as useState31 } from "react";
|
|
14305
|
+
import { useClient as useClient13 } from "sanity";
|
|
14306
|
+
function useTimelineData({
|
|
14307
|
+
mode,
|
|
14308
|
+
ownerTeam,
|
|
14309
|
+
rangeDays,
|
|
14310
|
+
source
|
|
14311
|
+
}) {
|
|
14312
|
+
const client = useClient13({ apiVersion: API_VERSION });
|
|
14313
|
+
const [dataPoints, setDataPoints] = useState31([]);
|
|
14314
|
+
const [loading, setLoading] = useState31(true);
|
|
14315
|
+
const [error, setError] = useState31(null);
|
|
14316
|
+
const [reloadCounter, setReloadCounter] = useState31(0);
|
|
14317
|
+
const refresh = useCallback40(() => {
|
|
14318
|
+
setReloadCounter((n) => n + 1);
|
|
14319
|
+
}, []);
|
|
14320
|
+
const effectiveStart = useMemo18(
|
|
14321
|
+
() => effectiveStartDate(rangeDays),
|
|
14322
|
+
[rangeDays]
|
|
14323
|
+
);
|
|
14324
|
+
useEffect17(() => {
|
|
14325
|
+
let cancelled = false;
|
|
14326
|
+
setLoading(true);
|
|
14327
|
+
setError(null);
|
|
14328
|
+
client.fetch(scoreTimelineQuery, {
|
|
14329
|
+
mode,
|
|
14330
|
+
ownerTeam,
|
|
14331
|
+
source,
|
|
14332
|
+
startDate: effectiveStart
|
|
14333
|
+
}).then((results) => {
|
|
14334
|
+
if (cancelled) return;
|
|
14335
|
+
setDataPoints(results ?? []);
|
|
14336
|
+
setLoading(false);
|
|
14337
|
+
}).catch((err) => {
|
|
14338
|
+
if (cancelled) return;
|
|
14339
|
+
setError(
|
|
14340
|
+
err instanceof Error ? err.message : "Failed to load timeline data"
|
|
14341
|
+
);
|
|
14342
|
+
setDataPoints([]);
|
|
14343
|
+
setLoading(false);
|
|
14344
|
+
});
|
|
14345
|
+
return () => {
|
|
14346
|
+
cancelled = true;
|
|
14347
|
+
};
|
|
14348
|
+
}, [client, effectiveStart, mode, ownerTeam, source, reloadCounter]);
|
|
14349
|
+
return {
|
|
14350
|
+
dataPoints,
|
|
14351
|
+
effectiveStart,
|
|
14352
|
+
error,
|
|
14353
|
+
loading,
|
|
14354
|
+
refresh
|
|
14355
|
+
};
|
|
14356
|
+
}
|
|
14357
|
+
|
|
14358
|
+
// src/components/ScoreTimeline.tsx
|
|
14359
|
+
import { Fragment as Fragment16, jsx as jsx70, jsxs as jsxs53 } from "react/jsx-runtime";
|
|
14360
|
+
function buildSearchParams2(filters) {
|
|
14361
|
+
const params = [];
|
|
14362
|
+
if (filters.range !== DEFAULT_RANGE) params.push(["range", filters.range]);
|
|
14363
|
+
if (filters.granularity !== DEFAULT_GRANULARITY) {
|
|
14364
|
+
params.push(["granularity", filters.granularity]);
|
|
14365
|
+
}
|
|
14366
|
+
if (filters.area) params.push(["area", filters.area]);
|
|
14367
|
+
if (filters.mode) params.push(["mode", filters.mode]);
|
|
14368
|
+
if (filters.source) params.push(["source", filters.source]);
|
|
14369
|
+
if (filters.ownerTeam) params.push(["ownerTeam", filters.ownerTeam]);
|
|
14370
|
+
if (filters.ma) params.push(["ma", "1"]);
|
|
14371
|
+
return params;
|
|
14372
|
+
}
|
|
14373
|
+
function ScoreTimeline({
|
|
14374
|
+
mode: defaultMode = null,
|
|
14375
|
+
source: defaultSource = null
|
|
14376
|
+
}) {
|
|
14377
|
+
const router = useRouter4();
|
|
14378
|
+
const routerState = router.state;
|
|
14379
|
+
const urlParams = routerState._searchParams;
|
|
14380
|
+
const [range, setRange] = useState32(() => {
|
|
14381
|
+
const v = readSearchParam(urlParams, "range");
|
|
14382
|
+
return v && isRangePreset(v) ? v : DEFAULT_RANGE;
|
|
14383
|
+
});
|
|
14384
|
+
const [granularity, setGranularity] = useState32(() => {
|
|
14385
|
+
const v = readSearchParam(urlParams, "granularity");
|
|
14386
|
+
return v && isGranularity(v) ? v : DEFAULT_GRANULARITY;
|
|
14387
|
+
});
|
|
14388
|
+
const [area, setArea] = useState32(
|
|
14389
|
+
() => readSearchParam(urlParams, "area")
|
|
14390
|
+
);
|
|
14391
|
+
const [mode, setMode] = useState32(
|
|
14392
|
+
() => readSearchParam(urlParams, "mode") ?? defaultMode
|
|
14393
|
+
);
|
|
14394
|
+
const [source, setSource] = useState32(
|
|
14395
|
+
() => readSearchParam(urlParams, "source") ?? defaultSource
|
|
14396
|
+
);
|
|
14397
|
+
const [ownerTeam, setOwnerTeam] = useState32(
|
|
14398
|
+
() => readSearchParam(urlParams, "ownerTeam")
|
|
14399
|
+
);
|
|
14400
|
+
const [showMovingAverage, setShowMovingAverage] = useState32(
|
|
14401
|
+
() => readSearchParam(urlParams, "ma") === "1"
|
|
14402
|
+
);
|
|
14403
|
+
useSyncSearchParams(
|
|
14404
|
+
() => buildSearchParams2({
|
|
14405
|
+
area,
|
|
14406
|
+
granularity,
|
|
14407
|
+
ma: showMovingAverage,
|
|
14408
|
+
mode,
|
|
14409
|
+
ownerTeam,
|
|
14410
|
+
range,
|
|
14411
|
+
source
|
|
14412
|
+
}),
|
|
14413
|
+
[range, granularity, area, mode, source, ownerTeam, showMovingAverage]
|
|
14414
|
+
);
|
|
14415
|
+
const rangeDays = rangePresetToDays(range);
|
|
14416
|
+
const { dataPoints, error, loading, refresh } = useTimelineData({
|
|
14417
|
+
mode,
|
|
14418
|
+
ownerTeam,
|
|
14419
|
+
rangeDays,
|
|
14420
|
+
source
|
|
14421
|
+
});
|
|
14422
|
+
const areaOptions = useMemo19(
|
|
14423
|
+
() => uniqueSorted(dataPoints, (dp) => dp.scores.map((s) => s.feature)),
|
|
14424
|
+
[dataPoints]
|
|
14425
|
+
);
|
|
14426
|
+
const modeOptions = useMemo19(
|
|
14427
|
+
() => uniqueSorted(dataPoints, (dp) => [dp.mode]),
|
|
14428
|
+
[dataPoints]
|
|
14429
|
+
);
|
|
14430
|
+
const sourceOptions = useMemo19(
|
|
14431
|
+
() => uniqueSorted(dataPoints, (dp) => [dp.source]),
|
|
14432
|
+
[dataPoints]
|
|
14433
|
+
);
|
|
14434
|
+
const ownerTeamOptions = useMemo19(
|
|
14435
|
+
() => uniqueSorted(dataPoints, (dp) => [dp.ownerTeam]),
|
|
14436
|
+
[dataPoints]
|
|
14437
|
+
);
|
|
14438
|
+
const chartPoints = useMemo19(
|
|
14439
|
+
() => aggregate(dataPoints, area, granularity),
|
|
14440
|
+
[area, dataPoints, granularity]
|
|
14441
|
+
);
|
|
14442
|
+
const stats = useMemo19(() => computeStats(chartPoints, 5), [chartPoints]);
|
|
14443
|
+
const handleSelectPoint = useCallback41(
|
|
14444
|
+
(point) => {
|
|
14445
|
+
const id = point.reportId ?? point._id;
|
|
14446
|
+
router.navigate({ reportId: id });
|
|
14447
|
+
},
|
|
14448
|
+
[router]
|
|
14449
|
+
);
|
|
14450
|
+
const handleCopyUrl = useCallback41(() => {
|
|
14451
|
+
if (typeof window === "undefined") return;
|
|
14452
|
+
void copyTextToClipboard(window.location.href);
|
|
14453
|
+
}, []);
|
|
14454
|
+
const handleCopyCsv = useCallback41(() => {
|
|
14455
|
+
void copyTextToClipboard(toCsv(dataPoints, area));
|
|
14456
|
+
}, [area, dataPoints]);
|
|
14457
|
+
const handleExportCsv = useCallback41(() => {
|
|
14458
|
+
const csv = toCsv(dataPoints, area);
|
|
14459
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-").slice(0, 19);
|
|
14460
|
+
downloadCsv(csv, `score-timeline-${ts}.csv`);
|
|
14461
|
+
}, [area, dataPoints]);
|
|
14462
|
+
return /* @__PURE__ */ jsxs53(Stack41, { space: 4, children: [
|
|
14463
|
+
/* @__PURE__ */ jsx70(
|
|
14464
|
+
TimelineFilters,
|
|
14465
|
+
{
|
|
14466
|
+
area,
|
|
14467
|
+
areaOptions,
|
|
14468
|
+
granularity,
|
|
14469
|
+
loading,
|
|
14470
|
+
mode,
|
|
14471
|
+
modeOptions,
|
|
14472
|
+
onAreaChange: setArea,
|
|
14473
|
+
onCopyCsv: handleCopyCsv,
|
|
14474
|
+
onCopyUrl: handleCopyUrl,
|
|
14475
|
+
onExportCsv: handleExportCsv,
|
|
14476
|
+
onGranularityChange: setGranularity,
|
|
14477
|
+
onModeChange: setMode,
|
|
14478
|
+
onOwnerTeamChange: setOwnerTeam,
|
|
14479
|
+
onRangeChange: setRange,
|
|
14480
|
+
onRefresh: refresh,
|
|
14481
|
+
onShowMovingAverageChange: setShowMovingAverage,
|
|
14482
|
+
onSourceChange: setSource,
|
|
14483
|
+
ownerTeam,
|
|
14484
|
+
ownerTeamOptions,
|
|
14485
|
+
range,
|
|
14486
|
+
showMovingAverage,
|
|
14487
|
+
source,
|
|
14488
|
+
sourceOptions
|
|
14489
|
+
}
|
|
14490
|
+
),
|
|
14491
|
+
error ? /* @__PURE__ */ jsx70(Card25, { padding: 4, radius: 2, tone: "critical", children: /* @__PURE__ */ jsxs53(Text52, { size: 2, children: [
|
|
14492
|
+
"Failed to load timeline: ",
|
|
14493
|
+
error
|
|
14494
|
+
] }) }) : null,
|
|
14495
|
+
loading ? /* @__PURE__ */ jsx70(Card25, { padding: 4, radius: 2, shadow: 1, children: /* @__PURE__ */ jsxs53(Flex38, { align: "center", gap: 2, justify: "center", style: { height: 220 }, children: [
|
|
14496
|
+
/* @__PURE__ */ jsx70(Spinner, { muted: true }),
|
|
14497
|
+
/* @__PURE__ */ jsx70(Text52, { muted: true, size: 2, children: "Loading timeline\u2026" })
|
|
14498
|
+
] }) }) : /* @__PURE__ */ jsxs53(Fragment16, { children: [
|
|
14499
|
+
/* @__PURE__ */ jsx70(
|
|
14500
|
+
TimelineChart,
|
|
14501
|
+
{
|
|
14502
|
+
movingAverage: stats.movingAverage,
|
|
14503
|
+
onSelectPoint: handleSelectPoint,
|
|
14504
|
+
points: chartPoints,
|
|
14505
|
+
showMovingAverage
|
|
14506
|
+
}
|
|
14507
|
+
),
|
|
14508
|
+
/* @__PURE__ */ jsx70(TimelineSummary, { area, stats })
|
|
13576
14509
|
] })
|
|
13577
14510
|
] });
|
|
13578
14511
|
}
|
|
13579
14512
|
var ScoreTimeline_default = ScoreTimeline;
|
|
13580
14513
|
|
|
13581
14514
|
// src/components/Dashboard.tsx
|
|
13582
|
-
import { jsx as
|
|
14515
|
+
import { jsx as jsx71, jsxs as jsxs54 } from "react/jsx-runtime";
|
|
13583
14516
|
var VIEW_PARAM_MAP = {
|
|
13584
14517
|
compare: "compare",
|
|
13585
14518
|
timeline: "timeline"
|
|
13586
14519
|
};
|
|
13587
14520
|
function Dashboard() {
|
|
13588
|
-
return /* @__PURE__ */
|
|
14521
|
+
return /* @__PURE__ */ jsx71(HelpProvider, { children: /* @__PURE__ */ jsx71(JudgmentDrawerProvider, { children: /* @__PURE__ */ jsx71(DashboardShell, {}) }) });
|
|
13589
14522
|
}
|
|
13590
14523
|
function DashboardShell() {
|
|
13591
|
-
const router =
|
|
14524
|
+
const router = useRouter5();
|
|
13592
14525
|
const { close: closeDrawer } = useJudgmentDrawer();
|
|
13593
14526
|
const routerState = router.state;
|
|
13594
14527
|
const reportId = routerState.reportId ?? null;
|
|
13595
|
-
|
|
14528
|
+
useEffect18(() => {
|
|
13596
14529
|
if (!reportId) closeDrawer();
|
|
13597
14530
|
}, [reportId, closeDrawer]);
|
|
13598
|
-
const handleJudgmentDrawerClose =
|
|
14531
|
+
const handleJudgmentDrawerClose = useCallback42(() => {
|
|
13599
14532
|
closeDrawer();
|
|
13600
14533
|
const state = { ...router.state };
|
|
13601
14534
|
if (state.focus) {
|
|
@@ -13603,21 +14536,21 @@ function DashboardShell() {
|
|
|
13603
14536
|
router.navigate(state);
|
|
13604
14537
|
}
|
|
13605
14538
|
}, [closeDrawer, router]);
|
|
13606
|
-
return /* @__PURE__ */
|
|
13607
|
-
/* @__PURE__ */
|
|
13608
|
-
/* @__PURE__ */
|
|
13609
|
-
/* @__PURE__ */
|
|
14539
|
+
return /* @__PURE__ */ jsxs54(Flex39, { style: { height: "100%" }, children: [
|
|
14540
|
+
/* @__PURE__ */ jsx71(Box37, { flex: 1, overflow: "auto", children: /* @__PURE__ */ jsx71(DashboardContent, {}) }),
|
|
14541
|
+
/* @__PURE__ */ jsx71(JudgmentDetailDrawerOutlet, { onClose: handleJudgmentDrawerClose }),
|
|
14542
|
+
/* @__PURE__ */ jsx71(HelpDrawer, {})
|
|
13610
14543
|
] });
|
|
13611
14544
|
}
|
|
13612
14545
|
function DashboardContent() {
|
|
13613
|
-
const router =
|
|
14546
|
+
const router = useRouter5();
|
|
13614
14547
|
const { openHelp } = useHelp();
|
|
13615
14548
|
const routerState = router.state;
|
|
13616
14549
|
const reportId = routerState.reportId ?? null;
|
|
13617
14550
|
const isDetail = reportId !== null;
|
|
13618
14551
|
const activeTab = isDetail ? "latest" : VIEW_PARAM_MAP[routerState.view ?? ""] ?? "latest";
|
|
13619
14552
|
const defaultTopic = deriveHelpTopic(routerState);
|
|
13620
|
-
const navigateToTab =
|
|
14553
|
+
const navigateToTab = useCallback42(
|
|
13621
14554
|
(tab) => {
|
|
13622
14555
|
if (tab === "latest") {
|
|
13623
14556
|
router.navigate({});
|
|
@@ -13627,13 +14560,13 @@ function DashboardContent() {
|
|
|
13627
14560
|
},
|
|
13628
14561
|
[router]
|
|
13629
14562
|
);
|
|
13630
|
-
const handleSelectReport =
|
|
14563
|
+
const handleSelectReport = useCallback42(
|
|
13631
14564
|
(id) => {
|
|
13632
14565
|
router.navigate({ reportId: id });
|
|
13633
14566
|
},
|
|
13634
14567
|
[router]
|
|
13635
14568
|
);
|
|
13636
|
-
const handleTabChange =
|
|
14569
|
+
const handleTabChange = useCallback42(
|
|
13637
14570
|
(tab, subTab, focus) => {
|
|
13638
14571
|
if (!routerState.reportId) return;
|
|
13639
14572
|
const state = {
|
|
@@ -13646,20 +14579,20 @@ function DashboardContent() {
|
|
|
13646
14579
|
},
|
|
13647
14580
|
[router, routerState.reportId]
|
|
13648
14581
|
);
|
|
13649
|
-
const handleBack =
|
|
14582
|
+
const handleBack = useCallback42(() => {
|
|
13650
14583
|
router.navigate({});
|
|
13651
14584
|
}, [router]);
|
|
13652
|
-
const handleOpenHelp =
|
|
14585
|
+
const handleOpenHelp = useCallback42(() => {
|
|
13653
14586
|
openHelp(defaultTopic);
|
|
13654
14587
|
}, [openHelp, defaultTopic]);
|
|
13655
|
-
return /* @__PURE__ */
|
|
13656
|
-
/* @__PURE__ */
|
|
13657
|
-
/* @__PURE__ */
|
|
13658
|
-
/* @__PURE__ */
|
|
13659
|
-
/* @__PURE__ */
|
|
14588
|
+
return /* @__PURE__ */ jsx71(Container, { width: 4, children: /* @__PURE__ */ jsxs54(Stack42, { padding: 4, space: 4, children: [
|
|
14589
|
+
/* @__PURE__ */ jsxs54(Flex39, { align: "center", gap: 3, children: [
|
|
14590
|
+
/* @__PURE__ */ jsxs54(Stack42, { flex: 1, space: 1, children: [
|
|
14591
|
+
/* @__PURE__ */ jsx71(Text53, { size: 4, weight: "bold", children: "AI Literacy Framework" }),
|
|
14592
|
+
/* @__PURE__ */ jsx71(Text53, { muted: true, size: 2, children: "Evaluation reports and score trends" })
|
|
13660
14593
|
] }),
|
|
13661
|
-
/* @__PURE__ */
|
|
13662
|
-
|
|
14594
|
+
/* @__PURE__ */ jsx71(
|
|
14595
|
+
Button15,
|
|
13663
14596
|
{
|
|
13664
14597
|
icon: HelpCircleIcon8,
|
|
13665
14598
|
mode: "bleed",
|
|
@@ -13669,8 +14602,8 @@ function DashboardContent() {
|
|
|
13669
14602
|
}
|
|
13670
14603
|
)
|
|
13671
14604
|
] }),
|
|
13672
|
-
!isDetail && /* @__PURE__ */
|
|
13673
|
-
/* @__PURE__ */
|
|
14605
|
+
!isDetail && /* @__PURE__ */ jsxs54(TabList3, { space: 1, children: [
|
|
14606
|
+
/* @__PURE__ */ jsx71(
|
|
13674
14607
|
Tab3,
|
|
13675
14608
|
{
|
|
13676
14609
|
"aria-controls": "latest-panel",
|
|
@@ -13680,7 +14613,7 @@ function DashboardContent() {
|
|
|
13680
14613
|
selected: activeTab === "latest"
|
|
13681
14614
|
}
|
|
13682
14615
|
),
|
|
13683
|
-
/* @__PURE__ */
|
|
14616
|
+
/* @__PURE__ */ jsx71(
|
|
13684
14617
|
Tab3,
|
|
13685
14618
|
{
|
|
13686
14619
|
"aria-controls": "timeline-panel",
|
|
@@ -13690,7 +14623,7 @@ function DashboardContent() {
|
|
|
13690
14623
|
selected: activeTab === "timeline"
|
|
13691
14624
|
}
|
|
13692
14625
|
),
|
|
13693
|
-
/* @__PURE__ */
|
|
14626
|
+
/* @__PURE__ */ jsx71(
|
|
13694
14627
|
Tab3,
|
|
13695
14628
|
{
|
|
13696
14629
|
"aria-controls": "compare-panel",
|
|
@@ -13701,10 +14634,10 @@ function DashboardContent() {
|
|
|
13701
14634
|
}
|
|
13702
14635
|
)
|
|
13703
14636
|
] }),
|
|
13704
|
-
!isDetail && activeTab === "latest" && /* @__PURE__ */
|
|
13705
|
-
!isDetail && activeTab === "timeline" && /* @__PURE__ */
|
|
13706
|
-
!isDetail && activeTab === "compare" && /* @__PURE__ */
|
|
13707
|
-
isDetail && reportId && /* @__PURE__ */
|
|
14637
|
+
!isDetail && activeTab === "latest" && /* @__PURE__ */ jsx71(TabPanel3, { "aria-labelledby": "latest-tab", id: "latest-panel", children: /* @__PURE__ */ jsx71(LatestReports, { onSelectReport: handleSelectReport }) }),
|
|
14638
|
+
!isDetail && activeTab === "timeline" && /* @__PURE__ */ jsx71(TabPanel3, { "aria-labelledby": "timeline-tab", id: "timeline-panel", children: /* @__PURE__ */ jsx71(ScoreTimeline_default, {}) }),
|
|
14639
|
+
!isDetail && activeTab === "compare" && /* @__PURE__ */ jsx71(TabPanel3, { "aria-labelledby": "compare-tab", id: "compare-panel", children: /* @__PURE__ */ jsx71(ComparisonView, {}) }),
|
|
14640
|
+
isDetail && reportId && /* @__PURE__ */ jsx71(
|
|
13708
14641
|
ReportDetail,
|
|
13709
14642
|
{
|
|
13710
14643
|
activeTab: routerState.tab ?? null,
|
|
@@ -13772,7 +14705,7 @@ var ailfStructure = (S) => S.list().id("root").title("Content").items([
|
|
|
13772
14705
|
// src/actions/RunEvaluationAction.tsx
|
|
13773
14706
|
import { BarChartIcon as BarChartIcon2 } from "@sanity/icons";
|
|
13774
14707
|
import { useToast as useToast10 } from "@sanity/ui";
|
|
13775
|
-
import { useCallback as
|
|
14708
|
+
import { useCallback as useCallback43, useEffect as useEffect19, useRef as useRef10, useState as useState33 } from "react";
|
|
13776
14709
|
import {
|
|
13777
14710
|
getReleaseIdFromReleaseDocumentId as getReleaseIdFromReleaseDocumentId3,
|
|
13778
14711
|
useClient as useClient14,
|
|
@@ -13803,10 +14736,10 @@ function createRunEvaluationAction(options = {}) {
|
|
|
13803
14736
|
const projectId = useProjectId2();
|
|
13804
14737
|
const currentUser = useCurrentUser4();
|
|
13805
14738
|
const toast = useToast10();
|
|
13806
|
-
const [state, setState] =
|
|
13807
|
-
const requestedAtRef =
|
|
14739
|
+
const [state, setState] = useState33({ status: "loading" });
|
|
14740
|
+
const requestedAtRef = useRef10(null);
|
|
13808
14741
|
const perspectiveId = getReleaseIdFromReleaseDocumentId3(release._id);
|
|
13809
|
-
|
|
14742
|
+
useEffect19(() => {
|
|
13810
14743
|
let cancelled = false;
|
|
13811
14744
|
client.fetch(contentImpactQuery, buildReportQueryParams(perspectiveId)).then((results) => {
|
|
13812
14745
|
if (cancelled) return;
|
|
@@ -13829,7 +14762,7 @@ function createRunEvaluationAction(options = {}) {
|
|
|
13829
14762
|
cancelled = true;
|
|
13830
14763
|
};
|
|
13831
14764
|
}, [client, perspectiveId]);
|
|
13832
|
-
|
|
14765
|
+
useEffect19(() => {
|
|
13833
14766
|
if (state.status !== "requested" && state.status !== "polling") return;
|
|
13834
14767
|
const { requestId, startedAt } = state;
|
|
13835
14768
|
if (state.status === "requested") {
|
|
@@ -13879,7 +14812,7 @@ function createRunEvaluationAction(options = {}) {
|
|
|
13879
14812
|
}, POLL_INTERVAL_MS2);
|
|
13880
14813
|
return () => clearInterval(interval);
|
|
13881
14814
|
}, [client, perspectiveId, state]);
|
|
13882
|
-
|
|
14815
|
+
useEffect19(() => {
|
|
13883
14816
|
if (state.status !== "error") return;
|
|
13884
14817
|
const timer = setTimeout(() => {
|
|
13885
14818
|
client.fetch(contentImpactQuery, buildReportQueryParams(perspectiveId)).then((results) => {
|
|
@@ -13899,7 +14832,7 @@ function createRunEvaluationAction(options = {}) {
|
|
|
13899
14832
|
}, 15e3);
|
|
13900
14833
|
return () => clearTimeout(timer);
|
|
13901
14834
|
}, [client, perspectiveId, state]);
|
|
13902
|
-
const handleRequest =
|
|
14835
|
+
const handleRequest = useCallback43(async () => {
|
|
13903
14836
|
const releaseTitle = release.metadata?.title ?? perspectiveId ?? "release";
|
|
13904
14837
|
const tag = `release-${slugify3(releaseTitle)}-${dateStamp3()}`;
|
|
13905
14838
|
const now = Date.now();
|