690402:2046 fix correspondence ATG Gemini Flash
This commit is contained in:
@@ -35,7 +35,7 @@ services:
|
|||||||
# ใช้ Command นี้เพื่อตั้ง Password
|
# ใช้ Command นี้เพื่อตั้ง Password
|
||||||
command: redis-server --requirepass "Center2025"
|
command: redis-server --requirepass "Center2025"
|
||||||
ports:
|
ports:
|
||||||
- '6379:6379'
|
- '16379:6379'
|
||||||
volumes:
|
volumes:
|
||||||
- redis_data:/data
|
- redis_data:/data
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -130,24 +130,34 @@ export class DashboardService {
|
|||||||
.createQueryBuilder('log')
|
.createQueryBuilder('log')
|
||||||
.leftJoin('log.user', 'user')
|
.leftJoin('log.user', 'user')
|
||||||
.select([
|
.select([
|
||||||
|
'log.auditId',
|
||||||
'log.action',
|
'log.action',
|
||||||
'log.entityType',
|
'log.entityType',
|
||||||
'log.entityId',
|
'log.entityId',
|
||||||
'log.detailsJson',
|
'log.detailsJson',
|
||||||
'log.createdAt',
|
'log.createdAt',
|
||||||
'user.username',
|
'user.username',
|
||||||
|
'user.firstName',
|
||||||
|
'user.lastName',
|
||||||
])
|
])
|
||||||
.orderBy('log.createdAt', 'DESC')
|
.orderBy('log.createdAt', 'DESC')
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
return logs.map((log) => ({
|
return logs.map((log) => ({
|
||||||
|
id: log.auditId,
|
||||||
action: log.action,
|
action: log.action,
|
||||||
entityType: log.entityType,
|
entityType: log.entityType,
|
||||||
entityId: log.entityId,
|
entityId: log.entityId,
|
||||||
details: log.detailsJson,
|
details: log.detailsJson,
|
||||||
createdAt: log.createdAt,
|
createdAt: log.createdAt,
|
||||||
username: log.user?.username,
|
user: log.user
|
||||||
|
? {
|
||||||
|
username: log.user.username,
|
||||||
|
firstName: log.user.firstName,
|
||||||
|
lastName: log.user.lastName,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ export class GetActivityDto {
|
|||||||
* DTO สำหรับ Response ของ Activity Item
|
* DTO สำหรับ Response ของ Activity Item
|
||||||
*/
|
*/
|
||||||
export class ActivityItemDto {
|
export class ActivityItemDto {
|
||||||
|
@ApiPropertyOptional({ description: 'ID ของ Activity' })
|
||||||
|
id!: string;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: 'Action ที่กระทำ' })
|
@ApiPropertyOptional({ description: 'Action ที่กระทำ' })
|
||||||
action!: string;
|
action!: string;
|
||||||
|
|
||||||
@@ -37,6 +40,10 @@ export class ActivityItemDto {
|
|||||||
@ApiPropertyOptional({ description: 'วันที่กระทำ' })
|
@ApiPropertyOptional({ description: 'วันที่กระทำ' })
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
@ApiPropertyOptional({ description: 'ชื่อผู้ใช้' })
|
@ApiPropertyOptional({ description: 'ข้อมูลผู้ใช้' })
|
||||||
username?: string;
|
user?: {
|
||||||
|
username: string;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export function PendingTasks({ tasks, isLoading }: PendingTasksProps) {
|
|||||||
) : (
|
) : (
|
||||||
tasks.map((task) => (
|
tasks.map((task) => (
|
||||||
<Link
|
<Link
|
||||||
key={task.id}
|
key={task.publicId}
|
||||||
href={task.url}
|
href={task.url}
|
||||||
className="block p-3 bg-muted/40 rounded-lg border hover:bg-muted/60 transition-colors group"
|
className="block p-3 bg-muted/40 rounded-lg border hover:bg-muted/60 transition-colors group"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,31 @@
|
|||||||
import apiClient from '@/lib/api/client';
|
import apiClient from '@/lib/api/client';
|
||||||
import { DashboardStats, ActivityLog, PendingTask } from '@/types/dashboard';
|
import { DashboardStats, ActivityLog, PendingTask } from '@/types/dashboard';
|
||||||
|
|
||||||
|
interface RawActivityLog {
|
||||||
|
id: string;
|
||||||
|
action: string;
|
||||||
|
entityType?: string;
|
||||||
|
entityId?: string;
|
||||||
|
details?: Record<string, unknown>;
|
||||||
|
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 = {
|
export const dashboardService = {
|
||||||
getStats: async (): Promise<DashboardStats> => {
|
getStats: async (): Promise<DashboardStats> => {
|
||||||
const response = await apiClient.get('/dashboard/stats');
|
const response = await apiClient.get('/dashboard/stats');
|
||||||
@@ -10,9 +35,23 @@ export const dashboardService = {
|
|||||||
getRecentActivity: async (): Promise<ActivityLog[]> => {
|
getRecentActivity: async (): Promise<ActivityLog[]> => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get('/dashboard/activity');
|
const response = await apiClient.get('/dashboard/activity');
|
||||||
// ตรวจสอบว่า response.data เป็น array จริงๆ
|
|
||||||
if (Array.isArray(response.data)) {
|
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 [];
|
return [];
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
@@ -23,14 +62,24 @@ export const dashboardService = {
|
|||||||
getPendingTasks: async (): Promise<PendingTask[]> => {
|
getPendingTasks: async (): Promise<PendingTask[]> => {
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.get('/dashboard/pending');
|
const response = await apiClient.get('/dashboard/pending');
|
||||||
// Backend คืน { data: [], meta: {} } ต้องดึง data ออกมา
|
const rawTasks = (response.data?.data || (Array.isArray(response.data) ? response.data : [])) as RawPendingTask[];
|
||||||
if (response.data?.data && Array.isArray(response.data.data)) {
|
|
||||||
return response.data.data;
|
return rawTasks.map((task) => {
|
||||||
}
|
const assignedAt = new Date(task.assignedAt);
|
||||||
if (Array.isArray(response.data)) {
|
const now = new Date();
|
||||||
return response.data;
|
const diffTime = now.getTime() - assignedAt.getTime();
|
||||||
}
|
const daysOverdue = Math.max(0, Math.floor(diffTime / (1000 * 60 * 60 * 24)));
|
||||||
return [];
|
|
||||||
|
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) {
|
} catch (_error) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,20 +8,32 @@ export interface DashboardStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ActivityLog {
|
export interface ActivityLog {
|
||||||
id: number;
|
id: string;
|
||||||
|
action: string;
|
||||||
|
entityType?: string;
|
||||||
|
entityId?: string;
|
||||||
|
details?: Record<string, unknown>;
|
||||||
|
createdAt: string;
|
||||||
|
username?: string;
|
||||||
user: {
|
user: {
|
||||||
name: string;
|
name: string;
|
||||||
initials: string;
|
initials: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
};
|
};
|
||||||
action: string;
|
|
||||||
description: string;
|
|
||||||
createdAt: string;
|
|
||||||
targetUrl: string;
|
targetUrl: string;
|
||||||
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PendingTask {
|
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;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
daysOverdue: number;
|
daysOverdue: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user