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:
@@ -28,7 +28,8 @@
|
||||
|
||||
### **Pridané:**
|
||||
- ✅ Configuration-driven architecture
|
||||
- ✅ ROOT Settings panel
|
||||
- ✅ ROOT/ADMIN Settings panel
|
||||
- ✅ User Management (CRUD, reset hesla, zmena roly)
|
||||
- ✅ External DB import pre zákazníkov
|
||||
- ✅ Dynamic workflow rules
|
||||
- ✅ Multi-entity tagging system
|
||||
@@ -173,6 +174,10 @@ model User {
|
||||
reminders Reminder[]
|
||||
activityLogs ActivityLog[]
|
||||
|
||||
// Comments & Notifications
|
||||
comments Comment[]
|
||||
notifications Notification[]
|
||||
|
||||
// Equipment
|
||||
createdEquipment Equipment[] @relation("EquipmentCreator")
|
||||
performedRevisions Revision[]
|
||||
@@ -511,11 +516,12 @@ model Task {
|
||||
createdById String
|
||||
createdBy User @relation("TaskCreator", fields: [createdById], references: [id])
|
||||
|
||||
assignees TaskAssignee[]
|
||||
reminders Reminder[]
|
||||
comments Comment[]
|
||||
tags TaskTag[]
|
||||
|
||||
assignees TaskAssignee[]
|
||||
reminders Reminder[]
|
||||
comments Comment[]
|
||||
tags TaskTag[]
|
||||
notifications Notification[]
|
||||
|
||||
@@index([projectId])
|
||||
@@index([parentId])
|
||||
@@index([statusId])
|
||||
@@ -572,18 +578,51 @@ model Comment {
|
||||
id String @id @default(cuid())
|
||||
taskId String
|
||||
userId String
|
||||
|
||||
|
||||
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
content String @db.Text
|
||||
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
|
||||
@@index([taskId])
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
// ==================== NOTIFICATIONS ====================
|
||||
|
||||
model Notification {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
type String // TASK_ASSIGNED, TASK_STATUS_CHANGED, TASK_COMMENT, etc.
|
||||
title String
|
||||
message String // Prázdne pre TASK_COMMENT - text sa načíta z Comment tabuľky
|
||||
|
||||
// Odkazy na entity
|
||||
taskId String?
|
||||
task Task? @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
rmaId String?
|
||||
rma RMA? @relation(fields: [rmaId], references: [id], onDelete: Cascade)
|
||||
|
||||
// Dodatočné dáta (JSON) - napr. commentId, actorName, oldStatus, newStatus
|
||||
data Json?
|
||||
|
||||
isRead Boolean @default(false)
|
||||
readAt DateTime?
|
||||
snoozedUntil DateTime? // Odloženie notifikácie
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([userId, isRead])
|
||||
@@index([userId, createdAt])
|
||||
@@index([taskId])
|
||||
@@index([rmaId])
|
||||
}
|
||||
|
||||
// ==================== EQUIPMENT MANAGEMENT ====================
|
||||
|
||||
model Equipment {
|
||||
@@ -760,7 +799,8 @@ model RMA {
|
||||
statusHistory RMAStatusHistory[]
|
||||
comments RMAComment[]
|
||||
tags RMATag[]
|
||||
|
||||
notifications Notification[]
|
||||
|
||||
@@index([rmaNumber])
|
||||
@@index([customerId])
|
||||
@@index([statusId])
|
||||
@@ -910,15 +950,16 @@ POST /api/auth/logout
|
||||
GET /api/auth/me
|
||||
```
|
||||
|
||||
### Users
|
||||
### Users (ROOT/ADMIN)
|
||||
|
||||
```
|
||||
GET /api/users // Stránkovaný zoznam (admin only)
|
||||
GET /api/users/simple // Jednoduchý zoznam pre selecty (server-side search: ?search=meno)
|
||||
POST /api/users // Vytvorenie používateľa (admin only)
|
||||
GET /api/users/:id
|
||||
PUT /api/users/:id
|
||||
DELETE /api/users/:id
|
||||
PATCH /api/users/:id/role
|
||||
PUT /api/users/:id // Úprava + reset hesla
|
||||
DELETE /api/users/:id // Soft delete (deaktivácia)
|
||||
PATCH /api/users/:id/role // Zmena roly
|
||||
```
|
||||
|
||||
### Projects
|
||||
@@ -1004,7 +1045,7 @@ GET /api/rma/:id/pdf // Generate PDF
|
||||
GET /api/rma/generate-number // Next RMA number
|
||||
```
|
||||
|
||||
### **🆕 Settings (ROOT only)**
|
||||
### **🆕 Settings (ROOT/ADMIN)**
|
||||
|
||||
```
|
||||
// Equipment Types
|
||||
@@ -1064,6 +1105,17 @@ PUT /api/settings/roles/:id
|
||||
DELETE /api/settings/roles/:id
|
||||
```
|
||||
|
||||
### **🆕 Notifications**
|
||||
|
||||
```
|
||||
GET /api/notifications // Zoznam notifikácií (limit, offset, unreadOnly)
|
||||
GET /api/notifications/unread-count // Počet neprečítaných
|
||||
POST /api/notifications/:id/read // Označiť ako prečítané
|
||||
POST /api/notifications/mark-all-read // Označiť všetky ako prečítané
|
||||
POST /api/notifications/:id/snooze // Odložiť notifikáciu (minutes)
|
||||
DELETE /api/notifications/:id // Vymazať notifikáciu
|
||||
```
|
||||
|
||||
### Dashboard
|
||||
|
||||
```
|
||||
@@ -1100,6 +1152,9 @@ src/
|
||||
│ │ ├── Sidebar.tsx
|
||||
│ │ └── MainLayout.tsx
|
||||
│ │
|
||||
│ ├── notifications/ # NEW (Fáza 2)
|
||||
│ │ └── NotificationCenter.tsx # Zvonček s dropdown v header
|
||||
│ │
|
||||
│ ├── dashboard/
|
||||
│ │ ├── DashboardView.tsx
|
||||
│ │ ├── TodaysTasks.tsx
|
||||
@@ -1153,6 +1208,9 @@ src/
|
||||
│ │
|
||||
│ ├── settings/ # NEW
|
||||
│ │ ├── SettingsDashboard.tsx
|
||||
│ │ ├── UserManagement.tsx # Správa používateľov (ROOT/ADMIN)
|
||||
│ │ ├── UserForm.tsx # Formulár vytvorenie/editácia
|
||||
│ │ ├── PasswordResetModal.tsx # Reset hesla
|
||||
│ │ ├── EquipmentTypesSettings.tsx
|
||||
│ │ ├── RevisionTypesSettings.tsx
|
||||
│ │ ├── RMAStatusSettings.tsx
|
||||
@@ -1266,9 +1324,9 @@ cd backend && npx prisma db seed
|
||||
|
||||
---
|
||||
|
||||
### **FÁZA 2: Core Features + Workflow** 🔥 (4-5 týždňov)
|
||||
### **FÁZA 2: Core Features + Workflow** 🔥 (4-5 týždňov) - *PREBIEHAJÚCA*
|
||||
|
||||
**Cieľ:** Swimlanes, revízie, RMA workflow, reminders
|
||||
**Cieľ:** Swimlanes, revízie, RMA workflow, reminders, notifikácie
|
||||
|
||||
**Backend:**
|
||||
- [ ] **Revision system**
|
||||
@@ -1284,8 +1342,15 @@ cd backend && npx prisma db seed
|
||||
- [ ] Dashboard aggregations
|
||||
- [ ] Email service (Postfix self-hosted)
|
||||
- [ ] WebSocket (Socket.IO)
|
||||
- [ ] File upload handling
|
||||
- [ ] **Task notifications** (databázové - viditeľné na všetkých zariadeniach)
|
||||
- [x] File upload handling
|
||||
- [x] **Notification system** ✅
|
||||
- [x] Notification model (Prisma)
|
||||
- [x] notification.service.ts - CRUD, enrichment komentárov
|
||||
- [x] notifyTaskComment - ukladá len commentId (žiadna duplicita)
|
||||
- [x] notifyTaskStatusChange - ukladá oldStatus, newStatus, actorName
|
||||
- [x] notifyTaskAssignment
|
||||
- [x] Snooze funkcionalita s konfigurovateľnými možnosťami
|
||||
- [x] SystemSetting NOTIFICATION_SNOOZE_OPTIONS
|
||||
|
||||
**Frontend:**
|
||||
- [ ] **Swimlanes Board** (dnd-kit)
|
||||
@@ -1300,7 +1365,7 @@ cd backend && npx prisma db seed
|
||||
- [ ] **RMA Workflow**
|
||||
- [ ] Status change UI
|
||||
- [ ] Approval buttons (admin)
|
||||
- [ ] File attachments
|
||||
- [x] File attachments
|
||||
- [ ] Comments
|
||||
- [ ] PDF export
|
||||
- [ ] **Inline Quick Actions**
|
||||
@@ -1308,21 +1373,29 @@ cd backend && npx prisma db seed
|
||||
- [ ] Reminder management UI
|
||||
- [ ] Filters & tags
|
||||
- [ ] Real-time updates (WebSocket)
|
||||
- [ ] **Notifikácie o nových komentároch/zmenách** (všetky zariadenia)
|
||||
- [x] **Notification UI** ✅
|
||||
- [x] NotificationCenter komponent (zvonček v header)
|
||||
- [x] Dashboard - prehľadné zobrazenie notifikácií
|
||||
- [x] Typ notifikácie + relatívny čas
|
||||
- [x] Názov úlohy + projekt
|
||||
- [x] Detail zmeny/komentára + autor
|
||||
- [x] markAsRead pri akcii (komentár/zmena stavu)
|
||||
- [x] Snooze dropdown s konfigurovateľnými možnosťami
|
||||
- [x] useSnoozeOptions hook (načíta z SystemSettings)
|
||||
|
||||
**Deliverable:**
|
||||
```
|
||||
✅ Všetko z Fázy 1 +
|
||||
✅ Swimlanes board
|
||||
✅ Revízny systém funguje
|
||||
✅ RMA workflow s approval
|
||||
✅ Email notifikácie
|
||||
✅ Live updates (WebSocket)
|
||||
⏳ Swimlanes board
|
||||
⏳ Revízny systém funguje
|
||||
⏳ RMA workflow s approval
|
||||
⏳ Email notifikácie
|
||||
⏳ Live updates (WebSocket)
|
||||
✅ File uploads
|
||||
✅ Task notifikácie (databázové, všetky zariadenia)
|
||||
```
|
||||
|
||||
**Čas:** 4-5 týždňov
|
||||
**Čas:** 4-5 týždňov
|
||||
**Náklady:** €15-25/mesiac
|
||||
|
||||
---
|
||||
@@ -2059,7 +2132,120 @@ async function getUpcomingRevisions(days: number = 30) {
|
||||
|
||||
---
|
||||
|
||||
### **5. External DB Import (Customer)**
|
||||
### **5. Notification Service (Fáza 2)**
|
||||
|
||||
```typescript
|
||||
// services/notification.service.ts
|
||||
|
||||
export enum NotificationType {
|
||||
TASK_ASSIGNED = 'TASK_ASSIGNED',
|
||||
TASK_STATUS_CHANGED = 'TASK_STATUS_CHANGED',
|
||||
TASK_COMMENT = 'TASK_COMMENT',
|
||||
TASK_DEADLINE_APPROACHING = 'TASK_DEADLINE_APPROACHING',
|
||||
TASK_UPDATED = 'TASK_UPDATED',
|
||||
RMA_ASSIGNED = 'RMA_ASSIGNED',
|
||||
RMA_STATUS_CHANGED = 'RMA_STATUS_CHANGED',
|
||||
RMA_COMMENT = 'RMA_COMMENT',
|
||||
}
|
||||
|
||||
export const notificationService = {
|
||||
// Vytvorenie notifikácie pre viacerých používateľov
|
||||
async createForUsers(userIds: string[], data: {
|
||||
type: NotificationType;
|
||||
title: string;
|
||||
message: string;
|
||||
taskId?: string;
|
||||
rmaId?: string;
|
||||
data?: object; // Dodatočné dáta (commentId, actorName, ...)
|
||||
}) {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
return prisma.notification.createMany({
|
||||
data: userIds.map((userId) => ({
|
||||
userId,
|
||||
type: data.type,
|
||||
title: data.title,
|
||||
message: data.message,
|
||||
taskId: data.taskId,
|
||||
rmaId: data.rmaId,
|
||||
data: data.data || undefined,
|
||||
})),
|
||||
});
|
||||
},
|
||||
|
||||
// Notifikácia o novom komentári - NEUKLADÁ text, len commentId
|
||||
async notifyTaskComment(taskId: string, commentId: string, commentByUserId: string, commentByUserName: string) {
|
||||
const task = await prisma.task.findUnique({
|
||||
where: { id: taskId },
|
||||
include: {
|
||||
assignees: { select: { userId: true } },
|
||||
createdBy: { select: { id: true } },
|
||||
},
|
||||
});
|
||||
|
||||
if (!task) return;
|
||||
|
||||
const userIds = new Set<string>();
|
||||
task.assignees.forEach((a) => userIds.add(a.userId));
|
||||
userIds.add(task.createdById);
|
||||
userIds.delete(commentByUserId); // Nenotifikovať autora
|
||||
|
||||
if (userIds.size === 0) return;
|
||||
|
||||
await this.createForUsers(Array.from(userIds), {
|
||||
type: NotificationType.TASK_COMMENT,
|
||||
title: 'Nový komentár',
|
||||
message: '', // Text sa načíta z Comment tabuľky (žiadna duplicita)
|
||||
taskId: task.id,
|
||||
data: { commentId, actorName: commentByUserName },
|
||||
});
|
||||
},
|
||||
|
||||
// Pri načítaní notifikácií - enrichment TASK_COMMENT
|
||||
async getForUser(userId: string, options?: { limit?: number; offset?: number }) {
|
||||
const rawNotifications = await prisma.notification.findMany({
|
||||
where: { userId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: options?.limit || 50,
|
||||
skip: options?.offset || 0,
|
||||
include: {
|
||||
task: { select: { id: true, title: true, project: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// Pre TASK_COMMENT načítaj text komentára z Comment tabuľky
|
||||
const notifications = await Promise.all(
|
||||
rawNotifications.map(async (notification) => {
|
||||
if (notification.type === 'TASK_COMMENT' && notification.taskId) {
|
||||
const data = notification.data as { commentId?: string } | null;
|
||||
|
||||
if (data?.commentId) {
|
||||
const comment = await prisma.comment.findUnique({
|
||||
where: { id: data.commentId },
|
||||
select: { content: true },
|
||||
});
|
||||
|
||||
if (comment) {
|
||||
const shortComment = comment.content.length > 100
|
||||
? comment.content.substring(0, 100) + '...'
|
||||
: comment.content;
|
||||
|
||||
return { ...notification, message: shortComment };
|
||||
}
|
||||
}
|
||||
}
|
||||
return notification;
|
||||
})
|
||||
);
|
||||
|
||||
return notifications;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **6. External DB Import (Customer)**
|
||||
|
||||
```typescript
|
||||
// services/import.service.ts
|
||||
@@ -2266,6 +2452,7 @@ helpdesk-system/
|
||||
│ │ │ ├── useTasks.ts
|
||||
│ │ │ ├── useEquipment.ts # NEW
|
||||
│ │ │ ├── useRMA.ts # NEW
|
||||
│ │ │ ├── useSnoozeOptions.ts # NEW (Fáza 2) - konfigurovateľné snooze možnosti
|
||||
│ │ │ └── useKeyboard.ts
|
||||
│ │ │
|
||||
│ │ ├── services/
|
||||
@@ -2276,13 +2463,15 @@ helpdesk-system/
|
||||
│ │ │ ├── customers.api.ts # NEW
|
||||
│ │ │ ├── equipment.api.ts # NEW
|
||||
│ │ │ ├── rma.api.ts # NEW
|
||||
│ │ │ └── settings.api.ts # NEW
|
||||
│ │ │ ├── settings.api.ts # NEW
|
||||
│ │ │ └── notification.api.ts # NEW (Fáza 2)
|
||||
│ │ │
|
||||
│ │ ├── store/
|
||||
│ │ │ ├── authStore.ts
|
||||
│ │ │ ├── configStore.ts # NEW
|
||||
│ │ │ ├── projectsStore.ts
|
||||
│ │ │ └── tasksStore.ts
|
||||
│ │ │ ├── tasksStore.ts
|
||||
│ │ │ └── notificationStore.ts # NEW (Fáza 2)
|
||||
│ │ │
|
||||
│ │ ├── types/
|
||||
│ │ ├── styles/
|
||||
@@ -2528,6 +2717,7 @@ CELKOM: ~€10-15/mesiac
|
||||
|
||||
---
|
||||
|
||||
*Dokument vytvorený: 02.02.2026*
|
||||
*Verzia: 2.0.0*
|
||||
*Dokument vytvorený: 02.02.2026*
|
||||
*Posledná aktualizácia: 19.02.2026*
|
||||
*Verzia: 2.2.0*
|
||||
*Autor: Claude (Anthropic) + Používateľ*
|
||||
|
||||
Reference in New Issue
Block a user