Files
MyClub/frontend/src/components/polls/EmbeddedPoll.tsx
T
Tomas Dvorak f5b6f83974 dev day #99
2025-11-21 08:44:44 +01:00

196 lines
6.3 KiB
TypeScript

import React from 'react';
import {
Box,
VStack,
Heading,
Spinner,
Text,
useColorModeValue,
SimpleGrid,
} from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { getPolls, getPoll } from '../../services/polls';
import PollCard from './PollCard';
interface EmbeddedPollProps {
articleId?: number;
eventId?: number;
videoUrl?: string;
title?: string;
showTitle?: boolean;
maxPolls?: number;
// When true, render without outer background/padding so parent wrapper controls layout
unstyled?: boolean;
}
/**
* EmbeddedPoll component - displays polls related to specific content
* Use in article pages, event pages, or video pages
*/
const EmbeddedPoll: React.FC<EmbeddedPollProps> = ({
articleId,
eventId,
videoUrl,
title = 'Hlasování',
showTitle = true,
maxPolls,
unstyled = false,
}) => {
const bgSection = useColorModeValue('gray.50', 'gray.900');
// Build query params based on what's provided
const queryParams: any = {};
if (articleId) queryParams.article_id = articleId;
if (eventId) queryParams.event_id = eventId;
if (videoUrl) queryParams.video_url = videoUrl;
// Fetch polls related to this content
const { data: polls, isLoading } = useQuery({
queryKey: ['embedded-polls', queryParams],
queryFn: () => getPolls(queryParams),
enabled: !!(articleId || eventId || videoUrl), // Only fetch if at least one param is provided
staleTime: 2 * 60 * 1000,
});
// Get full poll data for each (all linked polls)
const pollsToDisplay = polls || [];
const preSortedLimited = React.useMemo(() => {
const sorted = [...pollsToDisplay].sort((a, b) => {
const aRating = a.type === 'rating' ? 1 : 0;
const bRating = b.type === 'rating' ? 1 : 0;
if (aRating !== bRating) return bRating - aRating;
const aFeat = a.featured ? 1 : 0;
const bFeat = b.featured ? 1 : 0;
if (aFeat !== bFeat) return bFeat - aFeat;
const aDate = new Date(a.created_at).getTime();
const bDate = new Date(b.created_at).getTime();
return bDate - aDate;
});
return typeof maxPolls === 'number' ? sorted.slice(0, maxPolls) : sorted;
}, [pollsToDisplay, maxPolls]);
const { data: pollsData, isLoading: isLoadingPolls } = useQuery({
queryKey: ['embedded-polls-details', preSortedLimited.map((p) => p.id)],
queryFn: async () => {
const promises = preSortedLimited.map((poll) => getPoll(poll.id));
return await Promise.all(promises);
},
enabled: preSortedLimited.length > 0,
});
// Don't render anything if no content identifier provided
if (!articleId && !eventId && !videoUrl) {
return null;
}
// Don't render if loading initially
if (isLoading) {
return (
<Box py={4}>
<VStack spacing={2}>
<Spinner size="sm" />
<Text fontSize="sm" color="gray.500">
Načítání hlasování...
</Text>
</VStack>
</Box>
);
}
// Don't render if no polls found
if (!polls || polls.length === 0 || !pollsData || pollsData.length === 0) {
return null;
}
// Wrapper styling: allow transparent/compact when unstyled
const wrapperProps = unstyled
? { bg: 'transparent', py: 0, px: 0, borderRadius: 'none' as any, my: 0 }
: { bg: bgSection, py: 8, px: 4, borderRadius: 'xl' as any, my: 8 };
return (
<Box {...wrapperProps}>
<VStack spacing={6} maxW="6xl" mx="auto">
{showTitle && (
<Heading size="md" textAlign="center">
{title}
</Heading>
)}
{isLoadingPolls ? (
<VStack py={8}>
<Spinner />
<Text>Načítání...</Text>
</VStack>
) : (
(() => {
// Sort: rating first, then featured, then newest
const sorted = [...(pollsData || [])].sort((a, b) => {
const aRating = a.poll.type === 'rating' ? 1 : 0;
const bRating = b.poll.type === 'rating' ? 1 : 0;
if (aRating !== bRating) return bRating - aRating;
const aFeat = a.poll.featured ? 1 : 0;
const bFeat = b.poll.featured ? 1 : 0;
if (aFeat !== bFeat) return bFeat - aFeat;
const aDate = new Date(a.poll.created_at).getTime();
const bDate = new Date(b.poll.created_at).getTime();
return bDate - aDate;
});
const limited = typeof maxPolls === 'number' ? sorted.slice(0, maxPolls) : sorted;
const count = limited.length;
if (count === 1) {
const pollResponse = limited[0];
return (
<Box w="full">
<PollCard
poll={pollResponse.poll}
hasVoted={pollResponse.has_voted}
isActive={pollResponse.is_active}
canShowResults={pollResponse.can_show_results}
flat={unstyled}
/>
</Box>
);
}
if (count === 2) {
return (
<SimpleGrid w="full" columns={{ base: 1, md: 2 }} spacing={4}>
{limited.map((pollResponse) => (
<Box key={pollResponse.poll.id}>
<PollCard
poll={pollResponse.poll}
hasVoted={pollResponse.has_voted}
isActive={pollResponse.is_active}
canShowResults={pollResponse.can_show_results}
flat={unstyled}
/>
</Box>
))}
</SimpleGrid>
);
}
return (
<SimpleGrid w="full" columns={{ base: 1, sm: 2, lg: 3 }} spacing={4}>
{limited.map((pollResponse) => (
<Box key={pollResponse.poll.id}>
<PollCard
poll={pollResponse.poll}
hasVoted={pollResponse.has_voted}
isActive={pollResponse.is_active}
canShowResults={pollResponse.can_show_results}
flat={unstyled}
/>
</Box>
))}
</SimpleGrid>
);
})()
)}
</VStack>
</Box>
);
};
export default EmbeddedPoll;