import React, { useEffect, useMemo, useRef, useState } from 'react'; import { ArrowUpDown, Calendar, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Plus, Trash2, User } from 'lucide-react'; import { useTranslation } from '../../i18n/LanguageContext'; const toIsoDate = (date) => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; const parseIsoDate = (value) => { if (!value || !value.includes('-')) return null; const [year, month, day] = value.split('-').map(Number); return new Date(year, month - 1, day); }; const getCalendarDays = (monthDate) => { const startOfMonth = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1); const offset = (startOfMonth.getDay() + 6) % 7; const calendarStart = new Date(startOfMonth.getFullYear(), startOfMonth.getMonth(), 1 - offset); return Array.from({ length: 42 }, (_, index) => { const day = new Date(calendarStart.getFullYear(), calendarStart.getMonth(), calendarStart.getDate() + index); return { key: toIsoDate(day), date: day, isoValue: toIsoDate(day), isCurrentMonth: day.getMonth() === monthDate.getMonth() }; }); }; const PatList = ({ patTypes, assessments, overview, assessmentShareTokens = {}, testsVisible = true, onCreate, onEdit, onDelete, getPatTypeColor, getAchievement }) => { const t = useTranslation(); const datePickerRef = useRef(null); const [showCreateMenu, setShowCreateMenu] = useState(false); const [activeDatePicker, setActiveDatePicker] = useState(null); const [sortConfig, setSortConfig] = useState({ key: 'datum', direction: 'desc' }); const [filters, setFilters] = useState({ name: '', datum: '' }); const [visibleMonth, setVisibleMonth] = useState(() => { const today = new Date(); return new Date(today.getFullYear(), today.getMonth(), 1); }); const patTypeOptions = Object.keys(patTypes || {}); const openAssessments = assessments.filter((assessment) => !assessment.isFinalized); const finalizedAssessments = assessments.filter((assessment) => assessment.isFinalized); const getResultValue = (assessment) => assessment.exercises.reduce((sum, ex) => sum + (ex.points || 0), 0); const normalize = (value) => String(value || '').toLowerCase().trim(); const formatDateForFilter = (value) => { if (!value || !value.includes('-')) return ''; const [year, month, day] = value.split('-'); return `${day}.${month}.${year}`; }; const formatDateForRow = (value) => { if (!value || !value.includes('-')) return value || ''; const [year, month, day] = value.split('-'); return `${year}.${month}.${day}`; }; const monthLabel = useMemo( () => new Intl.DateTimeFormat('de-DE', { month: 'long', year: 'numeric' }).format(visibleMonth), [visibleMonth] ); const calendarDays = useMemo(() => getCalendarDays(visibleMonth), [visibleMonth]); useEffect(() => { if (!activeDatePicker) return undefined; const handlePointerDown = (event) => { if (datePickerRef.current && !datePickerRef.current.contains(event.target)) { setActiveDatePicker(null); } }; const handleKeyDown = (event) => { if (event.key === 'Escape') { setActiveDatePicker(null); } }; document.addEventListener('mousedown', handlePointerDown); document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('mousedown', handlePointerDown); document.removeEventListener('keydown', handleKeyDown); }; }, [activeDatePicker]); const matchesFilters = (assessment) => { const matchesName = !filters.name || normalize(assessment.name).includes(normalize(filters.name)); const matchesDate = !filters.datum || normalize(assessment.datum).includes(normalize(filters.datum)); return matchesName && matchesDate; }; const getSortValue = (assessment, key) => { if (key === 'name') return (assessment.name || '').toLowerCase(); if (key === 'datum') return assessment.datum || ''; if (key === 'result') return getResultValue(assessment); return ''; }; const sortAssessments = (entries) => [...entries].sort((left, right) => { const leftValue = getSortValue(left, sortConfig.key); const rightValue = getSortValue(right, sortConfig.key); if (leftValue < rightValue) return sortConfig.direction === 'asc' ? -1 : 1; if (leftValue > rightValue) return sortConfig.direction === 'asc' ? 1 : -1; return 0; }); const sortedOpenAssessments = useMemo( () => sortAssessments(openAssessments.filter(matchesFilters)), [openAssessments, sortConfig, filters] ); const sortedFinalizedAssessments = useMemo( () => sortAssessments(finalizedAssessments.filter(matchesFilters)), [finalizedAssessments, sortConfig, filters] ); const handleSort = (key) => { setSortConfig((current) => current.key === key ? { key, direction: current.direction === 'asc' ? 'desc' : 'asc' } : { key, direction: key === 'datum' ? 'desc' : 'asc' } ); }; const renderSortIcon = (key) => { if (sortConfig.key !== key) { return ; } return sortConfig.direction === 'asc' ? ( ) : ( ); }; const handleFilterChange = (key, value) => { setFilters((current) => ({ ...current, [key]: value })); }; const syncVisibleMonth = (value) => { const parsedDate = parseIsoDate(value); const baseDate = parsedDate || new Date(); setVisibleMonth(new Date(baseDate.getFullYear(), baseDate.getMonth(), 1)); }; const toggleDatePicker = (tableKey) => { setActiveDatePicker((current) => { if (current !== tableKey) { syncVisibleMonth(filters.datum); return tableKey; } return null; }); }; const selectDate = (value) => { handleFilterChange('datum', value); syncVisibleMonth(value); setActiveDatePicker(null); }; const clearDate = () => { handleFilterChange('datum', ''); syncVisibleMonth(''); setActiveDatePicker(null); }; const selectToday = () => { selectDate(toIsoDate(new Date())); }; const renderAssessmentTable = (tableKey, title, entries, totalEntries, emptyMessage) => { const isDatePickerOpen = activeDatePicker === tableKey; return (
{t('patlist.count', { count: entries.length })} {entries.length !== totalEntries ? ` ${t('patlist.of')} ${totalEntries}` : ''}
|
handleFilterChange('name', event.target.value)}
aria-label={t('patlist.filter_name')}
className="w-40 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-950 px-3 py-2 text-sm font-normal text-gray-700 dark:text-gray-200"
/>
|
{isDatePickerOpen && (
{monthLabel}
{t('patlist.days').split(' ').map((label, index) => (
{label}
))}
{calendarDays.map((day) => {
const isSelected = day.isoValue === filters.datum;
return (
);
})}
|
|
|
|---|---|---|---|
| {emptyMessage} | |||
|
{assessment.name}
{assessment.patType}
{hasShare && (
{testsVisible ? t('patlist.share_active') : t('patlist.share_paused')}
)}
{assessment.isFinalized && (
{t('patlist.finalized')}
)}
|
{formatDateForRow(assessment.datum)}
|
{totalPoints.toFixed(0)}
{achievement.name}
|
|
{t('patlist.no_assessments')}
{t('patlist.no_assessments_hint')}