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:
@@ -1,9 +1,11 @@
|
||||
import { useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { X, Send, Pencil, Calendar, User as UserIcon, Users, FolderOpen, CheckCircle2, ArrowLeft } from 'lucide-react';
|
||||
import { X, Send, Pencil, Calendar, User as UserIcon, Users, FolderOpen, CheckCircle2, ArrowLeft, Check, Clock } from 'lucide-react';
|
||||
import { tasksApi } from '@/services/tasks.api';
|
||||
import { settingsApi } from '@/services/settings.api';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { useNotificationStore } from '@/store/notificationStore';
|
||||
import { useSnoozeOptions, calculateSnoozeMinutes } from '@/hooks/useSnoozeOptions';
|
||||
import type { Task } from '@/types';
|
||||
import { Button, Badge, Textarea, Select } from '@/components/ui';
|
||||
import { TaskForm } from './TaskForm';
|
||||
@@ -22,13 +24,24 @@ interface TaskDetailProps {
|
||||
taskId: string;
|
||||
onClose: () => void;
|
||||
onEdit?: (task: Task) => void; // Optional - ak nie je, použije sa interný edit mód
|
||||
notificationId?: string; // Ak je detail otvorený z notifikácie
|
||||
}
|
||||
|
||||
export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
|
||||
export function TaskDetail({ taskId, onClose, notificationId }: TaskDetailProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const { user } = useAuthStore();
|
||||
const [newComment, setNewComment] = useState('');
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [snoozeOpen, setSnoozeOpen] = useState(false);
|
||||
|
||||
// Notifikácie - len ak bol detail otvorený z notifikácie
|
||||
const { notifications, markAsRead, snooze } = useNotificationStore();
|
||||
const snoozeOptions = useSnoozeOptions();
|
||||
|
||||
// Konkrétna notifikácia ak existuje
|
||||
const notification = notificationId
|
||||
? notifications.find((n) => n.id === notificationId && !n.isRead)
|
||||
: null;
|
||||
|
||||
const { data: taskData, isLoading } = useQuery({
|
||||
queryKey: ['task', taskId],
|
||||
@@ -56,6 +69,10 @@ export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
|
||||
queryClient.invalidateQueries({ queryKey: ['task-comments', taskId] });
|
||||
setNewComment('');
|
||||
toast.success('Komentár bol pridaný');
|
||||
// Označiť notifikáciu ako prečítanú ak existuje
|
||||
if (notificationId) {
|
||||
markAsRead(notificationId);
|
||||
}
|
||||
},
|
||||
onError: (error: unknown) => {
|
||||
const axiosError = error as { response?: { data?: { message?: string } } };
|
||||
@@ -70,6 +87,10 @@ export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
|
||||
queryClient.invalidateQueries({ queryKey: ['tasks'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['dashboard-today'] });
|
||||
toast.success('Úloha bola aktualizovaná');
|
||||
// Označiť notifikáciu ako prečítanú ak existuje
|
||||
if (notificationId) {
|
||||
markAsRead(notificationId);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Chyba pri aktualizácii úlohy');
|
||||
@@ -191,6 +212,47 @@ export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Tlačidlá pre notifikáciu */}
|
||||
{notification && (
|
||||
<>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => markAsRead(notification.id)}
|
||||
title="Označiť ako prečítané"
|
||||
>
|
||||
<Check className="h-4 w-4 mr-1" />
|
||||
Prečítané
|
||||
</Button>
|
||||
<div className="relative">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setSnoozeOpen(!snoozeOpen)}
|
||||
title="Odložiť notifikáciu"
|
||||
>
|
||||
<Clock className="h-4 w-4 mr-1" />
|
||||
Odložiť
|
||||
</Button>
|
||||
{snoozeOpen && (
|
||||
<div className="absolute right-0 top-full mt-1 bg-popover border rounded-md shadow-lg z-20 min-w-[140px]">
|
||||
{snoozeOptions.map((option) => (
|
||||
<button
|
||||
key={option.label}
|
||||
onClick={() => {
|
||||
snooze(notification.id, calculateSnoozeMinutes(option));
|
||||
setSnoozeOpen(false);
|
||||
}}
|
||||
className="w-full px-3 py-1.5 text-left text-sm hover:bg-accent first:rounded-t-md last:rounded-b-md"
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{canEdit && (
|
||||
<Button variant="outline" size="sm" onClick={() => setIsEditing(true)}>
|
||||
<Pencil className="h-4 w-4 mr-1" />
|
||||
|
||||
Reference in New Issue
Block a user