import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { CaseStoreService, PoliceCase } from '../shared/case-store.service'; // For PDF generation // import jsPDF from 'jspdf'; @Component({ selector: 'app-validationpage', templateUrl: './validationpage.component.html', styleUrls: ['./validationpage.component.css'] }) export class ValidationpageComponent implements OnInit { truePercentage: number = 72; falsePercentage: number = 28; modalOpen: boolean = false; reportDate: string = new Date().toLocaleString('default', { month: 'long', year: 'numeric' }); // Dashboard metrics (real-time from CaseStoreService) sessionCount: number = 0; officerCount: number = 0; suspectCount: number = 0; avgSessionDuration: string = ''; analysisCount: number = 0; consistencyIndex: number = 0; audioVideoCount: number = 0; reportCount: number = 0; // Donut chart (officer session distribution) officer1Percent: number = 0; officer2Percent: number = 0; officer3Percent: number = 0; // Audio/Video metrics (current values will be animated) audioMetric1: number = 0; // Speech audioMetric2: number = 0; // Eye Contact audioMetric3: number = 0; // Emotion audioMetric4: number = 0; // Clarity audioMetric5: number = 0; // Confidence // Targets for animations private audioMetric1Target = 0; private audioMetric2Target = 0; private audioMetric3Target = 0; private audioMetric4Target = 0; private audioMetric5Target = 0; // Outcome distribution outcome1: number = 0; // Consistent outcome2: number = 0; // Needs Review outcome3: number = 0; // Inconsistent // Track which section/tab is selected selectedSection: string = 'Dashboard'; // Analysis overview scores (for radial diagram) - displayed values audioAnalysisScore: number = 0; videoAnalysisScore: number = 0; verifiedScore: number = 0; // Analysis targets (final percentages to animate to) audioAnalysisTarget: number = 0; videoAnalysisTarget: number = 0; verifiedTarget: number = 0; // New: truthness/confidence fields used by template audioTruthness: number = 74; videoTruthness: number = 72; verifiedConfidence: number = 73; // SVG circle radii and computed values r1 = 80; // outer (blue) r2 = 64; // middle (green) r3 = 48; // inner (red) circumference1 = 2 * Math.PI * this.r1; circumference2 = 2 * Math.PI * this.r2; circumference3 = 2 * Math.PI * this.r3; // stroke-dashoffset values (start hidden = full circumference) offset1 = this.circumference1; offset2 = this.circumference2; offset3 = this.circumference3; constructor(private router: Router, private caseStore: CaseStoreService) { // Get latest cases const cases = this.caseStore.getPoliceCases(); this.sessionCount = cases.length; this.officerCount = Array.from(new Set(cases.map(c => c.police?.name))).length; this.suspectCount = Array.from(new Set(cases.map(c => c.accused?.name))).length; this.analysisCount = cases.length; this.reportCount = cases.length; // Dummy: avgSessionDuration this.avgSessionDuration = '00:42:18'; // Dummy: audio/video count this.audioVideoCount = cases.length; // Dummy: consistencyIndex (use a fallback value, or derive from available case data) this.consistencyIndex = Math.round((cases.reduce((sum, c) => sum + 72, 0) / (cases.length || 1))); // Donut chart: officer session distribution const officerSessions = cases.reduce((acc, c) => { const name = c.police?.name || 'Unknown'; acc[name] = (acc[name] || 0) + 1; return acc; }, {} as { [name: string]: number }); const officerNames = Object.keys(officerSessions); const totalSessions = cases.length || 1; this.officer1Percent = Math.round((officerSessions[officerNames[0]] || 0) * 100 / totalSessions); this.officer2Percent = Math.round((officerSessions[officerNames[1]] || 0) * 100 / totalSessions); this.officer3Percent = Math.round((officerSessions[officerNames[2]] || 0) * 100 / totalSessions); // Dummy: audio/video metric targets this.audioMetric1Target = 68; this.audioMetric2Target = 75; this.audioMetric3Target = 60; this.audioMetric4Target = 85; this.audioMetric5Target = 82; // Dummy: outcome distribution this.outcome1 = 60; this.outcome2 = 25; this.outcome3 = 15; // true/false percentage from router state const nav = this.router.getCurrentNavigation(); const state = nav?.extras?.state as { truePercentage?: number; falsePercentage?: number }; this.truePercentage = state?.truePercentage ?? 0; this.falsePercentage = state?.falsePercentage ?? 0; // compute overview targets (do NOT set displayed scores yet) this.computeOverviewScores(); } ngOnInit(): void { // Start load animations this.animateLoad(); } private animateLoad() { const duration = 3000; // ms — slow 3s animation per request const start = performance.now(); const animate = (now: number) => { const t = Math.min(1, (now - start) / duration); // ease-out const ease = 1 - Math.pow(1 - t, 3); // animate metric bars from their targets this.audioMetric1 = Math.round(this.audioMetric1Target * ease); this.audioMetric2 = Math.round(this.audioMetric2Target * ease); this.audioMetric3 = Math.round(this.audioMetric3Target * ease); this.audioMetric4 = Math.round(this.audioMetric4Target * ease); this.audioMetric5 = Math.round(this.audioMetric5Target * ease); // interpolate displayed radial scores from0 to targets this.audioAnalysisScore = Math.round(this.audioAnalysisTarget * ease); this.videoAnalysisScore = Math.round(this.videoAnalysisTarget * ease); this.verifiedScore = Math.round(this.verifiedTarget * ease); // Also update the truthness/confidence displayed details this.audioTruthness = this.audioAnalysisScore; this.videoTruthness = this.videoAnalysisScore; this.verifiedConfidence = this.verifiedScore; // update SVG offsets so stroke animates from full circumference to target offset this.offset1 = Math.round(this.circumference1 * (1 - this.audioAnalysisScore / 100)); this.offset2 = Math.round(this.circumference2 * (1 - this.videoAnalysisScore / 100)); this.offset3 = Math.round(this.circumference3 * (1 - this.verifiedScore / 100)); if (t < 1) { requestAnimationFrame(animate); } }; // ensure offsets start hidden (full circumference) this.offset1 = this.circumference1; this.offset2 = this.circumference2; this.offset3 = this.circumference3; requestAnimationFrame(animate); } computeOverviewScores() { // Audio analysis target: average of audio metric targets const audioVals = [this.audioMetric1Target, this.audioMetric2Target, this.audioMetric3Target, this.audioMetric4Target, this.audioMetric5Target].filter(v => typeof v === 'number'); this.audioAnalysisTarget = audioVals.length ? Math.round(audioVals.reduce((a, b) => a + b, 0) / audioVals.length) : 0; // Video analysis target: use consistencyIndex as proxy or derive from other available metrics this.videoAnalysisTarget = this.consistencyIndex || Math.round((this.audioMetric2Target + this.audioMetric3Target + this.audioMetric4Target) / 3); // Verified target: derive from truePercentage if available, else average of audio/video targets const v = this.truePercentage || Math.round((this.audioAnalysisTarget + this.videoAnalysisTarget) / 2); this.verifiedTarget = Math.round(v); // Do not set displayed scores or offsets here; animateLoad will handle the animated transition } percentToOffset(percent: number, circumference: number) { const pct = Math.max(0, Math.min(100, percent)); return Math.round(circumference * (1 - pct / 100)); } selectSection(section: string) { this.selectedSection = section; } navigateBackToCaseDetails() { this.router.navigate(['/case-details']); } openModal() { this.modalOpen = true; } closeModal() { this.modalOpen = false; } downloadPDF() { // Example: Use jsPDF to generate PDF // const doc = new jsPDF(); // doc.text('Investigation Report', 10, 10); // doc.text(`Pass Percentage: ${this.truePercentage}%`, 10, 30); // doc.text(`Fail Percentage: ${this.falsePercentage}%`, 10, 40); // doc.save('investigation-report.pdf'); alert('PDF download functionality to be implemented.'); } emailReport() { // Example: Use mailto or backend API window.location.href = `mailto:?subject=Investigation Report&body=Pass Percentage: ${this.truePercentage}%\nFail Percentage: ${this.falsePercentage}%`; } navigateHome() { this.router.navigate(['/']); } navigateBackToPyDetect() { this.router.navigate(['/py-detect']); } reAnalyze() { // TODO: Implement re-analysis logic alert('Re-Analyze Audio/Video functionality to be implemented.'); } // Navigate to question summary page goToQuestionSummary() { this.router.navigate(['/question-summary']); } getMetricGradient(metricValue: number): string { const p = Math.max(0, Math.min(100, Math.round(metricValue))); if (p >= 80) return 'linear-gradient(90deg, #10b981, #34d399)'; // green if (p >= 60) return 'linear-gradient(90deg, #3b82f6, #60a5fa)'; // blue if (p >= 40) return 'linear-gradient(90deg, #f59e0b, #f97316)'; // amber return 'linear-gradient(90deg, #ef4444, #fb7185)'; // red } getMetricTextColor(metricValue: number): string { const p = Math.max(0, Math.min(100, Math.round(metricValue))); return p >= 50 ? '#ffffff' : '#0f172a'; } }