'use client'; import { useState, useRef, useEffect } from 'react'; // Function to generate a random ID function generateRandomId(existingIds: string[]): string { const characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; const length = 8; let result: string; do { result = ''; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } } while (existingIds.includes(result)); return result; } interface VoteOptionsManagerProps { voteOptions: { id: string; label: string }[]; onVoteOptionsChange: (newOptions: { id: string; label: string }[]) => void; } export default function VoteOptionsManager({ voteOptions, onVoteOptionsChange }: VoteOptionsManagerProps) { const [showVoteOptions, setShowVoteOptions] = useState(false); const [newOptionId, setNewOptionId] = useState(''); const [newOptionLabel, setNewOptionLabel] = useState(''); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [editingOptionId, setEditingOptionId] = useState(null); const [editedLabel, setEditedLabel] = useState(''); const editInputRef = useRef(null); const labelInputRef = useRef(null); // Generate a random ID when the component mounts or when the form is reset useEffect(() => { if (!newOptionId && voteOptions) { const existingIds = voteOptions.map(option => option.id); setNewOptionId(generateRandomId(existingIds)); } }, [newOptionId, voteOptions]); // Add a new vote option const handleAddVoteOption = async () => { if (!newOptionId || !newOptionLabel) { setError('Bitte geben Sie sowohl eine ID als auch ein Label für die neue Option ein'); return; } // Check if ID already exists if (voteOptions.some(option => option.id === newOptionId)) { setError('Eine Option mit dieser ID existiert bereits'); return; } setIsLoading(true); setError(null); try { // Add to local state const updatedOptions = [...voteOptions, { id: newOptionId, label: newOptionLabel }]; // Save to server const response = await fetch('/api/editable-text', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ id: 'vote-options', content: updatedOptions }), }); if (!response.ok) { throw new Error('Fehler beim Speichern der Abstimmungsoptionen'); } // Update parent component state onVoteOptionsChange(updatedOptions); // Reset form and generate a new random ID setNewOptionLabel(''); const existingIds = updatedOptions.map(option => option.id); setNewOptionId(generateRandomId(existingIds)); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Start editing a vote option const handleEditVoteOption = (option: { id: string; label: string }) => { setEditingOptionId(option.id); setEditedLabel(option.label); // Focus the input field after it's rendered setTimeout(() => { if (editInputRef.current) { editInputRef.current.focus(); } }, 0); }; // Save the edited vote option const handleSaveVoteOption = async () => { if (!editingOptionId || !editedLabel.trim()) { setError('Das Label darf nicht leer sein'); return; } setIsLoading(true); setError(null); try { // Update in local state const updatedOptions = voteOptions.map(option => option.id === editingOptionId ? { ...option, label: editedLabel.trim() } : option ); // Save to server const response = await fetch('/api/editable-text', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ id: 'vote-options', content: updatedOptions }), }); if (!response.ok) { throw new Error('Fehler beim Speichern der Abstimmungsoptionen'); } // Update parent component state onVoteOptionsChange(updatedOptions); // Exit edit mode setEditingOptionId(null); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Cancel editing const handleCancelEdit = () => { setEditingOptionId(null); }; // Move option up in the list const handleMoveOptionUp = async (index: number) => { if (index <= 0) return; // Already at the top setIsLoading(true); setError(null); try { // Create a new array with the option moved up const updatedOptions = [...voteOptions]; [updatedOptions[index - 1], updatedOptions[index]] = [updatedOptions[index], updatedOptions[index - 1]]; // Save to server const response = await fetch('/api/editable-text', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ id: 'vote-options', content: updatedOptions }), }); if (!response.ok) { throw new Error('Fehler beim Speichern der Abstimmungsoptionen'); } // Update parent component state onVoteOptionsChange(updatedOptions); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Move option down in the list const handleMoveOptionDown = async (index: number) => { if (index >= voteOptions.length - 1) return; // Already at the bottom setIsLoading(true); setError(null); try { // Create a new array with the option moved down const updatedOptions = [...voteOptions]; [updatedOptions[index], updatedOptions[index + 1]] = [updatedOptions[index + 1], updatedOptions[index]]; // Save to server const response = await fetch('/api/editable-text', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ id: 'vote-options', content: updatedOptions }), }); if (!response.ok) { throw new Error('Fehler beim Speichern der Abstimmungsoptionen'); } // Update parent component state onVoteOptionsChange(updatedOptions); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; // Remove a vote option const handleRemoveVoteOption = async (optionId: string) => { // Don't allow removing if there are less than 2 options if (voteOptions.length <= 1) { setError('Es muss mindestens eine Abstimmungsoption vorhanden sein'); return; } // Show confirmation dialog const optionToRemove = voteOptions.find(option => option.id === optionId); if (!confirm(`Sind Sie sicher, dass Sie die Option "${optionToRemove?.label}" entfernen möchten? Bestehende Abstimmungen mit dieser Option werden nicht gelöscht, aber in den Statistiken möglicherweise nicht mehr korrekt angezeigt.`)) { return; } setIsLoading(true); setError(null); try { // Remove from local state const updatedOptions = voteOptions.filter(option => option.id !== optionId); // Save to server const response = await fetch('/api/editable-text', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ id: 'vote-options', content: updatedOptions }), }); if (!response.ok) { throw new Error('Fehler beim Speichern der Abstimmungsoptionen'); } // Update parent component state onVoteOptionsChange(updatedOptions); } catch (err) { setError(err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'); } finally { setIsLoading(false); } }; return (

Abstimmungsoptionen

{showVoteOptions && (

Aktuelle Optionen

{voteOptions.map((option, index) => (
{editingOptionId === option.id ? (
{option.id}: setEditedLabel(e.target.value)} className="px-2 py-1 border border-gray-300 focus:outline-none focus:border-[#0057a6]" onKeyDown={(e) => { if (e.key === 'Enter') handleSaveVoteOption(); if (e.key === 'Escape') handleCancelEdit(); }} />
) : (
{option.id}: {option.label}
)}
{editingOptionId !== option.id && ( )}
))}

Neue Option hinzufügen

setNewOptionId(e.target.value)} className="w-full px-3 py-2 border border-gray-300 focus:outline-none focus:border-[#0057a6]" placeholder="option-id" />

Die ID wird intern verwendet und sollte kurz und eindeutig sein.

setNewOptionLabel(e.target.value)} className="w-full px-3 py-2 border border-gray-300 focus:outline-none focus:border-[#0057a6]" placeholder="Anzeigename der Option" onFocus={() => { // Generate a random ID if the field is empty if (!newOptionId) { const existingIds = voteOptions.map(option => option.id); setNewOptionId(generateRandomId(existingIds)); } }} />

Der Anzeigename wird den Abstimmenden angezeigt.

{error && (
{error}
)}

Hinweis:

Das Ändern der Abstimmungsoptionen wirkt sich nur auf neue Abstimmungen aus. Bestehende Abstimmungsdaten bleiben unverändert.

)}
); }