This commit is contained in:
Tomas Dvorak
2025-10-24 14:52:46 +02:00
parent 70ea0c3c91
commit 8a7c292e54
41 changed files with 912 additions and 404 deletions
+12 -2
View File
@@ -76,8 +76,18 @@ const AuthPage: React.FC = () => {
duration: 3000,
isClosable: true,
});
// Redirect to original destination or dashboard
navigate(from, { replace: true });
// Role-based redirect after login
const role = String(user?.role || '').toLowerCase();
if (role === 'admin') {
navigate('/admin', { replace: true });
} else if (role === 'editor') {
navigate('/admin', { replace: true });
} else if (role === 'user') {
navigate('/', { replace: true });
} else {
// Fallback for unknown roles (e.g., fan): go to frontpage
navigate('/', { replace: true });
}
} catch (error: any) {
toast({
title: 'Přihlášení selhalo',
+16 -11
View File
@@ -1580,10 +1580,19 @@ const HomePage: React.FC = () => {
(matchingStanding.rows && matchingStanding.rows.length > 0)
);
const showNews = isVisible('news', true);
const showTable = isVisible('table', true) && hasStandingsForCurrentTab;
const variant = showNews && showTable ? undefined : 'standard';
if (!showNews && !showTable) return null;
return (
<>
{isVisible('news', true) && (
<section data-element="news" className="news-list" style={{ marginTop: 32, ...getStyles('news') }}>
<section
className="standings"
data-variant={variant}
style={{ marginTop: 32 }}
>
{showNews && (
<section data-element="news" className="news-list" style={{ ...getStyles('news') }}>
<div className="section-head" style={{ marginTop: 0 }}>
<h3>Další aktuality</h3>
</div>
@@ -1610,12 +1619,8 @@ const HomePage: React.FC = () => {
</section>
)}
{isVisible('table', true) && hasStandingsForCurrentTab && (
<section
data-element="table"
className="standings"
style={{ marginTop: 32, ...getStyles('table') }}
>
{showTable && (
<div data-element="table" style={{ ...getStyles('table') }}>
<div className="table-card">
<div className="section-head" style={{ marginTop: 0, marginBottom: 12 }}>
<h3>Tabulky</h3>
@@ -1699,9 +1704,9 @@ const HomePage: React.FC = () => {
</table>
</div>
</div>
</section>
</div>
)}
</>
</section>
);
})()}
+1 -1
View File
@@ -11,7 +11,7 @@ const OverlayScoreboardPage: React.FC = () => {
const { data, isLoading } = useQuery<ScoreboardState>({
queryKey: ['public-scoreboard'],
queryFn: getPublicScoreboard,
refetchInterval: 5000,
refetchInterval: 1000,
staleTime: 3000,
});
+29 -1
View File
@@ -92,6 +92,7 @@ const PollsAdminPage: React.FC = () => {
title: '',
description: '',
type: 'single',
style: 'auto',
status: 'draft',
allow_multiple: false,
max_choices: 1,
@@ -191,6 +192,7 @@ const PollsAdminPage: React.FC = () => {
title: '',
description: '',
type: 'single',
style: 'auto',
status: 'draft',
allow_multiple: false,
max_choices: 1,
@@ -221,6 +223,7 @@ const PollsAdminPage: React.FC = () => {
title: 'Hodnocení zápasu',
description: 'Ohodnoťte zápas (1 = nejhorší, 5 = nejlepší)',
type: 'rating',
style: 'rating-stars',
status: 'active',
allow_multiple: false,
max_choices: 1,
@@ -239,6 +242,7 @@ const PollsAdminPage: React.FC = () => {
title: 'Hodnocení zápasu (110)',
description: 'Ohodnoťte zápas (1 = nejhorší, 10 = nejlepší)',
type: 'rating',
style: 'rating-scale',
status: 'active',
allow_multiple: false,
max_choices: 1,
@@ -253,6 +257,7 @@ const PollsAdminPage: React.FC = () => {
title: 'Dorazíš na schůzku?',
description: 'Dej nám vědět, zda dorazíš.',
type: 'single',
style: 'choices-chips',
status: 'active',
allow_multiple: false,
max_choices: 1,
@@ -276,6 +281,7 @@ const PollsAdminPage: React.FC = () => {
title: poll.title,
description: poll.description,
type: poll.type,
style: (poll as any).style || 'auto',
status: poll.status,
start_date: poll.start_date,
end_date: poll.end_date,
@@ -566,7 +572,7 @@ const PollsAdminPage: React.FC = () => {
/>
</FormControl>
<SimpleGrid columns={2} spacing={4} w="full">
<SimpleGrid columns={3} spacing={4} w="full">
<FormControl>
<FormLabel>Typ</FormLabel>
<Select
@@ -595,6 +601,28 @@ const PollsAdminPage: React.FC = () => {
<option value="archived">Archivovaná</option>
</Select>
</FormControl>
<FormControl>
<FormLabel>Styl</FormLabel>
<Select
value={(formData as any).style || 'auto'}
onChange={(e) => setFormData({ ...formData, style: e.target.value as any })}
>
<option value="auto">Automaticky</option>
{formData.type === 'rating' ? (
<>
<option value="rating-stars">Hvězdičky</option>
<option value="rating-scale">Číselná stupnice</option>
</>
) : (
<>
<option value="choices-list">Seznam</option>
<option value="choices-chips">Štítky</option>
<option value="choices-cards">Karty</option>
</>
)}
</Select>
</FormControl>
</SimpleGrid>
<SimpleGrid columns={2} spacing={4} w="full">
@@ -39,6 +39,8 @@ import {
startTimer,
pauseTimer,
resetTimer,
swapSides,
startSecondHalf,
} from '@/services/scoreboard';
import { useFacrApi } from '@/hooks/useFacrApi';
import { SearchResult } from '@/services/facr/types';
@@ -434,6 +436,18 @@ const ScoreboardAdminPage: React.FC = () => {
<NumberInputField />
</NumberInput>
</FormControl>
<FormControl>
<FormLabel>Fauly domácích</FormLabel>
<NumberInput value={state.homeFouls || 0} min={0} max={5} onChange={async (_, n) => setPartial({ homeFouls: Math.max(0, Math.min(5, Number.isFinite(n) ? n : 0)) })}>
<NumberInputField />
</NumberInput>
</FormControl>
<FormControl>
<FormLabel>Fauly hostů</FormLabel>
<NumberInput value={state.awayFouls || 0} min={0} max={5} onChange={async (_, n) => setPartial({ awayFouls: Math.max(0, Math.min(5, Number.isFinite(n) ? n : 0)) })}>
<NumberInputField />
</NumberInput>
</FormControl>
<FormControl>
<FormLabel>Délka poločasu (min)</FormLabel>
<NumberInput value={state.halfLength} min={1} max={60} onChange={async (_, n) => setPartial({ halfLength: Number.isFinite(n) ? n : 45 })}>
@@ -448,6 +462,17 @@ const ScoreboardAdminPage: React.FC = () => {
))}
</Select>
</FormControl>
<FormControl display="flex" alignItems="center">
<FormLabel mb={0}>Přehodit strany (vizuálně)</FormLabel>
<Switch isChecked={!!state.sidesFlipped} onChange={async (e) => setPartial({ sidesFlipped: e.target.checked })} />
</FormControl>
<FormControl>
<FormLabel>Poločas</FormLabel>
<Select value={String(state.half || 1)} onChange={async (e) => setPartial({ half: parseInt(e.target.value, 10) || 1 })}>
<option value="1">1</option>
<option value="2">2</option>
</Select>
</FormControl>
</SimpleGrid>
</Box>
@@ -462,6 +487,18 @@ const ScoreboardAdminPage: React.FC = () => {
<FormLabel>Barva hostů</FormLabel>
<Input type="color" value={state.secondaryColor || '#2563eb'} onChange={async (e) => setPartial({ secondaryColor: e.target.value })} />
</FormControl>
<FormControl>
<FormLabel>QR interval (minuty)</FormLabel>
<NumberInput value={state.qrEvery || 5} min={1} max={120} onChange={async (_, n) => setPartial({ qrEvery: Math.max(1, Number.isFinite(n) ? n : 5) })}>
<NumberInputField />
</NumberInput>
</FormControl>
<FormControl>
<FormLabel>QR délka zobrazení (sekundy)</FormLabel>
<NumberInput value={state.qrDuration || 60} min={5} max={600} onChange={async (_, n) => setPartial({ qrDuration: Math.max(5, Number.isFinite(n) ? n : 60) })}>
<NumberInputField />
</NumberInput>
</FormControl>
</SimpleGrid>
<Divider my={4} />
@@ -528,6 +565,16 @@ const ScoreboardAdminPage: React.FC = () => {
const s = await getScoreboardState();
setState(s);
}}>Reset</Button>
<Button onClick={async () => {
await swapSides();
const s = await getScoreboardState();
setState(s);
}}>Přehodit strany</Button>
<Button colorScheme="purple" onClick={async () => {
await startSecondHalf();
const s = await getScoreboardState();
setState(s);
}}>Začít 2. poločas</Button>
</HStack>
</HStack>
<HStack mt={3} spacing={3} align="center">