import React, { useState } from 'react'; import { Box, VStack, HStack, FormControl, FormLabel, Button, Badge, Text, useToast, Select, Spinner, Alert, AlertIcon, IconButton, useColorModeValue, Collapse, Divider, Input, Textarea, Switch, Tabs, TabList, TabPanels, Tab, TabPanel, } from '@chakra-ui/react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { AddIcon, DeleteIcon, ChevronDownIcon, ChevronUpIcon, CloseIcon } from '@chakra-ui/icons'; import { FiPlus } from 'react-icons/fi'; import { getPolls, createPoll, updatePoll, Poll, CreatePollRequest } from '../../services/polls'; interface PollLinkerProps { articleId?: number; eventId?: number; onPollsChanged?: () => void; } /** * PollLinker - Component to manage poll associations with articles/events * Can be embedded in article and activity admin pages */ const PollLinker: React.FC = ({ articleId, eventId, onPollsChanged }) => { const toast = useToast(); const queryClient = useQueryClient(); const [isExpanded, setIsExpanded] = useState(false); const [selectedPollId, setSelectedPollId] = useState(''); const [showCreateForm, setShowCreateForm] = useState(false); // Poll creation form state const [newPollData, setNewPollData] = useState({ title: '', description: '', type: 'single', status: 'active', allow_multiple: false, max_choices: 1, show_results: 'after_vote', require_auth: false, allow_guest_vote: true, featured: false, options: [ { text: '', display_order: 0 }, { text: '', display_order: 1 }, ], }); const bgBox = useColorModeValue('gray.50', 'gray.700'); const borderColor = useColorModeValue('gray.200', 'gray.600'); // Query for existing polls const queryParams = articleId ? { article_id: articleId } : eventId ? { event_id: eventId } : {}; const { data: linkedPolls, isLoading: isLoadingLinked } = useQuery({ queryKey: ['linked-polls', queryParams], queryFn: () => getPolls(queryParams), enabled: !!(articleId || eventId), }); // Query for all available polls const { data: allPolls, isLoading: isLoadingAll } = useQuery({ queryKey: ['all-admin-polls'], queryFn: () => getPolls({ status: 'active' }), }); // Mutation to link existing poll const linkPollMutation = useMutation({ mutationFn: async (pollId: number) => { const updateData: any = {}; if (articleId) updateData.related_article_id = articleId; if (eventId) updateData.related_event_id = eventId; return updatePoll(pollId, updateData); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['linked-polls'] }); queryClient.invalidateQueries({ queryKey: ['all-admin-polls'] }); toast({ title: 'Anketa propojena', status: 'success', duration: 3000, }); setSelectedPollId(''); if (onPollsChanged) onPollsChanged(); }, onError: (error: any) => { toast({ title: 'Chyba', description: error.response?.data?.error || 'Nepodařilo se propojit anketu', status: 'error', duration: 5000, }); }, }); // Mutation to unlink poll const unlinkPollMutation = useMutation({ mutationFn: async (pollId: number) => { const updateData: any = {}; if (articleId) updateData.related_article_id = null; if (eventId) updateData.related_event_id = null; return updatePoll(pollId, updateData); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['linked-polls'] }); queryClient.invalidateQueries({ queryKey: ['all-admin-polls'] }); toast({ title: 'Anketa odpojena', status: 'success', duration: 3000, }); if (onPollsChanged) onPollsChanged(); }, onError: (error: any) => { toast({ title: 'Chyba', description: error.response?.data?.error || 'Nepodařilo se odpojit anketu', status: 'error', duration: 5000, }); }, }); // Mutation to create new poll const createPollMutation = useMutation({ mutationFn: async (pollData: CreatePollRequest) => { // Add relation to article or event const createData = { ...pollData }; if (articleId) createData.related_article_id = articleId; if (eventId) createData.related_event_id = eventId; return createPoll(createData); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['linked-polls'] }); queryClient.invalidateQueries({ queryKey: ['all-admin-polls'] }); toast({ title: 'Anketa vytvořena a propojena', status: 'success', duration: 3000, }); setShowCreateForm(false); resetNewPollForm(); if (onPollsChanged) onPollsChanged(); }, onError: (error: any) => { toast({ title: 'Chyba při vytváření ankety', description: error.response?.data?.error || 'Nepodařilo se vytvořit anketu', status: 'error', duration: 5000, }); }, }); const handleLinkPoll = () => { if (!selectedPollId) { toast({ title: 'Vyberte anketu', description: 'Prosím vyberte anketu ze seznamu', status: 'warning', duration: 3000, }); return; } linkPollMutation.mutate(parseInt(selectedPollId)); }; const handleUnlinkPoll = (pollId: number) => { if (window.confirm('Opravdu chcete odpojit tuto anketu?')) { unlinkPollMutation.mutate(pollId); } }; const resetNewPollForm = () => { setNewPollData({ title: '', description: '', type: 'single', status: 'active', allow_multiple: false, max_choices: 1, show_results: 'after_vote', require_auth: false, allow_guest_vote: true, featured: false, options: [ { text: '', display_order: 0 }, { text: '', display_order: 1 }, ], }); }; const handleCreatePoll = () => { // Validate form if (!newPollData.title.trim()) { toast({ title: 'Chybí název ankety', status: 'warning', duration: 3000, }); return; } const validOptions = newPollData.options.filter(opt => opt.text.trim()); if (validOptions.length < 2) { toast({ title: 'Anketa musí mít alespoň 2 možnosti', status: 'warning', duration: 3000, }); return; } // Submit with only valid options createPollMutation.mutate({ ...newPollData, options: validOptions, }); }; const addOption = () => { setNewPollData(prev => ({ ...prev, options: [...prev.options, { text: '', display_order: prev.options.length }], })); }; const removeOption = (index: number) => { if (newPollData.options.length <= 2) { toast({ title: 'Anketa musí mít alespoň 2 možnosti', status: 'warning', duration: 2000, }); return; } setNewPollData(prev => ({ ...prev, options: prev.options.filter((_, i) => i !== index).map((opt, idx) => ({ ...opt, display_order: idx })), })); }; const updateOption = (index: number, text: string) => { setNewPollData(prev => ({ ...prev, options: prev.options.map((opt, i) => i === index ? { ...opt, text } : opt), })); }; // Filter out polls that are already linked const linkedPollIds = new Set(linkedPolls?.map(p => p.id) || []); const availablePolls = allPolls?.filter(p => !linkedPollIds.has(p.id)) || []; if (!articleId && !eventId) { return null; } return ( Ankety ({linkedPolls?.length || 0}) {(linkedPolls?.length || 0) > 0 && ( {linkedPolls!.length} připojeno )} : } size="sm" variant="ghost" onClick={() => setIsExpanded(!isExpanded)} /> {isLoadingLinked ? ( Načítání anket... ) : linkedPolls && linkedPolls.length > 0 ? ( Připojené ankety: {linkedPolls.map((poll) => ( {poll.title} {poll.status} {poll.total_votes} hlasů } size="sm" colorScheme="red" variant="ghost" onClick={() => handleUnlinkPoll(poll.id)} isLoading={unlinkPollMutation.isPending} /> ))} ) : ( Žádné ankety nejsou připojeny )} Propojit existující Vytvořit novou {/* Tab 1: Link existing poll */} {isLoadingAll ? ( ) : availablePolls.length > 0 ? ( ) : ( Žádné dostupné ankety. Vytvořte novou v druhé záložce. )} {/* Tab 2: Create new poll */} Název ankety setNewPollData(prev => ({ ...prev, title: e.target.value }))} /> Popis (volitelné)