UAIDE / src /components /ForensicDashboard.jsx
ATS-27
Refreshed backend assets and scripts via Git LFS
adad1d3
Raw
History Blame Contribute Delete
4.52 kB
import { useState } from 'react';
import { motion } from 'framer-motion';
import { ArrowLeft, Download, RotateCcw, Loader2, CheckCircle2 } from 'lucide-react';
import MediaPanel from './MediaPanel';
import VerdictCard from './VerdictCard';
import TemporalTimeline from './TemporalTimeline';
import TechnicalMetadata from './TechnicalMetadata';
import AssessmentPanels from './AssessmentPanels';
import { downloadReport } from '../utils/generateReport';
import styles from './ForensicDashboard.module.css';
export default function ForensicDashboard({ result, previewUrl, onReset }) {
const isVideo = result.type === 'video';
const [exportState, setExportState] = useState('idle'); // idle | loading | done
const handleExport = async () => {
if (exportState !== 'idle') return;
setExportState('loading');
// Brief tick to let the UI update before the sync blob work
await new Promise(r => setTimeout(r, 80));
try {
downloadReport(result, previewUrl);
} catch (e) {
console.error('Export failed:', e);
}
setExportState('done');
setTimeout(() => setExportState('idle'), 2800);
};
return (
<motion.div
className={styles.dashboard}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.4 }}
>
{/* Top bar */}
<div className={styles.topBar}>
<div className={styles.topBarLeft}>
<button className={styles.backBtn} onClick={onReset}>
<ArrowLeft size={15} />
<span>New Analysis</span>
</button>
<div className={styles.breadcrumb}>
<span>Dashboard</span>
<span className={styles.breadcrumbSep}>/</span>
<span className={styles.breadcrumbFile}>{result.filename}</span>
</div>
</div>
<div className={styles.topBarRight}>
<button
className={`${styles.actionBtn} ${styles.actionBtnExport} ${exportState === 'done' ? styles.exportDone : ''}`}
onClick={handleExport}
disabled={exportState === 'loading'}
title="Export full forensic report as HTML"
>
{exportState === 'loading' ? (
<>
<Loader2 size={14} className={styles.spinIcon} />
<span>Generating…</span>
</>
) : exportState === 'done' ? (
<>
<CheckCircle2 size={14} />
<span>Downloaded!</span>
</>
) : (
<>
<Download size={14} />
<span>Export Report</span>
</>
)}
</button>
<button className={`${styles.actionBtn} ${styles.actionBtnPrimary}`} onClick={onReset}>
<RotateCcw size={14} />
<span>Analyse Again</span>
</button>
</div>
</div>
{/* Main split layout */}
<div className={styles.splitLayout}>
{/* Left: Media + timeline + metadata */}
<div className={styles.leftCol}>
<MediaPanel result={result} previewUrl={previewUrl} />
{isVideo && result.timeline && (
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }}
>
<TemporalTimeline
timeline={result.timeline}
duration={result.duration?.replace('00:00:', '') || '23'}
totalFrames={result.totalFrames}
/>
</motion.div>
)}
{(result.neurosymbolic || result.ethical) && (
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.35 }}
>
<AssessmentPanels result={result} />
</motion.div>
)}
<motion.div
initial={{ opacity: 0, y: 16 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.4 }}
>
<TechnicalMetadata result={result} />
</motion.div>
</div>
{/* Right: Verdict */}
<div className={styles.rightCol}>
<motion.div
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: 0.15 }}
>
<VerdictCard result={result} />
</motion.div>
</div>
</div>
</motion.div>
);
}