Initial commit
This commit is contained in:
12
resources/js/stores/contestRefreshStore.tsx
Normal file
12
resources/js/stores/contestRefreshStore.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
type ContestRefreshStore = {
|
||||
refreshKey: number;
|
||||
triggerRefresh: () => void;
|
||||
};
|
||||
|
||||
export const useContestRefreshStore = create<ContestRefreshStore>((set) => ({
|
||||
refreshKey: 0,
|
||||
triggerRefresh: () =>
|
||||
set((s) => ({ refreshKey: s.refreshKey + 1 })),
|
||||
}));
|
||||
87
resources/js/stores/contestStore.tsx
Normal file
87
resources/js/stores/contestStore.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export type ContestSummary = {
|
||||
id: number;
|
||||
name: string; // už přeložené jméno podle locale
|
||||
description?: string | null;
|
||||
is_active: boolean;
|
||||
is_test: boolean;
|
||||
is_mcr: boolean;
|
||||
is_sixhr: boolean;
|
||||
start_time: string | null; // ISO string z API
|
||||
duration: number; // hodiny
|
||||
logs_deadline_days: number;
|
||||
rounds?: RoundSummary[];
|
||||
};
|
||||
|
||||
export type RoundSummary = {
|
||||
id: number;
|
||||
contest_id: number;
|
||||
name: string;
|
||||
description?: string | null;
|
||||
bands?: { id: number; name: string }[];
|
||||
categories?: { id: number; name: string }[];
|
||||
power_categories?: { id: number; name: string }[];
|
||||
is_active: boolean;
|
||||
is_test: boolean;
|
||||
is_sixhr: boolean;
|
||||
start_time: string | null;
|
||||
end_time: string | null;
|
||||
logs_deadline: string | null;
|
||||
preliminary_evaluation_run_id?: number | null;
|
||||
official_evaluation_run_id?: number | null;
|
||||
test_evaluation_run_id?: number | null;
|
||||
};
|
||||
|
||||
type ContestState = {
|
||||
selectedContest: ContestSummary | null;
|
||||
selectedRound: RoundSummary | null;
|
||||
selectedContestRounds: RoundSummary[];
|
||||
setSelectedContest: (contest: ContestSummary | null) => void;
|
||||
setSelectedRound: (round: RoundSummary | null) => void;
|
||||
setSelectedContestRounds: (rounds: RoundSummary[]) => void;
|
||||
clearSelection: () => void;
|
||||
};
|
||||
|
||||
export const useContestStore = create<ContestState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
selectedContest: null,
|
||||
selectedRound: null,
|
||||
selectedContestRounds: [],
|
||||
setSelectedContest: (contest) =>
|
||||
set({
|
||||
selectedContest: contest,
|
||||
// při přepnutí závodu implicitně zruš vybrané kolo
|
||||
selectedRound: null,
|
||||
selectedContestRounds: contest?.rounds ?? [],
|
||||
}),
|
||||
setSelectedRound: (round) => set({ selectedRound: round }),
|
||||
setSelectedContestRounds: (rounds) => set({ selectedContestRounds: rounds }),
|
||||
clearSelection: () =>
|
||||
set({
|
||||
selectedContest: null,
|
||||
selectedRound: null,
|
||||
selectedContestRounds: [],
|
||||
}),
|
||||
}),
|
||||
{
|
||||
name: 'contest-store',
|
||||
partialize: (state) => ({
|
||||
selectedContest: state.selectedContest,
|
||||
selectedRound: state.selectedRound,
|
||||
selectedContestRounds: state.selectedContestRounds,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Použití:
|
||||
// import { useContestStore } from '@/stores/contestStore';
|
||||
// const selectedContest = useContestStore((s) => s.selectedContest);
|
||||
// const selectedRound = useContestStore((s) => s.selectedRound);
|
||||
// const setSelectedContest = useContestStore((s) => s.setSelectedContest);
|
||||
// const setSelectedRound = useContestStore((s) => s.setSelectedRound);
|
||||
// po kliknutí v seznamu závodů:
|
||||
//setSelectedContest(contestFromApi);
|
||||
69
resources/js/stores/languageStore.tsx
Normal file
69
resources/js/stores/languageStore.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export type Locale = 'cs' | 'en';
|
||||
|
||||
type LanguageState = {
|
||||
locale: Locale;
|
||||
setLocale: (locale: Locale) => void;
|
||||
};
|
||||
|
||||
const LOCALE_COOKIE = 'locale';
|
||||
|
||||
function detectInitialLocale(): Locale {
|
||||
if (typeof document !== 'undefined') {
|
||||
// 1) cookie
|
||||
const cookieMatch = document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith(`${LOCALE_COOKIE}=`));
|
||||
if (cookieMatch) {
|
||||
const value = cookieMatch.split('=')[1];
|
||||
if (value === 'cs' || value === 'en') return value;
|
||||
}
|
||||
|
||||
// 2) <html lang="...">
|
||||
const htmlLang = document.documentElement.lang;
|
||||
if (htmlLang.startsWith('cs')) return 'cs';
|
||||
if (htmlLang.startsWith('en')) return 'en';
|
||||
}
|
||||
|
||||
return 'cs';
|
||||
}
|
||||
|
||||
function setLocaleCookie(locale: Locale) {
|
||||
const expires = new Date();
|
||||
expires.setFullYear(expires.getFullYear() + 1);
|
||||
|
||||
document.cookie = [
|
||||
`${LOCALE_COOKIE}=${encodeURIComponent(locale)}`,
|
||||
`expires=${expires.toUTCString()}`,
|
||||
'path=/',
|
||||
'SameSite=Lax',
|
||||
].join('; ');
|
||||
}
|
||||
|
||||
export const useLanguageStore = create<LanguageState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
locale: detectInitialLocale(),
|
||||
setLocale: (locale) => {
|
||||
set({ locale });
|
||||
|
||||
// sync s DOM a Laravel
|
||||
if (typeof document !== 'undefined') {
|
||||
document.documentElement.lang = locale;
|
||||
setLocaleCookie(locale);
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'language-store',
|
||||
partialize: (state) => ({ locale: state.locale }),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Použití:
|
||||
// import { useLanguageStore } from '@/stores/languageStore';
|
||||
// const locale = useLanguageStore((s) => s.locale);
|
||||
// const setLocale = useLanguageStore((s) => s.setLocale);
|
||||
37
resources/js/stores/userStore.tsx
Normal file
37
resources/js/stores/userStore.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export type User = {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
is_admin?: boolean;
|
||||
is_active?: boolean;
|
||||
// případně další pole: roles, callsign, atd.
|
||||
};
|
||||
|
||||
type UserState = {
|
||||
user: User | null;
|
||||
setUser: (user: User | null) => void;
|
||||
clearUser: () => void;
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const useUserStore = create<UserState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
user: null,
|
||||
setUser: (user) => set({ user }),
|
||||
clearUser: () => set({ user: null }),
|
||||
}),
|
||||
{ name: 'user-store' }
|
||||
)
|
||||
);
|
||||
|
||||
// použití:
|
||||
// import { useUserStore } from '@/stores/userStore';
|
||||
// const user = useUserStore((s) => s.user); // získání přihlášeného uživatele
|
||||
// const isAuthenticated = !!user;
|
||||
// const setUser = useUserStore((s) => s.setUser);
|
||||
// const clearUser = useUserStore((s) => s.clearUser);
|
||||
Reference in New Issue
Block a user