mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
196 lines
6.3 KiB
TypeScript
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;
|
|
|