import { useCallback, useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { api, ApiError } from "../api/client"; import type { DataQualityIssue, ScanResult } from "../api/types"; import { useAuth } from "../context/AuthContext"; import { SeverityBadge, StatusBadge } from "../components/Badge"; import { EmptyState, ErrorState, LoadingState, PageHeader } from "../components/PageChrome"; const RULE_TYPES = [ "possible_duplicate_customer", "missing_required_field", "odometer_regression", "booking_overlap", "vehicle_status_conflict", ]; export function DataQuality() { const { t } = useTranslation("quality"); const { user } = useAuth(); const [issues, setIssues] = useState(null); const [error, setError] = useState(null); const [status, setStatus] = useState("open"); const [ruleType, setRuleType] = useState(""); const [scanning, setScanning] = useState(false); const [scanError, setScanError] = useState(null); const [scanResult, setScanResult] = useState(null); const [confirmingScan, setConfirmingScan] = useState(false); const [demoScenariosOnly, setDemoScenariosOnly] = useState(false); const load = useCallback(() => { if (user?.role !== "operations_manager") return; setIssues(null); setError(null); const params = new URLSearchParams(); if (status) params.set("status", status); if (ruleType) params.set("rule_type", ruleType); api .get(`/api/v1/data-quality/issues?${params.toString()}`) .then(setIssues) .catch(() => setError(t("list.unavailable"))); }, [status, ruleType, user]); useEffect(() => { load(); }, [load]); async function handleScan() { setScanError(null); setScanning(true); try { const result = await api.post("/api/v1/data-quality/scan"); setScanResult(result); setConfirmingScan(false); load(); } catch (err) { setScanError(err instanceof ApiError ? err.message : t("list.scanFailed")); } finally { setScanning(false); } } if (user?.role !== "operations_manager") { return (

{t("detail.managerOnlyDetail")}

); } const scanTotal = scanResult ? Object.values(scanResult.created).reduce((a, b) => a + b, 0) : 0; const visibleIssues = issues ? demoScenariosOnly ? issues.filter((i) => i.public_ref.startsWith("DQ-DEMO-")) : issues : []; return (
setConfirmingScan(true)} disabled={scanning}> {t("list.runScan")} ) : (

{t("list.confirmScanBody")}

) } /> {scanError &&

{scanError}

} {scanResult && (

{t("list.scanComplete", { summary: scanTotal === 0 ? t("list.scanNoNew") : Object.entries(scanResult.created) .map(([rule, count]) => `${count} ${t(`ruleTypes.${rule}`, { defaultValue: rule.replace(/_/g, " ") })}`) .join(", "), })}

)}
{error && } {!error && !issues && } {issues && issues.length === 0 && } {issues && issues.length > 0 && visibleIssues.length === 0 && ( )} {visibleIssues.length > 0 && (
{t("list.count", { count: visibleIssues.length })}{t("list.evidenceBacked")}
{visibleIssues.map((i) => ( ))}
{t("list.title")}
{t("list.columns.reference")} {t("list.columns.rule")} {t("list.columns.entity")} {t("list.columns.severity")} {t("list.columns.status")}
{i.public_ref} {i.public_ref} {t(`ruleTypes.${i.rule_type}`, { defaultValue: i.rule_type.replace(/_/g, " ") })} {i.entity_ref}
)}
); }