diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 2a9505a..1df6e97 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -35,7 +35,7 @@ services: # ใช้ Command นี้เพื่อตั้ง Password command: redis-server --requirepass "Center2025" ports: - - '6379:6379' + - '16379:6379' volumes: - redis_data:/data networks: diff --git a/backend/src/modules/dashboard/dashboard.service.ts b/backend/src/modules/dashboard/dashboard.service.ts index f50a977..86a697e 100644 --- a/backend/src/modules/dashboard/dashboard.service.ts +++ b/backend/src/modules/dashboard/dashboard.service.ts @@ -130,24 +130,34 @@ export class DashboardService { .createQueryBuilder('log') .leftJoin('log.user', 'user') .select([ + 'log.auditId', 'log.action', 'log.entityType', 'log.entityId', 'log.detailsJson', 'log.createdAt', 'user.username', + 'user.firstName', + 'user.lastName', ]) .orderBy('log.createdAt', 'DESC') .limit(limit) .getMany(); return logs.map((log) => ({ + id: log.auditId, action: log.action, entityType: log.entityType, entityId: log.entityId, details: log.detailsJson, createdAt: log.createdAt, - username: log.user?.username, + user: log.user + ? { + username: log.user.username, + firstName: log.user.firstName, + lastName: log.user.lastName, + } + : undefined, })); } diff --git a/backend/src/modules/dashboard/dto/get-activity.dto.ts b/backend/src/modules/dashboard/dto/get-activity.dto.ts index 82a7fde..98b05cd 100644 --- a/backend/src/modules/dashboard/dto/get-activity.dto.ts +++ b/backend/src/modules/dashboard/dto/get-activity.dto.ts @@ -22,6 +22,9 @@ export class GetActivityDto { * DTO สำหรับ Response ของ Activity Item */ export class ActivityItemDto { + @ApiPropertyOptional({ description: 'ID ของ Activity' }) + id!: string; + @ApiPropertyOptional({ description: 'Action ที่กระทำ' }) action!: string; @@ -37,6 +40,10 @@ export class ActivityItemDto { @ApiPropertyOptional({ description: 'วันที่กระทำ' }) createdAt!: Date; - @ApiPropertyOptional({ description: 'ชื่อผู้ใช้' }) - username?: string; + @ApiPropertyOptional({ description: 'ข้อมูลผู้ใช้' }) + user?: { + username: string; + firstName?: string; + lastName?: string; + }; } diff --git a/frontend/components/dashboard/pending-tasks.tsx b/frontend/components/dashboard/pending-tasks.tsx index ab253e8..c6aa3f7 100644 --- a/frontend/components/dashboard/pending-tasks.tsx +++ b/frontend/components/dashboard/pending-tasks.tsx @@ -53,7 +53,7 @@ export function PendingTasks({ tasks, isLoading }: PendingTasksProps) { ) : ( tasks.map((task) => ( diff --git a/frontend/lib/services/dashboard.service.ts b/frontend/lib/services/dashboard.service.ts index 22f1274..33dac4f 100644 --- a/frontend/lib/services/dashboard.service.ts +++ b/frontend/lib/services/dashboard.service.ts @@ -1,6 +1,31 @@ import apiClient from '@/lib/api/client'; import { DashboardStats, ActivityLog, PendingTask } from '@/types/dashboard'; +interface RawActivityLog { + id: string; + action: string; + entityType?: string; + entityId?: string; + details?: Record; + createdAt: string; + user?: { + username: string; + firstName?: string; + lastName?: string; + }; +} + +interface RawPendingTask { + instanceId: string; + workflowCode: string; + currentState: string; + entityType: string; + entityId: string; + documentNumber: string; + subject: string; + assignedAt: string; +} + export const dashboardService = { getStats: async (): Promise => { const response = await apiClient.get('/dashboard/stats'); @@ -10,9 +35,23 @@ export const dashboardService = { getRecentActivity: async (): Promise => { try { const response = await apiClient.get('/dashboard/activity'); - // ตรวจสอบว่า response.data เป็น array จริงๆ if (Array.isArray(response.data)) { - return response.data; + return (response.data as RawActivityLog[]).map((log) => { + const firstName = log.user?.firstName || ''; + const lastName = log.user?.lastName || ''; + const fullName = firstName || lastName ? `${firstName} ${lastName}`.trim() : log.user?.username || 'System'; + const initials = firstName && lastName ? `${firstName[0]}${lastName[0]}` : (log.user?.username?.[0] || 'S').toUpperCase(); + + return { + ...log, + user: { + name: fullName, + initials: initials, + }, + description: (log.details?.description as string) || `${log.action} ${log.entityType || ''} ${log.entityId || ''}`, + targetUrl: `/${(log.entityType || 'correspondence').toLowerCase()}s/${log.entityId || ''}`, + } as ActivityLog; + }); } return []; } catch (_error) { @@ -23,14 +62,24 @@ export const dashboardService = { getPendingTasks: async (): Promise => { try { const response = await apiClient.get('/dashboard/pending'); - // Backend คืน { data: [], meta: {} } ต้องดึง data ออกมา - if (response.data?.data && Array.isArray(response.data.data)) { - return response.data.data; - } - if (Array.isArray(response.data)) { - return response.data; - } - return []; + const rawTasks = (response.data?.data || (Array.isArray(response.data) ? response.data : [])) as RawPendingTask[]; + + return rawTasks.map((task) => { + const assignedAt = new Date(task.assignedAt); + const now = new Date(); + const diffTime = now.getTime() - assignedAt.getTime(); + const daysOverdue = Math.max(0, Math.floor(diffTime / (1000 * 60 * 60 * 24))); + + return { + ...task, + publicId: task.instanceId, + title: task.subject || task.documentNumber, + description: task.currentState || task.workflowCode, + daysOverdue, + url: `/${(task.entityType || 'correspondence').toLowerCase()}s/${task.entityId}`, + priority: daysOverdue > 2 ? 'HIGH' : 'MEDIUM', + }; + }); } catch (_error) { return []; } diff --git a/frontend/types/dashboard.ts b/frontend/types/dashboard.ts index bf8c5a0..42ea4e3 100644 --- a/frontend/types/dashboard.ts +++ b/frontend/types/dashboard.ts @@ -8,20 +8,32 @@ export interface DashboardStats { } export interface ActivityLog { - id: number; + id: string; + action: string; + entityType?: string; + entityId?: string; + details?: Record; + createdAt: string; + username?: string; user: { name: string; initials: string; avatar?: string; }; - action: string; - description: string; - createdAt: string; targetUrl: string; + description: string; } export interface PendingTask { - id: number; + publicId: string; + workflowCode: string; + currentState: string; + entityType: string; + entityId: string; + documentNumber: string; + subject: string; + assignedAt: string; + // Derived fields for UI title: string; description: string; daysOverdue: number;