'use client'; import { useState, useEffect, useRef } from 'react'; interface Member { memberNumber: string; hasVoted: boolean; lastLogin?: string; } export default function MembersManager() { const [members, setMembers] = useState([]); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [showAddForm, setShowAddForm] = useState(false); const [showUploadForm, setShowUploadForm] = useState(false); const [newMemberNumber, setNewMemberNumber] = useState(''); const [newPassword, setNewPassword] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const [editingMember, setEditingMember] = useState(null); const [editPassword, setEditPassword] = useState(''); const fileInputRef = useRef(null); // Stats const totalMembers = members.length; const votedMembers = members.filter(m => m.hasVoted).length; const notVotedMembers = totalMembers - votedMembers; // Load members on component mount useEffect(() => { fetchMembers(); }, []); // Fetch members from API const fetchMembers = async () => { setIsLoading(true); setError(null); try { const response = await fetch('/api/members'); if (!response.ok) { throw new Error('Fehler beim Abrufen der Mitglieder'); } const data = await response.json(); setMembers(data.members || []); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Add a new member const handleAddMember = async (e: React.FormEvent) => { e.preventDefault(); if (!newMemberNumber || !newPassword) { setError('Mitgliedsnummer und Passwort sind erforderlich'); return; } setIsLoading(true); setError(null); setSuccess(null); try { const response = await fetch('/api/members', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'add', memberNumber: newMemberNumber, password: newPassword }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Fehler beim Hinzufügen des Mitglieds'); } setSuccess('Mitglied erfolgreich hinzugefügt'); setNewMemberNumber(''); setNewPassword(''); setShowAddForm(false); // Refresh members list fetchMembers(); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Update a member const handleUpdateMember = async (memberNumber: string) => { if (!editPassword) { setError('Passwort ist erforderlich'); return; } setIsLoading(true); setError(null); setSuccess(null); try { const response = await fetch('/api/members', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'update', memberNumber, password: editPassword }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Fehler beim Aktualisieren des Mitglieds'); } setSuccess('Passwort erfolgreich aktualisiert'); setEditPassword(''); setEditingMember(null); // Refresh members list fetchMembers(); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Delete a member const handleDeleteMember = async (memberNumber: string) => { // Show confirmation dialog if (!confirm(`Sind Sie sicher, dass Sie das Mitglied "${memberNumber}" löschen möchten?`)) { return; } setIsLoading(true); setError(null); setSuccess(null); try { const response = await fetch('/api/members', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'delete', memberNumber }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Fehler beim Löschen des Mitglieds'); } setSuccess('Mitglied erfolgreich gelöscht'); // Refresh members list fetchMembers(); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Delete all members const handleDeleteAllMembers = async () => { // Show confirmation dialog if (!confirm('Sind Sie sicher, dass Sie ALLE Mitglieder löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.')) { return; } setIsLoading(true); setError(null); setSuccess(null); try { const response = await fetch('/api/members', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'deleteAll' }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Fehler beim Löschen aller Mitglieder'); } setSuccess('Alle Mitglieder erfolgreich gelöscht'); // Refresh members list fetchMembers(); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Toggle voting status const handleToggleVotingStatus = async (memberNumber: string, currentStatus: boolean) => { setIsLoading(true); setError(null); setSuccess(null); try { const response = await fetch('/api/members', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ action: 'update', memberNumber, hasVoted: !currentStatus }), }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Fehler beim Aktualisieren des Status'); } setSuccess('Status erfolgreich aktualisiert'); // Refresh members list fetchMembers(); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Handle CSV upload const handleUploadCSV = async (e: React.FormEvent) => { e.preventDefault(); if (!fileInputRef.current?.files?.length) { setError('Bitte wählen Sie eine CSV-Datei aus'); return; } const file = fileInputRef.current.files[0]; if (!file.name.endsWith('.csv')) { setError('Bitte wählen Sie eine CSV-Datei aus'); return; } setIsLoading(true); setError(null); setSuccess(null); try { const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/upload-members', { method: 'POST', body: formData, }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Fehler beim Hochladen der Datei'); } setSuccess(`${data.added} Mitglieder importiert, ${data.skipped} übersprungen`); setShowUploadForm(false); // Reset file input if (fileInputRef.current) { fileInputRef.current.value = ''; } // Refresh members list fetchMembers(); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Filter members by search term const filteredMembers = members.filter(member => member.memberNumber.toLowerCase().includes(searchTerm.toLowerCase()) ); return (

Mitgliederverwaltung

{/* Stats */}
{totalMembers}
Gesamt
{votedMembers}
Abgestimmt
{notVotedMembers}
Nicht abgestimmt
{/* Actions */}
{/* Add Member Form */} {showAddForm && (

Neues Mitglied hinzufügen

setNewMemberNumber(e.target.value)} className="w-full px-3 py-2 border border-gray-300 focus:outline-none focus:border-[#0057a6]" required />
setNewPassword(e.target.value)} className="w-full px-3 py-2 border border-gray-300 focus:outline-none focus:border-[#0057a6]" required />
)} {/* Upload CSV Form */} {showUploadForm && (

CSV-Datei importieren

Die CSV-Datei sollte zwei Spalten enthalten: Mitgliedsnummer und Passwort.

)} {/* Messages */} {error && (
{error}
)} {success && (
{success}
)} {/* Search */}
setSearchTerm(e.target.value)} placeholder="Mitgliedsnummer eingeben..." className="w-full px-3 py-2 border border-gray-300 focus:outline-none focus:border-[#0057a6]" />
{/* Members List */} {isLoading && members.length === 0 ? (
Lade Mitglieder...
) : filteredMembers.length === 0 ? (
{searchTerm ? 'Keine Mitglieder gefunden' : 'Keine Mitglieder vorhanden'}
) : (
{filteredMembers.map((member) => ( ))}
Mitgliedsnummer Status Letzte Anmeldung Aktionen
{member.memberNumber} {member.hasVoted ? 'Abgestimmt' : 'Nicht abgestimmt'} {member.lastLogin ? new Date(member.lastLogin).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : '-'} {editingMember === member.memberNumber ? (
setEditPassword(e.target.value)} placeholder="Neues Passwort" className="px-2 py-1 border border-gray-300 text-sm w-32" />
) : (
)}
)}
); }