kennzeichen/frontend/src/app/components/RecognitionRow.tsx
2025-11-10 07:12:06 +01:00

122 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useEffect, useState } from 'react';
import Progress from './Progress';
import { Recognition } from '../../types/plates';
import Checkbox from './Checkbox';
type Props = {
entry: Recognition;
isSelected?: boolean;
isNew?: boolean;
onClick?: () => void;
// neu für Checkbox:
checked?: boolean;
onToggle?: () => void;
};
export default function RecognitionRow({
entry,
isSelected = false,
isNew = false,
onClick,
checked = false,
onToggle,
}: Props) {
const [animatedConfidence, setAnimatedConfidence] = useState(0);
useEffect(() => {
if (isNew) {
setAnimatedConfidence(0);
setTimeout(() => setAnimatedConfidence(entry.confidence ?? 0), 50);
} else {
setAnimatedConfidence(entry.confidence ?? 0);
}
}, [entry.confidence, isNew]);
const rowClass = [
'cursor-pointer',
isSelected && !isNew ? 'bg-gray-200 dark:bg-neutral-700' : '',
!isSelected ? 'hover:bg-gray-100 dark:hover:bg-neutral-600' : '',
isNew ? 'bg-green-50 dark:bg-green-600' : ''
].filter(Boolean).join(' ');
const showDirIcon =
typeof entry.directionDegrees === 'number' && entry.directionDegrees > 0;
const dirText = entry.direction
? entry.direction.toLowerCase() === 'away'
? 'abfahrend'
: entry.direction.toLowerCase() === 'towards'
? 'ankommend'
: ''
: '';
return (
<tr onClick={onClick} className={rowClass}>
{/* 1) Checkbox-Spalte */}
<td
className="px-6 py-4 whitespace-nowrap text-sm text-center"
onClick={(e) => e.stopPropagation()}
>
<Checkbox
id={`row-${entry.id}`}
checked={checked}
// indeterminate brauchst du auf Zeilenebene nicht → weglassen oder false
onChange={() => onToggle?.()}
label={
<span className="sr-only">
Zeile für {entry.licenseFormatted ?? entry.license} auswählen
</span>
}
containerClassName="justify-center"
/>
</td>
{/* 2) restliche Spalten Anzahl muss zum Head passen */}
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-800 dark:text-neutral-200">
{entry.licenseFormatted ?? entry.license}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-neutral-300">
{entry.country ?? ''}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-neutral-300">
{entry.brand ?? ''}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-neutral-300">
{entry.model ?? ''}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-neutral-300">
<Progress value={animatedConfidence} />
</td>
{/* Richtung hat im Head colSpan={2} → hier 2 Zellen */}
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-neutral-300">
{showDirIcon && (
<svg
className="w-4 h-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
style={{ transform: `rotate(${entry.directionDegrees}deg)`, transformOrigin: 'center' }}
>
<path d="M12 2v20M5 9l7-7 7 7" />
</svg>
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-neutral-300">
{dirText}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-neutral-300">
{new Date(entry.timestampLocal).toLocaleString('de-DE')}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-600 dark:text-neutral-300">
{entry.cameraName ?? ''}
</td>
</tr>
);
}