Správa používateľov + notifikačný systém

- Pridaná kompletná správa používateľov (CRUD, reset hesla, zmena roly) pre ROOT/ADMIN
- Backend: POST /users endpoint, createUser controller, validácia
- Frontend: UserManagement, UserForm, PasswordResetModal komponenty
- Settings prístupné pre ROOT aj ADMIN (AdminRoute)
- Notifikačný systém s snooze funkcionalitou
- Aktualizácia HELPDESK_INIT_V2.md dokumentácie

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 15:30:27 +01:00
parent cbdd952bc1
commit 2ca0c4f4d8
36 changed files with 3116 additions and 522 deletions

View File

@@ -0,0 +1,82 @@
import { useQuery } from '@tanstack/react-query';
import { settingsApi } from '@/services/settings.api';
export interface SnoozeOption {
label: string;
minutes?: number; // Relatívny čas v minútach (ak nie je type)
type?: 'tomorrow' | 'today'; // Typ špeciálneho odloženia
hour?: number; // Hodina dňa pre type 'tomorrow' alebo 'today' (0-23)
}
const DEFAULT_SNOOZE_OPTIONS: SnoozeOption[] = [
{ label: '30 minút', minutes: 30 },
{ label: '1 hodina', minutes: 60 },
{ label: '3 hodiny', minutes: 180 },
{ label: 'Zajtra ráno', type: 'tomorrow', hour: 9 },
];
export function useSnoozeOptions() {
const { data: settings } = useQuery({
queryKey: ['system-settings'],
queryFn: () => settingsApi.getSystemSettings(),
staleTime: 1000 * 60, // Cache for 1 minute
refetchOnWindowFocus: true, // Obnoviť pri prepnutí okna/tabu
});
const snoozeSettings = settings?.data?.find(
(s) => s.key === 'NOTIFICATION_SNOOZE_OPTIONS'
);
const options: SnoozeOption[] = snoozeSettings?.value as SnoozeOption[] || DEFAULT_SNOOZE_OPTIONS;
// Filtrovať neplatné možnosti
const filteredOptions = options.filter(option => {
// Špeciálne časové možnosti (type) vyžadujú definovanú hodinu
if (option.type && option.hour === undefined) {
return false;
}
// "Dnes" možnosti - nezobrazovať ak čas už prešiel
if (option.type === 'today' && option.hour !== undefined) {
const now = new Date();
return now.getHours() < option.hour;
}
// Relatívne možnosti musia mať minutes > 0
if (!option.type && (!option.minutes || option.minutes <= 0)) {
return false;
}
return true;
});
return filteredOptions;
}
/**
* Výpočet minút pre odloženie
* - Ak minutes: vráti priamo hodnotu minutes (relatívny čas)
* - Ak type === 'tomorrow': zajtra o zadanej hodine
* - Ak type === 'today': dnes o zadanej hodine
*/
export function calculateSnoozeMinutes(option: SnoozeOption): number {
const { minutes, type, hour = 9 } = option;
if (type === 'tomorrow') {
// Zajtra o zadanej hodine
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(hour, 0, 0, 0);
return Math.ceil((tomorrow.getTime() - Date.now()) / 60000);
}
if (type === 'today') {
// Dnes o zadanej hodine
const target = new Date();
target.setHours(hour, 0, 0, 0);
return Math.ceil((target.getTime() - Date.now()) / 60000);
}
// Relatívny čas v minútach
return minutes || 0;
}