fastcheck/src/pages/evaluations/SingleEvaluation.tsx
2026-04-28 08:41:05 -04:00

635 lines
28 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTenant } from '../../contexts/TenantContext';
import { Search, AlertTriangle, Download, FileText, XCircle, CheckCircle, ChevronDown, ChevronUp, Info } from 'lucide-react';
import { validateChileanRut, formatChileanRut, apiClient } from '../../services/api'; // Assuming apiClient is your configured axios instance
// It's better to have specific service functions, but using apiClient directly for now
// import { EvaluationService } from '../../services/evaluationService'; // Original, might not be needed for new flow
import toast from 'react-hot-toast';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import HumanReadableJson from '../../components/common/HumanReadableJson';
import DuxiterSummaryCard from '../../components/common/DuxiterSummaryCard';
import GeneralInfoCard from '../../components/common/GeneralInfoCard';
import MarkdownResumeCard from '../../components/common/MarkdownResumeCard';
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
import { SheriffDataLogResponse } from '../../types/sheriff';
import { logger } from '../../utils/logger';
import { useTranslation } from 'react-i18next';
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Alert } from '@mui/material';
// Define types for the resume response and PDF data
interface AssociatedPdf {
title: string;
filename: string;
contentType?: string;
originalUrl: string;
sourcePath: string;
downloaded: boolean;
contentBase64?: string; // Will be in sheriffLogData.associatedPdfs
error?: string;
}
interface SheriffLogAssociatedPdf { // Matches backend model structure for base64
sourcePath: string;
originalUrl: string;
documentTitle?: string;
contentBase64?: string;
contentType?: string;
downloadedAt?: Date;
error?: string;
}
interface RutResumeResponse {
rut: string;
markdownResume: string;
duxiterMD: string;
sheriffLogData: SheriffDataLogResponse;
availablePdfs: AssociatedPdf[];
failedPdfDownloads: AssociatedPdf[];
riskAssessment?: {
summaryDocumentMD?: string;
allRules?: any[];
riskSummary?: any;
};
}
const showJsonData = false; // This can be a prop or state if you want to toggle it
const formatJsonToMarkdown = (data: Record<string, any>): string => {
let markdown = '## Interpretación de Datos\n\n';
// SII Data
if (data.summaryData?.data?.sii) {
const sii = data.summaryData.data.sii;
markdown += '### Información SII\n';
markdown += `- **Razón Social**: ${sii.razonSocial || 'No disponible'}\n`;
markdown += `- **Inicio Actividades**: ${sii.inicioActividades || 'No disponible'}\n`;
markdown += `- **Situación Actual**: ${sii.situacionActual || 'No disponible'}\n`;
if (sii.actividadEconomicaVigente?.length > 0) {
markdown += '\n#### Actividades Económicas\n';
sii.actividadEconomicaVigente.forEach((act: any) => {
markdown += `- ${act.activities} (Código: ${act.code})\n`;
});
}
}
// Business Size Information
if (data.summaryData?.data?.siiBusinessSize) {
const size = data.summaryData.data.siiBusinessSize;
markdown += '\n### Tamaño de la Empresa\n';
markdown += `- **Clasificación**: ${size.textSize || 'No disponible'}\n`;
markdown += `- **Ventas Anuales (UF)**: ${size.annualSales || 'No disponible'}\n`;
markdown += `- **Trabajadores**: ${size.dependentWorkers || 'No disponible'}\n`;
}
// Properties and Vehicles
if (data.summaryData?.data?.propertiesSummary || data.summaryData?.data?.vehiclesSummary) {
markdown += '\n### Activos\n';
if (data.summaryData?.data?.propertiesSummary) {
const props = data.summaryData.data.propertiesSummary;
markdown += `- **Propiedades**: ${props.propertiesQuantity || 0} (Avalúo Total: ${props.totalAppraisal || '0'})\n`;
}
if (data.summaryData?.data?.vehiclesSummary) {
markdown += `- **Vehículos**: ${data.summaryData.data.vehiclesSummary.vehiclesQuantity || 0}\n`;
}
}
// Credit Scoring
if (data.creditScoringData?.data) {
const scoring = data.creditScoringData.data;
markdown += '\n### Evaluación Crediticia\n';
markdown += `- **Clasificación**: ${scoring.classificationLabel || 'No disponible'}\n`;
markdown += `- **Porcentaje**: ${scoring.classificationPercentage ? scoring.classificationPercentage + '%' : 'No disponible'}\n`;
if (scoring.financesPercentage !== undefined) {
markdown += `- **Finanzas**: ${scoring.financesPercentage}%\n`;
}
if (scoring.judicialPercentage !== undefined) {
markdown += `- **Judicial**: ${scoring.judicialPercentage}%\n`;
}
}
// Legal Cases Summary
const hasLegalCases = data.civilCasesData?.data?.length > 0 ||
data.laboralCasesData?.data?.length > 0 ||
data.cobranzaCasesData?.data?.length > 0;
if (hasLegalCases) {
markdown += '\n### Casos Legales\n';
markdown += `- **Casos Civiles**: ${data.civilCasesData?.data?.length || 0}\n`;
markdown += `- **Casos Laborales**: ${data.laboralCasesData?.data?.length || 0}\n`;
markdown += `- **Casos de Cobranza**: ${data.cobranzaCasesData?.data?.length || 0}\n`;
}
return markdown;
};
const SingleEvaluation: React.FC = () => {
const { t } = useTranslation();
const [rut, setRut] = useState('');
const [formattedRut, setFormattedRut] = useState('');
const [name, setName] = useState(''); // Kept for potential future use or if form still needs it
const [isValidRut, setIsValidRut] = useState<boolean | null>(null);
// const [isSubmitting, setIsSubmitting] = useState(false); // Replaced by more granular loading states
const [isLoadingLookup, setIsLoadingLookup] = useState(false);
const [isLoadingResume, setIsLoadingResume] = useState(false);
const [markdownResult, setMarkdownResult] = useState<string | null>(null);
const [downloadablePdfs, setDownloadablePdfs] = useState<AssociatedPdf[]>([]);
const [erroredPdfs, setErroredPdfs] = useState<AssociatedPdf[]>([]);
const [processError, setProcessError] = useState<string | null>(null);
const [termsAccepted, setTermsAccepted] = useState(false);
const [rawData, setRawData] = useState<RutResumeResponse | null>(null);
const [showRawData, setShowRawData] = useState(false);
const [showHumanReadableData, setShowHumanReadableData] = useState(false);
const [showInterpretation, setShowInterpretation] = useState(false);
const [showDuxiterSummary, setShowDuxiterSummary] = useState(false);
const markdownRef = useRef<HTMLDivElement>(null);
// Estado para el modal de créditos agotados
const [showNoCreditsDialog, setShowNoCreditsDialog] = useState(false);
const { tenant, loading: tenantLoading, updateTenant } = useTenant();
const availableCredits = tenant?.creditBalance?.availableCredits || 0;
const navigate = useNavigate();
useEffect(() => {
if (!tenantLoading && tenant && availableCredits === 0) {
setShowNoCreditsDialog(true);
}
}, [availableCredits, tenantLoading, tenant]);
const handleCloseNoCreditsDialog = () => {
setShowNoCreditsDialog(false);
};
useEffect(() => {
if (markdownResult || processError) {
setMarkdownResult(null);
setDownloadablePdfs([]);
setErroredPdfs([]);
setProcessError(null);
setRawData(null);
}
}, [rut]);
const handleRutChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setRut(value);
// Format RUT as user types
if (value) {
const formatted = formatChileanRut(value);
setFormattedRut(formatted);
// Validate RUT
const isValid = validateChileanRut(value);
setIsValidRut(isValid);
} else {
setFormattedRut('');
setIsValidRut(null);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!isValidRut) {
toast.error(t('singleEvaluation.toast.invalidRut'));
return;
}
if (!termsAccepted) {
toast.error(t('singleEvaluation.toast.acceptTerms'));
return;
}
if (!tenant) {
toast.error(t('singleEvaluation.toast.tenantError'));
return;
}
setIsLoadingLookup(true);
// Sanitize RUT for API: remove dots, keep hyphen
const rutForApi = formattedRut.replace(/\./g, '');
try {
// First check if we already have an evaluation in the database
try {
const checkResponse = await apiClient.get(`/rut/results/rut/${rutForApi}`);
if (checkResponse.data) {
// Evaluation already exists, redirect to FastCheck
logger.log('Evaluation already exists, redirecting to FastCheck');
navigate(`/fast-check-ex?rut=${rutForApi}`, { replace: true });
setIsLoadingLookup(false);
return;
}
} catch (error: any) {
// If 404, it means no evaluation exists, which is expected
// For other errors, log but continue with creating a new evaluation
if (error.response?.status !== 404) {
logger.error('Error checking for existing evaluation:', error);
}
}
// Start a new evaluation and wait for it to complete
setIsLoadingResume(true);
toast.loading(t('singleEvaluation.searching'), { id: 'evaluation-toast' });
const response = await apiClient.post<RutResumeResponse>('/rut/lookup', {
rut: rutForApi,
isMonitoring: false
});
logger.log(229, 'Evaluation response:', response);
// Evaluation completed successfully
toast.success(t('singleEvaluation.toast.success'), { id: 'evaluation-toast' });
// Now navigate to FastCheck page with the RUT as a parameter
navigate(`/fast-check-ex?rut=${rutForApi}`, { replace: true });
} catch (error) {
logger.error('Error during evaluation process:', error);
toast.error(t('singleEvaluation.toast.unknownError'), { id: 'evaluation-toast' });
} finally {
// Always reset loading states
setIsLoadingLookup(false);
setIsLoadingResume(false);
}
};
const createDataUrl = (base64: string, contentType: string): string => {
return `data:${contentType};base64,${base64}`;
};
const handlePdfDownload = async () => {
if (!markdownRef.current || !rawData?.riskAssessment?.summaryDocumentMD) return;
try {
// Show loading state
const loadingToast = document.createElement('div');
loadingToast.className = 'fixed top-4 right-4 bg-blue-600 text-white px-4 py-2 rounded-lg z-50';
loadingToast.textContent = 'Generando PDF...';
document.body.appendChild(loadingToast);
// Wait for next render cycle
await new Promise(resolve => setTimeout(resolve, 100));
const canvas = await html2canvas(markdownRef.current, {
scale: 2,
logging: false,
useCORS: true,
allowTaint: true
});
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'mm',
format: 'a4'
});
const imgWidth = 210; // A4 width in mm
const pageHeight = 297; // A4 height in mm
const imgHeight = (canvas.height * imgWidth) / canvas.width;
let heightLeft = imgHeight;
let position = 0;
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
// Add new pages if content exceeds one page
while (heightLeft > 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
pdf.save(`reporte_${formattedRut}.pdf`);
// Remove loading toast
document.body.removeChild(loadingToast);
} catch (error) {
logger.error('Error generating PDF:', error);
alert('Error al generar el PDF. Por favor, intente nuevamente.');
}
};
return (
<div className="min-h-screen">
{/* Modal de créditos agotados */}
<Dialog open={showNoCreditsDialog} onClose={handleCloseNoCreditsDialog}>
<DialogTitle>Créditos no disponibles</DialogTitle>
<DialogContent>
<Alert severity="info" sx={{ mt: 2 }}>
No quedan créditos disponibles
</Alert>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseNoCreditsDialog} color="info" variant="contained">
Aceptar
</Button>
</DialogActions>
</Dialog>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6">
<div>
<p className="text-sm text-gray-500 dark:text-gray-400">{t('singleEvaluation.reportLabel')}</p>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">{t('singleEvaluation.title')}</h1>
<h3 className="text-2xl font-bold text-gray-900 dark:text-white"> <span>Creditos disponibles:</span> {availableCredits}</h3>
</div>
<div className="mt-4 sm:mt-0 flex items-center space-x-2">
<button
onClick={() => {
if (markdownResult) {
const blob = new Blob([markdownResult], { type: 'text/markdown' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `reporte_${formattedRut}.md`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// Notification is no longer needed
} else {
setProcessError(t('singleEvaluation.noReportToDownload'));
}
}}
disabled={!markdownResult}
className="bg-primary-600 text-white px-4 py-2 rounded-md shadow-sm hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center"
>
<Download className="w-5 h-5 mr-2" />
{t('singleEvaluation.downloadMd')}
</button>
<button
onClick={handlePdfDownload}
disabled={!rawData?.riskAssessment?.summaryDocumentMD}
className="bg-green-600 text-white px-4 py-2 rounded-md shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 disabled:bg-gray-400 disabled:cursor-not-allowed flex items-center"
>
<Download className="w-5 h-5 mr-2" />
{t('singleEvaluation.downloadPdf')}
</button>
</div>
</div>
{/* Form Card */}
<div className="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6 mb-8">
<h2 className="text-xl font-semibold text-gray-800 dark:text-gray-100 mb-1">{t('singleEvaluation.searchSupplierTitle')}</h2>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-6">{t('singleEvaluation.searchSupplierSubtitle')}</p>
{tenant && tenant.usageStats.evaluationsRemaining <= 5 && (
<div className="bg-yellow-50 dark:bg-yellow-900/20 border-l-4 border-yellow-400 dark:border-yellow-500 text-yellow-700 dark:text-yellow-300 p-4 mb-6" role="alert">
<div className="flex">
<div className="py-1"><AlertTriangle className="h-5 w-5 text-yellow-400 dark:text-yellow-500 mr-3" /></div>
<div>
<p className="font-bold">{t('singleEvaluation.warning.remaining', { count: tenant.usageStats.evaluationsRemaining })}</p>
<p className="text-sm">{t('singleEvaluation.warning.planAlmostExhausted')}</p>
</div>
</div>
</div>
)}
<form onSubmit={handleSubmit} className="grid grid-cols-1 md:grid-cols-3 gap-6 items-end">
<div className="md:col-span-2 grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label htmlFor="rut" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{t('singleEvaluation.rutLabel')} <span className="text-red-500">*</span>
</label>
<div className="relative">
<input
type="text"
id="rut"
value={formattedRut}
onChange={handleRutChange}
placeholder={t('singleEvaluation.rutPlaceholder')}
className={`w-full pl-3 pr-10 py-2 border rounded-md shadow-sm text-sm bg-white dark:bg-gray-700 ${isValidRut === false
? 'border-red-500 text-red-900 dark:text-red-200 focus:ring-red-500 focus:border-red-500'
: isValidRut === true
? 'border-green-500 text-green-900 dark:text-green-200 focus:ring-green-500 focus:border-green-500'
: 'border-gray-300 dark:border-gray-600 text-gray-900 dark:text-white focus:ring-primary-500 focus:border-primary-500'
}`}
required
disabled={isLoadingLookup || isLoadingResume}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
{isValidRut === true && <CheckCircle className="h-5 w-5 text-green-500" />}
{isValidRut === false && <XCircle className="h-5 w-5 text-red-500" />}
</div>
</div>
</div>
<div>
<label htmlFor="name" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
{t('singleEvaluation.nameLabel')} <span className="text-sm font-normal text-gray-500">{t('common.optional')}</span>
</label>
<input
type="text"
id="name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={t('singleEvaluation.namePlaceholder')}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500 sm:text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-white"
disabled={isLoadingLookup || isLoadingResume}
/>
</div>
</div>
<div className="md:col-span-1">
<button
type="submit"
disabled={isLoadingLookup || isLoadingResume || !isValidRut || !termsAccepted}
className="w-full flex justify-center items-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 disabled:bg-gray-400 dark:disabled:bg-gray-600 disabled:cursor-not-allowed"
>
{(isLoadingLookup || isLoadingResume) ? (
<>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
{isLoadingLookup ? t('singleEvaluation.searching') : t('singleEvaluation.generatingSummary')}
</>
) : (
<>
<Search className="w-5 h-5 mr-2" />
{t('singleEvaluation.submit')}
</>
)}
</button>
</div>
</form>
<div className="mt-4 flex items-center">
<input
id="terms"
name="terms"
type="checkbox"
checked={termsAccepted}
onChange={(e) => setTermsAccepted(e.target.checked)}
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 dark:border-gray-600 rounded bg-gray-100 dark:bg-gray-700"
/>
<label htmlFor="terms" className="ml-2 block text-sm text-gray-600 dark:text-gray-400">
{t('singleEvaluation.acceptLabelPrefix')}{' '}
<a href="/terms" target="_blank" rel="noopener noreferrer" className="font-medium text-primary-600 hover:underline dark:text-primary-400 dark:hover:text-primary-300">
{t('singleEvaluation.terms')}
</a>{' '}
{t('singleEvaluation.and')}{' '}
<a href="/privacy" target="_blank" rel="noopener noreferrer" className="font-medium text-primary-600 hover:underline dark:text-primary-400 dark:hover:text-primary-300">
{t('singleEvaluation.privacy')}
</a>.
</label>
</div>
</div>
{/* Results Section */}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left Column: Report */}
<div className="lg:col-span-2 space-y-8">
{(markdownResult || processError || isLoadingLookup || isLoadingResume) && (
<div className="bg-white dark:bg-gray-800 shadow-md rounded-lg">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-xl font-semibold text-gray-800 dark:text-gray-100">{t('singleEvaluation.reportTitle')}</h2>
</div>
<div className="p-6">
{processError && (
<div className="bg-red-100 dark:bg-red-900/20 border-l-4 border-red-500 text-red-700 dark:text-red-300 p-4 mb-6" role="alert">
<p className="font-bold">{t('singleEvaluation.toast.errorPrefix')}</p>
<p>{processError}</p>
</div>
)}
{(isLoadingLookup || isLoadingResume) && !processError && (
<div className="text-center py-10">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mx-auto"></div>
<p className="mt-4 text-gray-600 dark:text-gray-400">{isLoadingLookup ? t('singleEvaluation.searching') : t('singleEvaluation.generatingSummary')}</p>
</div>
)}
</div>
</div>
)}
{rawData?.duxiterMD && (
<div className="bg-white dark:bg-gray-800 shadow-md rounded-lg">
<button
onClick={() => setShowDuxiterSummary(!showDuxiterSummary)}
className="w-full p-6 flex items-center justify-between text-left"
>
<div>
<h3 className="text-xl font-semibold text-gray-800 dark:text-gray-100">{t('singleEvaluation.duxiterSummary.title')}</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">{t('singleEvaluation.duxiterSummary.subtitle')}</p>
</div>
{showDuxiterSummary ? <ChevronUp className="h-5 w-5 text-gray-500 dark:text-gray-400" /> : <ChevronDown className="h-5 w-5 text-gray-500 dark:text-gray-400" />}
</button>
{showDuxiterSummary && rawData && (
<div className="px-6 pb-6">
<DuxiterSummaryCard rut={formattedRut} data={rawData} />
</div>
)}
</div>
)}
{rawData?.riskAssessment?.summaryDocumentMD && (
<div className="bg-white dark:bg-gray-800 shadow-md rounded-lg p-6">
<div ref={markdownRef}>
<MarkdownResumeCard mdResume={rawData.riskAssessment.summaryDocumentMD} />
</div>
</div>
)}
</div>
{/* Right Column: Documents & Data */}
<div className="lg:col-span-1 space-y-8">
{(downloadablePdfs.length > 0 || erroredPdfs.length > 0) && (
<div className="bg-white dark:bg-gray-800 shadow-md rounded-lg">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<h3 className="text-xl font-semibold text-gray-800 dark:text-gray-100">{t('singleEvaluation.documents.title')}</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">{t('singleEvaluation.documents.subtitle')}</p>
</div>
<div className="p-6">
{downloadablePdfs.length > 0 && (
<div className="mb-6">
<h4 className="text-md font-semibold text-gray-700 dark:text-gray-300 mb-3">{t('singleEvaluation.documents.available')}</h4>
<ul className="space-y-2">
{downloadablePdfs.map((pdf, index) => (
<li key={index}>
<a
href={pdf.contentBase64 ? createDataUrl(pdf.contentBase64, pdf.contentType || 'application/pdf') : '#'}
download={pdf.filename}
className="text-primary-600 hover:text-primary-800 hover:underline dark:text-primary-400 dark:hover:text-primary-300 flex items-center text-sm"
onClick={(e) => { if (!pdf.contentBase64) e.preventDefault(); }}
>
<FileText className="w-4 h-4 mr-2 flex-shrink-0" />
<span className="truncate">{pdf.title}</span>
</a>
</li>
))}
</ul>
</div>
)}
{erroredPdfs.length > 0 && (
<div>
<h4 className="text-md font-semibold text-gray-700 dark:text-gray-300 mb-3">{t('singleEvaluation.documents.unavailable')}</h4>
<ul className="space-y-2">
{erroredPdfs.map((pdf, index) => (
<li key={index} className="text-sm text-red-600 dark:text-red-400 flex items-start">
<XCircle className="w-4 h-4 mr-2 mt-0.5 flex-shrink-0" />
<div>
<span className="font-semibold">{pdf.title}</span>
<p className="text-xs text-red-500">{t('singleEvaluation.documents.errorPrefix')} {pdf.error}</p>
</div>
</li>
))}
</ul>
</div>
)}
</div>
</div>
)}
{rawData && showJsonData && (
<div className="bg-white dark:bg-gray-800 shadow-md rounded-lg">
<button
onClick={() => setShowRawData(!showRawData)}
className="w-full p-6 flex items-center justify-between text-left"
>
<div>
<h3 className="text-xl font-semibold text-gray-800 dark:text-gray-100">{t('singleEvaluation.techData.title')}</h3>
<p className="text-sm text-gray-500 dark:text-gray-400">{t('singleEvaluation.techData.subtitle')}</p>
</div>
{showRawData ? <ChevronUp className="h-5 w-5 text-gray-500 dark:text-gray-400" /> : <ChevronDown className="h-5 w-5 text-gray-500 dark:text-gray-400" />}
</button>
{showRawData && (
<div className="px-6 pb-6">
<div className="p-4 bg-gray-900 rounded-md">
<pre className="overflow-x-auto text-sm text-white whitespace-pre-wrap">
{JSON.stringify({
...rawData.sheriffLogData,
associatedPdfs: `[${rawData.sheriffLogData.associatedPdfs?.length || 0} PDFs]`
}, null, 2)}
</pre>
</div>
</div>
)}
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default SingleEvaluation;