'use client'; /** * Patient List Component * E3.S2: Patiëntenlijst met zoekfunctie, filters, paginatie en sortering */ import { useState } from 'react'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { User, Search, Filter, ChevronLeft, ChevronRight, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'; import type { FHIRPatient } from '@/lib/fhir'; interface PatientListProps { patients: FHIRPatient[]; total: number; page: number; pageSize: number; } // Status badge component function StatusBadge({ status }: { status?: string }) { const badges = { planned: { label: 'Screening', className: 'bg-amber-100 text-amber-800 border-amber-200' }, active: { label: 'Actief', className: 'bg-emerald-100 text-emerald-800 border-emerald-200' }, finished: { label: 'Afgerond', className: 'bg-slate-100 text-slate-800 border-slate-200' }, cancelled: { label: 'Afgemeld', className: 'bg-red-100 text-red-800 border-red-200' }, }; const badge = status && status in badges ? badges[status as keyof typeof badges] : null; if (!badge) { return -; } return ( {badge.label} ); } export function PatientList({ patients, total, page, pageSize }: PatientListProps) { const router = useRouter(); const searchParams = useSearchParams(); const [searchTerm, setSearchTerm] = useState(searchParams.get('search') || ''); const [statusFilter, setStatusFilter] = useState(searchParams.get('status') || 'all'); const [genderFilter, setGenderFilter] = useState(searchParams.get('gender') || 'all'); const [sortBy, setSortBy] = useState(searchParams.get('sortBy') || ''); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>((searchParams.get('sortOrder') as 'asc' | 'desc') || 'asc'); // Calculate pagination info const totalPages = Math.ceil(total / pageSize); const startIndex = (page - 1) * pageSize + 1; const endIndex = Math.min(page * pageSize, total); // Build URL params helper const buildUrlParams = (updates: Record) => { const params = new URLSearchParams(); const allParams = { search: searchTerm, status: statusFilter, gender: genderFilter, sortBy: sortBy, sortOrder: sortOrder, page: page.toString(), ...updates, }; Object.entries(allParams).forEach(([key, value]) => { if (value && value !== 'all' && value !== '1') { params.set(key, value.toString()); } }); return params.toString(); }; // Handle search const handleSearch = (e: React.FormEvent) => { e.preventDefault(); router.push(`/epd/patients?${buildUrlParams({ page: 1 })}`); }; // Handle status filter change const handleStatusFilterChange = (newStatus: string) => { setStatusFilter(newStatus); router.push(`/epd/patients?${buildUrlParams({ status: newStatus, page: 1 })}`); }; // Handle gender filter change const handleGenderFilterChange = (newGender: string) => { setGenderFilter(newGender); router.push(`/epd/patients?${buildUrlParams({ gender: newGender, page: 1 })}`); }; // Handle pagination const handlePageChange = (newPage: number) => { router.push(`/epd/patients?${buildUrlParams({ page: newPage })}`); }; // Handle sorting const handleSort = (column: string) => { const newSortOrder = sortBy === column && sortOrder === 'asc' ? 'desc' : 'asc'; setSortBy(column); setSortOrder(newSortOrder); router.push(`/epd/patients?${buildUrlParams({ sortBy: column, sortOrder: newSortOrder, page: 1 })}`); }; // Render sort icon const renderSortIcon = (column: string) => { if (sortBy !== column) { return ; } return sortOrder === 'asc' ? ( ) : ( ); }; if (patients.length === 0) { return (
{/* Search and Filter Bar */}
{/* Search Bar */}
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-transparent" />
{/* Status Filter */}
{/* Gender Filter */}
{/* Empty State */}

Geen patiënten gevonden

{searchTerm || statusFilter !== 'all' || genderFilter !== 'all' ? 'Probeer een andere zoekopdracht of filter.' : 'Begin met het toevoegen van een nieuwe patiënt.'}

); } return (
{/* Search and Filter Bar */}
{/* Search Bar */}
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-teal-500 focus:border-transparent" />
{/* Status Filter */}
{/* Gender Filter */}
{/* Patient Table */}
{patients.map((patient) => { const name = patient.name?.[0]; const fullName = [ ...(name?.prefix || []), ...(name?.given || []), name?.family, ] .filter(Boolean) .join(' '); const bsn = patient.identifier?.find( (id) => id.system === 'http://fhir.nl/fhir/NamingSystem/bsn' )?.value; // Get status from extension (we'll add this to the FHIR mapping) const statusExtension = patient.extension?.find( (ext) => ext.url === 'http://mini-epd.local/fhir/StructureDefinition/episode-status' ); const status = statusExtension?.valueCode; // Format updated_at date const updatedAt = patient.meta?.lastUpdated ? new Date(patient.meta.lastUpdated).toLocaleDateString('nl-NL', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }) : '-'; return ( router.push(`/epd/patients/${patient.id}`)} > ); })}
BSN
{name?.given?.[0]?.[0]} {name?.family?.[0]}
{fullName}
{bsn || '-'}
{patient.birthDate ? new Date(patient.birthDate).toLocaleDateString('nl-NL') : '-'}
{updatedAt}
{/* Pagination Controls */} {totalPages > 1 && (
{/* Results info */}
Resultaten {startIndex}-{endIndex} van {total}
{/* Pagination buttons */}
{/* Page numbers */}
{Array.from({ length: Math.min(totalPages, 7) }, (_, i) => { let pageNum; if (totalPages <= 7) { pageNum = i + 1; } else if (page <= 4) { pageNum = i + 1; } else if (page >= totalPages - 3) { pageNum = totalPages - 6 + i; } else { pageNum = page - 3 + i; } if (pageNum < 1 || pageNum > totalPages) return null; return ( ); })}
)}
); }