diff --git a/backend/src/modules/ai/ai-queue.service.ts b/backend/src/modules/ai/ai-queue.service.ts index 20630d69..930cc75d 100644 --- a/backend/src/modules/ai/ai-queue.service.ts +++ b/backend/src/modules/ai/ai-queue.service.ts @@ -4,6 +4,7 @@ // - 2026-05-14: เพิ่ม JSDoc idempotency contract สำหรับทุก enqueue method (💡 S3). // - 2026-05-21: เพิ่มการลงทะเบียน QUEUE_AI_BATCH และ enqueueSandboxJob สำหรับ Superadmin sandbox. // - 2026-05-21: แก้ไข ESLint error โดยการเปลี่ยน Queue เป็น Queue สำหรับ batchQueue +// - 2026-06-14: เพิ่ม sandbox-rag-prep ใน enqueueSandboxJob (T039) import { Injectable } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue, JobsOptions } from 'bullmq'; @@ -122,7 +123,8 @@ export class AiQueueService { | 'sandbox-rag' | 'sandbox-extract' | 'sandbox-ocr-only' - | 'sandbox-ai-extract', + | 'sandbox-ai-extract' + | 'sandbox-rag-prep', payload: { idempotencyKey: string; projectPublicId?: string; diff --git a/backend/src/modules/ai/ai.controller.ts b/backend/src/modules/ai/ai.controller.ts index 82136ef8..d959d3de 100644 --- a/backend/src/modules/ai/ai.controller.ts +++ b/backend/src/modules/ai/ai.controller.ts @@ -16,6 +16,7 @@ // - 2026-06-11: แก้ไขการส่งพารามิเตอร์ให้กับ queueSuggestJob ใน suggestDocumentMetadata // - 2026-06-13: T024-T026 — เพิ่ม sandbox parameter endpoints (GET/PUT/POST reset) ตาม ADR-036 // - 2026-06-13: T036, T037, T039, T040, T041 — เพิ่ม endpoints apply sandbox profile และ get production parameters พร้อม idempotency, CASL, validation และ audit +// - 2026-06-14: เพิ่ม POST /ai/admin/sandbox/rag-prep endpoint (T033) // Controller สำหรับ AI Gateway Endpoints (ADR-023) import { @@ -63,6 +64,7 @@ import { import { AiRagService } from './ai-rag.service'; import { AiQueueService } from './ai-queue.service'; import { AiRagQueryDto } from './dto/ai-rag-query.dto'; +import { SandboxRagPrepDto } from './dto/sandbox-rag-prep.dto'; import { ExtractDocumentDto } from './dto/extract-document.dto'; import { AiCallbackDto } from './dto/ai-callback.dto'; import { CreateAiJobDto } from './dto/create-ai-job.dto'; @@ -430,6 +432,7 @@ export class AiController { @ApiBearerAuth() @RequirePermission('system.manage_all') @HttpCode(HttpStatus.ACCEPTED) + @Throttle({ default: { limit: 10, ttl: 60000 } }) @ApiOperation({ summary: 'AI Admin Sandbox RAG Query — ส่ง sandbox RAG เข้า queue ai-batch (T035)', @@ -483,6 +486,7 @@ export class AiController { @RequirePermission('system.manage_all') @UseInterceptors(FileInterceptor('file')) @HttpCode(HttpStatus.ACCEPTED) + @Throttle({ default: { limit: 10, ttl: 60000 } }) @ApiOperation({ summary: 'AI Admin Sandbox OCR Extract — อัปโหลดไฟล์เพื่อทำ OCR Sandbox (T041 & T042)', @@ -542,6 +546,7 @@ export class AiController { @RequirePermission('system.manage_all') @UseInterceptors(FileInterceptor('file')) @HttpCode(HttpStatus.ACCEPTED) + @Throttle({ default: { limit: 10, ttl: 60000 } }) @ApiOperation({ summary: 'Step 1: Run OCR Only — สำหรับตรวจคุณภาพ OCR ก่อนทดสอบ AI', description: @@ -636,6 +641,7 @@ export class AiController { @Post('admin/sandbox/ai-extract') @UseGuards(JwtAuthGuard, RbacGuard) @RequirePermission('system.manage_all') + @Throttle({ default: { limit: 10, ttl: 60000 } }) @ApiOperation({ summary: 'Step 2: Run AI Extraction — ใช้ OCR text ที่ cache จาก Step 1', description: @@ -668,6 +674,29 @@ export class AiController { return { requestPublicId, jobId, status: 'queued' }; } + @Post('admin/sandbox/rag-prep') + @UseGuards(JwtAuthGuard, RbacGuard) + @RequirePermission('system.manage_all') + @Throttle({ default: { limit: 10, ttl: 60000 } }) + @ApiOperation({ + summary: 'Step 3: Run RAG Prep Sandbox testing (T033)', + description: + 'รับข้อความ OCR และ profileId แล้วรัน semantic chunking และ embedding preview', + }) + async submitSandboxRagPrep( + @Body() dto: SandboxRagPrepDto + ): Promise<{ requestPublicId: string; jobId: string; status: string }> { + const requestPublicId = uuidv7(); + const jobId = await this.aiQueueService.enqueueSandboxJob( + 'sandbox-rag-prep', + { + idempotencyKey: requestPublicId, + extraPayload: { text: dto.text, profileId: dto.profileId }, + } + ); + return { requestPublicId, jobId, status: 'queued' }; + } + // --- Webhook Callback จาก n8n (Service Account) --- @Post('callback') diff --git a/backend/src/modules/ai/dto/context-config.dto.ts b/backend/src/modules/ai/dto/context-config.dto.ts new file mode 100644 index 00000000..aca967c1 --- /dev/null +++ b/backend/src/modules/ai/dto/context-config.dto.ts @@ -0,0 +1,38 @@ +// File: backend/src/modules/ai/dto/context-config.dto.ts +// Change Log: +// - 2026-06-14: Created ContextConfigDto for prompt context management (conforming to task T006) + +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsInt, IsString, IsObject, Min } from 'class-validator'; + +export class ContextFilterDto { + @ApiPropertyOptional({ type: String, nullable: true }) + @IsOptional() + @IsString() + projectId!: string | null; + + @ApiPropertyOptional({ type: String, nullable: true }) + @IsOptional() + @IsString() + contractId!: string | null; +} + +export class ContextConfigDto { + @ApiPropertyOptional({ type: ContextFilterDto, nullable: true }) + @IsOptional() + @IsObject() + filter?: ContextFilterDto | null; + + @ApiProperty({ type: Number, minimum: 1 }) + @IsInt() + @Min(1) + pageSize!: number; + + @ApiProperty({ type: String }) + @IsString() + language!: string; + + @ApiProperty({ type: String }) + @IsString() + outputLanguage!: string; +} diff --git a/backend/src/modules/ai/dto/create-execution-profile.dto.ts b/backend/src/modules/ai/dto/create-execution-profile.dto.ts new file mode 100644 index 00000000..c3047ae5 --- /dev/null +++ b/backend/src/modules/ai/dto/create-execution-profile.dto.ts @@ -0,0 +1,66 @@ +// File: backend/src/modules/ai/dto/create-execution-profile.dto.ts +// Change Log: +// - 2026-06-14: Created CreateExecutionProfileDto for AI execution profile creation (conforming to task T008) + +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsNotEmpty, + IsOptional, + IsString, + IsNumber, + IsInt, + Min, + Max, +} from 'class-validator'; + +export class CreateExecutionProfileDto { + @ApiProperty({ description: 'Profile Name' }) + @IsNotEmpty() + @IsString() + profileName!: string; + + @ApiPropertyOptional({ + description: 'Canonical Model', + enum: ['np-dms-ai', 'np-dms-ocr'], + }) + @IsOptional() + @IsString() + canonicalModel?: 'np-dms-ai' | 'np-dms-ocr'; + + @ApiProperty({ description: 'Temperature parameter' }) + @IsNumber() + @Min(0.0) + @Max(1.0) + temperature!: number; + + @ApiProperty({ description: 'Top-P parameter' }) + @IsNumber() + @Min(0.0) + @Max(1.0) + topP!: number; + + @ApiProperty({ description: 'Repeat penalty parameter' }) + @IsNumber() + @Min(1.0) + @Max(2.0) + repeatPenalty!: number; + + @ApiPropertyOptional({ description: 'Maximum tokens to generate' }) + @IsOptional() + @IsInt() + @Min(1) + maxTokens?: number | null; + + @ApiPropertyOptional({ + description: 'Context window size (num_ctx / ctxSize)', + }) + @IsOptional() + @IsInt() + @Min(1) + ctxSize?: number | null; + + @ApiProperty({ description: 'Keep alive in seconds' }) + @IsInt() + @Min(0) + keepAlive!: number; +} diff --git a/backend/src/modules/ai/dto/sandbox-rag-prep.dto.ts b/backend/src/modules/ai/dto/sandbox-rag-prep.dto.ts new file mode 100644 index 00000000..52a7fc80 --- /dev/null +++ b/backend/src/modules/ai/dto/sandbox-rag-prep.dto.ts @@ -0,0 +1,18 @@ +// File: backend/src/modules/ai/dto/sandbox-rag-prep.dto.ts +// Change Log: +// - 2026-06-14: Created SandboxRagPrepDto for Sandbox RAG Prep testing (conforming to task T007) + +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class SandboxRagPrepDto { + @ApiProperty({ description: 'Text to prepare for RAG (OCR text)' }) + @IsNotEmpty() + @IsString() + text!: string; + + @ApiPropertyOptional({ description: 'Execution profile public ID to use' }) + @IsOptional() + @IsString() + profileId?: string | null; +} diff --git a/backend/src/modules/ai/dto/update-execution-profile.dto.ts b/backend/src/modules/ai/dto/update-execution-profile.dto.ts new file mode 100644 index 00000000..d4cf56dd --- /dev/null +++ b/backend/src/modules/ai/dto/update-execution-profile.dto.ts @@ -0,0 +1,47 @@ +// File: backend/src/modules/ai/dto/update-execution-profile.dto.ts +// Change Log: +// - 2026-06-14: Created UpdateExecutionProfileDto for AI execution profile updates (conforming to task T009) + +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsNumber, IsInt, Min, Max } from 'class-validator'; + +export class UpdateExecutionProfileDto { + @ApiPropertyOptional({ description: 'Temperature parameter' }) + @IsOptional() + @IsNumber() + @Min(0.0) + @Max(1.0) + temperature?: number; + + @ApiPropertyOptional({ description: 'Top-P parameter' }) + @IsOptional() + @IsNumber() + @Min(0.0) + @Max(1.0) + topP?: number; + + @ApiPropertyOptional({ description: 'Repeat penalty parameter' }) + @IsOptional() + @IsNumber() + @Min(1.0) + @Max(2.0) + repeatPenalty?: number; + + @ApiPropertyOptional({ description: 'Maximum tokens to generate' }) + @IsOptional() + @IsInt() + @Min(1) + maxTokens?: number | null; + + @ApiPropertyOptional({ description: 'Context window size' }) + @IsOptional() + @IsInt() + @Min(1) + ctxSize?: number | null; + + @ApiPropertyOptional({ description: 'Keep alive in seconds' }) + @IsOptional() + @IsInt() + @Min(0) + keepAlive?: number; +} diff --git a/backend/src/modules/ai/processors/ai-batch.processor.ts b/backend/src/modules/ai/processors/ai-batch.processor.ts index d00d98b1..0da7c20e 100644 --- a/backend/src/modules/ai/processors/ai-batch.processor.ts +++ b/backend/src/modules/ai/processors/ai-batch.processor.ts @@ -1,6 +1,7 @@ // File: backend/src/modules/ai/processors/ai-batch.processor.ts // Change Log // - 2026-06-08: แก้ไขปัญหา LLM JSON response truncated โดยการเพิ่ม num_ctx เป็น 16384 ใน sandbox-extract, sandbox-ai-extract และ migrate-document (แก้ไขโดย AGY Gemini 3.5 Flash (Medium)) +// - 2026-06-14: เพิ่ม case sandbox-rag-prep และ processSandboxRagPrep (T035) // - 2026-05-15: เพิ่ม processor สำหรับ ai-batch queue ตาม ADR-023A. // - 2026-05-15: เพิ่ม EmbeddingService สำหรับ embed-document logic (T022). // - 2026-05-21: เพิ่มการรองรับ sandbox-rag และ sandbox-extract สำหรับ Superadmin sandbox. @@ -70,6 +71,7 @@ export type AiBatchJobType = | 'sandbox-extract' | 'sandbox-ocr-only' | 'sandbox-ai-extract' + | 'sandbox-rag-prep' | 'migrate-document' | 'rag-prepare' | 'ai-suggest' @@ -294,7 +296,10 @@ export class AiBatchProcessor extends WorkerHost { async process(job: Job): Promise { const isSandbox = job.data.jobType === 'sandbox-rag' || - job.data.jobType === 'sandbox-extract'; + job.data.jobType === 'sandbox-extract' || + job.data.jobType === 'sandbox-ocr-only' || + job.data.jobType === 'sandbox-ai-extract' || + job.data.jobType === 'sandbox-rag-prep'; if (!isSandbox) { await this.setAiProcessingStatus(job.data.documentPublicId, 'PROCESSING'); } @@ -362,6 +367,12 @@ export class AiBatchProcessor extends WorkerHost { ); await this.processSandboxAiExtract(job.data); return; + case 'sandbox-rag-prep': + this.logger.log( + `Sandbox RAG Prep job processing — jobId=${String(job.id)}` + ); + await this.processSandboxRagPrep(job.data); + return; case 'migrate-document': this.logger.log( `Migrate document job processing — jobId=${String(job.id)}` @@ -1530,4 +1541,149 @@ export class AiBatchProcessor extends WorkerHost { const confidence = suggestion['confidenceScore']; return typeof confidence === 'number' ? confidence : undefined; } + + private async processSandboxRagPrep(data: AiBatchJobData): Promise { + const { idempotencyKey, payload } = data; + const text = payload.text as string; + const profileId = payload.profileId as string | undefined; + await this.redis.setex( + `ai:rag:result:${idempotencyKey}`, + 3600, + JSON.stringify({ + requestPublicId: idempotencyKey, + status: 'processing', + }) + ); + try { + if (!text) { + throw new Error('text is required for sandbox-rag-prep job'); + } + const activePrompt = + await this.aiPromptsService.getActive('rag_prep_prompt'); + if (!activePrompt) { + throw new Error('No active rag_prep_prompt version found'); + } + const promptText = activePrompt.template + .replace('{{text}}', text) + .replace('{{ocr_text}}', text); + let sandboxParams; + if (profileId) { + try { + sandboxParams = + await this.aiPolicyService.getSandboxParameters(profileId); + } catch (err) { + this.logger.warn( + `Failed to fetch sandbox parameters for profileId=${profileId}: ${String(err)}` + ); + } + } + if (!sandboxParams) { + try { + sandboxParams = + await this.aiPolicyService.getSandboxParameters('standard'); + } catch (err) { + this.logger.warn( + `Failed to fetch sandbox parameters for standard: ${String(err)}` + ); + } + } + const generateOptions = { + options: { + num_ctx: sandboxParams?.numCtx ?? 8192, + num_predict: sandboxParams?.maxTokens ?? 4096, + temperature: sandboxParams?.temperature, + top_p: sandboxParams?.topP, + repeat_penalty: sandboxParams?.repeatPenalty, + }, + }; + const llmOutput = await this.ollamaService.generate( + promptText, + generateOptions + ); + const parsed = this.parseChunkTags(llmOutput); + const chunks = + parsed.length > 0 ? parsed : this.fixedSizeChunk(text, 512, 64); + const ragChunks: Array<{ text: string; summary: string }> = []; + const ragVectors: number[][] = []; + for (const chunk of chunks) { + try { + const embedResult = await this.ocrService.embedViaSidecar(chunk.text); + ragChunks.push({ + text: chunk.text, + summary: chunk.topic, + }); + ragVectors.push(embedResult.dense); + } catch (err) { + this.logger.error( + `Sandbox embed failed for chunk: ${chunk.topic}`, + err + ); + } + } + await this.redis.setex( + `ai:rag:result:${idempotencyKey}`, + 3600, + JSON.stringify({ + requestPublicId: idempotencyKey, + status: 'completed', + ragChunks, + ragVectors, + completedAt: new Date().toISOString(), + }) + ); + } catch (err: unknown) { + const errMsg = err instanceof Error ? err.message : String(err); + this.logger.error(`Sandbox RAG Prep failed: ${errMsg}`); + await this.redis.setex( + `ai:rag:result:${idempotencyKey}`, + 3600, + JSON.stringify({ + requestPublicId: idempotencyKey, + status: 'failed', + errorMessage: errMsg, + completedAt: new Date().toISOString(), + }) + ); + throw err; + } + } + + private parseChunkTags( + llmOutput: string + ): Array<{ topic: string; text: string }> { + const chunks: Array<{ topic: string; text: string }> = []; + const regex = /([\s\S]*?)<\/chunk\s*>/gi; + let match; + while ((match = regex.exec(llmOutput)) !== null) { + const topic = match[1]?.trim() || 'ทั่วไป'; + const text = match[2]?.trim(); + if (text) { + chunks.push({ topic, text }); + } + } + return chunks; + } + + private fixedSizeChunk( + text: string, + chunkSize: number, + overlap: number + ): Array<{ topic: string; text: string }> { + const chunks: Array<{ topic: string; text: string }> = []; + const cleanText = text.replace(/\s+/g, ' ').trim(); + const textLength = cleanText.length; + let startIndex = 0; + let chunkIndex = 0; + while (startIndex < textLength) { + const endIndex = Math.min(startIndex + chunkSize, textLength); + const chunkText = cleanText.substring(startIndex, endIndex); + chunks.push({ + topic: `ส่วนที่ ${chunkIndex + 1}`, + text: chunkText, + }); + startIndex += chunkSize - overlap; + chunkIndex += 1; + } + return chunks; + } } diff --git a/backend/src/modules/ai/prompts/ai-prompts.controller.ts b/backend/src/modules/ai/prompts/ai-prompts.controller.ts index 6c346ab7..3a515d53 100644 --- a/backend/src/modules/ai/prompts/ai-prompts.controller.ts +++ b/backend/src/modules/ai/prompts/ai-prompts.controller.ts @@ -6,6 +6,7 @@ import { Controller, Get, Post, + Put, Delete, Patch, Body, @@ -26,6 +27,7 @@ import { AiPrompt } from './ai-prompts.entity'; import { CreateAiPromptDto } from './dto/create-ai-prompt.dto'; import { UpdatePromptNoteDto } from './dto/update-prompt-note.dto'; import { AiPromptResponseDto } from './dto/ai-prompt-response.dto'; +import { ContextConfigDto } from '../dto/context-config.dto'; import { plainToInstance } from 'class-transformer'; import { JwtAuthGuard } from '../../../common/guards/jwt-auth.guard'; import { RbacGuard } from '../../../common/guards/rbac.guard'; @@ -137,4 +139,42 @@ export class AiPromptsController { ); return { data: this.mapToDto(updated) }; } + + @Get(':promptType/:versionNumber/context-config') + @RequirePermission('system.manage_all') + @ApiOperation({ summary: 'ดึง Context Config ของ Prompt Version ที่กำหนด' }) + @ApiParam({ name: 'promptType', example: 'ocr_extraction' }) + @ApiParam({ name: 'versionNumber', type: Number }) + async getContextConfig( + @Param('promptType') promptType: string, + @Param('versionNumber', ParseIntPipe) versionNumber: number + ): Promise<{ data: Record | null }> { + const config = await this.promptsService.getContextConfig( + promptType, + versionNumber + ); + return { data: config }; + } + + @Put(':promptType/:versionNumber/context-config') + @RequirePermission('system.manage_all') + @Audit('ai_prompt.update_context_config', 'AiPrompt') + @HttpCode(HttpStatus.OK) + @ApiOperation({ + summary: 'อัปเดต Context Config ของ Prompt Version ที่กำหนด', + }) + @ApiParam({ name: 'promptType', example: 'ocr_extraction' }) + @ApiParam({ name: 'versionNumber', type: Number }) + async updateContextConfig( + @Param('promptType') promptType: string, + @Param('versionNumber', ParseIntPipe) versionNumber: number, + @Body() dto: ContextConfigDto + ): Promise<{ data: Record }> { + const updated = await this.promptsService.updateContextConfig( + promptType, + versionNumber, + dto + ); + return { data: updated }; + } } diff --git a/backend/src/modules/ai/prompts/ai-prompts.service.spec.ts b/backend/src/modules/ai/prompts/ai-prompts.service.spec.ts index de1e4551..979cfc7f 100644 --- a/backend/src/modules/ai/prompts/ai-prompts.service.spec.ts +++ b/backend/src/modules/ai/prompts/ai-prompts.service.spec.ts @@ -223,7 +223,7 @@ describe('AiPromptsService', () => { }); }); describe('create', () => { - it('ควรปฏิเสธ template ที่ไม่มี {{ocr_text}} placeholder', async () => { + it('ควรปฏิเสธ template ที่ไม่มี {{ocr_text}} placeholder สำหรับ ocr_extraction', async () => { await expect( service.create( 'ocr_extraction', @@ -232,6 +232,36 @@ describe('AiPromptsService', () => { ) ).rejects.toThrow(ValidationException); }); + it('ควรปฏิเสธ template ที่ไม่มี {{query}} หรือ {{context}} placeholder สำหรับ rag_query_prompt', async () => { + await expect( + service.create( + 'rag_query_prompt', + { template: 'Invalid template context' }, + 1 + ) + ).rejects.toThrow(ValidationException); + await expect( + service.create( + 'rag_query_prompt', + { template: 'Invalid template query {{query}}' }, + 1 + ) + ).rejects.toThrow(ValidationException); + }); + it('ควรปฏิเสธ template ที่ไม่มี {{text}} placeholder สำหรับ rag_prep_prompt', async () => { + await expect( + service.create('rag_prep_prompt', { template: 'Invalid template' }, 1) + ).rejects.toThrow(ValidationException); + }); + it('ควรปฏิเสธ template ที่ไม่มี {{document_text}} placeholder สำหรับ classification_prompt', async () => { + await expect( + service.create( + 'classification_prompt', + { template: 'Invalid template' }, + 1 + ) + ).rejects.toThrow(ValidationException); + }); it('ควรปฏิเสธ template ที่ตัวอักษรเกิน 4,000 ตัว', async () => { const longTemplate = 'a'.repeat(4005) + '{{ocr_text}}'; await expect( @@ -363,4 +393,84 @@ describe('AiPromptsService', () => { expect(mockAiPromptRepo.findOne).toHaveBeenCalled(); }); }); + + describe('contextConfig CRUD', () => { + it('ควร getContextConfig สำเร็จ', async () => { + const prompt = { + id: 1, + promptType: 'ocr_extraction', + versionNumber: 1, + contextConfig: { + pageSize: 5, + language: 'th', + outputLanguage: 'th', + filter: null, + }, + }; + mockAiPromptRepo.findOne.mockResolvedValue(prompt); + const result = await service.getContextConfig('ocr_extraction', 1); + expect(result).toEqual(prompt.contextConfig); + }); + + it('ควรโยน NotFoundException เมื่อ getContextConfig ไม่พบเวอร์ชัน', async () => { + mockAiPromptRepo.findOne.mockResolvedValue(null); + await expect( + service.getContextConfig('ocr_extraction', 99) + ).rejects.toThrow(NotFoundException); + }); + + it('ควร updateContextConfig สำเร็จและตรวจสอบโครงการ/สัญญาสำเร็จ', async () => { + const prompt = { + id: 1, + promptType: 'ocr_extraction', + versionNumber: 1, + contextConfig: null, + }; + mockAiPromptRepo.findOne.mockResolvedValue(prompt); + mockAiPromptRepo.save.mockResolvedValue({ + ...prompt, + contextConfig: { + pageSize: 5, + language: 'th', + outputLanguage: 'th', + filter: { projectId: 'p-1', contractId: 'c-1' }, + }, + }); + + // จำลองให้โครงการและสัญญาถูกต้องใน DB + mockQueryBuilder.getRawOne + .mockResolvedValueOnce({ id: 10 }) // project check + .mockResolvedValueOnce({ id: 20 }); // contract check + + const result = await service.updateContextConfig('ocr_extraction', 1, { + pageSize: 5, + language: 'th', + outputLanguage: 'th', + filter: { projectId: 'p-1', contractId: 'c-1' }, + }); + + expect(result.pageSize).toBe(5); + expect(mockAiPromptRepo.save).toHaveBeenCalled(); + }); + + it('ควรโยน NotFoundException เมื่อ updateContextConfig ส่ง project UUID ที่ไม่มีอยู่ใน DB', async () => { + const prompt = { + id: 1, + promptType: 'ocr_extraction', + versionNumber: 1, + contextConfig: null, + }; + mockAiPromptRepo.findOne.mockResolvedValue(prompt); + mockQueryBuilder.getRawOne.mockResolvedValueOnce(null); // project not found + + await expect( + service.updateContextConfig('ocr_extraction', 1, { + pageSize: 5, + language: 'th', + outputLanguage: 'th', + filter: { projectId: 'invalid-proj-uuid', contractId: null }, + }) + ).rejects.toThrow(NotFoundException); + }); + }); }); diff --git a/backend/src/modules/ai/prompts/ai-prompts.service.ts b/backend/src/modules/ai/prompts/ai-prompts.service.ts index be9d0d49..eb44c131 100644 --- a/backend/src/modules/ai/prompts/ai-prompts.service.ts +++ b/backend/src/modules/ai/prompts/ai-prompts.service.ts @@ -13,6 +13,7 @@ import { randomUUID } from 'crypto'; import { AiPrompt } from './ai-prompts.entity'; import { AuditLog } from '../../../common/entities/audit-log.entity'; import { CreateAiPromptDto } from './dto/create-ai-prompt.dto'; +import { ContextConfigDto } from '../dto/context-config.dto'; import { BusinessException, ValidationException, @@ -343,8 +344,31 @@ export class AiPromptsService { dto: CreateAiPromptDto, userId: number ): Promise { - if (!dto.template.includes('{{ocr_text}}')) { - throw new ValidationException('template ต้องมี {{ocr_text}} placeholder'); + if (promptType === 'ocr_extraction') { + if (!dto.template.includes('{{ocr_text}}')) { + throw new ValidationException( + 'template ต้องมี {{ocr_text}} placeholder' + ); + } + } else if (promptType === 'rag_query_prompt') { + if ( + !dto.template.includes('{{query}}') || + !dto.template.includes('{{context}}') + ) { + throw new ValidationException( + 'template ต้องมี {{query}} และ {{context}} placeholder' + ); + } + } else if (promptType === 'rag_prep_prompt') { + if (!dto.template.includes('{{text}}')) { + throw new ValidationException('template ต้องมี {{text}} placeholder'); + } + } else if (promptType === 'classification_prompt') { + if (!dto.template.includes('{{document_text}}')) { + throw new ValidationException( + 'template ต้องมี {{document_text}} placeholder' + ); + } } if (dto.template.length > 4000) { throw new ValidationException('Template exceeds 4,000 character limit'); @@ -527,6 +551,76 @@ export class AiPromptsService { } } + /** + * ดึง Context Config ของ Prompt Version ที่กำหนด + */ + async getContextConfig( + promptType: string, + versionNumber: number + ): Promise | null> { + const prompt = await this.aiPromptRepo.findOne({ + where: { promptType, versionNumber }, + }); + if (!prompt) { + throw new NotFoundException('AiPrompt', versionNumber.toString()); + } + return prompt.contextConfig; + } + + /** + * อัปเดต Context Config ของ Prompt Version ที่กำหนด พร้อมทั้งตรวจเช็คความถูกต้องของโครงการและสัญญาใน DB + */ + async updateContextConfig( + promptType: string, + versionNumber: number, + dto: ContextConfigDto + ): Promise> { + const prompt = await this.aiPromptRepo.findOne({ + where: { promptType, versionNumber }, + }); + if (!prompt) { + throw new NotFoundException('AiPrompt', versionNumber.toString()); + } + + // Validation (T027): ตรวจสอบโครงการ/สัญญาใน DB + if (dto.filter?.projectId) { + const projectExists = (await this.dataSource.manager + .createQueryBuilder() + .select('p.id') + .from('projects', 'p') + .where('p.uuid = :uuid', { uuid: dto.filter.projectId }) + .andWhere('p.deleted_at IS NULL') + .getRawOne()) as unknown; + if (!projectExists) { + throw new NotFoundException('Project', dto.filter.projectId); + } + } + + if (dto.filter?.contractId) { + const contractExists = (await this.dataSource.manager + .createQueryBuilder() + .select('c.id') + .from('contracts', 'c') + .where('c.uuid = :uuid', { uuid: dto.filter.contractId }) + .getRawOne()) as unknown; + if (!contractExists) { + throw new NotFoundException('Contract', dto.filter.contractId); + } + } + + // บันทึกลง DB + const newContextConfig = { + filter: dto.filter || null, + pageSize: dto.pageSize, + language: dto.language, + outputLanguage: dto.outputLanguage, + }; + prompt.contextConfig = newContextConfig; + await this.aiPromptRepo.save(prompt); + + return newContextConfig; + } + /** * บันทึกข้อมูลการปฏิบัติการของผู้ใช้ลงในตารางหลัก audit_logs */ diff --git a/backend/src/modules/ai/services/ollama.service.spec.ts b/backend/src/modules/ai/services/ollama.service.spec.ts index d7a1503a..1989dfb7 100644 --- a/backend/src/modules/ai/services/ollama.service.spec.ts +++ b/backend/src/modules/ai/services/ollama.service.spec.ts @@ -3,6 +3,7 @@ // - 2026-06-03: สร้าง unit test สำหรับ OllamaService ครอบคลุม generate() model option, // getOcrModelName(), และ loadModel() keepAlive param ตาม ADR-034 // - 2026-06-13: ADR-036 — อัปเดต expected model tags เป็น np-dms-ai/np-dms-ocr +// - 2026-06-14: เพิ่ม tests สำหรับ generateEmbedding, checkHealth, unloadModel เพื่อเพิ่ม branch coverage import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; @@ -132,5 +133,125 @@ describe('OllamaService (ADR-034)', () => { expect(result).toBe(false); expect(mockedAxios.post).not.toHaveBeenCalled(); }); + it('ควรคืน false และ log error เมื่อ axios throw ระหว่าง loadModel', async () => { + mockedAxios.get = jest + .fn() + .mockRejectedValueOnce(new Error('ECONNREFUSED')); + const result = await service.loadModel('np-dms-ai:latest'); + expect(result).toBe(false); + }); + }); + describe('getEmbeddingModelName()', () => { + it('ควรคืน nomic-embed-text เป็น embedding model', () => { + expect(service.getEmbeddingModelName()).toBe('nomic-embed-text'); + }); + }); + describe('generateEmbedding()', () => { + it('ควรคืน embedding vector เมื่อ Ollama ตอบกลับสำเร็จ', async () => { + const mockVector = [0.1, 0.2, 0.3]; + mockedAxios.post = jest.fn().mockResolvedValueOnce({ + data: { embedding: mockVector }, + }); + const result = await service.generateEmbedding('test text'); + expect(result).toEqual(mockVector); + expect(mockedAxios.post).toHaveBeenCalledWith( + expect.stringContaining('/api/embeddings'), + expect.objectContaining({ + model: 'nomic-embed-text', + prompt: 'test text', + }), + expect.anything() + ); + }); + it('ควร throw error เมื่อ Ollama embedding ล้มเหลว', async () => { + mockedAxios.post = jest + .fn() + .mockRejectedValueOnce(new Error('Embedding failed')); + await expect(service.generateEmbedding('test')).rejects.toThrow( + 'Embedding failed' + ); + }); + }); + describe('checkHealth()', () => { + it('ควรคืน HEALTHY พร้อมโมเดลที่โหลดอยู่จาก /api/ps เมื่อ Ollama ตอบกลับสำเร็จ', async () => { + mockedAxios.get = jest + .fn() + .mockResolvedValueOnce({ data: {} }) // /api/tags + .mockResolvedValueOnce({ + data: { models: [{ name: 'np-dms-ai:latest' }] }, + }); // /api/ps + const result = await service.checkHealth(); + expect(result.status).toBe('HEALTHY'); + expect(result.models).toContain('np-dms-ai:latest'); + expect(result.latencyMs).toBeGreaterThanOrEqual(0); + }); + it('ควรคืน HEALTHY พร้อม fallback models เมื่อ /api/ps ไม่มีข้อมูล', async () => { + mockedAxios.get = jest + .fn() + .mockResolvedValueOnce({ data: {} }) // /api/tags OK + .mockResolvedValueOnce({ data: { models: [] } }); // /api/ps empty + const result = await service.checkHealth(); + expect(result.status).toBe('HEALTHY'); + expect(result.models).toContain('np-dms-ai:latest'); // fallback + }); + it('ควรคืน HEALTHY แม้ /api/ps throw error (graceful degradation)', async () => { + mockedAxios.get = jest + .fn() + .mockResolvedValueOnce({ data: {} }) // /api/tags OK + .mockRejectedValueOnce(new Error('ps endpoint error')); // /api/ps fails + const result = await service.checkHealth(); + expect(result.status).toBe('HEALTHY'); + }); + it('ควรคืน DEGRADED เมื่อ /api/tags timeout', async () => { + mockedAxios.get = jest + .fn() + .mockRejectedValueOnce(new Error('timeout error')); + const result = await service.checkHealth(); + expect(result.status).toBe('DEGRADED'); + expect(result.error).toContain('timeout'); + }); + it('ควรคืน DEGRADED เมื่อ error message มี code ECONNABORTED', async () => { + mockedAxios.get = jest + .fn() + .mockRejectedValueOnce(new Error('code ECONNABORTED')); + const result = await service.checkHealth(); + expect(result.status).toBe('DEGRADED'); + }); + it('ควรคืน DOWN เมื่อ connection ถูกปฏิเสธ (ไม่ใช่ timeout)', async () => { + mockedAxios.get = jest + .fn() + .mockRejectedValueOnce(new Error('ECONNREFUSED')); + const result = await service.checkHealth(); + expect(result.status).toBe('DOWN'); + }); + }); + describe('unloadModel()', () => { + it('ควรคืน true เมื่อ unload สำเร็จ', async () => { + mockedAxios.post = jest.fn().mockResolvedValueOnce({ data: {} }); + const result = await service.unloadModel('np-dms-ocr:latest'); + expect(result).toBe(true); + expect(mockedAxios.post).toHaveBeenCalledWith( + expect.stringContaining('/api/generate'), + expect.objectContaining({ model: 'np-dms-ocr:latest', keep_alive: 0 }), + expect.anything() + ); + }); + it('ควรคืน false เมื่อ unload ล้มเหลว', async () => { + mockedAxios.post = jest + .fn() + .mockRejectedValueOnce(new Error('Unload failed')); + const result = await service.unloadModel('np-dms-ocr:latest'); + expect(result).toBe(false); + }); + }); + describe('generate() error path', () => { + it('ควร throw error เมื่อ Ollama generate ล้มเหลว', async () => { + mockedAxios.post = jest + .fn() + .mockRejectedValueOnce(new Error('LLM timeout')); + await expect(service.generate('test prompt')).rejects.toThrow( + 'LLM timeout' + ); + }); }); }); diff --git a/backend/src/modules/ai/services/sandbox-ocr-engine.service.spec.ts b/backend/src/modules/ai/services/sandbox-ocr-engine.service.spec.ts new file mode 100644 index 00000000..22dbf49f --- /dev/null +++ b/backend/src/modules/ai/services/sandbox-ocr-engine.service.spec.ts @@ -0,0 +1,260 @@ +// File: src/modules/ai/services/sandbox-ocr-engine.service.spec.ts +// Change Log: +// - 2026-06-14: สร้าง unit tests สำหรับ SandboxOcrEngineService ครอบคลุม detectAndExtract ทุก engine + +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigService } from '@nestjs/config'; +import axios from 'axios'; +import * as fs from 'fs'; +import { SandboxOcrEngineService } from './sandbox-ocr-engine.service'; +import { OcrService } from './ocr.service'; + +jest.mock('axios'); +jest.mock('fs'); + +const mockedAxios = axios as jest.Mocked; +const mockedFs = fs as jest.Mocked; + +/** OcrService mock สำหรับ tesseract/fast-path */ +const mockOcrService = { + detectAndExtract: jest.fn(), +}; + +/** ConfigService mock */ +const mockConfigService = { + get: jest.fn((key: string, defaultValue?: T): T | undefined => { + const cfg: Record = { + OCR_API_URL: 'http://localhost:8765', + OCR_SIDECAR_API_KEY: 'test-api-key-2026', + }; + return (cfg[key] as T | undefined) ?? defaultValue; + }), +}; + +describe('SandboxOcrEngineService', () => { + let service: SandboxOcrEngineService; + + beforeEach(async () => { + jest.clearAllMocks(); + const module: TestingModule = await Test.createTestingModule({ + providers: [ + SandboxOcrEngineService, + { provide: ConfigService, useValue: mockConfigService }, + { provide: OcrService, useValue: mockOcrService }, + ], + }).compile(); + service = module.get(SandboxOcrEngineService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('detectAndExtract() — engine=auto', () => { + it('ควร route ไปยัง OcrService เมื่อ engine=auto', async () => { + mockOcrService.detectAndExtract.mockResolvedValueOnce({ + text: 'auto extracted text', + ocrUsed: true, + }); + const result = await service.detectAndExtract('/tmp/file.pdf', 'auto'); + expect(result.text).toBe('auto extracted text'); + expect(result.engineUsed).toBe('tesseract'); + expect(result.fallbackUsed).toBe(false); + expect(mockOcrService.detectAndExtract).toHaveBeenCalledWith({ + pdfPath: '/tmp/file.pdf', + }); + }); + + it('ควรใช้ fast-path engineUsed เมื่อ OcrService คืน ocrUsed=false', async () => { + mockOcrService.detectAndExtract.mockResolvedValueOnce({ + text: 'embedded text', + ocrUsed: false, + }); + const result = await service.detectAndExtract('/tmp/file.pdf', 'auto'); + expect(result.engineUsed).toBe('fast-path'); + expect(result.fallbackUsed).toBe(false); + }); + }); + + describe('detectAndExtract() — engine=tesseract', () => { + it('ควร route ไปยัง OcrService เมื่อ engine=tesseract', async () => { + mockOcrService.detectAndExtract.mockResolvedValueOnce({ + text: 'tesseract text', + ocrUsed: true, + }); + const result = await service.detectAndExtract( + '/tmp/file.pdf', + 'tesseract' + ); + expect(result.engineUsed).toBe('tesseract'); + expect(result.fallbackUsed).toBe(false); + }); + }); + + describe('detectAndExtract() — engine=typhoon-np-dms-ocr (legacy alias)', () => { + it('ควรแปลง typhoon-np-dms-ocr เป็น np-dms-ocr และส่งไปยัง sidecar', async () => { + const mockBuffer = Buffer.from('pdf content'); + (mockedFs.readFileSync as jest.Mock).mockReturnValueOnce(mockBuffer); + mockedAxios.post = jest.fn().mockResolvedValueOnce({ + data: { + text: 'ocr text via alias', + ocrUsed: true, + engineUsed: 'np-dms-ocr', + }, + }); + const result = await service.detectAndExtract( + '/tmp/file.pdf', + 'typhoon-np-dms-ocr' + ); + expect(result.text).toBe('ocr text via alias'); + expect(result.engineUsed).toBe('np-dms-ocr'); + expect(result.fallbackUsed).toBe(false); + }); + }); + + describe('detectAndExtract() — engine=np-dms-ocr (sidecar path)', () => { + it('ควรส่ง file ไปยัง sidecar /ocr-upload สำเร็จ', async () => { + const mockBuffer = Buffer.from('pdf binary data'); + (mockedFs.readFileSync as jest.Mock).mockReturnValueOnce(mockBuffer); + mockedAxios.post = jest.fn().mockResolvedValueOnce({ + data: { + text: 'extracted from typhoon', + ocrUsed: true, + engineUsed: 'np-dms-ocr', + }, + }); + const result = await service.detectAndExtract( + '/tmp/doc.pdf', + 'np-dms-ocr' + ); + expect(result.text).toBe('extracted from typhoon'); + expect(result.ocrUsed).toBe(true); + expect(result.engineUsed).toBe('np-dms-ocr'); + expect(result.fallbackUsed).toBe(false); + expect(mockedAxios.post).toHaveBeenCalledWith( + expect.stringContaining('/ocr-upload'), + expect.any(FormData), + expect.objectContaining({ + headers: expect.objectContaining({ + 'X-API-Key': 'test-api-key-2026', + }), + }) + ); + }); + + it('ควรส่ง typhoonOptions (temperature, topP, repeatPenalty) ไปใน form data', async () => { + const mockBuffer = Buffer.from('pdf data'); + (mockedFs.readFileSync as jest.Mock).mockReturnValueOnce(mockBuffer); + mockedAxios.post = jest.fn().mockResolvedValueOnce({ + data: { text: 'result', ocrUsed: true, engineUsed: 'np-dms-ocr' }, + }); + await service.detectAndExtract('/tmp/doc.pdf', 'np-dms-ocr', { + temperature: 0.5, + topP: 0.8, + repeatPenalty: 1.2, + }); + expect(mockedAxios.post).toHaveBeenCalled(); + }); + + it('ควรใช้ fallback values เมื่อ sidecar response ไม่มี text/ocrUsed/engineUsed', async () => { + const mockBuffer = Buffer.from('pdf data'); + (mockedFs.readFileSync as jest.Mock).mockReturnValueOnce(mockBuffer); + mockedAxios.post = jest.fn().mockResolvedValueOnce({ + data: {}, + }); + const result = await service.detectAndExtract( + '/tmp/doc.pdf', + 'np-dms-ocr' + ); + expect(result.text).toBe(''); + expect(result.ocrUsed).toBe(true); + expect(result.engineUsed).toBe('np-dms-ocr'); // resolvedEngineType fallback + }); + + it('ควร fallback ไปยัง Tesseract เมื่อ fs.readFileSync ล้มเหลว (outer catch fallback)', async () => { + (mockedFs.readFileSync as jest.Mock).mockImplementationOnce(() => { + throw new Error('ENOENT: file not found'); + }); + // service จะ catch error และ fallback ไปยัง Tesseract + mockOcrService.detectAndExtract.mockResolvedValueOnce({ + text: 'tesseract fallback text', + ocrUsed: true, + }); + const result = await service.detectAndExtract( + '/tmp/missing.pdf', + 'np-dms-ocr' + ); + expect(result.fallbackUsed).toBe(true); + expect(result.engineUsed).toBe('tesseract'); + }); + + it('ควร fallback ไปยัง Tesseract เมื่อ sidecar HTTP error เกิดขึ้น', async () => { + const mockBuffer = Buffer.from('pdf data'); + (mockedFs.readFileSync as jest.Mock).mockReturnValueOnce(mockBuffer); + mockedAxios.post = jest.fn().mockRejectedValueOnce( + Object.assign(new Error('Request failed'), { + response: { status: 500, data: { detail: 'Internal Server Error' } }, + }) + ); + mockOcrService.detectAndExtract.mockResolvedValueOnce({ + text: 'tesseract fallback result', + ocrUsed: true, + }); + const result = await service.detectAndExtract( + '/tmp/doc.pdf', + 'np-dms-ocr' + ); + expect(result.text).toBe('tesseract fallback result'); + expect(result.fallbackUsed).toBe(true); + expect(result.engineUsed).toBe('tesseract'); + }); + + it('ควร fallback ไปยัง fast-path เมื่อ sidecar error และ OcrService ส่ง ocrUsed=false', async () => { + const mockBuffer = Buffer.from('pdf data'); + (mockedFs.readFileSync as jest.Mock).mockReturnValueOnce(mockBuffer); + mockedAxios.post = jest + .fn() + .mockRejectedValueOnce(new Error('Connection refused')); + mockOcrService.detectAndExtract.mockResolvedValueOnce({ + text: 'embedded text', + ocrUsed: false, + }); + const result = await service.detectAndExtract( + '/tmp/doc.pdf', + 'np-dms-ocr' + ); + expect(result.engineUsed).toBe('fast-path'); + expect(result.fallbackUsed).toBe(true); + }); + }); + + describe('detectAndExtract() — default engine (no arg)', () => { + it('ควรใช้ auto เป็น default engine เมื่อไม่ระบุ engineType', async () => { + mockOcrService.detectAndExtract.mockResolvedValueOnce({ + text: 'default text', + ocrUsed: false, + }); + const result = await service.detectAndExtract('/tmp/file.pdf'); + expect(result.fallbackUsed).toBe(false); + }); + }); + + describe('detectAndExtract() — edge cases', () => { + it('ควร handle axios error ที่ไม่มี response.status gracefully', async () => { + const mockBuffer = Buffer.from('pdf data'); + (mockedFs.readFileSync as jest.Mock).mockReturnValueOnce(mockBuffer); + mockedAxios.post = jest + .fn() + .mockRejectedValueOnce(new Error('Network unreachable')); + mockOcrService.detectAndExtract.mockResolvedValueOnce({ + text: 'fallback text', + ocrUsed: true, + }); + const result = await service.detectAndExtract( + '/tmp/doc.pdf', + 'np-dms-ocr' + ); + expect(result.fallbackUsed).toBe(true); + }); + }); +}); diff --git a/backend/src/modules/ai/tests/ai-execution-profiles.service.spec.ts b/backend/src/modules/ai/tests/ai-execution-profiles.service.spec.ts new file mode 100644 index 00000000..a3e633b5 --- /dev/null +++ b/backend/src/modules/ai/tests/ai-execution-profiles.service.spec.ts @@ -0,0 +1,422 @@ +// File: backend/src/modules/ai/tests/ai-execution-profiles.service.spec.ts +// Change Log: +// - 2026-06-14: สร้าง unit tests สำหรับ AiPolicyService ที่ครอบคลุม execution profile management (T041) + +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { AiPolicyService } from '../services/ai-policy.service'; +import { AiExecutionProfile } from '../entities/ai-execution-profile.entity'; +import { AiSandboxProfile } from '../entities/ai-sandbox-profile.entity'; +import { BadRequestException } from '@nestjs/common'; + +/** Mock Redis สำหรับ inject */ +const mockRedis = { + get: jest.fn(), + set: jest.fn(), + del: jest.fn(), +}; + +/** Mock repository สำหรับ AiExecutionProfile */ +const mockProfileRepo = { + findOne: jest.fn(), + create: jest.fn(), + save: jest.fn(), +}; + +/** Mock repository สำหรับ AiSandboxProfile */ +const mockSandboxRepo = { + findOne: jest.fn(), + create: jest.fn(), + save: jest.fn(), +}; + +/** สร้าง AiExecutionProfile stub */ +const makeProfile = ( + overrides: Partial = {} +): AiExecutionProfile => + ({ + id: 1, + profileName: 'standard', + canonicalModel: 'np-dms-ai', + temperature: 0.5, + topP: 0.8, + maxTokens: 4096, + numCtx: 8192, + repeatPenalty: 1.15, + keepAliveSeconds: 600, + isActive: true, + updatedBy: undefined, + createdAt: new Date(), + updatedAt: new Date(), + ...overrides, + }) as AiExecutionProfile; + +/** สร้าง AiSandboxProfile stub */ +const makeSandbox = ( + overrides: Partial = {} +): AiSandboxProfile => + ({ + id: 1, + profileName: 'standard', + canonicalModel: 'np-dms-ai', + temperature: 0.6, + topP: 0.9, + maxTokens: 4096, + numCtx: 8192, + repeatPenalty: 1.15, + keepAliveSeconds: 600, + updatedBy: undefined, + createdAt: new Date(), + updatedAt: new Date(), + ...overrides, + }) as AiSandboxProfile; + +describe('AiPolicyService — Execution Profile Management (T041)', () => { + let service: AiPolicyService; + + beforeEach(async () => { + jest.clearAllMocks(); + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AiPolicyService, + { + provide: getRepositoryToken(AiExecutionProfile), + useValue: mockProfileRepo, + }, + { + provide: getRepositoryToken(AiSandboxProfile), + useValue: mockSandboxRepo, + }, + { + provide: 'default_IORedisModuleConnectionToken', + useValue: mockRedis, + }, + ], + }).compile(); + service = module.get(AiPolicyService); + }); + + // ─── getCanonicalModelName ─────────────────────────────────────────────────── + describe('getCanonicalModelName()', () => { + it('ควรคืน np-dms-ocr เมื่อ modelName มีคำว่า ocr', () => { + expect(service.getCanonicalModelName('np-dms-ocr:latest')).toBe( + 'np-dms-ocr' + ); + }); + it('ควรคืน np-dms-ocr เมื่อ modelName มีคำว่า typhoon-np-dms-ocr', () => { + expect(service.getCanonicalModelName('typhoon-np-dms-ocr:latest')).toBe( + 'np-dms-ocr' + ); + }); + it('ควรคืน np-dms-ai สำหรับ model ทั่วไปที่ไม่มีคำว่า ocr', () => { + expect(service.getCanonicalModelName('np-dms-ai:latest')).toBe( + 'np-dms-ai' + ); + }); + it('ควรคืน np-dms-ai สำหรับ typhoon2.5 model (main model)', () => { + expect(service.getCanonicalModelName('typhoon2.5-np-dms:latest')).toBe( + 'np-dms-ai' + ); + }); + }); + + // ─── getProfileForJobType ──────────────────────────────────────────────────── + describe('getProfileForJobType()', () => { + it('ควรคืน quality สำหรับ auto-fill-document', () => { + expect(service.getProfileForJobType('auto-fill-document')).toBe( + 'quality' + ); + }); + it('ควรคืน quality สำหรับ migrate-document', () => { + expect(service.getProfileForJobType('migrate-document')).toBe('quality'); + }); + it('ควรคืน standard สำหรับ rag-query', () => { + expect(service.getProfileForJobType('rag-query')).toBe('standard'); + }); + it('ควรคืน interactive สำหรับ intent-classify', () => { + expect(service.getProfileForJobType('intent-classify')).toBe( + 'interactive' + ); + }); + it('ควรคืน interactive สำหรับ tool-suggest', () => { + expect(service.getProfileForJobType('tool-suggest')).toBe('interactive'); + }); + it('ควรคืน deep-analysis สำหรับ sandbox-analysis', () => { + expect(service.getProfileForJobType('sandbox-analysis')).toBe( + 'deep-analysis' + ); + }); + it('ควรคืน standard เป็น default สำหรับ ocr-extract', () => { + expect(service.getProfileForJobType('ocr-extract')).toBe('standard'); + }); + }); + + // ─── getProfileParameters ──────────────────────────────────────────────────── + describe('getProfileParameters()', () => { + it('ควรคืนจาก Redis cache เมื่อมี cache hit', async () => { + const cachedPolicy = { + canonicalModel: 'np-dms-ai', + temperature: 0.5, + topP: 0.8, + maxTokens: 4096, + numCtx: 8192, + repeatPenalty: 1.15, + keepAliveSeconds: 600, + }; + mockRedis.get.mockResolvedValueOnce(JSON.stringify(cachedPolicy)); + const result = await service.getProfileParameters('standard'); + expect(result.temperature).toBe(0.5); + expect(mockProfileRepo.findOne).not.toHaveBeenCalled(); + }); + + it('ควร fallback ไปยัง DB เมื่อ Redis cache miss', async () => { + mockRedis.get.mockResolvedValueOnce(null); // cache miss + mockProfileRepo.findOne.mockResolvedValueOnce( + makeProfile({ temperature: 0.3 }) + ); + mockRedis.set.mockResolvedValueOnce('OK'); + const result = await service.getProfileParameters('standard'); + expect(result.temperature).toBe(0.3); + expect(mockProfileRepo.findOne).toHaveBeenCalledTimes(1); + }); + + it('ควร fallback ไปยัง hardcoded defaults เมื่อ DB ก็ไม่มีข้อมูล', async () => { + mockRedis.get.mockResolvedValueOnce(null); + mockProfileRepo.findOne.mockResolvedValueOnce(null); // ไม่มีใน DB + const result = await service.getProfileParameters('quality'); + expect(result.temperature).toBe(0.1); // default quality profile + }); + + it('ควร fallback ไปยัง DB เมื่อ Redis throw error', async () => { + mockRedis.get.mockRejectedValueOnce(new Error('Redis CONN error')); + mockProfileRepo.findOne.mockResolvedValueOnce(makeProfile()); + mockRedis.set.mockResolvedValueOnce('OK'); + const result = await service.getProfileParameters('standard'); + expect(result).toBeDefined(); + }); + + it('ควร fallback ไปยัง defaults เมื่อ DB ก็ throw error', async () => { + mockRedis.get.mockResolvedValueOnce(null); + mockProfileRepo.findOne.mockRejectedValueOnce(new Error('DB timeout')); + const result = await service.getProfileParameters('interactive'); + expect(result.temperature).toBe(0.7); // default interactive profile + }); + + it('ควรไม่ throw เมื่อ cache write ล้มเหลว (graceful)', async () => { + mockRedis.get.mockResolvedValueOnce(null); + mockProfileRepo.findOne.mockResolvedValueOnce(makeProfile()); + mockRedis.set.mockRejectedValueOnce(new Error('Redis write failed')); + const result = await service.getProfileParameters('standard'); + expect(result).toBeDefined(); + }); + }); + + // ─── getModelDefaults ──────────────────────────────────────────────────────── + describe('getModelDefaults()', () => { + it('ควรคืนจาก Redis cache เมื่อมี cache hit', async () => { + const cachedOcrPolicy = { + canonicalModel: 'np-dms-ocr', + temperature: 0.1, + topP: 0.1, + maxTokens: null, + numCtx: null, + repeatPenalty: 1.1, + keepAliveSeconds: 0, + }; + mockRedis.get.mockResolvedValueOnce(JSON.stringify(cachedOcrPolicy)); + const result = await service.getModelDefaults('np-dms-ocr'); + expect(result.canonicalModel).toBe('np-dms-ocr'); + expect(result.temperature).toBe(0.1); + }); + + it('ควร fallback ไปยัง DB เมื่อ Redis cache miss', async () => { + mockRedis.get.mockResolvedValueOnce(null); + mockProfileRepo.findOne.mockResolvedValueOnce( + makeProfile({ canonicalModel: 'np-dms-ocr', temperature: 0.05 }) + ); + mockRedis.set.mockResolvedValueOnce('OK'); + const result = await service.getModelDefaults('np-dms-ocr'); + expect(result.temperature).toBe(0.05); + }); + + it('ควรคืน defaultOcrPolicy เมื่อไม่มีใน DB (np-dms-ocr)', async () => { + mockRedis.get.mockResolvedValueOnce(null); + mockProfileRepo.findOne.mockResolvedValueOnce(null); + const result = await service.getModelDefaults('np-dms-ocr'); + expect(result.canonicalModel).toBe('np-dms-ocr'); + expect(result.keepAliveSeconds).toBe(0); + }); + + it('ควรคืน standard defaults เมื่อไม่มีใน DB (np-dms-ai)', async () => { + mockRedis.get.mockResolvedValueOnce(null); + mockProfileRepo.findOne.mockResolvedValueOnce(null); + const result = await service.getModelDefaults('np-dms-ai'); + expect(result.canonicalModel).toBe('np-dms-ai'); + }); + }); + + // ─── saveSandboxDraft ──────────────────────────────────────────────────────── + describe('saveSandboxDraft()', () => { + it('ควรอัปเดต draft ที่มีอยู่แล้ว', async () => { + const existingDraft = makeSandbox({ temperature: 0.5 }); + mockSandboxRepo.findOne.mockResolvedValueOnce(existingDraft); + mockSandboxRepo.save.mockResolvedValueOnce({ + ...existingDraft, + temperature: 0.8, + }); + const result = await service.saveSandboxDraft('standard', { + temperature: 0.8, + }); + expect(result.temperature).toBe(0.8); + }); + + it('ควรสร้าง draft ใหม่จาก production เมื่อยังไม่มี draft', async () => { + mockSandboxRepo.findOne.mockResolvedValueOnce(null); // ไม่มี draft + // getProductionPolicy → getProfileParameters → Redis miss → DB + mockRedis.get.mockResolvedValueOnce(null); + mockProfileRepo.findOne.mockResolvedValueOnce(makeProfile()); + mockRedis.set.mockResolvedValueOnce('OK'); + const newDraft = makeSandbox({ topP: 0.9 }); + mockSandboxRepo.create.mockReturnValueOnce(newDraft); + mockSandboxRepo.save.mockResolvedValueOnce({ ...newDraft, topP: 0.9 }); + const result = await service.saveSandboxDraft( + 'standard', + { topP: 0.9 }, + 1 + ); + expect(result.topP).toBe(0.9); + }); + }); + + // ─── resetSandboxToProduction ──────────────────────────────────────────────── + describe('resetSandboxToProduction()', () => { + it('ควร reset draft ที่มีอยู่ให้ตรงกับ production', async () => { + mockRedis.get.mockResolvedValueOnce(null); + mockProfileRepo.findOne.mockResolvedValueOnce( + makeProfile({ temperature: 0.5 }) + ); + mockRedis.set.mockResolvedValueOnce('OK'); + const existingDraft = makeSandbox({ temperature: 0.9 }); + mockSandboxRepo.findOne.mockResolvedValueOnce(existingDraft); + mockSandboxRepo.save.mockResolvedValueOnce({ + ...existingDraft, + temperature: 0.5, + }); + const result = await service.resetSandboxToProduction('standard', 1); + expect(result.temperature).toBe(0.5); + }); + + it('ควรสร้าง draft ใหม่เมื่อยังไม่มี draft แล้ว reset ไปยัง production', async () => { + mockRedis.get.mockResolvedValueOnce(null); + mockProfileRepo.findOne.mockResolvedValueOnce( + makeProfile({ temperature: 0.5 }) + ); + mockRedis.set.mockResolvedValueOnce('OK'); + mockSandboxRepo.findOne.mockResolvedValueOnce(null); // ไม่มี draft + const newDraft = makeSandbox(); + mockSandboxRepo.create.mockReturnValueOnce(newDraft); + mockSandboxRepo.save.mockResolvedValueOnce({ + ...newDraft, + temperature: 0.5, + }); + const result = await service.resetSandboxToProduction('standard'); + expect(result).toBeDefined(); + }); + }); + + // ─── createJobPayload ──────────────────────────────────────────────────────── + describe('createJobPayload()', () => { + it('ควรสร้าง payload ที่ถูกต้องสำหรับ rag-query job', async () => { + mockRedis.get.mockResolvedValue(null); + mockProfileRepo.findOne.mockResolvedValue(makeProfile()); + mockRedis.set.mockResolvedValue('OK'); + const payload = await service.createJobPayload( + 'rag-query', + 'doc-id-123', + 'att-id-456' + ); + expect(payload.jobType).toBe('rag-query'); + expect(payload.documentPublicId).toBe('doc-id-123'); + expect(payload.canonicalModel).toBe('np-dms-ai'); + expect(payload.snapshotParams.temperature).toBeDefined(); + expect(payload.ocrSnapshotParams).toBeUndefined(); // rag-query ไม่มี OCR snapshot + }); + + it('ควรสร้าง payload ที่มี ocrSnapshotParams สำหรับ migrate-document job', async () => { + mockRedis.get.mockResolvedValue(null); + mockProfileRepo.findOne.mockResolvedValue(makeProfile()); + mockRedis.set.mockResolvedValue('OK'); + const payload = await service.createJobPayload( + 'migrate-document', + 'doc-id-789' + ); + expect(payload.canonicalModel).toBe('np-dms-ai'); // main model for migrate + expect(payload.ocrSnapshotParams).toBeDefined(); + expect(payload.ocrSnapshotParams?.temperature).toBeDefined(); + }); + + it('ควรสร้าง payload ที่ใช้ np-dms-ocr สำหรับ ocr-extract job', async () => { + mockRedis.get.mockResolvedValue(null); + mockProfileRepo.findOne.mockResolvedValue( + makeProfile({ canonicalModel: 'np-dms-ocr', temperature: 0.1 }) + ); + mockRedis.set.mockResolvedValue('OK'); + const payload = await service.createJobPayload( + 'ocr-extract', + 'doc-id-ocr' + ); + expect(payload.canonicalModel).toBe('np-dms-ocr'); + expect(payload.ocrSnapshotParams).toBeDefined(); + }); + }); + + // ─── applyProfile validation ───────────────────────────────────────────────── + describe('applyProfile() — parameter validation', () => { + it('ควรโยน BadRequestException เมื่อ temperature > 1', async () => { + const draft = makeSandbox({ temperature: 1.5, profileName: 'standard' }); + mockSandboxRepo.findOne.mockResolvedValueOnce(draft); + await expect(service.applyProfile('standard', 1)).rejects.toBeInstanceOf( + BadRequestException + ); + }); + + it('ควรโยน BadRequestException เมื่อ topP < 0', async () => { + const draft = makeSandbox({ + temperature: 0.5, + topP: -0.1, + profileName: 'standard', + }); + mockSandboxRepo.findOne.mockResolvedValueOnce(draft); + await expect(service.applyProfile('standard', 1)).rejects.toBeInstanceOf( + BadRequestException + ); + }); + + it('ควรโยน BadRequestException เมื่อ repeatPenalty < 1', async () => { + const draft = makeSandbox({ + temperature: 0.5, + topP: 0.8, + repeatPenalty: 0.9, + profileName: 'standard', + }); + mockSandboxRepo.findOne.mockResolvedValueOnce(draft); + await expect(service.applyProfile('standard')).rejects.toBeInstanceOf( + BadRequestException + ); + }); + + it('ควรโยน BadRequestException เมื่อ keepAliveSeconds < 0', async () => { + const draft = makeSandbox({ + temperature: 0.5, + topP: 0.8, + repeatPenalty: 1.1, + keepAliveSeconds: -1, + profileName: 'standard', + }); + mockSandboxRepo.findOne.mockResolvedValueOnce(draft); + await expect(service.applyProfile('standard')).rejects.toBeInstanceOf( + BadRequestException + ); + }); + }); +}); diff --git a/backend/src/modules/ai/tests/vram-monitor.service.spec.ts b/backend/src/modules/ai/tests/vram-monitor.service.spec.ts index b04f58a8..8479d8ba 100644 --- a/backend/src/modules/ai/tests/vram-monitor.service.spec.ts +++ b/backend/src/modules/ai/tests/vram-monitor.service.spec.ts @@ -1,6 +1,7 @@ // File: backend/src/modules/ai/tests/vram-monitor.service.spec.ts // Change Log: // - 2026-06-11: สร้าง unit tests สำหรับ VramMonitorService (US5) +// - 2026-06-14: เพิ่ม tests สำหรับ getVramStatus และ invalidateCache เพื่อเพิ่ม branch/function coverage import { Test, TestingModule } from '@nestjs/testing'; import { ConfigService } from '@nestjs/config'; @@ -99,4 +100,61 @@ describe('VramMonitorService', () => { expect(result).toBe(false); }); }); + describe('getVramStatus', () => { + it('ควรคืน status ที่ถูกต้องเมื่อ Ollama คืน models', async () => { + mockedAxios.get + .mockResolvedValueOnce({ + // first call: /api/ps ใน getVramStatus + data: { + models: [ + { name: 'np-dms-ai:latest', size_vram: 3 * 1024 * 1024 * 1024 }, + ], + }, + }) + .mockResolvedValueOnce({ + // second call: /api/ps ใน getVramHeadroom + data: { + models: [ + { name: 'np-dms-ai:latest', size_vram: 3 * 1024 * 1024 * 1024 }, + ], + }, + }); + const status = await service.getVramStatus(4000); + expect(status.loadedModels).toContain('np-dms-ai:latest'); + expect(status.totalVramMb).toBe(8192); + expect(status.hasCapacity).toBe(true); // 8192MB - 3072MB = 5120MB free > 4000MB required + }); + it('ควรคืน hasCapacity=true เมื่อมี VRAM เหลือเพียงพอ', async () => { + mockedAxios.get + .mockResolvedValueOnce({ + data: { + models: [ + { name: 'np-dms-ai:latest', size_vram: 1 * 1024 * 1024 * 1024 }, + ], + }, + }) + .mockResolvedValueOnce({ + data: { + models: [ + { name: 'np-dms-ai:latest', size_vram: 1 * 1024 * 1024 * 1024 }, + ], + }, + }); + const status = await service.getVramStatus(4000); + // 8192MB total - 1024MB used = 7168MB free > 4000MB + expect(status.hasCapacity).toBe(true); + }); + it('ควรคืน fallback (hasCapacity=false) เมื่อ /api/ps ล้มเหลว', async () => { + mockedAxios.get.mockRejectedValue(new Error('Network error')); + const status = await service.getVramStatus(); + expect(status.hasCapacity).toBe(false); + expect(status.freeVramMb).toBe(0); + expect(status.loadedModels).toEqual([]); + }); + }); + describe('invalidateCache', () => { + it('ควร resolve โดยไม่ throw (no-op)', async () => { + await expect(service.invalidateCache()).resolves.toBeUndefined(); + }); + }); }); diff --git a/backend/src/modules/correspondence/correspondence.controller.ts b/backend/src/modules/correspondence/correspondence.controller.ts index 79982d33..813bebee 100644 --- a/backend/src/modules/correspondence/correspondence.controller.ts +++ b/backend/src/modules/correspondence/correspondence.controller.ts @@ -4,6 +4,7 @@ import { Post, Body, UseGuards, + UseInterceptors, Request, Param, Query, @@ -36,6 +37,8 @@ import { RbacGuard } from '../../common/guards/rbac.guard'; import { RequirePermission } from '../../common/decorators/require-permission.decorator'; import { Audit } from '../../common/decorators/audit.decorator'; import { ParseUuidPipe } from '../../common/pipes/parse-uuid.pipe'; +import { ValidationException } from '../../common/exceptions'; +import { IdempotencyInterceptor } from '../../common/interceptors/idempotency.interceptor'; import type { RequestWithUser } from '../../common/interfaces/request-with-user.interface'; @ApiTags('Correspondences') @@ -52,6 +55,8 @@ export class CorrespondenceController { @ApiOperation({ summary: 'Process workflow action (Approve/Reject/Review)' }) @ApiResponse({ status: 201, description: 'Action processed successfully.' }) @RequirePermission('workflow.action_review') + @Audit('correspondence.workflow_action', 'correspondence') + @UseInterceptors(IdempotencyInterceptor) processAction( @Body() actionDto: WorkflowActionDto, @Request() req: RequestWithUser @@ -62,7 +67,9 @@ export class CorrespondenceController { // Use Unified Workflow Engine via CorrespondenceWorkflowService if (!actionDto.instanceId) { - throw new Error('instanceId is required for workflow action'); + throw new ValidationException( + 'instanceId is required for workflow action' + ); } return this.workflowService.processAction( @@ -85,6 +92,7 @@ export class CorrespondenceController { }) @RequirePermission('correspondence.create') @Audit('correspondence.create', 'correspondence') + @UseInterceptors(IdempotencyInterceptor) create( @Body() createDto: CreateCorrespondenceDto, @Request() req: RequestWithUser @@ -125,6 +133,7 @@ export class CorrespondenceController { }) @RequirePermission('correspondence.create') @Audit('correspondence.submit', 'correspondence') + @UseInterceptors(IdempotencyInterceptor) async submit( @Param('uuid', ParseUuidPipe) uuid: string, @Body() submitDto: SubmitCorrespondenceDto, @@ -158,8 +167,9 @@ export class CorrespondenceController { status: 200, description: 'Correspondence updated successfully.', }) - @RequirePermission('correspondence.create') // Assuming create permission is enough for draft update, or add 'correspondence.edit' + @RequirePermission('correspondence.edit') @Audit('correspondence.update', 'correspondence') + @UseInterceptors(IdempotencyInterceptor) async update( @Param('uuid', ParseUuidPipe) uuid: string, @Body() updateDto: UpdateCorrespondenceDto, @@ -241,6 +251,7 @@ export class CorrespondenceController { @ApiOperation({ summary: 'Bulk cancel correspondences (Org Admin+)' }) @RequirePermission('correspondence.cancel') @Audit('correspondence.bulk_cancel', 'correspondence') + @UseInterceptors(IdempotencyInterceptor) async bulkCancel( @Body() dto: BulkCancelDto, @Request() req: RequestWithUser @@ -274,6 +285,7 @@ export class CorrespondenceController { }) @RequirePermission('correspondence.cancel') @Audit('correspondence.cancel', 'correspondence') + @UseInterceptors(IdempotencyInterceptor) async cancel( @Param('uuid', ParseUuidPipe) uuid: string, @Body() cancelDto: CancelCorrespondenceDto, diff --git a/backend/src/modules/correspondence/correspondence.service.ts b/backend/src/modules/correspondence/correspondence.service.ts index ba1697c0..bf1550f8 100644 --- a/backend/src/modules/correspondence/correspondence.service.ts +++ b/backend/src/modules/correspondence/correspondence.service.ts @@ -890,6 +890,11 @@ export class CorrespondenceService { const updated = await this.findOne(id); // Re-index updated document in Elasticsearch (fire-and-forget) + // ใช้ status จริงจาก current revision แทนการ hardcode 'DRAFT' + const currentRevisionStatus = + updated.revisions?.find((r) => r.isCurrent)?.status?.statusCode ?? + updated.revisions?.[0]?.status?.statusCode ?? + 'DRAFT'; void this.searchService.indexDocument({ id: updated.id, publicId: updated.publicId, @@ -897,7 +902,7 @@ export class CorrespondenceService { docNumber: updated.correspondenceNumber, title: updateDto.subject ?? updated.revisions?.[0]?.subject, description: updateDto.description ?? updated.revisions?.[0]?.description, - status: 'DRAFT', + status: currentRevisionStatus, projectId: updated.projectId, createdAt: updated.createdAt, }); @@ -1141,7 +1146,10 @@ export class CorrespondenceService { try { await this.cancel(publicId, reason, user); succeeded.push(publicId); - } catch { + } catch (err) { + this.logger.warn( + `Bulk cancel failed for ${publicId}: ${(err as Error).message}` + ); failed.push(publicId); } } @@ -1150,7 +1158,12 @@ export class CorrespondenceService { } async exportCsv(searchDto: SearchCorrespondenceDto): Promise { - const { data } = await this.findAll(searchDto); + // ดึงทุกแถวที่ตรงเงื่อนไข — ไม่ใช้ pagination สำหรับ export + const { data } = await this.findAll({ + ...searchDto, + page: 1, + limit: 10000, + }); const header = [ 'Document No.', @@ -1182,9 +1195,12 @@ export class CorrespondenceService { } private escapeCsv(value: string): string { - if (value.includes(',') || value.includes('"') || value.includes('\n')) { - return `"${value.replace(/"/g, '""')}"`; + // กัน CSV formula injection (OWASP) + let v = value; + if (/^[=+\-@\t\r]/.test(v)) v = `'${v}`; + if (v.includes(',') || v.includes('"') || v.includes('\n')) { + return `"${v.replace(/"/g, '""')}"`; } - return value; + return v; } } diff --git a/backend/src/modules/correspondence/dto/create-correspondence.dto.ts b/backend/src/modules/correspondence/dto/create-correspondence.dto.ts index 86487d77..4d4b7fc7 100644 --- a/backend/src/modules/correspondence/dto/create-correspondence.dto.ts +++ b/backend/src/modules/correspondence/dto/create-correspondence.dto.ts @@ -7,9 +7,32 @@ import { IsObject, IsDateString, IsArray, + IsIn, + ValidateNested, } from 'class-validator'; +import { Type } from 'class-transformer'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +/** + * DTO ของผู้รับเอกสาร — ใช้กับ @ValidateNested เพื่อตรวจสอบแต่ละ element ใน recipients array + */ +export class RecipientDto { + @ApiProperty({ + description: 'Organization ID or UUID ของผู้รับ', + example: '019505a1-7c3e-7000-8000-abc123def456', + }) + @IsNotEmpty() + organizationId!: number | string; + + @ApiProperty({ + description: 'ประเภทผู้รับ: TO หรือ CC', + enum: ['TO', 'CC'], + example: 'TO', + }) + @IsIn(['TO', 'CC']) + type!: 'TO' | 'CC'; +} + export class CreateCorrespondenceDto { @ApiProperty({ description: 'Project ID or UUID', example: 1 }) @IsNotEmpty() @@ -125,9 +148,15 @@ export class CreateCorrespondenceDto { @ApiPropertyOptional({ description: 'Recipients', - example: [{ organizationId: 1, type: 'TO' }], + example: [ + { organizationId: '019505a1-7c3e-7000-8000-abc123def456', type: 'TO' }, + ], + type: () => RecipientDto, + isArray: true, }) @IsArray() @IsOptional() - recipients?: { organizationId: number | string; type: 'TO' | 'CC' }[]; + @ValidateNested({ each: true }) + @Type(() => RecipientDto) + recipients?: RecipientDto[]; } diff --git a/backend/src/modules/rfa/dto/submit-rfa.dto.ts b/backend/src/modules/rfa/dto/submit-rfa.dto.ts index e8374b9f..e2b1700b 100644 --- a/backend/src/modules/rfa/dto/submit-rfa.dto.ts +++ b/backend/src/modules/rfa/dto/submit-rfa.dto.ts @@ -1,16 +1,10 @@ // File: src/modules/rfa/dto/submit-rfa.dto.ts +// Change Log: +// - 2026-06-14: ตัด templateId ออก — ย้ายไปใช้ Unified Workflow Engine (ADR-001) import { ApiProperty } from '@nestjs/swagger'; -import { IsInt, IsNotEmpty, IsOptional, IsUUID } from 'class-validator'; +import { IsOptional, IsUUID } from 'class-validator'; export class SubmitRfaDto { - @ApiProperty({ - description: 'ID ของ Routing Template ที่จะใช้เดินเรื่อง', - example: 1, - }) - @IsInt() - @IsNotEmpty() - templateId!: number; - @ApiProperty({ description: 'publicId ของ Review Team สำหรับ Parallel Review (ADR-019)', example: '019505a1-7c3e-7000-8000-abc123def456', diff --git a/backend/src/modules/rfa/rfa.controller.ts b/backend/src/modules/rfa/rfa.controller.ts index 900c144b..2a49b5df 100644 --- a/backend/src/modules/rfa/rfa.controller.ts +++ b/backend/src/modules/rfa/rfa.controller.ts @@ -1,11 +1,14 @@ // File: src/modules/rfa/rfa.controller.ts // Change Log: // - 2026-05-13: Wire submit reviewTeamPublicId through to the submit workflow for parallel review task creation. +// - 2026-06-14: ADR-016 Idempotency-Key enforcement on mutations; pass RBAC roles to Unified Workflow Engine; drop templateId. import { + BadRequestException, Body, Controller, Delete, Get, + Headers, HttpCode, HttpStatus, Param, @@ -51,13 +54,34 @@ export class RfaController { private readonly uuidResolver: UuidResolverService ) {} + /** ADR-016: บังคับให้ทุก mutation ส่ง Idempotency-Key header */ + private assertIdempotencyKey(idempotencyKey?: string): void { + if (!idempotencyKey) { + throw new BadRequestException('Idempotency-Key header is required'); + } + } + + /** ดึง role name จาก user assignments เพื่อส่งให้ Unified Workflow Engine ตรวจ DSL requirements */ + private extractRoles(user: User): string[] { + return ( + user.assignments + ?.map((a) => a.role?.roleName) + .filter((name): name is string => Boolean(name)) ?? [] + ); + } + @Post() @ApiOperation({ summary: 'Create new RFA (Draft)' }) @ApiBody({ type: CreateRfaDto }) @ApiResponse({ status: 201, description: 'RFA created successfully' }) @RequirePermission('rfa.create') @Audit('rfa.create', 'rfa') - create(@Body() createDto: CreateRfaDto, @CurrentUser() user: User) { + create( + @Body() createDto: CreateRfaDto, + @CurrentUser() user: User, + @Headers('Idempotency-Key') idempotencyKey: string + ) { + this.assertIdempotencyKey(idempotencyKey); return this.rfaService.create(createDto, user); } @@ -74,15 +98,17 @@ export class RfaController { async submit( @Param('uuid', ParseUuidPipe) uuid: string, @Body() submitDto: SubmitRfaDto, - @CurrentUser() user: User + @CurrentUser() user: User, + @Headers('Idempotency-Key') idempotencyKey: string ) { + this.assertIdempotencyKey(idempotencyKey); // ADR-019: resolve UUID → internal INT id via findOneByUuidRaw const rfa = await this.rfaService.findOneByUuidRaw(uuid); return this.rfaService.submit( rfa.id, - submitDto.templateId, user, - submitDto.reviewTeamPublicId + submitDto.reviewTeamPublicId, + this.extractRoles(user) ); } @@ -102,11 +128,18 @@ export class RfaController { async processAction( @Param('uuid', ParseUuidPipe) uuid: string, @Body() actionDto: WorkflowActionDto, - @CurrentUser() user: User + @CurrentUser() user: User, + @Headers('Idempotency-Key') idempotencyKey: string ) { + this.assertIdempotencyKey(idempotencyKey); // ADR-019: resolve UUID → internal INT id const rfa = await this.rfaService.findOneByUuidRaw(uuid); - return this.rfaService.processAction(rfa.id, actionDto, user); + return this.rfaService.processAction( + rfa.id, + actionDto, + user, + this.extractRoles(user) + ); } @Get() @@ -145,8 +178,10 @@ export class RfaController { async update( @Param('uuid', ParseUuidPipe) uuid: string, @Body() updateDto: UpdateRfaDto, - @CurrentUser() user: User + @CurrentUser() user: User, + @Headers('Idempotency-Key') idempotencyKey: string ) { + this.assertIdempotencyKey(idempotencyKey); return this.rfaService.update(uuid, updateDto, user); } @@ -159,8 +194,10 @@ export class RfaController { @Audit('rfa.cancel', 'rfa') async cancel( @Param('uuid', ParseUuidPipe) uuid: string, - @CurrentUser() user: User + @CurrentUser() user: User, + @Headers('Idempotency-Key') idempotencyKey: string ) { + this.assertIdempotencyKey(idempotencyKey); return this.rfaService.cancel(uuid, user); } } diff --git a/backend/src/modules/rfa/rfa.module.ts b/backend/src/modules/rfa/rfa.module.ts index 352f089b..23023834 100644 --- a/backend/src/modules/rfa/rfa.module.ts +++ b/backend/src/modules/rfa/rfa.module.ts @@ -1,16 +1,15 @@ // File: src/modules/rfa/rfa.module.ts +// Change Log: +// - 2026-06-14: ตัด deprecated routing-template entities + RfaWorkflowService ออก (ADR-001 migration) import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; // Entities -import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity'; import { Correspondence } from '../correspondence/entities/correspondence.entity'; import { CorrespondenceRevision } from '../correspondence/entities/correspondence-revision.entity'; import { CorrespondenceStatus } from '../correspondence/entities/correspondence-status.entity'; import { CorrespondenceType } from '../correspondence/entities/correspondence-type.entity'; import { CorrespondenceRecipient } from '../correspondence/entities/correspondence-recipient.entity'; -import { RoutingTemplate } from '../correspondence/entities/routing-template.entity'; -import { RoutingTemplateStep } from '../correspondence/entities/routing-template-step.entity'; import { Organization } from '../organization/entities/organization.entity'; import { AsBuiltDrawingRevision } from '../drawing/entities/asbuilt-drawing-revision.entity'; import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity'; @@ -20,13 +19,9 @@ import { RfaItem } from './entities/rfa-item.entity'; import { RfaRevision } from './entities/rfa-revision.entity'; import { RfaStatusCode } from './entities/rfa-status-code.entity'; import { RfaType } from './entities/rfa-type.entity'; -import { RfaWorkflowTemplateStep } from './entities/rfa-workflow-template-step.entity'; -import { RfaWorkflowTemplate } from './entities/rfa-workflow-template.entity'; -import { RfaWorkflow } from './entities/rfa-workflow.entity'; import { Rfa } from './entities/rfa.entity'; // Services & Controllers -import { RfaWorkflowService } from './rfa-workflow.service'; // Register Service import { RfaController } from './rfa.controller'; import { RfaService } from './rfa.service'; @@ -55,12 +50,6 @@ import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module' AsBuiltDrawingRevision, ShopDrawingRevision, Discipline, - RfaWorkflow, - RfaWorkflowTemplate, - RfaWorkflowTemplateStep, - CorrespondenceRouting, - RoutingTemplate, - RoutingTemplateStep, CorrespondenceRecipient, Organization, ]), @@ -72,7 +61,7 @@ import { WorkflowEngineModule } from '../workflow-engine/workflow-engine.module' WorkflowEngineModule, NotificationModule, ], - providers: [RfaService, RfaWorkflowService], + providers: [RfaService], controllers: [RfaController], exports: [RfaService], }) diff --git a/backend/src/modules/rfa/rfa.service.ts b/backend/src/modules/rfa/rfa.service.ts index b4e48da6..3d0040c6 100644 --- a/backend/src/modules/rfa/rfa.service.ts +++ b/backend/src/modules/rfa/rfa.service.ts @@ -1,6 +1,9 @@ // File: src/modules/rfa/rfa.service.ts // Change Log: // - 2026-05-13: Invoke TaskCreationService during submit when a review team is selected. +// - 2026-06-14: ADR-001/021 migration — submit()/processAction() เดินผ่าน Unified Workflow Engine +// (เลิกใช้ RoutingTemplate/CorrespondenceRouting), ตัด templateId, ย้าย notification ออกนอก transaction, +// ทำ EC-RFA-001 ให้ race-safe (lock FOR UPDATE), เลิก hardcode approve code. import { Injectable, Logger } from '@nestjs/common'; import { @@ -15,14 +18,11 @@ import { InjectRepository } from '@nestjs/typeorm'; import { DataSource, In, Repository } from 'typeorm'; // Entities -import { CorrespondenceRouting } from '../correspondence/entities/correspondence-routing.entity'; import { CorrespondenceRecipient } from '../correspondence/entities/correspondence-recipient.entity'; import { Correspondence } from '../correspondence/entities/correspondence.entity'; import { CorrespondenceRevision } from '../correspondence/entities/correspondence-revision.entity'; import { CorrespondenceStatus } from '../correspondence/entities/correspondence-status.entity'; import { CorrespondenceType } from '../correspondence/entities/correspondence-type.entity'; -import { RoutingTemplate } from '../correspondence/entities/routing-template.entity'; -import { RoutingTemplateStep } from '../correspondence/entities/routing-template-step.entity'; import { AsBuiltDrawingRevision } from '../drawing/entities/asbuilt-drawing-revision.entity'; import { ShopDrawingRevision } from '../drawing/entities/shop-drawing-revision.entity'; import { Discipline } from '../master/entities/discipline.entity'; @@ -49,6 +49,10 @@ type CorrRevWithRfa = CorrespondenceRevision & { rfaRevision?: RfaRevision }; export interface RfaMapped extends Rfa { publicId?: string; // ADR-019: top-level publicId from correspondence revisions: CorrRevWithRfa[]; + // ADR-021: expose Unified Workflow Engine state for IntegratedBanner/WorkflowLifecycle + workflowInstanceId?: string; + workflowState?: string; + availableActions?: string[]; } // Interfaces & Enums @@ -67,6 +71,20 @@ import { TaskCreationService } from '../review-team/services/task-creation.servi export class RfaService { private readonly logger = new Logger(RfaService.name); + /** ADR-001: รหัส Workflow ที่ลงทะเบียนใน seed DSL */ + static readonly WORKFLOW_CODE = 'RFA_APPROVAL'; + + /** แมป Workflow State → RFA Status Code ตาม seed data */ + static readonly STATE_TO_STATUS: Record = { + DRAFT: 'DFT', + CONSULTANT_REVIEW: 'FRE', + OWNER_REVIEW: 'FAP', + APPROVED: 'FCO', + }; + + /** รหัสอนุมัติเริ่มต้นเมื่อถึงสถานะ Terminal */ + static readonly DEFAULT_APPROVED_CODE = '1A'; + private async hasSystemManageAllPermission(userId: number): Promise { const permissions = await this.userService.getUserPermissions(userId); return permissions.includes('system.manage_all'); @@ -99,12 +117,6 @@ export class RfaService { private asBuiltDrawingRevRepo: Repository, @InjectRepository(ShopDrawingRevision) private shopDrawingRevRepo: Repository, - @InjectRepository(CorrespondenceRouting) - private routingRepo: Repository, - @InjectRepository(RoutingTemplate) - private templateRepo: Repository, - @InjectRepository(RoutingTemplateStep) - private templateStepRepo: Repository, @InjectRepository(Organization) private orgRepo: Repository, @@ -274,30 +286,6 @@ export class RfaService { ); } - // EC-RFA-001: Check for existing active RFA per Shop Drawing Revision - if (shopDrawingRevisionIds.length > 0) { - const conflictingItems = await this.rfaItemRepo - .createQueryBuilder('item') - .innerJoin('item.rfaRevision', 'rfaRev') - .innerJoin('rfaRev.statusCode', 'status') - .where('item.shopDrawingRevisionId IN (:...ids)', { - ids: shopDrawingRevisionIds, - }) - .andWhere('status.statusCode NOT IN (:...codes)', { - codes: ['CC', 'OBS'], - }) - .getMany(); - - if (conflictingItems.length > 0) { - throw new BusinessException( - 'EC_RFA_001_ACTIVE_RFA_EXISTS', - '[EC-RFA-001] One or more selected Shop Drawing Revisions already have an active RFA.', - 'Shop Drawing Revision ที่เลือกมี RFA ที่ยังใช้งานอยู่แล้ว', - ['ตรวจสอบ RFA ที่มีอยู่', 'เลือก Shop Drawing Revision อื่น'] - ); - } - } - // Fetch real Organization Code for document numbering const userOrg = await this.orgRepo.findOne({ where: { id: userOrgId }, @@ -310,7 +298,42 @@ export class RfaService { await queryRunner.startTransaction(); try { + // EC-RFA-001 (race-safe): ล็อกแถว shop_drawing_revisions ที่เลือกด้วย FOR UPDATE + // ภายใน transaction เพื่อ serialize การสร้าง RFA พร้อมกันบน drawing เดียวกัน + // จากนั้นค่อยตรวจว่ามี RFA ที่ยัง active อยู่หรือไม่ (กัน TOCTOU) + if (shopDrawingRevisionIds.length > 0) { + await queryRunner.manager.query( + `SELECT id FROM shop_drawing_revisions WHERE id IN (${shopDrawingRevisionIds + .map(() => '?') + .join(',')}) FOR UPDATE`, + shopDrawingRevisionIds + ); + + const conflictingItems = await queryRunner.manager + .createQueryBuilder(RfaItem, 'item') + .innerJoin('item.rfaRevision', 'rfaRev') + .innerJoin('rfaRev.statusCode', 'status') + .where('item.shopDrawingRevisionId IN (:...ids)', { + ids: shopDrawingRevisionIds, + }) + .andWhere('status.statusCode NOT IN (:...codes)', { + codes: ['CC', 'OBS'], + }) + .getMany(); + + if (conflictingItems.length > 0) { + throw new BusinessException( + 'EC_RFA_001_ACTIVE_RFA_EXISTS', + '[EC-RFA-001] One or more selected Shop Drawing Revisions already have an active RFA.', + 'Shop Drawing Revision ที่เลือกมี RFA ที่ยังใช้งานอยู่แล้ว', + ['ตรวจสอบ RFA ที่มีอยู่', 'เลือก Shop Drawing Revision อื่น'] + ); + } + } + // [UPDATED] Generate Document Number with Discipline + // ADR-002: generateNextNumber ใช้ transaction ของตัวเอง (Redlock + optimistic counter) + // จึงรับประกัน "ไม่ซ้ำ" แต่ "อาจมี gap" ได้หาก transaction นี้ rollback — ยอมรับได้ตาม ADR-002 const docNumber = await this.numberingService.generateNextNumber({ projectId: internalProjectId, originatorOrganizationId: userOrgId, @@ -443,11 +466,13 @@ export class RfaService { await queryRunner.commitTransaction(); - // [NEW V1.5.1] Start Unified Workflow Instance + // [NEW V1.5.1] Start Unified Workflow Instance (ADR-001) // [C2 FIX] Drawing types (DDW/SDW/ADW) are RFA subtypes — all use RFA_APPROVAL + // Instance ถูกสร้างใน state DRAFT; submit() จะ transition 'SUBMIT' ต่อ + // หาก createInstance ล้มเหลว → log error (ไม่ fatal); submit() จะ self-heal สร้าง instance ให้ try { await this.workflowEngine.createInstance( - 'RFA_APPROVAL', + RfaService.WORKFLOW_CODE, 'rfa', savedRfa.id.toString(), { @@ -459,8 +484,8 @@ export class RfaService { } ); } catch (error: unknown) { - this.logger.warn( - `Workflow not started for ${docNumber.number}: ${(error as Error).message}` + this.logger.error( + `Workflow instance not started for ${docNumber.number} (will self-heal on submit): ${(error as Error).message}` ); } @@ -495,8 +520,6 @@ export class RfaService { } } - // ... (ส่วน findOne, submit, processAction คงเดิมจากไฟล์ที่แนบมา แค่ปรับปรุงเล็กน้อยตาม Context) ... - async findAll(query: SearchRfaDto, _user?: User) { const { page = 1, @@ -552,11 +575,25 @@ export class RfaService { } // RBAC: DFT documents are visible only to the originator org (spec §3.3.10) - if (_user?.primaryOrganizationId) { - queryBuilder.andWhere( - '(rfaRev.id IS NULL OR status.statusCode != :dftCode OR corr.originatorId = :userOrgId)', - { dftCode: 'DFT', userOrgId: _user.primaryOrganizationId } + // ผู้มีสิทธิ์ system.manage_all เห็นทุก DFT; ผู้อื่นเห็นเฉพาะของ org ตน + // ผู้ที่ไม่มี org และไม่มีสิทธิ์ manage_all → ไม่เห็น DFT เลย (กัน data leak) + if (_user) { + const canViewAllDft = await this.hasSystemManageAllPermission( + _user.user_id ); + if (!canViewAllDft) { + if (_user.primaryOrganizationId) { + queryBuilder.andWhere( + '(rfaRev.id IS NULL OR status.statusCode != :dftCode OR corr.originatorId = :userOrgId)', + { dftCode: 'DFT', userOrgId: _user.primaryOrganizationId } + ); + } else { + queryBuilder.andWhere( + '(rfaRev.id IS NULL OR status.statusCode != :dftCode)', + { dftCode: 'DFT' } + ); + } + } } const [items, total] = await queryBuilder @@ -599,7 +636,7 @@ export class RfaService { * ADR-019: Find RFA by the parent Correspondence publicId (public identifier). * Resolves correspondence.publicId → internal rfa.id */ - async findOneByUuid(publicId: string) { + async findOneByUuid(publicId: string): Promise { const correspondence = await this.correspondenceRepo.findOne({ where: { publicId }, select: ['id'], @@ -607,7 +644,17 @@ export class RfaService { if (!correspondence) { throw new NotFoundException('RFA', publicId); } - return this.findOne(correspondence.id); + const mapped = (await this.findOne(correspondence.id)) as RfaMapped; + + // ADR-021: ดึง Workflow Instance (nullable — DRAFT ที่ยังไม่เริ่ม submit ก็มี instance DRAFT) + const wfInstance = await this.workflowEngine.getInstanceByEntity( + 'rfa', + correspondence.id.toString() + ); + mapped.workflowInstanceId = wfInstance?.id; + mapped.workflowState = wfInstance?.currentState; + mapped.availableActions = wfInstance?.availableActions ?? []; + return mapped; } async findOneByUuidRaw(publicId: string) { @@ -668,11 +715,16 @@ export class RfaService { return mappedRfa; } + /** + * Submit RFA เข้า Unified Workflow Engine (ADR-001) + * - ใช้ Workflow Instance ที่สร้างไว้ตอน create() (state DRAFT); ถ้าไม่มีจะ self-heal + * - Engine จัดการ Redlock + pessimistic lock + version CAS ป้องกัน double-submit + */ async submit( rfaId: number, - templateId: number, user: User, - reviewTeamPublicId?: string + reviewTeamPublicId?: string, + roles: string[] = [] ) { const rfa = await this.findOne(rfaId, true); const corrRevisions = @@ -692,109 +744,93 @@ export class RfaService { ); } - const template = await this.templateRepo.findOne({ - where: { id: templateId }, - // relations: ['steps'], // Deprecated relation removed - }); - - if (!template) { - throw new BusinessException( - 'ROUTING_TEMPLATE_NOT_FOUND', - 'Invalid routing template', - 'ไม่พบ Routing Template ที่กำหนด', - ['ตรวจสอบ Routing Template ที่ตั้งค่าไว้'] + // ADR-001: หา Workflow Instance ที่สร้างไว้ตอน create() — ถ้าไม่มีให้ self-heal + let instance = await this.workflowEngine.getInstanceByEntity( + 'rfa', + rfaId.toString() + ); + if (instance && instance.currentState !== 'DRAFT') { + throw new WorkflowException( + 'RFA_ALREADY_SUBMITTED', + `RFA already submitted (state: ${instance.currentState})`, + 'RFA นี้ถูกส่งเข้า Workflow ไปแล้ว', + ['รีเฟรชหน้าเพื่อดูสถานะล่าสุด'] ); } - - // Manual fetch of steps - const steps = await this.templateStepRepo.find({ - where: { templateId: template.id }, - order: { sequence: 'ASC' }, - }); - - if (steps.length === 0) { - throw new BusinessException( - 'ROUTING_TEMPLATE_EMPTY', - 'Routing template has no steps', - 'Routing Template ไม่มีขั้นตอนกำหนดไว้', - ['เพิ่ม Step ใน Routing Template'] + if (!instance) { + const created = await this.workflowEngine.createInstance( + RfaService.WORKFLOW_CODE, + 'rfa', + rfaId.toString(), + { + projectId: rfa.correspondence.projectId, + originatorId: rfa.correspondence.originatorId, + disciplineId: rfa.correspondence.disciplineId, + initiatorId: user.user_id, + } ); + instance = { + id: created.id, + currentState: created.currentState, + availableActions: [], + }; } - const statusForApprove = await this.rfaStatusRepo.findOne({ - where: { statusCode: 'FAP' }, - }); - if (!statusForApprove) - throw new SystemException('Status FAP not found in Master Data'); + // ADR-001: transition 'SUBMIT' (DSL ต้องการ role CONTRACTOR ผ่าน context.roles) + const result = await this.workflowEngine.processTransition( + instance.id, + 'SUBMIT', + user.user_id, + 'RFA Submitted', + { roles }, + undefined, + user.publicId + ); - const queryRunner = this.dataSource.createQueryRunner(); - await queryRunner.connect(); - await queryRunner.startTransaction(); + // Sync สถานะ RFA Revision ตาม state ใหม่ (เช่น CONSULTANT_REVIEW → FRE) + await this.syncRevisionStatus(currentRfaRev, result.nextState); + currentCorrRev.issuedDate = new Date(); + await this.corrRevRepo.save(currentCorrRev); - try { - // Update Revision Status - currentRfaRev.rfaStatusCodeId = statusForApprove.id; - currentCorrRev.issuedDate = new Date(); - await queryRunner.manager.save(currentRfaRev); - await queryRunner.manager.save(currentCorrRev); - - // Create First Routing Step - const firstStep = steps[0]; - const routing = queryRunner.manager.create(CorrespondenceRouting, { - correspondenceId: rfa.correspondence.id, // ✅ Use master correspondence id - templateId: template.id, - sequence: 1, - fromOrganizationId: user.primaryOrganizationId, - toOrganizationId: firstStep.toOrganizationId, - stepPurpose: firstStep.stepPurpose, - status: 'SENT', - dueDate: new Date( - Date.now() + (firstStep.expectedDays || 7) * 24 * 60 * 60 * 1000 - ), - processedByUserId: user.user_id, - processedAt: new Date(), - }); - await queryRunner.manager.save(routing); - - if (reviewTeamPublicId) { + // FR-003: สร้าง Parallel Review Tasks (ถ้าเลือก Review Team) — transaction ของตัวเอง + if (reviewTeamPublicId) { + await this.dataSource.transaction(async (manager) => { await this.taskCreationService.createParallelTasks( currentRfaRev.id, currentCorrRev.publicId, // ADR-019: Pass UUID reviewTeamPublicId, - routing.dueDate ?? new Date(), - queryRunner.manager, + currentCorrRev.dueDate ?? new Date(), + manager, rfa.correspondence.projectId, rfa.rfaType.typeCode ); - } - - // Notify - const recipientUserId = await this.userService.findDocControlIdByOrg( - firstStep.toOrganizationId - ); - if (recipientUserId) { - await this.notificationService.send({ - userId: recipientUserId, - title: `RFA Submitted: ${currentCorrRev.subject}`, - message: `RFA ${rfa.correspondence.correspondenceNumber} submitted for approval.`, - type: 'SYSTEM', - entityType: 'rfa', - entityId: rfa.id, - }); - } - - await queryRunner.commitTransaction(); - return { message: 'RFA Submitted successfully', routing }; - } catch (err) { - await queryRunner.rollbackTransaction(); - throw err; - } finally { - await queryRunner.release(); + }); } + + // ADR-008: แจ้งเตือนแบบ fire-and-forget หลังทุกอย่างสำเร็จ (ไม่ค้างใน transaction) + void this.notifyRecipients( + rfa.correspondence.id, + rfa.correspondence.correspondenceNumber, + currentCorrRev.subject + ).catch((err: unknown) => + this.logger.warn( + `RFA submit notification failed: ${err instanceof Error ? err.message : String(err)}` + ) + ); + + return { instanceId: instance.id, currentState: result.nextState }; } - async processAction(rfaId: number, dto: WorkflowActionDto, user: User) { - // Logic คงเดิม: หา Current Routing -> Check Permission -> Call Workflow Engine -> Update DB + /** + * ดำเนินการ Workflow Action (APPROVE/REJECT) ผ่าน Unified Workflow Engine (ADR-001) + * Engine จัดการ pessimistic lock + version CAS ป้องกัน double-approval / TOCTOU + */ + async processAction( + rfaId: number, + dto: WorkflowActionDto, + user: User, + roles: string[] = [] + ) { const rfa = await this.findOne(rfaId, true); const corrRevisions = (rfa.correspondence?.revisions as CorrRevWithRfa[] | undefined) ?? []; @@ -804,118 +840,107 @@ export class RfaService { const currentRfaRev = currentCorrRev.rfaRevision; - const currentRouting = await this.routingRepo.findOne({ - where: { - correspondenceId: rfa.correspondence.id, // ✅ Use master correspondence id - status: 'SENT', - }, - order: { sequence: 'DESC' }, - relations: ['toOrganization'], - }); - - if (!currentRouting) + const instance = await this.workflowEngine.getInstanceByEntity( + 'rfa', + rfaId.toString() + ); + if (!instance) { throw new WorkflowException( 'NO_ACTIVE_WORKFLOW_STEP', - 'No active workflow step found', - 'ไม่พบขั้นตอน Workflow ที่ยังเปิดอยู่', + 'No active workflow instance found', + 'ไม่พบ Workflow ที่ยังเปิดอยู่', ['ตรวจสอบสถานะ Workflow ของเอกสาร'] ); - if (currentRouting.toOrganizationId !== user.primaryOrganizationId) { - throw new PermissionException('rfa workflow step', 'process'); } - const template = await this.templateRepo.findOne({ - where: { id: currentRouting.templateId }, - // relations: ['steps'], - }); + const approveCodeStr = + typeof dto.payload?.approveCode === 'string' + ? dto.payload.approveCode + : undefined; - if (!template) - throw new SystemException( - 'Routing Template not found for workflow processing' - ); - - // Manual fetch steps - const steps = await this.templateStepRepo.find({ - where: { templateId: template.id }, - order: { sequence: 'ASC' }, - }); - - if (steps.length === 0) - throw new SystemException('Routing Template steps not found'); - - // Call Engine to calculate next step - const result = this.workflowEngine.processAction( - currentRouting.sequence, - steps.length, + const result = await this.workflowEngine.processTransition( + instance.id, dto.action, - dto.returnToSequence + user.user_id, + dto.comment ?? dto.comments, + { roles, approveCode: approveCodeStr }, + undefined, + user.publicId ); - const queryRunner = this.dataSource.createQueryRunner(); - await queryRunner.connect(); - await queryRunner.startTransaction(); + // Sync RFA Revision status + approve code ตาม state ใหม่ + await this.syncRevisionStatus( + currentRfaRev, + result.nextState, + dto.action === WorkflowAction.REJECT ? undefined : approveCodeStr, + result.isCompleted + ); - try { - // Update current routing - currentRouting.status = - dto.action === WorkflowAction.REJECT ? 'REJECTED' : 'ACTIONED'; - currentRouting.processedByUserId = user.user_id; - currentRouting.processedAt = new Date(); - currentRouting.comments = dto.comments; - await queryRunner.manager.save(currentRouting); + return { message: 'Action processed', result }; + } - // Create next routing if available - if (result.nextStepSequence && dto.action !== WorkflowAction.REJECT) { - const nextStep = steps.find( - (s) => s.sequence === result.nextStepSequence - ); - if (nextStep) { - const nextRouting = queryRunner.manager.create( - CorrespondenceRouting, - { - correspondenceId: rfa.correspondence.id, // ✅ Use master correspondence id - templateId: template.id, - sequence: result.nextStepSequence, - fromOrganizationId: user.primaryOrganizationId, - toOrganizationId: nextStep.toOrganizationId, - stepPurpose: nextStep.stepPurpose, - status: 'SENT', - dueDate: new Date( - Date.now() + (nextStep.expectedDays || 7) * 24 * 60 * 60 * 1000 - ), - } - ); - await queryRunner.manager.save(nextRouting); - } - } else if (result.nextStepSequence === null) { - // Workflow Ended (Completed or Rejected) - // Update RFA Status (Approved/Rejected Code) - if (dto.action !== WorkflowAction.REJECT) { - const approveCode = await this.rfaApproveRepo.findOne({ - where: { - approveCode: dto.action === WorkflowAction.APPROVE ? '1A' : '4X', - }, - }); // Logic Map Code อย่างง่าย - if (approveCode) { - currentRfaRev.rfaApproveCodeId = approveCode.id; - currentRfaRev.approvedDate = new Date(); - } - } else { - const rejectCode = await this.rfaApproveRepo.findOne({ - where: { approveCode: '4X' }, - }); - if (rejectCode) currentRfaRev.rfaApproveCodeId = rejectCode.id; - } - await queryRunner.manager.save(currentRfaRev); + /** + * Helper: Map Workflow State → RFA Status Code (+ Approve Code เมื่อถึง Terminal) แล้วบันทึก + * เลิก hardcode magic string — ใช้ STATE_TO_STATUS map + payload override + */ + private async syncRevisionStatus( + revision: RfaRevision, + workflowState: string, + approveCodeStr?: string, + isTerminalApproved = false + ): Promise { + const targetStatusCode = RfaService.STATE_TO_STATUS[workflowState] ?? 'DFT'; + const status = await this.rfaStatusRepo.findOne({ + where: { statusCode: targetStatusCode }, + }); + if (status) { + revision.rfaStatusCodeId = status.id; + } + + if (isTerminalApproved) { + const codeToUse = approveCodeStr ?? RfaService.DEFAULT_APPROVED_CODE; + const approveCode = await this.rfaApproveRepo.findOne({ + where: { approveCode: codeToUse }, + }); + if (approveCode) { + revision.rfaApproveCodeId = approveCode.id; + revision.approvedDate = new Date(); } + } - await queryRunner.commitTransaction(); - return { message: 'Action processed', result }; - } catch (err) { - await queryRunner.rollbackTransaction(); - throw err; - } finally { - await queryRunner.release(); + await this.rfaRevisionRepo.save(revision); + this.logger.log( + `Synced RFA Revision ${revision.id}: state=${workflowState} → status=${targetStatusCode}` + ); + } + + /** + * ADR-008: แจ้งเตือน Document Controller ของ org ผู้รับ (TO) แบบ async + * เรียกหลัง transaction สำเร็จเท่านั้น (ห้ามค้างใน request transaction) + */ + private async notifyRecipients( + correspondenceId: number, + correspondenceNumber: string, + subject?: string + ): Promise { + const recipients = await this.dataSource.manager.find( + CorrespondenceRecipient, + { where: { correspondenceId, recipientType: 'TO' } } + ); + for (const r of recipients) { + const targetUserId = await this.userService.findDocControlIdByOrg( + r.recipientOrganizationId + ); + if (targetUserId) { + await this.notificationService.send({ + userId: targetUserId, + title: `RFA Submitted: ${subject ?? correspondenceNumber}`, + message: `RFA ${correspondenceNumber} submitted for approval.`, + type: 'SYSTEM', + entityType: 'rfa', + entityId: correspondenceId, + }); + } } } diff --git a/backend/tests/integration/modules/ai/ai-policy.service.integration.spec.ts b/backend/tests/integration/modules/ai/ai-policy.service.integration.spec.ts deleted file mode 100644 index 13bc14da..00000000 --- a/backend/tests/integration/modules/ai/ai-policy.service.integration.spec.ts +++ /dev/null @@ -1,290 +0,0 @@ -// File: backend/tests/integration/modules/ai/ai-policy.service.integration.spec.ts -// Change Log: -// - 2026-06-13: T034 — Integration test สำหรับ apply flow (sandbox draft → validate → production + cache DEL) - -import { Test, TestingModule } from '@nestjs/testing'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { NotFoundException, BadRequestException } from '@nestjs/common'; -import { AiPolicyService } from '../../../../src/modules/ai/services/ai-policy.service'; -import { AiExecutionProfile } from '../../../../src/modules/ai/entities/ai-execution-profile.entity'; -import { AiSandboxProfile } from '../../../../src/modules/ai/entities/ai-sandbox-profile.entity'; - -const DEFAULT_REDIS_TOKEN = 'default_IORedisModuleConnectionToken'; - -/** - * Integration test สำหรับ Apply Profile Flow (T034 — ADR-036) - * - * ครอบคลุม cross-service interactions: - * 1. Full apply flow: sandbox draft → validation → copy to production → Redis cache DEL - * 2. Idempotency logic: duplicate key ใน Redis ต้องไม่ apply ซ้ำ - * 3. Parameter range validation propagation - * 4. Cache miss → DB fallback → cache set → subsequent cache hit - */ -describe('AiPolicyService — Apply Flow Integration (T034)', () => { - let service: AiPolicyService; - - const productionRow = { - profileName: 'standard', - canonicalModel: 'np-dms-ai' as const, - isActive: true, - temperature: 0.4, - topP: 0.85, - maxTokens: 3000, - numCtx: 6000, - repeatPenalty: 1.2, - keepAliveSeconds: 300, - updatedBy: undefined as number | undefined, - }; - - const sandboxDraft = { - profileName: 'standard', - canonicalModel: 'np-dms-ai' as const, - temperature: 0.65, - topP: 0.9, - maxTokens: 4096, - numCtx: 8192, - repeatPenalty: 1.15, - keepAliveSeconds: 600, - }; - - let mockProfileRepo: { - findOne: jest.Mock; - create: jest.Mock; - save: jest.Mock; - }; - - let mockSandboxProfileRepo: { - findOne: jest.Mock; - create: jest.Mock; - save: jest.Mock; - }; - - let mockRedis: { - get: jest.Mock; - set: jest.Mock; - del: jest.Mock; - }; - - beforeEach(async () => { - const savedProductionRow = { ...productionRow }; - mockProfileRepo = { - findOne: jest.fn().mockResolvedValue({ ...savedProductionRow }), - create: jest.fn((input: unknown) => ({ ...(input as object) })), - save: jest.fn((input: unknown) => { - Object.assign(savedProductionRow, input as object); - return Promise.resolve({ ...savedProductionRow }); - }), - }; - mockSandboxProfileRepo = { - findOne: jest.fn().mockResolvedValue({ ...sandboxDraft }), - create: jest.fn((input: unknown) => ({ ...(input as object) })), - save: jest.fn((input: unknown) => - Promise.resolve({ ...(input as object) }) - ), - }; - mockRedis = { - get: jest.fn().mockResolvedValue(null), - set: jest.fn().mockResolvedValue('OK'), - del: jest.fn().mockResolvedValue(1), - }; - - const module: TestingModule = await Test.createTestingModule({ - providers: [ - AiPolicyService, - { - provide: getRepositoryToken(AiExecutionProfile), - useValue: mockProfileRepo, - }, - { - provide: getRepositoryToken(AiSandboxProfile), - useValue: mockSandboxProfileRepo, - }, - { provide: DEFAULT_REDIS_TOKEN, useValue: mockRedis }, - ], - }).compile(); - service = module.get(AiPolicyService); - }); - - describe('Full apply flow: draft → validate → production → cache DEL', () => { - it('ควรคัดลอกค่าจาก sandbox draft ไปยัง production row และลบ Redis cache ทั้งสองคีย์', async () => { - const result = await service.applyProfile('standard', 42); - - expect(mockSandboxProfileRepo.findOne).toHaveBeenCalledWith({ - where: { profileName: 'standard' }, - }); - expect(mockProfileRepo.findOne).toHaveBeenCalledWith({ - where: { profileName: 'standard' }, - }); - - expect(mockProfileRepo.save).toHaveBeenCalledWith( - expect.objectContaining({ - temperature: 0.65, - topP: 0.9, - maxTokens: 4096, - numCtx: 8192, - repeatPenalty: 1.15, - keepAliveSeconds: 600, - updatedBy: 42, - }) - ); - - expect(mockRedis.del).toHaveBeenCalledWith( - 'ai_execution_profiles:standard' - ); - expect(mockRedis.del).toHaveBeenCalledWith( - 'ai_execution_profiles:model:np-dms-ai' - ); - - expect(result.temperature).toBe(0.65); - expect(result.topP).toBe(0.9); - expect(result.keepAliveSeconds).toBe(600); - }); - - it('ควรสร้าง production row ใหม่หากยังไม่มีอยู่ใน DB', async () => { - mockProfileRepo.findOne.mockResolvedValue(null); - mockProfileRepo.create.mockImplementation((input: unknown) => ({ - ...(input as object), - })); - mockProfileRepo.save.mockImplementation((input: unknown) => - Promise.resolve({ ...(input as object) }) - ); - - const result = await service.applyProfile('standard', 1); - - expect(mockProfileRepo.create).toHaveBeenCalledWith( - expect.objectContaining({ profileName: 'standard', isActive: true }) - ); - expect(result.temperature).toBe(sandboxDraft.temperature); - }); - - it('ควรยังคง apply ได้แม้ Redis DEL ล้มเหลว (cache failure tolerant)', async () => { - mockRedis.del.mockRejectedValue(new Error('Redis connection lost')); - - const result = await service.applyProfile('standard', 7); - - expect(result.temperature).toBe(sandboxDraft.temperature); - }); - }); - - describe('NotFoundException เมื่อไม่มี sandbox draft', () => { - it('ควรโยน NotFoundException เมื่อ sandbox draft ไม่มีอยู่ใน DB', async () => { - mockSandboxProfileRepo.findOne.mockResolvedValue(null); - - await expect(service.applyProfile('standard')).rejects.toThrow( - NotFoundException - ); - }); - }); - - describe('Parameter range validation propagation', () => { - const makeInvalidDraft = ( - overrides: Partial - ): unknown => ({ - ...sandboxDraft, - ...overrides, - }); - - it.each([ - ['temperature เกิน 1', { temperature: 1.01 }], - ['temperature ต่ำกว่า 0', { temperature: -0.01 }], - ['topP เกิน 1', { topP: 1.1 }], - ['topP ต่ำกว่า 0', { topP: -0.1 }], - ['repeatPenalty ต่ำกว่า 1', { repeatPenalty: 0.99 }], - ['repeatPenalty เกิน 2', { repeatPenalty: 2.01 }], - ['keepAliveSeconds ติดลบ', { keepAliveSeconds: -1 }], - ])('ควรโยน BadRequestException เมื่อ %s', async (_label, invalidValue) => { - mockSandboxProfileRepo.findOne.mockResolvedValue( - makeInvalidDraft(invalidValue) - ); - - await expect(service.applyProfile('standard')).rejects.toThrow( - BadRequestException - ); - }); - }); - - describe('Cache lifecycle หลัง apply', () => { - it('ควรให้ cache miss หลัง apply เพื่อบังคับ fresh read จาก DB รอบถัดไป', async () => { - await service.applyProfile('standard', 1); - - expect(mockRedis.del).toHaveBeenCalledTimes(2); - - mockRedis.get.mockResolvedValue(null); - mockProfileRepo.findOne.mockResolvedValue({ - profileName: 'standard', - canonicalModel: 'np-dms-ai', - isActive: true, - temperature: 0.65, - topP: 0.9, - maxTokens: 4096, - numCtx: 8192, - repeatPenalty: 1.15, - keepAliveSeconds: 600, - }); - - const freshParams = await service.getProfileParameters('standard'); - expect(freshParams.temperature).toBe(0.65); - expect(mockProfileRepo.findOne).toHaveBeenCalledTimes(2); - }); - - it('ควรเขียน cache ใหม่หลัง getProfileParameters อ่านจาก DB', async () => { - await service.applyProfile('standard', 1); - - mockRedis.get.mockResolvedValue(null); - mockProfileRepo.findOne.mockResolvedValue({ - profileName: 'standard', - canonicalModel: 'np-dms-ai', - isActive: true, - temperature: 0.65, - topP: 0.9, - maxTokens: 4096, - numCtx: 8192, - repeatPenalty: 1.15, - keepAliveSeconds: 600, - }); - - await service.getProfileParameters('standard'); - - expect(mockRedis.set).toHaveBeenCalledWith( - 'ai_execution_profiles:standard', - expect.stringContaining('"temperature":0.65'), - 'EX', - 60 - ); - }); - }); - - describe('Dual-model: apply ของ OCR profile', () => { - it('ควรลบ model cache key ของ np-dms-ocr เมื่อ apply ocr-extract profile', async () => { - const ocrDraft = { - profileName: 'ocr-extract', - canonicalModel: 'np-dms-ocr' as const, - temperature: 0.12, - topP: 0.18, - maxTokens: null, - numCtx: null, - repeatPenalty: 1.05, - keepAliveSeconds: 0, - }; - mockSandboxProfileRepo.findOne.mockResolvedValue(ocrDraft); - mockProfileRepo.findOne.mockResolvedValue({ - profileName: 'ocr-extract', - canonicalModel: 'np-dms-ocr', - isActive: true, - ...ocrDraft, - }); - mockProfileRepo.save.mockImplementation((input: unknown) => - Promise.resolve({ ...(input as object) }) - ); - - await service.applyProfile('ocr-extract', 5); - - expect(mockRedis.del).toHaveBeenCalledWith( - 'ai_execution_profiles:ocr-extract' - ); - expect(mockRedis.del).toHaveBeenCalledWith( - 'ai_execution_profiles:model:np-dms-ocr' - ); - }); - }); -}); diff --git a/frontend/.understand-anything/.understandignore b/frontend/.understand-anything/.understandignore deleted file mode 100644 index ca73455e..00000000 --- a/frontend/.understand-anything/.understandignore +++ /dev/null @@ -1,21 +0,0 @@ -# ยกเว้นไฟล์ทดสอบและ specs -*.spec.ts -*.test.ts -*.spec.tsx -*.test.tsx -__tests__/ -tests/ -test/ - -# ยกเว้น Next.js แคชและไฟล์บิลด์ -.next/ -out/ -build/ -coverage/ -tsconfig.tsbuildinfo -eslint-frontend.json -npm-audit-frontend.json - -# ยกเว้นโฟลเดอร์มีเดียและโมดูล -public/ -node_modules/ diff --git a/frontend/.understand-anything/knowledge-graph.json b/frontend/.understand-anything/knowledge-graph.json deleted file mode 100644 index 9cbd8143..00000000 --- a/frontend/.understand-anything/knowledge-graph.json +++ /dev/null @@ -1,5416 +0,0 @@ -{ - "version": "1.0.0", - "project": { - "name": "lcbp3-frontend", - "languages": [ - "typescript", - "javascript", - "markdown", - "json" - ], - "frameworks": [ - "nextjs", - "react", - "tailwindcss" - ], - "description": "Next.js frontend for LCBP3 project", - "analyzedAt": "2026-06-13T13:24:06.067Z", - "gitCommitHash": "190b9a3af5f505e9ec59ba8d447c4720b2cb7dae" - }, - "nodes": [ - { - "id": "file:lib/api/client.ts", - "type": "file", - "name": "client.ts", - "filePath": "lib/api/client.ts", - "summary": "คลาสหรือออบเจกต์ที่ใช้จัดการการเรียกดู API จากแหล่งข้อมูลภายนอก โดยมีหน้าที่เป็นตัวกลางในการสื่อสารระหว่างแอปพลิเคชันและเซิร์ฟเวอร์", - "tags": [ - "api-client", - "http-client" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/admin-ai.service.ts", - "type": "file", - "name": "admin-ai.service.ts", - "filePath": "lib/services/admin-ai.service.ts", - "summary": "บริการสำหรับจัดการข้อมูล AI และสถานะโมเดล โดยมีฟังก์ชันหลักคือ extractData, normalizeLoadedModels และ normalizeVramStatus เพื่อเตรียมข้อมูลให้พร้อมใช้งานในระบบ", - "tags": [ - "service", - "ai-management", - "data-normalization" - ], - "complexity": "moderate" - }, - { - "id": "file:types/ai.ts", - "type": "file", - "name": "ai.ts", - "filePath": "types/ai.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน ai.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/ai-intent.service.ts", - "type": "file", - "name": "ai-intent.service.ts", - "filePath": "lib/services/ai-intent.service.ts", - "summary": "บริการสำหรับดึงข้อมูลจาก AI โดยใช้ client API เพื่อวิเคราะห์เจตนาของผู้ใช้งาน ฟังก์ชัน extractData มีหน้าที่รับข้อความและส่งไปยัง backend เพื่อดำเนินการประมวลผล", - "tags": [ - "service", - "ai-intent", - "api-client" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/ai-prompts.service.ts", - "type": "file", - "name": "ai-prompts.service.ts", - "filePath": "lib/services/ai-prompts.service.ts", - "summary": "บริการสำหรับจัดการคำขอ AI prompts โดยมีฟังก์ชัน extractData และ unwrapResponse ที่ใช้ในการดึงข้อมูลและแปลงรูปแบบคำตอบจาก API ส่งกลับมาให้เหมาะสมกับโครงสร้างของระบบ", - "tags": [ - "service", - "ai-prompts", - "api-client" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/ai.service.ts", - "type": "file", - "name": "ai.service.ts", - "filePath": "lib/services/ai.service.ts", - "summary": "ไฟล์นี้เป็นบริการสำหรับจัดการการทำงานของ AI โดยมีฟังก์ชัน extractData และ normalizePaginated ที่ใช้ในการดึงข้อมูลและปรับรูปแบบผลลัพธ์ให้อยู่ในรูปแบบมาตรฐานตามความต้องการ", - "tags": [ - "service", - "ai", - "data-processing" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/migration.service.ts", - "type": "file", - "name": "migration.service.ts", - "filePath": "lib/services/migration.service.ts", - "summary": "บริการสำหรับจัดการข้อมูลย้ายฐานข้อมูล โดยมีฟังก์ชัน extractNestedData ใช้ดึงข้อมูลที่ซ้อนอยู่ในโครงสร้างหลายระดับ และ normalizePaginatedResponse เปลี่ยนรูปแบบคำตอบจาก API เพื่อให้ง่ายต่อการประมวลผล", - "tags": [ - "service", - "migration", - "data-processing" - ], - "complexity": "moderate" - }, - { - "id": "file:types/ai-prompts.ts", - "type": "file", - "name": "ai-prompts.ts", - "filePath": "types/ai-prompts.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน ai-prompts.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/layout/global-search.tsx", - "type": "file", - "name": "global-search.tsx", - "filePath": "components/layout/global-search.tsx", - "summary": "คอมโพเนนต์ค้นหาทั่วไปสำหรับผู้ใช้งาน โดยแสดงปุ่มค้นหาและช่องกรอกข้อมูลเพื่อค้นหารายการในระบบ", - "tags": [ - "search", - "global" - ], - "complexity": "simple" - }, - { - "id": "file:components/layout/header.tsx", - "type": "file", - "name": "header.tsx", - "filePath": "components/layout/header.tsx", - "summary": "คอมโพเนนต์หัวข้อ (Header) ที่ใช้แสดงแถบเมนูหลักและองค์ประกอบการจัดการโปรเจกต์ เช่น การค้นหาทั่วไป ส่วนย่อยของระบบแจ้งเตือน และปุ่มสลับธีม โดยเชื่อมโยงเข้ากับคอมโพเนนต์อื่นๆ ในโครงสร้าง layout", - "tags": [ - "header", - "layout", - "navigation" - ], - "complexity": "moderate" - }, - { - "id": "file:components/layout/notifications-dropdown.tsx", - "type": "file", - "name": "notifications-dropdown.tsx", - "filePath": "components/layout/notifications-dropdown.tsx", - "summary": "คอมโพเนนต์สำหรับแสดงเมนูแจ้งเตือนในส่วนหัวหน้าเว็บไซต์ โดยมีฟังก์ชัน NotificationsDropdown ที่ใช้จัดวางและควบคุมการแสดงผลของรายการแจ้งเตือน", - "tags": [ - "component", - "layout", - "notifications-dropdown" - ], - "complexity": "moderate" - }, - { - "id": "file:components/layout/project-switcher.tsx", - "type": "file", - "name": "project-switcher.tsx", - "filePath": "components/layout/project-switcher.tsx", - "summary": "คอมโพเนนต์สำหรับสลับโครงการ โดยมีฟังก์ชัน ProjectSwitcher ที่ใช้ในการแสดงรายการโครงการและให้ผู้ใช้งานเลือกได้ เหมาะสำหรับการจัดการหลายโครงการในแอปพลิเคชัน", - "tags": [ - "component", - "layout", - "project-switch" - ], - "complexity": "moderate" - }, - { - "id": "file:components/layout/sidebar.tsx", - "type": "file", - "name": "sidebar.tsx", - "filePath": "components/layout/sidebar.tsx", - "summary": "ไฟล์นี้เป็นส่วนประกอบหลักสำหรับการแสดงเมนูด้านซ้ายของระบบ โดยมีฟังก์ชัน Sidebar และ MobileSidebar ที่ใช้จัดวางโครงสร้างเมนูตามหน้าจอและอุปกรณ์ต่าง ๆ", - "tags": [ - "layout", - "sidebar", - "navigation" - ], - "complexity": "moderate" - }, - { - "id": "file:components/layout/theme-toggle.tsx", - "type": "file", - "name": "theme-toggle.tsx", - "filePath": "components/layout/theme-toggle.tsx", - "summary": "คอมโพเนนต์ ThemeToggle เป็นส่วนหนึ-่งของระบบการจัดรูปแบบธีม โดยให้ผู้ใช้งานสามารถสลับระหว่างโหมดสว่างและมืดได้อย่างยืดหยุ่น", - "tags": [ - "component", - "theme-toggle", - "layout" - ], - "complexity": "simple" - }, - { - "id": "file:components/layout/user-menu.tsx", - "type": "file", - "name": "user-menu.tsx", - "filePath": "components/layout/user-menu.tsx", - "summary": "คอมโพเนนต์เมนูผู้ใช้งานที่แสดงข้อมูลโปรไฟล์และฟังก์ชันการจัดการบัญชี เช่น การเข้าสู่ระบบหรือออกจากระบบ โดยมีโครงสร้างภายในประกอบด้วยปุ่มย่อยต่างๆ และเชื่อมโยงไปยังหน้าต่างบริหารจัดการผู้ใช้งาน", - "tags": [ - "component", - "layout", - "user-interface" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/rag/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/rag/page.tsx", - "summary": "หน้าแดชบอร์ดสำหรับการใช้งาน AI Chat Widget โดยมีฟังก์ชัน RagPage ที่จัดการการแสดงผลและเชื่อมโยงกับส่วนประกอบต่าง ๆ เช่น RagChatWidget และข้อมูลจาก project-store", - "tags": [ - "dashboard", - "ai-chat-widget", - "rag-page", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/RagChatWidget.tsx", - "type": "file", - "name": "RagChat", - "filePath": "components/ai/RagChatWidget.tsx", - "summary": "คอมโพเนนต์แชทบอท AI แบบใช้ Retrieval-Augmented Generation (RAG) โดยมีฟังก์ชันแสดงสถานะการทำงานของระบบ และรายการคำอ้างอิงจากเอกสารที่ใช้อ้างอิงในการตอบสนอง", - "tags": [ - "ai-chatbot", - "rag-component", - "citation-list", - "status-badge" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/stores/project-store.ts", - "type": "file", - "name": "project-store.ts", - "filePath": "lib/stores/project-store.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน project-store.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "service:.dockerignore", - "type": "service", - "name": ".dockerignore", - "filePath": ".dockerignore", - "summary": "ไฟล์บริการเสริม/คอนเทนเนอร์ .dockerignore", - "tags": [ - "infrastructure" - ], - "complexity": "simple" - }, - { - "id": "config:.env.example", - "type": "config", - "name": ".env.example", - "filePath": ".env.example", - "summary": "ไฟล์ตั้งค่าฝั่งหน้าบ้าน .env.example", - "tags": [ - "configuration" - ], - "complexity": "simple" - }, - { - "id": "document:README.md", - "type": "document", - "name": "README.md", - "filePath": "README.md", - "summary": "เอกสาร README.md", - "tags": [ - "documentation" - ], - "complexity": "simple" - }, - { - "id": "config:components.json", - "type": "config", - "name": "components.json", - "filePath": "components.json", - "summary": "ไฟล์ตั้งค่าฝั่งหน้าบ้าน components.json", - "tags": [ - "configuration", - "component" - ], - "complexity": "simple" - }, - { - "id": "config:tsconfig.json", - "type": "config", - "name": "tsconfig.json", - "filePath": "tsconfig.json", - "summary": "ไฟล์ตั้งค่าฝั่งหน้าบ้าน tsconfig.json", - "tags": [ - "configuration" - ], - "complexity": "simple" - }, - { - "id": "file:app/(admin)/admin/access-control/organizations/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/access-control/organizations/page.tsx", - "summary": "หน้าจัดการองค์กรสำหรับผู้ดูแลระบบ โดยมีฟังก์ชัน OrganizationsPage ที่ใช้จัดแสดงข้อมูลองค์กรและรองรับการทำงานของระบบรักษาความปลอดภัยระดับสูง", - "tags": [ - "admin", - "access-control", - "organizations", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/access-control/roles/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/access-control/roles/page.tsx", - "summary": "หน้าจัดการบทบาท (Roles) สำหรับระบบบริหารจัดการ โดยมีฟังก์ชัน RolesPage เป็นหลักที่ใช้ในการแสดงผลข้อมูลบทบาทต่าง ๆ และจัดการสิทธิ์ผู้ใช้งาน", - "tags": [ - "admin", - "access-control", - "roles" - ], - "complexity": "moderate" - }, - { - "id": "file:/app/(admin)/admin/access-control/users/page.tsx", - "type": "file", - "name": "users/page.tsx", - "filePath": "/app/(admin)/admin/access-control/users/page.tsx", - "summary": "หน้าจัดการผู้ใช้งาน (Users Page) สำหรับระบบบริหารจัดการภายใน โดยมีฟังก์ชัน UsersPage เป็นหลักที่ดูแลการแสดงผลและตรรกะการทำงานของหน้านี้ มีโค้ดรวมทั้งหมด 214 บรรทัด", - "tags": [ - "admin", - "access-control", - "users-management", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/ai/intent-classification/analytics/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/ai/intent-classification/analytics/page.tsx", - "summary": "หน้าแดชบอร์ดวิเคราะห์ข้อมูลการจำแนกประเภทเจตนา (Intent Classification Analytics) สำหรับผู้ดูแลระบบ โดยมีฟังก์ชันหลักคือ IntentAnalyticsPage ที่จัดแสดงสถิติด้านประสิทธิภาพของโมเดล AI และพฤติกรรมข้อมูลการใช้งาน", - "tags": [ - "analytics", - "intent-classification", - "admin-dashboard" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/ai/intent-classification/[intentCode]/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/ai/intent-classification/[intentCode]/page.tsx", - "summary": "หน้าแสดงรายละเอียดการจัดประเภทเจตนา (Intent) โดยใช้ระบบ AI สำหรับบริษัท lcbp3-frontend มีฟังก์ชันหลักคือ IntentDetailPage ซึ่งทำงานร่วมกับโครงสร้าง routing และ context เพื่อแสดงข้อมูลเจตนาเฉพาะตามรหัส intentCode", - "tags": [ - "intent-classification", - "admin-panel", - "ai-service", - "dynamic-routing" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/ai/intent-classification/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/ai/intent-classification/page.tsx", - "summary": "หน้าเว็บไซต์สำหรับการจำแนกเจตนาของผู้ใช้ด้วยปัญญาประดิษฐ์ โดยมีฟังก์ชันหลักคือ IntentClassificationPage ที่จัดการการแสดงผลและตรรกะการทำงาน", - "tags": [ - "page", - "intent-classification", - "ai" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/ai/intent-classification/test-console/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/ai/intent-classification/test-console/page.tsx", - "summary": "หน้าเว็บสำหรับทดสอบระบบจำแนกเจตนาของผู้ใช้ โดยมีฟังก์ชันหลักคือ TestConsolePage ที่จัดการการแสดงผลและโต้ตอบกับผู้ใช้งานในบริบทของการพัฒนาระบบ AI", - "tags": [ - "page", - "admin", - "ai", - "intent-classification", - "test-console" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/ai/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/ai/page.tsx", - "summary": "หน้าจัดการ AI เป็นไปตามโครงสร้างของระบบบริหารจัดการ โดยมีฟังก์ชันหลักคือ AiAdminConsolePage ที่ใช้ในการแสดงผลและควบคุมโมเดลต่าง ๆ เนื้อหาภายในประกอบด้วยการแปลงข้อมูลแบบ canonical และ normalizeLoadedModels เพื่อกำหนดรูปแบบการแสดงผล", - "tags": [ - "admin", - "ai-console", - "page-component" - ], - "complexity": "complex" - }, - { - "id": "file:app/(admin)/admin/audit-logs/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/audit-logs/page.tsx", - "summary": "หน้าแสดงรายการบันทึกการตรวจสอบ (Audit Logs) สำหรับผู้ดูแลระบบ โดยมีฟังก์ชันหลักชื่อ AuditLogsPage ซึ่งจัดวางโครงสร้างการแสดงข้อมูลอย่างเป็นระเบียบ", - "tags": [ - "admin", - "audit-logs", - "page" - ], - "complexity": "simple" - }, - { - "id": "file:app/(admin)/admin/doc-control/contracts/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/doc-control/contracts/page.tsx", - "summary": "หน้าจัดการสัญญา (ContractsPage) ใช้ hook useContracts และ useProjectsList เพื่อดึงข้อมูลจาก backend โดยมีโครงสร้างหลักเป็นการแสดงผลรายการสัญญาและโปรเจกต์ที่เกี่ยวข้อง มีการจัดวางองค์ประกอบ UI เรียบร้อยพร้อมใช้งานในระบบบริหารเอกสาร", - "tags": [ - "admin-dashboard", - "contracts-management", - "hook-based-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/drawings/contract/categories/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/drawings/contract/categories/page.tsx", - "summary": "หน้าจัดการหมวดหมู่สัญญา โดยมีฟังก์ชันหลักคือ ContractCategoriesPage ที่แสดงข้อมูลและควบคุมการแสดงผลของแต่ละหมวดหมู่ ส่วน ManageMappings เป็นคอมโพเนนต์สำหรับจัดการ mapping และ CategoryMappingSection เป็นส่วนย่อยที่แสดงรายละเอียดการแมป โดยใช้ ErrorBoundary เพื่อจัดการข้อผิดพลาดในกระบวนการ render", - "tags": [ - "admin", - "contract", - "category-management", - "mapping-controller" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/drawings/contract/sub-categories/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/drawings/contract/sub-categories/page.tsx", - "summary": "หน้าจัดการหมวดหมู่สัญญาในระบบบริหารเอกสารแบบดีบุ๊ก โดยใช้ Component ContractSubCategoriesPage เพื่อแสดงข้อมูลรายละเอียดของแต่ละหมวดหมู่ และรองรับการทำงานตามโครงสร้างของระบบบริหารงานภายใน", - "tags": [ - "admin", - "contract", - "sub-categories", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/drawings/contract/volumes/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/drawings/contract/volumes/page.tsx", - "summary": "หน้าแสดงรายการปริมาตรสัญญา โดยมีฟังก์ชันหลักชื่อ ContractVolumesPage ที่ใช้จัดการการแสดงผลข้อมูลและปฏิสัมพันธ์ผู้ใช้งานในระดับหน่วยงานบริหาร", - "tags": [ - "admin", - "contract", - "volume-management", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/drawings/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/doc- control/drawings/page.tsx", - "summary": "หน้าจัดการร่างแบบดีไซน์สำหรับผู้ดูแลระบบ โดยมีฟังก์ชัน DrawingsAdminPage ที่ใช้ในการแสดงข้อมูลและควบคุมการทำงานของเอกสารการออกแบบ", - "tags": [ - "admin", - "doc-control", - "drawings", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/drawings/shop/main-categories/page.tsx", - "type": "file", - "name": "main-categories/page.tsx", - "filePath": "/app/(admin)/admin/doc-control/drawings/shop/main-categories/page.tsx", - "summary": "หน้าแสดงรายการหมวดหมู่สินค้าสำหรับร้านค้า โดยมีฟังก์ชัน ShopMainCategoriesPage ที่จัดการการแสดงผลและตรรกะหลักของหน้าเว็บไซต์นี้ มีโค้ดรวมทั้งหมด 115 บรรทัด", - "tags": [ - "page", - "admin", - "shop", - "main-categories" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/drawings/shop/sub-categories/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/drawings/shop/sub-categories/page.tsx", - "summary": "หน้าแสดงรายการหมวดหมู่ย่อยของร้านค้าในระบบบริหารจัดการเอกสาร การเขียนโค้ดมีโครงสร้างชัดเจน โดยใช้งานฟังก์ชัน ShopSubCategoriesPage ซึ่งทำงานเป็นหน้าเว็บไซต์สำหรับแสดงข้อมูลหมวดหมู่ย่อยของร้านค้า", - "tags": [ - "page", - "admin", - "shop", - "sub-categories" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/numbering/[id]/edit/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/numbering/[id]/edit/page.tsx", - "summary": "หน้าจัดการแก้ไขระบบตัวเลขเอกสารสำหรับผู้ดูแลระบบ โดยมีฟังก์ชันหลักคือ EditTemplatePage ที่ใช้ในการแสดงและปรับแต่งข้อมูลตัวเลขเอกสารตาม ID เอกสาร", - "tags": [ - "admin", - "doc-control", - "numbering", - "edit" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/numbering/new/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/numbering/new/page.tsx", - "summary": "หน้าจัดการเลขที่เอกสารใหม่ โดยมีฟังก์ชันหลักคือ NewTemplatePage ซึ่งใช้ในการสร้างหรือกำหนดรูปแบบเลขที่เอกสารต่าง ๆ เนื้อหาภายในยังไม่มีรายละเอียดเพิ่มเติม", - "tags": [ - "admin", - "doc-control", - "numbering" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/numbering/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/numbering/page.tsx", - "summary": "หน้าจัดการเลขที่เอกสาร โดยมีฟังก์ชัน NumberingPage ซึ่งทำงานร่วมกับระบบบริหารจัดการเอกสารภายในโครงการ lcbp3-frontend", - "tags": [ - "page", - "admin", - "doc-control" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/projects/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/doc-ctrl/projects/page.tsx", - "summary": "หน้าจัดการโครงการสำหรับผู้ดูแลระบบ โดยมีฟังก์ชัน ProjectsPage ที่ใช้ในการแสดงข้อมูลและจัดการรายการโครงการต่าง ๆ มีโค้ดรวมทั้งหมด 231 บรรทัด", - "tags": [ - "admin-page", - "projects-management", - "document-control" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/doc- control/reference/correspondence-types/page.tsx", - "summary": "หน้าแสดงรายการประเภทจดหมายภายในระบบบริหารเอกสาร โดยมีฟังก์ชันหลักคือ CorrespondenceTypesPage ที่ใช้ในการจัดการและนำเสนอข้อมูลเกี่ยวกับประเภทจดหมายต่าง ๆ ในระบบทั้งหมด", - "tags": [ - "admin", - "doc-control", - "reference", - "correspondence-types", - "page" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/reference/disciplines/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/doc-control/reference/disciplines/page.tsx", - "summary": "หน้าแสดงรายละเอียดของสาขาต่าง ๆ ในระบบบริหารเอกสาร โดยมีฟังก์ชันหลักคือ DisciplinesPage ที่จัดการการแสดงผลข้อมูลสาขาตามโครงสร้างของระบบ และรองรับการทำงานร่วมกับโมดูลอื่น ๆ เช่น การจัดเก็บข้อมูลและการแสดงผลในหน้าเว็บ", - "tags": [ - "admin", - "doc-control", - "discipline-management", - "reference-page" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/reference/drawing-categories/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/reference/drawing-categories/page.tsx", - "summary": "หน้าจัดการหมวดหมู่ภาพวาด โดยมีฟังก์ชัน DrawingCategoriesPage ที่ใช้ในการแสดงข้อมูลและจัดการรายการหมวดหมู่ต่าง ๆ เน้นการแสดงผลแบบ responsive และรองรับการทำงานภายใต้ระบบทั่วไป", - "tags": [ - "page", - "admin", - "drawing-categories" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/reference/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-ctrl/reference/page.tsx", - "summary": "หน้าจัดการข้อมูลอ้างอิงสำหรับระบบบริหารเอกสาร โดยมีฟังก์ชันหลักคือ ReferenceDataPage ที่ใช้ในการแสดงผลและจัดการข้อมูลต่าง ๆ เช่น การดูรายละเอียดและการปรับแต่งข้อมูล", - "tags": [ - "admin", - "doc-control", - "reference-data" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/reference/rfa-types/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/doc-control/reference/rfa-types/page.tsx", - "summary": "หน้าแสดงข้อมูลประเภทเอกสาร RFA โดยมีฟังก์ชันหลักคือ RfaTypesPage ที่จัดการการแสดงผลและตรรกะการทำงานของหน้านี้ มีโค้ดรวมทั้งหมด 136 บรรทัด", - "tags": [ - "page", - "admin", - "doc-control", - "rfa-types" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/reference/tags/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/doc", - "summary": "หน้าจัดการแท็กสำหรับระบบเอกสารภายในบริษัท โดยมีฟังก์ชันหลักคือ TagsPage ซึ่งใช้ในการแสดงและจัดการข้อมูลแท็กต่าง ๆ เน้นความเรียบง่ายและรองรับการทำงานร่วมกับระบบที่เกี่ยวข้อง", - "tags": [ - "admin", - "doc-control", - "reference", - "tag-management" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/workflows/[id]/edit/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-ctrl/workflows/[id]/edit/page.tsx", - "summary": "หน้าจัดการแก้ไขกระบวนการ (workflow) โดยใช้งานระบบ routing แบบ dynamic parameter [id] เพื่อแสดงข้อมูลเฉพาะรายตาม ID เก็บไว้ใน state และเชื่อมโยงกับ component อื่น ๆ เช่น form, preview, และ action buttons", - "tags": [ - "admin", - "workflow-edit", - "dynamic-routing", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/workflows/new/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/workflows/new/page.tsx", - "summary": "หน้าจัดการสร้างงานใหม่สำหรับระบบบริหารเอกสาร โดยมีฟังก์ชันหลักคือ NewWorkflowPage ที่ใช้ในการแสดงผลและจัดการข้อมูลแบบเรียลไทม์", - "tags": [ - "admin", - "workflow", - "document-control" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/doc-control/workflows/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/doc-control/workflows/page.tsx", - "summary": "หน้าแสดงรายการ workflow โดยมีฟังก์ชัน WorkflowsPage ที่ใช้จัดการการแสดงผลและตรรกะการทำงานของแต่ละ workflow เน้นความเรียบง่ายในการเข้าถึงข้อมูลจาก backend และแสดงผลผ่าน UI", - "tags": [ - "page", - "admin", - "workflow" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/migration/errors/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/migration/errors/page.tsx", - "summary": "หน้าแสดงข้อผิดพลาดในการย้ายข้อมูล โดยมีฟังก์ชัน MigrationErrorsPage ที่ใช้จัดการการแสดงผลข้อความและสถานะของข้อผิดพลาดต่าง ๆ เน้นการออกแบบให้มีความเข้าใจง่ายสำหรับผู้ใช้งานระดับบริหาร", - "tags": [ - "page", - "admin", - "migration-error" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/migration/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/migration/page.tsx", - "summary": "หน้าจัดการย้ายข้อมูล (Migration Management Page) สำหรับระบบบริหารจัดการผู้ใช้งาน โดยมีแท็บต่าง ๆ เช่น AiMigrationTab และ LegacyQueueTab เพื่อให้ผู้ดูแลระบบสามารถตรวจสอบและดำเนินการย้ายข้อมูลได้อย่างเป็นระบบ", - "tags": [ - "migration-management", - "admin-dashboard", - "ai-migration-tab", - "legacy-queue-tab" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/migration/review/[id]/page.tsx", - "type": "file", - "name": "MigrationReviewPage", - "filePath": "/app/(admin)/admin/migration/review/[id]/page.tsx", - "summary": "หน้าแสดงผลการตรวจสอบการย้ายข้อมูล (migration review) โดยมีฟังก์ชันหลักชื่อ MigrationReviewPage ที่ใช้จัดรูปแบบการแสดงผลข้อมูลตาม ID จาก URL และรองรับการทำงานในสภาพแวดล้อมบริหารจัดการระบบ", - "tags": [ - "admin", - "migration-review", - "page-component", - "dynamic-route" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/monitoring/audit-logs/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/monitoring/audit-logs/page.tsx", - "summary": "หน้าแสดงรายการบันทึกการตรวจสอบสิทธิ์ (Audit Logs) สำหรับผู้ดูแลระบบ โดยมีฟังก์ชันหลักคือ AuditLogsPage ซึ่งจัดวางโครงสร้างการแสดงผลตามมาตรฐานของแอปพลิเคชัน และรองรับการทำงานภายใต้ระบบที่ต้องการความปลอดภัยสูง", - "tags": [ - "audit-logs", - "admin-dashboard", - "monitoring" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/monitoring/sessions/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/monitoring/sessions/page.tsx", - "summary": "หน้าจัดการเซสชันสำหรับผู้ดูแลระบบ โดยมีฟังก์ชัน SessionManagementPage ที่ใช้จัดแสดงข้อมูลและควบคุมการทำงานของเซสชันต่าง ๆ มีโค้ดรวมทั้งหมด 115 บรรทัด", - "tags": [ - "admin", - "monitoring", - "session-management", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/monitoring/system-logs/numbering/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/monitoring/system-logs/numbering/page.tsx", - "summary": "หน้าแสดงรายการบันทึกเลขลำดับระบบ โดยมีฟังก์ชัน NumberingLogsPage รับค่าจาก component และจัดการการแสดงผลข้อมูลตามเงื่อนไขต่าง ๆ เช่น การกรองหรือเรียงลำดับ", - "tags": [ - "page", - "admin", - "monitoring", - "system-logs", - "numbering" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/numbering/[id]/edit/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/numbering/[id]/edit/page.tsx", - "summary": "หน้าแก้ไขเลขลำดับสำหรับระบบบริหารจัดการหมายเลข โดยใช้งานเฉพาะผู้มีสิทธิ์เข้าถึงระดับ admin เท่านั้น มีโครงสร้างเป็นหน้าเว็บไซต์แบบ single-page application ซึ่งเชื่อมโยงกับระบบบริหารจัดการหมายเลขโดยตรง", - "tags": [ - "admin", - "edit", - "numbering" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/numbering/new/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/numbering/new/page.tsx", - "summary": "หน้าจัดทำเลขที่ใหม่สำหรับระบบบริหารจัดการเอกสาร โดยมีฟังก์ชันหลักคือ NumberingNewPage ซึ่งใช้ในการสร้างและจัดเก็บเลขที่ตามลำดับอัตโนมัติ", - "tags": [ - "admin", - "numbering", - "new-page" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/numbering/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/numbering/page.tsx", - "summary": "หน้าจัดเรียงเลข (Numbering Page) สำหรับระบบบริหารจัดการภายใน โดยมีฟังก์ชันหลักคือ NumberingPage เน้นการแสดงผลและจัดการลำดับเลขตามต้องการ", - "tags": [ - "page", - "admin", - "numbering" - ], - "complexity": "simple" - }, - { - "id": "file:app/(admin)/admin/organizations/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/organizations/page.tsx", - "summary": "หน้าจัดการองค์กรสำหรับผู้ดูแลระบบ โดยมีฟังก์ชัน OrganizationsPage ที่ใช้แสดงข้อมูลองค์กรและจัดการรายการต่าง ๆ เน้นความเรียบง่ายและประสิทธิภาพในการทำงาน", - "tags": [ - "admin", - "organizations", - "page" - ], - "complexity": "simple" - }, - { - "id": "file:app/(admin)/admin/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/page.tsx", - "summary": "หน้าจัดการระบบ (Admin Page) โดยมีฟังก์ชันหลักคือ AdminPage ซึ่งใช้สำหรับแสดงข้อมูลและควบคุมการทำงานภายในระบบทั้งหมด มีโครงสร้างโค้ดยาวถึง 159 บรรทัด", - "tags": [ - "admin", - "page", - "dashboard" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/settings/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/settings/page.tsx", - "summary": "หน้าตั้งค่าของระบบบริหารจัดการ โดยมีฟังก์ชัน SettingsPage ที่ใช้งานรวมถึงการจัดวางโครงสร้าง UI และการแสดงผลข้อมูลต่าง ๆ เน้นความเรียบง่ายและเข้าใจได้ง่าย", - "tags": [ - "admin", - "settings", - "page" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/users/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(admin)/admin/users/page.tsx", - "summary": "หน้าจัดการผู้ใช้งาน (Users Page) สำหรับระบบบริหารจัดการภายใน โดยมีฟังก์ชัน UsersPage ที่สร้างขึ้นเพื่อแสดงรายการผู้ใช้งานและจัดการข้อมูลได้อย่างเป็นระเบียบ", - "tags": [ - "admin", - "user-management", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/workflows/[id]/edit/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/workflows/[id]/edit/page.tsx", - "summary": "หน้าจัดการแก้ไขกระบวนการ (Workflow) โดยใช้งานระบบ routing แบบ dynamic เพื่อแสดงข้อมูลตาม id และเชื่อมโยงกับ component WorkflowEditPage เน้นการแสดงผลและการควบคุมการทำงานของ workflow ในระบบที่มีโครงสร้าง admin", - "tags": [ - "admin", - "workflow-edit", - "dynamic-routing" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/workflows/new/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/workflows/new/page.tsx", - "summary": "หน้าสร้างงานใหม่สำหรับระบบบริหารจัดการ workflow โดยมีฟังก์ชัน WorkflowNewPage ที่ใช้ในการแสดงผลและจัดการข้อมูลการทำงาน", - "tags": [ - "page", - "workflow", - "admin" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/admin/workflows/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(admin)/admin/workflows/page.tsx", - "summary": "หน้าจัดการกระบวนการ (workflows) โดยมีฟังก์ชัน WorkflowsPage ที่ใช้แสดงข้อมูลและควบคุมการทำงานของระบบตามความต้องการของผู้ใช้งาน", - "tags": [ - "page", - "admin", - "workflow" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(admin)/error.tsx", - "type": "file", - "name": "error.tsx", - "filePath": "app/(admin)/error.tsx", - "summary": "ไฟล์นี้เป็นหน้าข้อผิดพลาดสำหรับระบบบริหารจัดการ โดยมีฟังก์ชัน AdminError ที่ใช้ในการแสดงผลข้อความผิดพลาดเมื่อเกิดเหตุการณ์ข้อผิดพลาดในส่วนของ admin", - "tags": [ - "error-handler", - "admin-module" - ], - "complexity": "simple" - }, - { - "id": "file:app/(admin)/layout.tsx", - "type": "file", - "name": "layout.tsx", - "filePath": "/app/(admin)/layout.tsx", - "summary": "ไฟล์นี้เป็นโครงสร้างหลักสำหรับหน้าแดชบอร์ดผู้ดูแลระบบ โดยมีฟังก์ชัน AdminLayout ที่ใช้จัดวางองค์ประกอบต่าง ๆ เช่น header, sidebar และ content area เข้าด้วยกัน", - "tags": [ - "layout", - "admin-dashboard" - ], - "complexity": "moderate" - }, - { - "id": "file:app/api/ai/chat/route.ts", - "type": "file", - "name": "route.ts", - "filePath": "/app/api/ai/chat/route.ts", - "summary": "ไฟล์นี้เป็น route handler สำหรับ API endpoint เกี่ยวกับการสนทนาด้วย AI โดยรองรับคำขอ POST เพื่อรับข้อมูลจากผู้ใช้งานและส่งคำตอบกลับคืนมาในรูปแบบ JSON", - "tags": [ - "api-handler", - "ai-chat" - ], - "complexity": "moderate" - }, - { - "id": "file:app/api/auth/[...nextauth]/route.ts", - "type": "file", - "name": "route.ts", - "filePath": "app/api/auth/[...nextauth]/route.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน route.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:app/(auth)/layout.tsx", - "type": "file", - "name": "layout.tsx", - "filePath": "/app/(auth)/layout.tsx", - "summary": "ไฟล์นี้เป็นโครงสร้างหลักสำหรับหน้าเข้าสู่ระบบ โดยมีการประกาศฟังก์ชัน AuthLayout ที่ใช้จัดวางองค์ประกอบการแสดงผล และนำเข้า dynamic เพื่อใช้งานในบริบทของ Next.js", - "tags": [ - "auth-layout", - "layout-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(auth)/login/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(auth)/login/page.tsx", - "summary": "หน้าเข้าสู่ระบบของแอปพลิเคชัน โดยมีฟังก์ชัน LoginPage ที่ใช้จัดการกระบวนการล็อกอินผู้ใช้งาน มีโค้ดรวมทั้งหมดประมาณ 124 บรรทัด และเป็นหนึ-าหลักสำหรับการเข้าสู่ระบบ", - "tags": [ - "auth", - "login", - "authentication" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/ai-staging/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/ai-staging/page.tsx", - "summary": "หน้าเว็บไซต์สำหรับการแสดงผลข้อมูล AI Staging โดยมีการจัดวางโครงสร้างโค้ดอย่างชัดเจน มีฟังก์ชันสนับสนุน เช่น getMetadataText และ getStatusVariant ที่ใช้ในการกำหนด metadata และสถานะของหน้าเว็บ ส่วนหลัก ๆ เป็นคอมโพเนนต์ AiStagingPage ซึ่งมีขนาดใหญ่มากถึง 518 line โดยรวมแล้วเป็นหน้าจอแสดงผลข้อมูล AI Staging", - "tags": [ - "page-component", - "ai-dashboard", - "dashboard-page" - ], - "complexity": "complex" - }, - { - "id": "file:app/(dashboard)/circulation/new/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/circulation/new/page.tsx", - "summary": "หน้าสร้างรายการการส่งต่อ (Circulation) โดยใช้ฟังก์ชัน CreateCirculationPage ซึ่งมีขนาดโค้ดรวมทั้งหมดประมาณ 251 บรรทัด มีการนำเข้า dynamic และ fetchCache มาใช้งานเพื่อจัดการข้อมูลและโหลดหน้าเว็บอย่างเหมาะสม", - "tags": [ - "page", - "dashboard", - "circulation", - "create-form" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/circulation/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/circulation/page.tsx", - "summary": "หน้าแสดงข้อมูลการเคลื่อนย้ายสินค้า โดยมีฟังก์ชัน CirculationPage ที่จัดวางโครงสร้าง UI และควบคุมการแสดงผลตามความต้องการของระบบ", - "tags": [ - "page", - "dashboard" - ], - "complexity": "moderate" - }, - { - "id": "file:/app/(dashboard)/circulation/[uuid]/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/circulation/[uuid]/page.tsx", - "summary": "หน้าแสดงรายละเอียดการส่งต่อเอกสาร โดยมีฟังก์ชันตรวจสอบสถานะการล่าช้า (isOverdue), คำนำหน้าชื่อผู้ใช้งาน (getInitials) และแปลงสถานะเป็นโทนสีหรือรูปแบบการแสดงผล (getStatusVariant) เต็มไปด้วยโค้ดหลักสำหรับการจัดแสดงข้อมูลในหน้านี้", - "tags": [ - "page", - "dashboard", - "circulation", - "detail-page" - ], - "complexity": "complex" - }, - { - "id": "file:app/(dashboard)/correspondences/new/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/correspondences/new/page.tsx", - "summary": "หน้าสร้างเอกสารสื่อสารใหม่ โดยมีฟังก์ชัน NewCorrespondencePage ที่ใช้จัดการการแสดงผลและตรรกะของหน้านี้ มีการนำเข้า dynamic และ fetchCache เพื่อเพิ่มประสิทธิภาพการทำงาน", - "tags": [ - "page", - "dashboard", - "correspondences" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/correspondences/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/correspondences/page.tsx", - "summary": "หน้าจัดการเอกสารสื่อสารภายในองค์กร โดยมีฟังก์ชันหลักเป็น CorrespondencesPage ที่ใช้ในการแสดงรายการและจัดการข้อมูลเอกสารต่าง ๆ เน้นความเรียบง่ายและรองรับการทำงานแบบ responsive", - "tags": [ - "dashboard", - "correspondence", - "page-component", - "dynamic-import" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/correspondences/[uuid]/edit/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/correspondences/[uuid]/edit/page.tsx", - "summary": "หน้าแก้ไขข้อมูลการสื่อสาร โดยมีฟังก์ชัน EditCorrespondencePage ที่จัดการการแสดงผลและตรรกะการทำงานของหน้านี้ มีโค้ดรวมทั้งหมด 48 บรรทัด", - "tags": [ - "page", - "edit", - "correspondence" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/correspondences/[uuid]/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/correspondences/[uuid]/page.tsx", - "summary": "หน้าแสดงรายละเอียดการสื่อสาร (Correspondence) โดยใช้ UUID เป็นตัวระบุรายการเดียว มีฟังก์ชันหลักชื่อ CorrespondenceDetailPage ที่มีขนาดโค้ดประมาณ 104 บรรทัด และเปิดเผยให้ใช้งานผ่านการ export", - "tags": [ - "page", - "dashboard", - "correspondence-detail" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/dashboard/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/dashboard/page.tsx", - "summary": "หน้าแดชบอร์ดหลักของระบบ โดยมีฟังก์ชัน DashboardPage ที่จัดวางองค์ประกอบต่าง ๆ เช่น เมนูนำทาง และส่วนแสดงข้อมูลสำคัญ เรียงตามลำดับการใช้งานจริง", - "tags": [ - "dashboard", - "main-page", - "layout" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/delegation/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/delegation/page.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน page.tsx", - "tags": [ - "utility", - "entry-point" - ], - "complexity": "simple" - }, - { - "id": "file:app/(dashboard)/distribution-matrices/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/distribution-matrices/page.tsx", - "summary": "หน้าจัดแสดงข้อมูลตารางการกระจายตัวของรหัส โดยมีฟังก์ชัน splitCodes สำหรับแยกรหัสออกเป็นส่วนย่อย และ Component DistributionMatricesPage เป็นหลักการทำงานทั้งหมด มีความซับซ้อนปานกลางเนื่องจากรวมถึงการจัดวางหน้าเว็บและการประมวลผลข้อมูล", - "tags": [ - "dashboard", - "distribution-matrix", - "code-splitting", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/drawings/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/drawings/page.tsx", - "summary": "หน้าจัดแสดงภาพวาด (Drawings) โดยมีการสร้างคอมโพเนนต์ DrawingsPage และ DrawingTabs เพื่อจัดวางแท็บสำหรับการแสดงข้อมูลภาพวาดในระบบบริหารงานออกแบบกราฟิก", - "tags": [ - "dashboard", - "drawings", - "page-component", - "tabs" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/drawings/upload/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/drawings/upload/page.tsx", - "summary": "หน้าแสดงผลการอัปโหลดภาพวาด โดยมีฟังก์ชัน DrawingUploadPage ที่จัดการกระบวนการอัปโหลดไฟล์จากผู้ใช้งาน และรองรับการทำงานแบบ dynamic เนื่องจากรายงานข้อมูลจาก backend", - "tags": [ - "page", - "upload", - "drawing" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/drawings/[uuid]/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/drawings/[uuid]/page.tsx", - "summary": "หน้าแสดงรายละเอียดการวาดแบบ โดยใช้ UUID เป็นตัวระบุแต่ละรายการ การเรียกข้อมูลจากเซิร์ฟเวอร์ผ่าน fetchDrawingByUuid เพื่อโหลดข้อมูลเฉพาะรายการที่เลือก เน้นการแสดงผลและจัดการฟอร์มสำหรับแก้ไขรายละเอียดและการอัปโหลดฉบับปรับปรุง", - "tags": [ - "dashboard", - "drawing-detail", - "uuid-based-routing", - "api-handler" - ], - "complexity": "moderate" - }, - { - "id": "function:fetchDrawingByUuid", - "type": "function", - "name": "fetchDrawingByUuid", - "filePath": "app/(dashboard)/drawings/[uuid]/page.tsx", - "summary": "ฟังก์ชันสำหรับดึงข้อมูลแบบวาดรายละเอียดจากเซิร์ฟเวอร์โดยใช้ UUID เป็นพารามิเตอร์ เรียก API เพื่อให้ได้ข้อมูลเฉพาะรายการที่ต้องการ", - "tags": [ - "api-fetch", - "uuid" - ], - "complexity": "simple" - }, - { - "id": "file:component:DrawingDetailPage", - "type": ", ", - "complexity": "moderate", - "name": "DrawingDetailPage", - "summary": "ส่วนติดต่อผู้ใช้ DrawingDetailPage", - "tags": [ - "utility" - ] - }, - { - "id": "file:app/(dashboard)/error.tsx", - "type": "file", - "name": "error.tsx", - "filePath": "app/(dashboard)/error.tsx", - "summary": "ไฟล์นี้เป็นหน้าแสดงข้อผิดพลาดสำหรับแดชบอร์ด โดยมีฟังก์ชัน DashboardError ที่ใช้ในการจัดการสถานะข้อผิดพลาดของระบบ และส่งกลับผลลัพธ์ให้ผู้ใช้งานได้อย่างเหมาะสม", - "tags": [ - "error-handling", - "dashboard", - "component" - ], - "complexity": "simple" - }, - { - "id": "file:app/(dashboard)/layout.tsx", - "type": "file", - "name": "layout.tsx", - "filePath": "app/(dashboard)/layout.tsx", - "summary": "ไฟล์นี้เป็นโครงสร้างหลักของหน้าแดชบอร์ด โดยมีฟังก์ชัน DashboardLayout ที่ใช้จัดวางองค์ประกอบต่าง ๆ เช่น เห็นได้ว่ามีการนำเข้า dynamic และ export DashboardLayout มาใช้งาน", - "tags": [ - "layout", - "dashboard" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/migration/review/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/migration/review/page.tsx", - "summary": "หน้าแสดงผลการตรวจสอบข้อมูลย้ายระบบ โดยมีฟังก์ชัน MigrationReviewPage ที่จัดการการแสดงผลและตรรกะการทำงานของหน้านี้ มีโค้ดรวมทั้งหมด 152 บรรทัด และเป็นส่วนหนึ่งของระบบที่เกี่ยวข้องกับการย้ายระบบฐานข้อมูล", - "tags": [ - "page", - "dashboard", - "migration" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/profile/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/profile/page.tsx", - "summary": "หน้าโปรไฟล์ผู้ใช้งานที่แสดงข้อมูลส่วนตัวและสามารถจัดการได้ เช่น การอัปเดตข้อมูล หรือการเปลี่ยนแปลงรหัสผ่าน โดยมีโครงสร้างเป็น React Component เดียวที่เก็บหน้าที่แสดงผลลัพธ์ไว้ในตัว", - "tags": [ - "profile", - "dashboard", - "user-profile" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/projects/new/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/projects/new/page.tsx", - "summary": "หน้าสร้างโครงการใหม่ในระบบบริหารจัดการโปรเจกต์ โดยมีฟังก์ชัน CreateProjectPage ที่ใช้ในการจัดการข้อมูลและแสดงผลลัพธ์บนหน้าเว็บไซต์", - "tags": [ - "page", - "dashboard", - "project-management" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/projects/page_backup.tsx", - "type": "file", - "name": "page_backup.tsx", - "filePath": "app/(dashboard)/projects/page_backup.tsx", - "summary": "หน้าแสดงรายการโครงการในระบบ โดยมีฟังก์ชัน ProjectsPage ที่จัดการการแสดงผลและตรรกะหลักของหน้านี้ มีโค้ดรวมทั้งหมดประมาณ 191 บรรทัด และเป็นหนึ", - "tags": [ - "page", - "dashboard", - "projects" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/projects/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/projects/page.tsx", - "summary": "หน้าแสดงรายการโครงการ โดยมีฟังก์ชัน ProjectsPage ที่จัดการการแสดงผลและตรรกะหลักของหน้านี้ มีโค้ดรวมทั้งหมด 191 บรรทัด และเป็นหน้าหลักสำหรับผู้ใช้งานเข้าชมโครงการ", - "tags": [ - "page", - "dashboard" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/response-codes/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/response-codes/page.tsx", - "summary": "หน้าแสดงรหัสคำตอบ (response codes) โดยมีฟังก์ชัน ResponseCodesPage ที่จัดการการแสดงผลข้อมูลรหัสคำตอบในระบบ เน้นความเรียบง่ายและใช้งานได้จริงสำหรับผู้ดูแลระบบ", - "tags": [ - "dashboard", - "response-codes", - "page" - ], - "complexity": "simple" - }, - { - "id": "file:app/(dashboard)/rfa/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/rfa/page.tsx", - "summary": "หน้าแสดงผลการจัดการ RFA โดยใช้คอมโพเนนต์ RfaLegacyPage ซึ่งมีขนาดเล็กและเรียบง่าย มุ่งเน้นการแสดงข้อมูลพื้นฐานเท่านั้น", - "tags": [ - "dashboard", - "rfa", - "legacy" - ], - "complexity": "simple" - }, - { - "id": "file:app/(dashboard)/rfas/new/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/rfas/new/page.tsx", - "summary": "หน้าสร้างรายการ RFA โดยใช้โครงสร้าง Next.js และแสดงผลผ่านคอมโพเนนต์ NewRFAPage ซึ่งมีการจัดการโค้ดแบบ dynamic และ fetchCache เพื่อเพิ่มประสิทธิภาพการทำงาน", - "tags": [ - "page", - "dashboard", - "rfas", - "new" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/rfas/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/rfas/page.tsx", - "summary": "หน้าแสดงผลรวมรายการ RFAs โดยมีฟังก์ชันหลักคือ RFAsPage ที่ใช้จัดการการแสดงผลข้อมูลและโครงสร้างหน้าเว็บ", - "tags": [ - "page", - "dashboard" - ], - "complexity": "simple" - }, - { - "id": "file:app/(dashboard)/rfas/[uuid]/edit/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/rfas/[uuid]/edit/page.tsx", - "summary": "หน้าแก้ไขข้อมูล RFA โดยใช้งานคอมโพเนนต์ RFAEditPage ซึ่งมีความยาวโค้ดรวมทั้งหมด 167 บรรษา มีการจัดวางโครงสร้างเพื่อให้สามารถแสดงและปรับแต่งข้อมูลได้อย่างเหมาะสม", - "tags": [ - "page", - "edit", - "rfa" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/rfas/[uuid]/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/rfas/[uuid]/page.tsx", - "summary": "หน้าแสดงรายละเอียดของรายการ RFAs โดยใช้ชื่อฟังก์ชัน RFADetailPage ซึ่งมีขนาดโค้ดประมาณ 101 บรรทัด มีการส่งออกเฉพาะตัวแปรหรือฟังก์ชันนี้เพื่อนำไปใช้งานในระบบ", - "tags": [ - "page", - "rfas", - "dashboard", - "detail-page" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/search/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/search/page.tsx", - "summary": "หน้าแสดงผลการค้นหาข้อมูล โดยมีองค์ประกอบหลักเป็น SearchContent ที่จัดวางเนื้อหาและฟังก์ชันการค้นหา และ SearchPage เป็นตัวรวมของหน้าเว็บไซต์", - "tags": [ - "search-page", - "dashboard-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/settings/delegation/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/settings/delegation/page.tsx", - "summary": "หน้าจัดการสิทธิ์การมอบหมาย (Delegation) สำหรับระบบบริหารจัดการภายใน โดยมีฟังก์ชันหลักคือ DelegationPage เน้นการแสดงผลข้อมูลและควบคุมการทำงานตามบทบาทของผู้ใช้งาน", - "tags": [ - "dashboard", - "settings", - "delegation", - "page" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/settings/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/settings/page.tsx", - "summary": "หน้าตั้งค่าของระบบ โดยมีฟังก์ชัน SettingsPage ที่ใช้งานรวมถึงการจัดวางโครงสร้าง UI และการเชื่อมโยงกับบริการต่าง ๆ เพื่อให้ผู้ใช้งานสามารถปรับแต่งได้อย่างเหมาะสม", - "tags": [ - "page", - "dashboard", - "settings" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/settings/reminder-rules/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/settings/reminder-rules/page.tsx", - "summary": "หน้าจัดการกฎเตือน (Reminder Rules) สำหรับระบบบริหารงาน โดยมีฟังก์ชันหลักคือ ReminderRulesPage เน้นการแสดงผลและจัดการข้อมูลกฎเตือนได้อย่างเป็นระเบียบ มีโครงสร้างโค้ดยาวถึง 182 บรรทัด", - "tags": [ - "dashboard", - "settings", - "reminder-rules", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/settings/review-teams/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/(dashboard)/settings/review-teams/page.tsx", - "summary": "หน้าจัดการทีมตรวจสอบ (Review Teams) สำหรับระบบบริหารงานภายใน โดยใช้คอมโพเนนต์ ReviewTeamsPage เพื่อแสดงข้อมูลและให้ผู้ใช้งานสามารถดูรายละเอียดของแต่ละทีมได้อย่างชัดเจน", - "tags": [ - "dashboard", - "settings", - "review-teams", - "page-component" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/transmittals/new/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/transmittals/new/page.tsx", - "summary": "หน้าสร้างเอกสารส่งมอบใหม่ โดยมีฟังก์ชัน CreateTransmittalPage ที่จัดการการแสดงผลและตรรกะการทำงานของหน้านี้ มีการใช้ dynamic และ fetchCache เพื่อจัดการ caching และโหลดข้อมูลอย่างเหมาะสม", - "tags": [ - "dashboard", - "transmittals", - "create-page", - "api-handler" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/transmittals/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/transmittals/page.tsx", - "summary": "หน้าแสดงรายการเอกสารส่งต่อ โดยมีฟังก์ชัน TransmittalPage ที่จัดการการแสดงผลและตรรกะหลักของหน้านี้ มีโค้ดรวมประมาณ 91 บรรทัด และเป็นหนึ", - "tags": [ - "page", - "dashboard", - "transmittals" - ], - "complexity": "moderate" - }, - { - "id": "file:app/(dashboard)/transmittals/[uuid]/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "app/(dashboard)/transmittals/[uuid]/page.tsx", - "summary": "หน้าแสดงรายละเอียดการส่งเอกสาร (Transmittal) โดยใช้ UUID เป็นตัวระบุรายการเดียว มีฟังก์ชันหลักชื่อ TransmittalDetailPage ที่มีขนาดใหญ่ถึง 172 บรรทัด และเป็นจุดออกทาง API เพื่อดึงข้อมูลจาก backend", - "tags": [ - "transmittal-detail-page", - "dashboard-component", - "uuid-router" - ], - "complexity": "moderate" - }, - { - "id": "file:app/error.tsx", - "type": "file", - "name": "error.tsx", - "filePath": "/src/app/error.tsx", - "summary": "ไฟล์นี้จัดการหน้าข้อผิดพลาดของระบบ โดยมีฟังก์ชัน Error ที่ใช้แสดงผลข้อความหรือสถานะข้อผิดพลาดต่าง ๆ เมื่อเกิดเหตุการณ์ผิดพลาดในแอปพลิเคชัน", - "tags": [ - "error-handling", - "page" - ], - "complexity": "moderate" - }, - { - "id": "file:app/global-error.tsx", - "type": "file", - "name": "global-error.tsx", - "filePath": "/app/global-error.tsx", - "summary": "ไฟล์นี้จัดการข้อผิดพลาดระดับโลกของแอปพลิเคชัน โดยมีฟังก์ชัน GlobalError ที่ใช้แสดงผลข้อความหรือหน้าแจ้งเตือนเมื่อเกิดข้อผิดพลาดในระหว่างการทำงานของระบบ", - "tags": [ - "global-error", - "error-handling" - ], - "complexity": "moderate" - }, - { - "id": "file:app/globals.css", - "type": "file", - "name": "globals.css", - "filePath": "app/globals.css", - "summary": "ไฟล์โค้ดหน้าบ้าน globals.css", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:app/layout.tsx", - "type": "file", - "name": "layout.tsx", - "filePath": "app/layout.tsx", - "summary": "ไฟล์นี้เป็นโครงสร้างหลักของแอปพลิเคชัน โดยมีหน้าที่จัดการ layout ร่วมสำหรับหน้าเว็บไซต์ทั้งหมด มีฟังก์ชัน RootLayout ซึ่งใช้กำหนดโครงสร้างพื้นฐาน เช่น การแสดงผล header, footer และการรวม CSS โดยอ้างอิงจาก globals.css", - "tags": [ - "layout", - "root-layout", - "app-structure" - ], - "complexity": "moderate" - }, - { - "id": "file:app/page.tsx", - "type": "file", - "name": "page.tsx", - "filePath": "/app/page.tsx", - "summary": "หน้าหลักของแอปพลิเคชัน โดยมีฟังก์ชัน RootPage เป็นตัวกลางในการจัดการการแสดงผลและโครงสร้างพื้นฐานของหน้าเว็บไซต์", - "tags": [ - "root-layout", - "main-page" - ], - "complexity": "moderate" - }, - { - "id": "file:build-map.js", - "type": "file", - "name": "build-map.js", - "filePath": "build-map.js", - "summary": "ไฟล์นี้มีหน้าที่สร้างแผนผังโครงสร้างของโค้ด โดยใช้งานฟังก์ชันต่าง ๆ เช่น generateSimpleNode, callOllama และ runAnalysis เพื่อจัดการข้อมูลและผลลัพธ์จากโมเดล Ollama อย่างมีประสิทธิภาพ", - "tags": [ - "map-generation", - "ollama-integration", - "code-analysis" - ], - "complexity": "moderate" - }, - { - "id": "function:generateSimpleNode", - "type": "function", - "name": "generateSimpleNode", - "filePath": "build-map.js", - "summary": "ฟังก์ชันนี้ใช้สร้างโหนดพื้นฐานในแผนผัง โดยรับข้อมูลเข้ามาแล้วแปลงเป็นโครงสร้างที่เหมาะสมสำหรับการจัดวางในระบบ", - "tags": [ - "node-generation", - "structure-building" - ], - "complexity": "simple" - }, - { - "id": "function:callOllama", - "type": ", ", - "complexity": "moderate", - "name": "callOllama", - "summary": "ส่วนติดต่อผู้ใช้ callOllama", - "tags": [ - "utility" - ] - }, - { - "id": "file:components/admin/ai/OcrEngineSelector.tsx", - "type": "file", - "name": "OcrEngineSelector.tsx", - "filePath": "components/admin/ai/Ocr0CrEngineSelector.tsx", - "summary": "คอมโพเนนต์สำหรับเลือกเครื่องมือ OCR ใช้งานได้หลากหลาย โดยมีฟังก์ชันหลักคือ OcrEngineSelector ที่จัดการการแสดงผลและพฤติกรรมของแต่ละตัวเลือกอย่างเป็นระบบ", - "tags": [ - "component", - "admin", - "ai", - "ocr-engine-selector" - ], - "complexity": "moderate" - }, - { - "id": "file:components/admin/ai/OcrSandboxPromptManager.tsx", - "type": "file", - "name": "OcrSandboxPromptTmanager.tsx", - "filePath": "components/admin/ai/OcrSandboxPromptManager.tsx", - "summary": "คอมโพเนนต์สำหรับจัดการคำขอ (prompt) ในการใช้งาน OCR Sandbox โดยมีหน้าที่แสดงผลและจัดการประวัติเวอร์ชันของ prompt เหมือนกับระบบบริหารจัดการ prompt อื่น ๆ ในระบบทั้งหมด", - "tags": [ - "component", - "admin", - "ai", - "ocr-sandbox", - "prompt-manager" - ], - "complexity": "moderate" - }, - { - "id": "file:components/admin/ai/PromptVersionHistory.tsx", - "type": "file", - "name": "PromptVersionHistory.tsx", - "filePath": "components/admin/ai/PromptVersionHistory.tsx", - "summary": "คอมโพเนนต์แสดงประวัติเวอร์ชันของ Prompt สำหรับผู้ดูแลระบบ โดยมีฟังก์ชันหลักคือการจัดรูปแบบข้อมูลเวอร์ชันพร็อโมทและแสดงผลลัพธ์ในหน้าแดชบอร์ด", - "tags": [ - "component", - "admin", - "ai", - "prompt-history" - ], - "complexity": "moderate" - }, - { - "id": "file:components/admin/organization-dialog.tsx", - "type": "file", - "name": "organization-dialog.tsx", - "filePath": "components/admin/organization-dialog.tsx", - "summary": "องค์ประกอบสำหรับการแสดงรายละเอียดและจัดการองค์กรในระบบบริหารจัดการ โดยมีฟังก์ชัน OrganizationDialog ที่ใช้ในการสร้าง Dialog เพื่อแสดงข้อมูลองค์กรและการดำเนินการต่าง ๆ เช่น การแก้ไขหรือลบองค์กร", - "tags": [ - "dialog", - "organization-management", - "admin-component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/admin/reference/generic-crud-table.tsx", - "type": "file", - "name": "generic-crud-table.tsx", - "filePath": "components/admin/reference/generic-crud- crud-table.tsx", - "summary": "คอมโพเนนต์ทั่วไปสำหรับการแสดงข้อมูลแบบ CRUD (Create, Read, Update, Delete) โดยมีฟังก์ชันหลักชื่อ GenericCrudTable ซึ่งใช้ในการจัดการตารางข้อมูลอย่างยืดหยุ่น", - "tags": [ - "crud", - "table-component", - "admin-interface" - ], - "complexity": "moderate" - }, - { - "id": "file:components/admin/security/rbac-matrix.tsx", - "type": "file", - "name": "rbac-matrix.tsx", - "filePath": "components/admin/security/rbac-matrix.tsx", - "summary": "ไฟล์นี้สร้างคอมโพเนนต์ RbacMatrix สำหรับแสดงตารางการควบคุมสิทธิ์ (Role-Based Access Control) โดยใช้ฟังก์ชัน extractArrayData เพื่อดึงข้อมูลจากอาร์เรย์และจัดรูปแบบการแสดงผลตามโครงสร้างของสิทธิ์แต่ละระดับ", - "tags": [ - "rbac", - "access-control", - "component", - "table" - ], - "complexity": "moderate" - }, - { - "id": "file:components/admin/sidebar.tsx", - "type": "file", - "name": "sidebar.tsx", - "filePath": "components/admin/sidebar.tsx", - "summary": "ไฟล์นี้เป็นคอมโพเนนต์สำหรับแสดงเมนูด้านซ้ายของหน้าแดชบอร์ดผู้ดูแลระบบ โดยมีฟังก์ชันหลักคือ AdminSidebar และ AdminMobileSidebar ที่ใช้จัดวางรายการเมนูลงไว้ในรูปแบบ responsive layout เพื่อรองรับการแสดงผลบนอุปกรณ์ต่าง ๆ", - "tags": [ - "admin-panel", - "sidebar-component", - "responsive-layout" - ], - "complexity": "moderate" - }, - { - "id": "file:components/admin/user-dialog.tsx", - "type": "file", - "name": "user-dialog.tsx", - "filePath": "components/admin/user-dialog.tsx", - "summary": "องค์ประกอบ (component) สำหรับการแสดงรายละเอียดผู้ใช้งานในระบบบริหารจัดการ โดยมีฟังก์ชันหลักชื่อ UserDialog ที่ครอบคลุมการทำงานครบถ้วนจาก line 1 ถึง line 291", - "tags": [ - "component", - "admin", - "user-dialog" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/ai-chat-input.tsx", - "type": "file", - "name": "ai-chat-input.tsx", - "filePath": "components/ai/ai-chat-input.tsx", - "summary": "คอมโพเนนต์สำหรับการแสดงผลและจัดการข้อมูลจากช่องป้อนคำสั่งผ่าน AI โดยมีฟังก์ชันหลักคือ AiChatInput ที่ใช้ร่วมกับระบบแชทบอทในแอปพลิเคชัน", - "tags": [ - "component", - "ai-chat", - "input" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/ai-chat-messages.tsx", - "type": "file", - "name": "ai-chat-messages.tsx", - "filePath": "components/ai/ai-chat-messages.tsx", - "summary": "คอมโพเนนต์แสดงข้อความสนทนาด้วยปัญญาประดิษฐ์ โดยมีฟังก์ชันหลักชื่อ AiChatMessages ซึ่งจัดการการแสดงผลข้อความจากผู้ใช้งานและระบบอย่างเป็นระเบียบ มีโค้ดรวมทั้งหมด 159 บรรทัด", - "tags": [ - "ai-chat-messages", - "component", - "chat-interface" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/ai-chat-panel.tsx", - "type": "file", - "name": "ai-chat-panel.tsx", - "filePath": "components/ai/ai-chat-panel.tsx", - "summary": "คอมโพเนนต์แสดงหน้าจอสนทนาด้วยปัญญาประดิษฐ์ โดยมีฟังก์ชันหลักคือ AiChatPanel ซึ่งใช้งานร่วมกับระบบ AI เพื่อให้ผู้ใช้สามารถพูดคุยกับเครื่องจักรได้อย่างโต้ตอบ", - "tags": [ - "ai-chat-panel", - "component", - "chat-interface" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/ai-chat-toggle.tsx", - "type": "file", - "name": "ai-chat-toggle.tsx", - "filePath": "components/ai/ai-chat-toggle.tsx", - "summary": "คอมโพเนนต์ที่ใช้แสดงปุ่มสลับการสนทนาด้วย AI โดยมีฟังก์ชันหลักคือ AiChatToggle ซึ่งทำงานร่วมกับระบบ UI เพื่อให้ผู้ใช้งานสามารถเปิด-ปิดโหมดการสนทนาได้อย่างสะดวก", - "tags": [ - "component", - "ai-chat-toggle", - "toggle-button" - ], - "complexity": "simple" - }, - { - "id": "file:components/ai/ai-status-banner-host.tsx", - "type": "file", - "name": "AiStatusBannerHost", - "filePath": "components/ai/ai-status-banner-host.tsx", - "summary": "คอมโพเนนต์โฮสต์สำหรับแสดงสถานะ AI โดยใช้ AiStatusBanner เป็นองค์ประกอบหลัก มีหน้าที่จัดวางและควบคุมการแสดงผลของสถานะ AI ผ่านการนำเข้าจาก components/ai/AiStatusBanner.tsx", - "tags": [ - "component", - "ai-status-banner-host", - "middleware" - ], - "complexity": "simple" - }, - { - "id": "file:components/ai/AiStatusBanner.tsx", - "type": "file", - "name": "AiStatusBanner.tsx", - "filePath": "components/ai/AiStatusBanner.tsx", - "summary": "องค์ประกอบแสดงสถานะของระบบ AI โดยมีฟังก์ชันหลักชื่อ AiStatusBanner ที่ใช้ในการจัดวางข้อมูลสถานะ เช่น การทำงานหรือความพร้อมใช้งานของโมเดล AI เน้นการแสดงผลอย่างชัดเจนและเข้าใจง่าย", - "tags": [ - "component", - "ai-status-banner" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/ai-suggestion-button.tsx", - "type": "file", - "name": "ai-suggestion-button.tsx", - "filePath": "components/ai/ai-suggestion-button.tsx", - "summary": "คอมโพเนนต์ปุ่มแนะนำ AI ที่ใช้ในการเรียกใช้งานฟีเจอร์คำแนะนำจากโมเดล AI โดยมีการจัดวาง UI และเชื่อมโยงกับระบบหลักผ่าน event handler", - "tags": [ - "ai-button", - "suggestion-component", - "interactive-element" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/ai-suggestion-field.tsx", - "type": "file", - "name": "ai-suggestion-field.tsx", - "filePath": "components/ai/ai-suggestion-field.tsx", - "summary": "คอมโพเนนต์สำหรับแสดงผลการแนะนำจาก AI โดยมีฟังก์ชันสนับสนุนในการจัดรูปแบบความเชื่อมั่นของคำตอบ เช่น การกำหนดคลาส CSS และคำอธิบายความเชื่อมั่นตามระดับต่าง ๆ", - "tags": [ - "ai-component", - "suggestion-field", - "confidence-indicator" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/document-comparison-view.tsx", - "type": "file", - "name": "document-comparison-view.tsx", - "filePath": "components/ai/document-comparison-view.tsx", - "summary": "คอมโพเนนต์แสดงผลการเปรียบเทียบเอกสาร โดยใช้ฟังก์ชัน DocumentComparisonView ที่จัดวางโครงสร้างการแสดงข้อมูลอย่างมีระเบียบ มีการนำเข้า AI suggestion field เพื่อสนับสนุนการทำงาน", - "tags": [ - "component", - "ai-suggestion-field", - "document-comparison" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/intent-classification/analytics/analytics-summary-cards.tsx", - "type": "file", - "name": "analytics-summary-cards.tsx", - "filePath": "components/ai/intent-classification/analytics/analytics-summary-cards.tsx", - "summary": "คอมโพเนนต์แสดงผลสรุปข้อมูลการจำแนกเจตนาจาก AI โดยมีฟังก์ชันหลักคือ AnalyticsSummaryCards ที่จัดเรียงข้อมูลเป็นบล็อกย่อยๆ เพื่อให้ผู้ใช้งานเข้าใจสถานะและแนวโน้มของระบบได้อย่างรวดเร็ว", - "tags": [ - "component", - "analytics", - "intent-classification" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/intent-classification/analytics/intent-breakdown-table.tsx", - "type": "file", - "name": "intent-breakdown-table.tsx", - "filePath": "components/ai/intent-classification/analytics/intent-breakdown-table.tsx", - "summary": "คอมโพเนนต์ตารางแสดงผลการจำแนกประเภทเจตนาของระบบปัญญาประดิษฐ์ โดยมีหน้าที่จัดรูปแบบข้อมูลและนำเสนอผลลัพธ์ในรูปแบบตารางให้ผู้ใช้งานเห็นภาพรวมได้อย่างชัดเจน", - "tags": [ - "table", - "ai-intent-classification", - "analytics" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/intent-classification/analytics/method-breakdown-table.tsx", - "type": "file", - "name": "method-breakdown-table.tsx", - "filePath": "components/ai/intent-classification/analytics/method-breakdown-table.tsx", - "summary": "คอมโพเนนต์แสดงข้อมูลการแบ่งประเภทวิธีการทำงานของระบบ AI โดยมีฟังก์ชัน methodBadge สำหรับการแสดงป้ายสถานะแต่ละวิธี และ Component MethodBreakdownTable เป็นหลักที่จัดเรียงและแสดงผลรวมตามกลุ่มวิธีต่าง ๆ", - "tags": [ - "ai-intent-classification", - "analytics", - "table-component", - "method-breakdown" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/intent-classification/analytics/recalibration-panel.tsx", - "type": "file", - "name": "recalibration-panel.tsx", - "filePath": "components/ai/intent-classification/analytics/recalibration-panel.tsx", - "summary": "ส่วนประกอบหน้าแดชบอร์ดสำหรับการปรับค่าโมเดลจำแนกเจตนาอัตโนมัติ โดยแสดงข้อมูลวัดผลและให้ผู้ใช้งานสามารถเรียกร้องการปรับแต่งใหม่ได้อย่างยืดหยุ่น", - "tags": [ - "component", - "analytics", - "intent-classification" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/intent-classification/classification-result-card.tsx", - "type": "file", - "name": "classification-result-card.tsx", - "filePath": "components/ai/intent- classification/classification-result-card.tsx", - "summary": "คอมโพเนนต์แสดงผลลัพธ์การจำแนกเจตนาของข้อความ โดยจัดรูปแบบข้อมูลให้เข้าใจง่ายและสื่อสารได้อย่างชัดเจน", - "tags": [ - "component", - "ai-intent-classification", - "result-display" - ], - "complexity": "simple" - }, - { - "id": "file:components/ai/intent-classification/intent-form.tsx", - "type": "file", - "name": "intent-form.tsx", - "filePath": "components/ai/intent-classification/intent-form.tsx", - "summary": "คอมโพเนนต์แบบฟอร์มสำหรับการจัดประเภทเจตนาของผู้ใช้ โดยมีหน้าที่รับข้อมูลจากผู้ใช้และส่งไปยังระบบประมวลผลเพื่อจำแนกเจตนา", - "tags": [ - "form-component", - "intent-classification", - "ai-module" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/intent-classification/pattern-form.tsx", - "type": "file", - "name": "pattern-form.tsx", - "filePath": "components/ai/intent-classification/pattern-form.tsx", - "summary": "คอมโพเนนต์ PatternForm เป็นหน่วยงานหลักสำหรับการจัดประเภทเจตนาของระบบ AI โดยมีฟังก์ชันหลักคือการสร้างแบบฟอร์มเพื่อให้ผู้ใช้งานสามารถป้อนข้อมูลและกำหนดรูปแบบพฤติกรรมที่ต้องการตรวจจับได้อย่างแม่นยำ", - "tags": [ - "component", - "ai-intent-classification", - "pattern-form" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/intent-classification/test-console-panel.tsx", - "type": "file", - "name": "test-console-panel.tsx", - "filePath": "components/ai/intent-classification/test-console-panel.tsx", - "summary": "คอมโพเนนต์สำหรับแสดงผลการทดสอบระบบจำแนกเจตนาของ AI โดยมีหน้าที่จัดวางองค์ประกอบการแสดงผล เช่น ผลลัพธ์การจำแนก และช่องทางส่งข้อมูลไปยังโมเดล", - "tags": [ - "component", - "ai-intent-classification", - "test-console" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ai/processing-indicator.tsx", - "type": "file", - "name": "processing-indicator.tsx", - "filePath": "components/ai/processing-indicator.tsx", - "summary": "คอมโพเนนต์แสดงสถานะการประมวลผล AI โดยมีฟังก์ชัน AiProcessingIndicator ที่ใช้ในการจัดรูปแบบการแสดงข้อมูล เช่น สัญลักษณ์กำลังทำงานหรือข้อความแจ้งเตือน", - "tags": [ - "ai", - "processing-indicator", - "component" - ], - "complexity": "simple" - }, - { - "id": "file:components/auth/auth-sync.tsx", - "type": "file", - "name": "auth-sync.tsx", - "filePath": "components/auth/auth-sync.tsx", - "summary": "ไฟล์นี้เป็นคอมโพเนนต์สำหรับจัดการการทำงานของระบบตรวจสอบสิทธิ์ (Authentication) โดยมีฟังก์ชันหลักชื่อ AuthSync ที่ทำงานร่วมกับบริการอื่น ๆ เพื่อรักษาระดับความปลอดภัยและสถานะการเข้าสู่ระบบทั้งหมด", - "tags": [ - "auth", - "middleware", - "sync-handler" - ], - "complexity": "moderate" - }, - { - "id": "file:components/circulation/circulation-list.tsx", - "type": "file", - "name": "circulation-list.tsx", - "filePath": "components/circ-ulation/circulation-list.tsx", - "summary": "ไฟล์นี้เป็นคอมโพเนนต์แสดงรายการการเคลื่อนย้ายสินค้า โดยมีฟังก์ชันสนับสนุนหลายอย่าง เช่น การคำนวณความคืบหน้า (getProgress) และแปลงสถานะเป็นรูปแบบที่ใช้งานได้ (getStatusVariant) เพื่อให้การแสดงผลสอดคล้องกับข้อมูลจริง", - "tags": [ - "component", - "circulation-list", - "status-variant", - "progress-calculation" - ], - "complexity": "moderate" - }, - { - "id": "file:components/common/can.tsx", - "type": "file", - "name": "can.tsx", - "filePath": "components/common/can.tsx", - "summary": "คอมโพเนนต์ Can เป็นองค์ประกอบที่ใช้ตรวจสอบสิทธิ์การเข้าถึงหน้าเว็บ โดยมีฟังก์ชันหลักชื่อว่า Can ซึ่งทำงานร่วมกับระบบ middleware เพื่อกำหนดพฤติกรรมการแสดงผลตามบทบาทผู้ใช้งาน", - "tags": [ - "component", - "authorization", - "middleware" - ], - "complexity": "moderate" - }, - { - "id": "file:components/common/confirm-dialog.tsx", - "type": "file", - "name": "confirm-dialog.tsx", - "filePath": "components/common/confirm-dialog.tsx", - "summary": "องค์ประกอบ (component) สำหรับแสดงกล่องยืนยันการกระทำ เช่น การลบข้อมูล โดยมีฟังก์ชันหลักชื่อ ConfirmDialog ที่ใช้ในการจัดวางและควบคุมการแสดงผลของกล่องยืนยัน", - "tags": [ - "component", - "dialog", - "confirmation" - ], - "complexity": "moderate" - }, - { - "id": "file:components/common/data-table.tsx", - "type": "file", - "name": "data-table.tsx", - "filePath": "components/common/data-table.tsx", - "summary": "คอมโพเนนต์ DataTable เป็นองค์ประกอบหลักสำหรับการแสดงข้อมูลในรูปแบบตาราง โดยมีฟังก์ชันสร้างตาราง (DataTable) ที่ใช้งานได้หลากหลาย เช่น การจัดเรียงแถว, กรองข้อมูล และการควบคุมการแสดงผลตามเงื่อนไขต่าง ๆ", - "tags": [ - "component", - "table", - "data-display" - ], - "complexity": "moderate" - }, - { - "id": "file:components/common/error-display.tsx", - "type": "file", - "name": "error-display.tsx", - "filePath": "components/common/error-display.tsx", - "summary": "ไฟล์นี้จัดการการแสดงข้อผิดพลาดต่าง ๆ ในแอปพลิเคชัน โดยมีฟังก์ชันสนับสนุนหลายอย่าง เช่น การแยกข้อมูลจาก payload ข้อผิดพลาด API, การแปลงระดับความรุนแรงของข้อผิดพลาดเป็นสไตล์การแสดงผล และการสร้างคอมโพเนนต์แสดงข้อผิดพลาด โดยมีการส่งออกฟังก์ชัน ErrorDisplay และ parseApiError ให้ใช้งานได้ในที่อื่น ๆ", - "tags": [ - "error-handling", - "api-error-parser", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/common/file-preview-modal.tsx", - "type": "file", - "name": "file-preview-modal.tsx", - "filePath": "components/common/file-preview-modal.tsx", - "summary": "ไฟล์นี้สร้าง modal สำหรับแสดงข้อมูลไฟล์ต่าง ๆ โดยมีฟังก์ชันช่วยในการแปลงขนาดหน่วยไบต์ และระบุประเภทไฟล์เพื่อการแสดงผลใน modal เหมาะใช้ร่วมกับการดูไฟล์แบบออนไลน์หรือดาวน์โหลดได้อย่างเหมาะสม", - "tags": [ - "modal", - "file-preview", - "utility-functions" - ], - "complexity": "moderate" - }, - { - "id": "file:components/common/pagination.tsx", - "type": "file", - "name": "pagination.tsx", - "filePath": "components/common/pagination.tsx", - "summary": "คอมโพเนนต์สำหรับการแสดงผลการpaginate โดยมีฟังก์ชัน Pagination ที่ใช้จัดเรียงข้อมูลตามหน้า และแสดงปุ่มควบคุมหน้าถัดไป/หน้าก่อนหน้า เหมาะสำหรับใช้งานในหน้ารายการข้อมูลจำนวนมาก", - "tags": [ - "pagination", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/common/status-badge.tsx", - "type": "file", - "name": "status-badge.tsx", - "filePath": "components/common/status-badge.tsx", - "summary": "คอมโพเนนต์แสดงสถานะในรูปแบบ badge โดยมีฟังก์ชัน StatusBadge ที่ใช้ในการสร้าง UI ตามค่าสถานะต่าง ๆ เช่น active, pending, completed เป็นต้น มีการจัดวางโครงสร้างให้มีความยืดหยุ่นและสามารถนำไปใช้งานซ้ำได้อย่างมีประสิทธิภาพ", - "tags": [ - "component", - "badge", - "status-indicator" - ], - "complexity": "simple" - }, - { - "id": "file:components/common/workflow-error-boundary.tsx", - "type": "file", - "name": "workflow-error-boundary.tsx", - "filePath": "components/common/workflow-error-boundary.tsx", - "summary": "คอมโพเนนต์ WorkflowErrorBoundary เป็นค่าป้องกันข้อผิดพลาด (error boundary) สำหรับจัดการข้อผิดพลาดในกระบวนการ workflow โดยมีเมธอดหลัก ๆ เช่น constructor, getDerivedStateFromError และ render เพื่อแสดงผลข้อผิดพลาดและรักษาความต่อเนื่องของหน้าเว็บ", - "tags": [ - "error-boundary", - "workflow", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/correspondences/circulation-status-card.tsx", - "type": "file", - "name": "circulation-status-card.tsx", - "filePath": "components/correspondences/circulation-status-card.tsx", - "summary": "คอมโพเนนต์แสดงสถานะการส่งเอกสาร โดยมีฟังก์ชัน RoutingStep และ CirculationItem รองรับขั้นตอนการไหลของเอกสาร ส่วนหลัก ๆ เป็น CirculationStatusCard ที่จัดวางองค์ประกอบและควบคุมการแสดงผลตามสถานะ", - "tags": [ - "component", - "correspondences", - "circulation-status", - "status-card" - ], - "complexity": "moderate" - }, - { - "id": "file:components/correspondences/correspondences-content.tsx", - "type": "file", - "name": "correspondences-content.tsx", - "filePath": "components/correspondsents/correspondences-content.tsx", - "summary": "ไฟล์นี้เป็นหน่วยงานหลักสำหรับการแสดงข้อมูลการสื่อสารระหว่างฝ่ายต่าง ๆ โดยมีฟังก์ชัน extractArrayData ใช้ในการจัดรูปแบบข้อมูล และ Component CorrespondencesContent เป็นองค์ประกอบหลักที่แสดงผลข้อมูลตามโครงสร้างที่กำหนดไว้", - "tags": [ - "component", - "correspondence", - "data-display" - ], - "complexity": "moderate" - }, - { - "id": "file:components/correspondences/detail.tsx", - "type": "file", - "name": "detail.tsx", - "filePath": "components/correspondences/detail.tsx", - "summary": "ไฟล์นี้เป็นหน้าแสดงรายละเอียดการสื่อสารระหว่างบุคคล โดยมีฟังก์ชัน normalizeRecipientType ที่ใช้ปรับรูปแบบข้อมูลผู้รับ และ Component CorrespondenceDetail หลัก ๆ เป็นโครงสร้างการแสดงผลตามตัวอย่างในระบบ", - "tags": [ - "component", - "correspondence-detail", - "recipient-type-normalizer" - ], - "complexity": "moderate" - }, - { - "id": "file:components/correspondences/form.tsx", - "type": "file", - "name": "form.tsx", - "filePath": "components/correspondences/form.tsx", - "summary": "ไฟล์นี้เป็นส่วนหนึ-่งของระบบจัดการเอกสารทางราชการ โดยมีหน้าที่สร้างฟอร์มสำหรับกรอกข้อมูลเกี่ยวกับความสัมพันธ์ระหว่างบุคคลต่าง ๆ ประกอบด้วยฟังก์ชันช่วยเหลือหลายตัว เช่น normalizePublicId, normalizeUuid และ extractArrayData เพื่อจัดรูปแบบข้อมูลให้เหมาะสม ก่อนนำไปใช้งานในส่วนของคอมโพเนนต์หลักคือ CorrespondenceForm ซึ่งมีขนาดใหญ่มากถึง 586 line โดยเป็นโครงสร้างหลักที่ผู้ใช้งานโต้ตอบกับระบบ", - "tags": [ - "form-component", - "correspondence-management", - "data-normalization" - ], - "complexity": "complex" - }, - { - "id": "file:components/correspondences/list.tsx", - "type": "file", - "name": "list.tsx", - "filePath": "components/correspondences/list.tsx", - "summary": "คอมโพเนนต์รายการเอกสารส่ง-รับระหว่างหน่วยงาน โดยมีฟังก์ชันหลักชื่อ CorrespondenceList ที่จัดแสดงข้อมูลตามโครงสร้างของระบบบริหารจัดการเอกสาร ส่งผลให้ผู้ใช้งานสามารถดูและจัดการรายการได้อย่างเป็นระเบียบ", - "tags": [ - "component", - "list", - "correspondence" - ], - "complexity": "moderate" - }, - { - "id": "file:components/correspondences/reference-selector.tsx", - "type": "file", - "name": "reference-selector.tsx", - "filePath": "components/correspondences/reference-selector.tsx", - "summary": "คอมโพเนนต์สำหรับเลือกอ้างอิงข้อมูล โดยมีฟังก์ชันหลักคือ ReferenceSelector ที่ใช้ในการจัดการการแสดงผลและพฤติกรรมของรายการเลือกได้อ้างอิง", - "tags": [ - "component", - "reference-selector" - ], - "complexity": "moderate" - }, - { - "id": "file:components/correspondences/revision-history.tsx", - "type": "file", - "name": "revision-history.tsx", - "filePath": "components/correspondences/revision-history.tsx", - "summary": "คอมโพเนนต์แสดงประวัติการแก้ไข (Revision History) สำหรับเอกสารส่งออก โดยมีฟังก์ชันหลักชื่อ RevisionHistory ที่ใช้งานจริงในหน้าเว็บไซต์ และมีขนาดโค้ดรวมประมาณ 71 บรรทัด", - "tags": [ - "component", - "revision-history", - "correspondence" - ], - "complexity": "moderate" - }, - { - "id": "file:components/correspondences/tag-manager.tsx", - "type": "file", - "name": "tag-manager.tsx", - "filePath": "components/correspondences/tag-manager.tsx", - "summary": "ไฟล์นี้มีหน้าที่จัดการกับแท็ก (tags) โดยมีฟังก์ชันหลักชื่อ TagManager ซึ่งใช้ในการสร้างหรือจัดการข้อมูลแท็กต่าง ๆ ในระบบ", - "tags": [ - "tag-manager", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/correspondences/ux-flow-dialog.tsx", - "type": "file", - "name": "ux-flow-dialog.tsx", - "filePath": "components/correspondences/ux-flow-dialog.tsx", - "summary": "คอมโพเนนต์สำหรับแสดงการไหลของข้อมูลในระบบจดหมายส่งกลับ โดยมีหน้าที่ควบคุมการแสดงผลและการโต้ตอบกับผู้ใช้งาน เช่น การยืนยันการดำเนินการ หรือเปลี่ยนแปลงสถานะรายการจดหมาย", - "tags": [ - "dialog", - "ux-flow", - "correspondence" - ], - "complexity": "moderate" - }, - { - "id": "file:components/custom/file-upload-zone.tsx", - "type": "file", - "name": "file-upload-zone.tsx", - "filePath": "components/custom/file-upload-zone.tsx", - "summary": "ไฟล์นี้สร้างคอมโพเนนต์ FileUploadZone สำหรับใช้ในการอัปโหลดไฟล์ โดยมีฟังก์ชัน formatBytes เพื่อแปลงขนาดไฟล์เป็นรูปแบบที่เข้าใจง่าย เช่น KB, MB และ GB นอกจากนี้ยังมีการจัดวางโครงสร้างให้มีความเรียบร้อยและใช้งานได้สะดวกในระบบแอปพลิเคชัน", - "tags": [ - "component", - "file-upload", - "format-bytes" - ], - "complexity": "moderate" - }, - { - "id": "file:components/custom/workflow-visualizer.tsx", - "type": "file", - "name": "workflow-visualizer.tsx", - "filePath": "components/custom/workflow-visualizer.tsx", - "summary": "คอมโพเนนต์แสดงแผนผังการทำงาน (Workflow) แบบกราฟิก โดยมีหน้าที่จัดการการแสดงผลขั้นตอนต่าง ๆ ในระบบอย่างเป็นรูปธรรม และรองรับการโต้ตอบกับผู้ใช้งานได้อย่างลื่นไหล", - "tags": [ - "component", - "workflow-visualizer", - "diagram" - ], - "complexity": "moderate" - }, - { - "id": "file:components/dashboard/pending-tasks.tsx", - "type": "file", - "name": "pending-tasks.tsx", - "filePath": "components/dashboard/pending-tasks.tsx", - "summary": "คอมโพเนนต์แสดงรายการงานที่ยังไม่ได้รับการดำเนินการ โดยมีฟังก์ชันหลักชื่อ PendingTasks ซึ่งจัดเรียงและแสดงข้อมูลงานตามลำดับความสำคัญ", - "tags": [ - "component", - "dashboard", - "pending-tasks" - ], - "complexity": "moderate" - }, - { - "id": "file:components/dashboard/quick-actions.tsx", - "type": "file", - "name": "quick-actions.tsx", - "filePath": "components/dashboard/quick-actions.tsx", - "summary": "คอมโพเนนต์ QuickActions เป็นหน้าที่ใช้แสดงปุ่มการกระทำเร่งด่วนสำหรับผู้ใช้งานในแดชบอร์ด โดยมีฟังก์ชันหลักชื่อว่า QuickActions ซึ่งทำงานร่วมกับระบบ UI เพื่อให้ผู้ใช้สามารถดำเนินการได้อย่างรวดเร็ว", - "tags": [ - "component", - "dashboard", - "quick-action" - ], - "complexity": "moderate" - }, - { - "id": "file:components/dashboard/recent-activity.tsx", - "type": "file", - "name": "recent-activity.tsx", - "filePath": "components/dashboard/recent-activity.tsx", - "summary": "คอมโพเนนต์แสดงกิจกรรมล่าสุดในแดชบอร์ด โดยมีฟังก์ชัน RecentActivity ที่ใช้ในการจัดการการแสดงผลข้อมูลกิจกรรม เช่น การอัปเดตสถานะหรือเหตุการณ์สำคัญ", - "tags": [ - "component", - "dashboard", - "recent-activity" - ], - "complexity": "moderate" - }, - { - "id": "file:components/dashboard/stats-cards.tsx", - "type": "file", - "name": "stats-cards.tsx", - "filePath": "components/dashboard/stats-cards.tsx", - "summary": "คอมโพเนนต์แสดงข้อมูลสถิติในแดชบอร์ด โดยมีฟังก์ชัน StatsCards ที่จัดเรียงการแสดงผลตามประเภทของข้อมูล เช่น การใช้งานระบบหรือยอดผู้ใช้รายวัน", - "tags": [ - "component", - "dashboard", - "stats" - ], - "complexity": "moderate" - }, - { - "id": "file:components/delegation/DelegationForm.tsx", - "type": "file", - "name": "DelegationForm.tsx", - "filePath": "components/delegation/DelegationForm.tsx", - "summary": "คอมโพเนนต์แบบฟอร์มสำหรับการมอบอำนาจ โดยมีหน้าที่จัดการกับการแสดงผลและพฤติกรรมของฟอร์ม เช่น การกรอกข้อมูล ตรวจสอบความถูกต้อง และส่งข้อมูลไปยังระบบหลัก", - "tags": [ - "form-component", - "delegation", - "ui-component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/distribution/DistributionStatus.tsx", - "type": "file", - "name": "DistributionStatus.tsx", - "filePath": "components/distribution/DistributionStatus.tsx", - "summary": "คอมโพเนนต์แสดงสถานะการกระจายสินค้า โดยมีฟังก์ชันหลักชื่อ DistributionStatus ที่ใช้ในการจัดรูปแบบข้อมูลสถานะ เช่น ส่งมอบแล้ว, เดินทางอยู่, รอการจัดส่ง เป็นต้น", - "tags": [ - "component", - "status-display", - "distribution" - ], - "complexity": "moderate" - }, - { - "id": "file:components/documents/common/server-data-table.tsx", - "type": "file", - "name": "server-data-table.tsx", - "filePath": "components/documents/common/server-data-table.tsx", - "summary": "คอมโพเนนต์ที่ใช้แสดงข้อมูลจากเซิร์ฟเวอร์ในรูปแบบตาราง โดยมีการจัดวางโครงสร้างให้มีความยืดหยุ่นและปรับขนาดได้ง่าย มีหน้าที่หลักคือการแสดงผลรายการข้อมูลผ่าน API และรองรับการจัดเรียงหรือกรองข้อมูล", - "tags": [ - "component", - "table", - "server-data" - ], - "complexity": "moderate" - }, - { - "id": "file:components/drawings/card.tsx", - "type": "file", - "name": "card.tsx", - "filePath": "components/drawings/card.tsx", - "summary": "คอมโพเนนต์ DrawingCard เป็นองค์ประกอบหลักสำหรับแสดงข้อมูลการวาดภาพ โดยมีโค้ดรวมทั้งหมด 74 บรรทัด มุ่งเน้นการแสดงผลอย่างชัดเจนและใช้งานได้ง่ายในระบบที่เกี่ยวข้องกับการจัดการงานวาดภาพ", - "tags": [ - "component", - "drawing", - "card-layout" - ], - "complexity": "moderate" - }, - { - "id": "file:components/drawings/columns.tsx", - "type": "file", - "name": "columns.tsx", - "filePath": "components/drawings/columns.tsx", - "summary": "ไฟล์นี้เป็นส่วนประกอบ (component) สำหรับการแสดงข้อมูลในรูปแบบคอลัมน์ โดยมีการนำออกเฉพาะตัวแปร columns เพื่อใช้งานในบริบทอื่น ๆ เท่านั้น", - "tags": [ - "component", - "drawing" - ], - "complexity": "simple" - }, - { - "id": "file:components/drawings/list.tsx", - "type": "file", - "name": "list.tsx", - "filePath": "components/drawings/list.tsx", - "summary": "คอมโพเนนต์รายการวาดภาพ โดยมีฟังก์ชัน DrawingList ที่ใช้จัดแสดงข้อมูลจากตาราง columns และรองรับการเลื่อนหน้าหรือกรองข้อมูลได้อย่างยืดหยุ่น", - "tags": [ - "component", - "drawing-list", - "table" - ], - "complexity": "moderate" - }, - { - "id": "file:components/drawings/revision-history.tsx", - "type": "file", - "name": "revision-history.tsx", - "filePath": "components/draws/revisions/history.tsx", - "summary": "คอมโพเนนต์แสดงประวัติการแก้ไข (Revision History) สำหรับแผนผังการออกแบบ โดยมีฟังก์ชันหลักชื่อ RevisionHistory ที่จัดวางข้อมูลตามลำดับเวลาและแสดงสถานะแต่ละเวอร์ชันได้อย่างชัดเจน", - "tags": [ - "component", - "revision-history", - "drawing" - ], - "complexity": "moderate" - }, - { - "id": "file:components/drawings/upload-form.tsx", - "type": "file", - "name": "upload-form.tsx", - "filePath": "components/drawings/upload-form.tsx", - "summary": "คอมโพเนนต์สำหรับการอัปโหลดภาพวาด โดยมีฟังก์ชัน DrawingUploadForm ที่ใช้จัดการกระบวนการอัปโหลดไฟล์ผ่านระบบกรอกข้อมูลแบบโต้ตอบได้อย่างราบรื่น", - "tags": [ - "upload-form", - "drawing-component", - "file-upload" - ], - "complexity": "moderate" - }, - { - "id": "file:components/layout/dashboard-shell.tsx", - "type": "file", - "name": "dashboard-shell.tsx", - "filePath": "components/layout/dashboard-shell.tsx", - "summary": "คอมโพเนนต์ DashboardShell เป็นโครงสร้างหลักสำหรับหน้าแดชบอร์ด โดยมีหน้าที่จัดวางองค์ประกอบภายใน เช่น header, sidebar และ main content area ไว้อย่างเป็นระบบ มีการใช้งานฟังก์ชัน DashboardShell เพื่อสร้างโครงสร้างหน้าเว็บให้สอดคล้องตามแนวทางการออกแบบของโปรเจกต์", - "tags": [ - "layout", - "dashboard", - "component" - ], - "complexity": "simple" - }, - { - "id": "file:components/layout/navbar.tsx", - "type": "file", - "name": "navbar.tsx", - "filePath": "components/layout/navbar.tsx", - "summary": "คอมโพเนนต์แถบนำทางหลักของเว็บไซต์ มีหน้าที่แสดงเมนูการใช้งานและข้อมูลผู้ใช้ โดยเชื่อมโยงไปยัง user-nav เพื่อจัดวางปุ่มหรือไอคอนสำหรับการเข้าสู่ระบบ", - "tags": [ - "layout", - "navigation", - "user-interface" - ], - "complexity": "moderate" - }, - { - "id": "file:components/layout/user-nav.tsx", - "type": "file", - "name": "user-nav.tsx", - "filePath": "components/layout/user-nav.tsx", - "summary": "คอมโพเนนต์การนำทางผู้ใช้งานที่แสดงข้อมูลโปรไฟล์และเมนูหลักสำหรับผู้ใช้ระบบ โดยมีฟังก์ชัน UserNav ซึ่งจัดวางองค์ประกอบการแสดงผลตามโครงสร้าง UI", - "tags": [ - "component", - "layout", - "user-interface" - ], - "complexity": "moderate" - }, - { - "id": "file:components/migration/review-queue-table.tsx", - "type": "file", - "name": "review-queue-table.tsx", - "filePath": "components/migration/review-queue-table.tsx", - "summary": "ไฟล์นี้สร้างคอมโพเนนต์ตารางแสดงรายการงานที่รอการตรวจสอบ โดยมีฟังก์ชันสนับสนุนหลายตัว เช่น getStringField, toReviewTag และ getIssueText เพื่อจัดรูปแบบข้อมูลให้เหมาะสมสำหรับการแสดงผลในตาราง", - "tags": [ - "table-component", - "review-queue", - "migration" - ], - "complexity": "moderate" - }, - { - "id": "file:components/numbering/audit-logs-table.tsx", - "type": "file", - "name": "audit-logs-table.tsx", - "filePath": "components/numbering/audit-logs-table.tsx", - "summary": "คอมโพเนนต์ตารางแสดงประวัติการตรวจสอบ (Audit Logs Table) ที่ใช้จัดรูปแบบข้อมูลรายการบันทึกการเข้าถึงระบบ โดยมีฟังก์ชันหลักคือ AuditLogsTable เปลี่ยนแปลงสถานะการแสดงผลตามเงื่อนไขต่าง ๆ เช่น การกรองหรือเรียงลำดับ", - "tags": [ - "table", - "audit-logs", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/numbering/bulk-import-form.tsx", - "type": "file", - "name": "bulk-import-form.tsx", - "filePath": "components/numbering/bulk-import-form.tsx", - "summary": "คอมโพเนนต์สำหรับการนำเข้าข้อมูลจำนวนมาก โดยมีฟังก์ชันหลักชื่อ BulkImportForm ที่ครอบคลุมการทำงานครบถ้วนภายในเอง มีโค้ดรวมประมาณ 44 บรรทัด", - "tags": [ - "form-component", - "bulk-import", - "numbering" - ], - "complexity": "moderate" - }, - { - "id": "file:components/numbering/cancel-number-form.tsx", - "type": "file", - "name": "cancel-number-form.tsx", - "filePath": "components/numbering/cancel-number-form.tsx", - "summary": "คอมโพเนนต์สำหรับแสดงฟอร์มการยกเลิกหมายเลข โดยมีหน้าที่จัดการกับเหตุการณ์การยืนยันและการส่งข้อมูลไปยังระบบหลัก", - "tags": [ - "form", - "cancel-number", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/numbering/manual-override-form.tsx", - "type": "file", - "name": "manual-override-form.tsx", - "filePath": "components/numbering/manual-override-form.tsx", - "summary": "คอมโพเนนต์แบบฟอร์มสำหรับการกำหนดค่าเลขลำดับโดยตรง โดยมีหน้าที่รับข้อมูลจากผู้ใช้และส่งกลับไปยังระบบหลักเพื่อปรับเปลี่ยนลำดับเลขของเอกสาร", - "tags": [ - "form-component", - "manual-override", - "numbering-control" - ], - "complexity": "moderate" - }, - { - "id": "file:components/numbering/metrics-dashboard.tsx", - "type": "file", - "name": "metrics-dashboard.tsx", - "filePath": "components/numbering/metrics-dashboard.tsx", - "summary": "คอมโพเนนต์แสดงข้อมูลเชิงสถิติ โดยมีฟังก์ชัน MetricsDashboard ที่ใช้จัดรูปแบบการแสดงผลข้อมูลต่าง ๆ เช่น กราฟหรือค่าเฉลี่ย เป็นต้น", - "tags": [ - "component", - "dashboard", - "metrics" - ], - "complexity": "moderate" - }, - { - "id": "file:components/numbering/sequence-viewer.tsx", - "type": "file", - "name": "sequence-viewer.tsx", - "filePath": "components/numbering/sequence-viewer.tsx", - "summary": "คอมโพเนนต์แสดงลำดับข้อมูลแบบไลน์อาร์เรย์ โดยมีฟังก์ชัน SequenceViewer ที่ใช้จัดรูปแบบการแสดงผลตามจำนวนแถวและโครงสร้างข้อมูลในระบบ", - "tags": [ - "component", - "sequence-viewer" - ], - "complexity": "moderate" - }, - { - "id": "file:components/numbering/template-editor.tsx", - "type": "file", - "name": "template-editor.tsx", - "filePath": "components/numbering/template-editor.tsx", - "summary": "คอมโพเนนต์ TemplateEditor ใช้จัดการการแสดงผลและควบคุมการทำงานของ editor โดยมีฟังก์ชันหลัก ๆ เริ่มจากบรรทัดที่ 181 เป็นต้นไป มีหน้าที่แสดงข้อมูลแบบ template และรองรับการปรับแต่งได้ตามความต้องการ", - "tags": [ - "component", - "editor", - "template" - ], - "complexity": "moderate" - }, - { - "id": "file:components/numbering/template-tester.tsx", - "type": "file", - "name": "template-tester.tsx", - "filePath": "components/numbering/template-tester.tsx", - "summary": "คอมโพเนนต์ TemplateTester เป็นหน่วยงานทดสอบรูปแบบการแสดงผลเลขลำดับ โดยมีฟังก์ชันหลักที่ใช้ในการจัดการและแสดงข้อมูลตามลำดับที่กำหนดไว้ มีขนาดโค้ดยาวถึง 200 line และเปิดเผยตัวแปรออกสู่โมดูลภายนอก", - "tags": [ - "component", - "template-tester" - ], - "complexity": "moderate" - }, - { - "id": "file:components/numbering/void-replace-form.tsx", - "type": "file", - "name": "void-replace-form.tsx", - "filePath": "components/numbering/void-replace-form.tsx", - "summary": "คอมโพเนนต์ VoidReplaceForm เป็นหน้าจอสำหรับแทนที่ข้อมูลว่าง (void) โดยมีฟังก์ชันหลักคือการจัดวางโครงสร้างแบบฟอร์มเพื่อให้ผู้ใช้งานสามารถกรอกข้อมูลใหม่ได้อย่างสะดวก", - "tags": [ - "form-component", - "numbering-system", - "void-replacement" - ], - "complexity": "moderate" - }, - { - "id": "file:components/reminder/ReminderHistory.tsx", - "type": "file", - "name": "ReminderHistory.tsx", - "filePath": "/src/components/reminder/ReminderHistory.tsx", - "summary": "คอมโพเนนต์แสดงประวัติการแจ้งเตือน โดยมีฟังก์ชันหลักคือ ReminderHistoryViewer ที่ใช้ในการจัดรูปแบบข้อมูลและแสดงผลลัพธ์ให้ผู้ใช้ดู", - "tags": [ - "component", - "reminder", - "history" - ], - "complexity": "moderate" - }, - { - "id": "file:components/reminder/ReminderRuleForm.tsx", - "type": "file", - "name": "ReminderRuleForm.tsx", - "filePath": "components/reminder/ReminderRuleForm.tsx", - "summary": "คอมโพเนนต์แบบฟอร์มสำหรับการกำหนดกฎเตือนเวลา โดยมีหน้าที่รับข้อมูลจากผู้ใช้และส่งกลับไปยังระบบหลักเพื่อจัดเก็บหรือประมวลผล", - "tags": [ - "form", - "reminder-rule", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/response-code/CodeImplications.tsx", - "type": "file", - "name": "CodeImplications.tsx", - "filePath": "components/response-code/CodeImplications.tsx", - "summary": "องค์ประกอบที่แสดงผลการตีความรหัสสถานะ HTTP โดยมีฟังก์ชัน CodeImplications ซึ่งใช้ในการจัดรูปแบบข้อมูลตามประเภทของรหัสรหัสสถานะ เช่น 2xx, 4xx และ 5xx เพื่อให้ผู้ใช้งานเข้าใจความหมายได้อย่างชัดเจน", - "tags": [ - "component", - "response-code", - "http-status" - ], - "complexity": "moderate" - }, - { - "id": "file:components/response-code/MatrixEditor.tsx", - "type": "file", - "name": "MatrixEditor.tsx", - "filePath": "components/response-code/MatrixEditor.tsx", - "summary": "คอมโพเนนต์ MatrixEditor เป็นหน้าที่ใช้จัดการการแสดงผลและโต้ตอบกับข้อมูลเมทริกซ์ โดยมีฟังก์ชันหลักชื่อว่า MatrixEditor ที่ครอบคลุมโค้ดจำนวน 118 บรรทัด", - "tags": [ - "component", - "matrix-editor" - ], - "complexity": "moderate" - }, - { - "id": "file:components/response-code/ProjectOverrideManager.tsx", - "type": "file", - "name": "ProjectOverrideManager.tsx", - "filePath": "components/response-code/ProjectOverrideManager.tsx", - "summary": "คอมโพเนนต์สำหรับจัดการข้อมูลที่เกินกว่าขอบเขตของโปรเจกต์ โดยมีฟังก์ชันหลักชื่อ ProjectOverrideManager ซึ่งทำงานร่วมกับระบบบริหารจัดการโปรเจกต์เพื่อกำหนดค่าคงที่หรือพฤติกรรมเฉพาะสำหรับแต่ละโปรเจกต์", - "tags": [ - "component", - "response-code", - "project-override" - ], - "complexity": "moderate" - }, - { - "id": "file:components/response-code/ResponseCodeSelector.tsx", - "type": "file", - "name": "ResponseCodeSelector.tsx", - "filePath": "components/response-code/ResponseCodeSelector.tsx", - "summary": "คอมโพเนนต์สำหรับแสดงรหัสข้อผิดพลาด (response code) โดยมีฟังก์ชัน CodeBadge ใช้แสดงป้ายสถานะของแต่ละรหัสร่วมด้วย มีการจัดวางโครงสร้างให้มุมมองที่ชัดเจนและเข้าใจง่าย", - "tags": [ - "component", - "response-code", - "code-badge" - ], - "complexity": "moderate" - }, - { - "id": "file:components/review-task/CompleteReviewForm.tsx", - "type": "file", - "name": "CompleteReviewForm.tsx", - "filePath": "components/review-task/CompleteReviewForm.tsx", - "summary": "คอมโพเนนต์สำหรับแสดงและจัดการแบบฟอร์มยืนยันการตรวจสอบงาน โดยมีหน้าที่รับข้อมูลจากผู้ใช้ ประมวลผล และส่งกลับไปยังระบบหลักเพื่อยืนยันการทำงาน", - "tags": [ - "component", - "form", - "review-task" - ], - "complexity": "moderate" - }, - { - "id": "file:components/review-task/DelegatedBadge.tsx", - "type": "file", - "name": "DelegatedBadge.tsx", - "filePath": "components/review-task/DelegatedBadge.tsx", - "summary": "องค์ประกอบ (component) แสดงป้ายชี้แจงว่างานนั้นถูกมอบหมายให้ผู้อื่นดูแล โดยมีการใช้งานฟังก์ชัน DelegatedBadge เพียงหนึ-่งเท่านั้น", - "tags": [ - "component", - "badge" - ], - "complexity": "simple" - }, - { - "id": "file:components/review-task/ParallelProgress.tsx", - "type": "file", - "name": "ParallelProgress.tsx", - "filePath": "components/review-task/ParallelProgress.tsx", - "summary": "คอมโพเนนต์แสดงความคืบหน้าแบบขนานสำหรับงานตรวจสอบ โดยมีฟังก์ชันหลักชื่อ ParallelProgress ที่ใช้จัดรูปแบบการแสดงผลความคืบหน้าของแต่ละขั้นตอนในงานอย่างเป็นระบบ", - "tags": [ - "component", - "progress-bar", - "parallel-task", - "review-flow" - ], - "complexity": "moderate" - }, - { - "id": "file:components/review-task/ReviewTaskInbox.tsx", - "type": "file", - "name": "ReviewTaskInbox.tsx", - "filePath": "components/review-task/ReviewTaskInbox.tsx", - "summary": "คอมโพเนนต์แสดงรายการงานที่รอการตรวจสอบ โดยมีฟังก์ชันหลักคือ ReviewTaskInbox ซึ่งจัดวางองค์ประกอบการแสดงผลตามลำดับความสำคัญและสถานะของแต่ละงานอย่างเป็นระบบ", - "tags": [ - "component", - "inbox", - "review-task" - ], - "complexity": "moderate" - }, - { - "id": "file:components/review-task/VetoOverrideDialog.tsx", - "type": "file", - "name": "VetoOverrideDialog.tsx", - "filePath": "components/review-task/VetoOverrideDialog.tsx", - "summary": "คอมโพเนนต์สำหรับแสดงและจัดการหน้าต่างยืนยันการยกเลิกข้อเสนอแนะ (veto override) โดยมีฟังก์ชันหลักคือ VetoOverrideDialog ที่ใช้ในการควบคุมการทำงานของ dialog และตอบสนองเหตุการณ์ต่าง ๆ เช่น การยืนยันหรือปิดหน้าต่าง", - "tags": [ - "dialog", - "veto-override", - "review-task", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/review-team/ReviewTeamForm.tsx", - "type": "file", - "name": "ReviewTeamForm.tsx", - "filePath": "components/review-team/ReviewTearmForm.tsx", - "summary": "คอมโพเนนต์แบบฟอร์มสำหรับการตรวจสอบทีมงาน โดยมีหน้าที่รับข้อมูลจากผู้ใช้งานและส่งไปยังระบบหลักเพื่อประมวลผล การจัดวางองค์ประกอบบนหน้าจอเป็นไปตามแนวทางการออกแบบ UI อย่างชัดเจน", - "tags": [ - "form", - "team-review", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/review-team/ReviewTeamSelector.tsx", - "type": "file", - "name": "ReviewTeamSelector.tsx", - "filePath": "components/review-team/ReviewTeamSelector.tsx", - "summary": "คอมโพเนนต์สำหรับเลือกทีมผู้ตรวจสอบ โดยแสดงรายการทีมพร้อมการกรองและเลือกได้ตามความต้องการของระบบ", - "tags": [ - "component", - "team-selector", - "review" - ], - "complexity": "moderate" - }, - { - "id": "file:components/review-team/TeamMemberManager.tsx", - "type": "file", - "name": "TeamMemberManager.tsx", - "filePath": "components/review-team/TeamMemberManager.tsx", - "summary": "คอมโพเนนต์สำหรับจัดการสมาชิกทีมงาน โดยมีฟังก์ชันหลักคือ TeamMemberManager ซึ่งใช้ในการแสดงและควบคุมข้อมูลของแต่ละคนในทีม", - "tags": [ - "component", - "team-member-manager" - ], - "complexity": "moderate" - }, - { - "id": "file:components/rfas/detail.tsx", - "type": "file", - "name": "detail.tsx", - "filePath": "components/rfas/detail.tsx", - "summary": "ไฟล์นี้เป็นหน้าแสดงรายละเอียดของ RFAs โดยมีฟังก์ชันหลักชื่อว่า RFADetail ซึ่งครอบคลุมการทำงานทั้งหมดเกี่ยวกับการแสดงผลข้อมูลและโครงสร้าง UI", - "tags": [ - "rfas", - "detail-view", - "component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/rfas/form.tsx", - "type": "file", - "name": "form.tsx", - "filePath": "components/rfas/form.tsx", - "summary": "ไฟล์นี้เป็นส่วนประกอบหลักสำหรับฟอร์ม RFA โดยมีการจัดเตรียมข้อมูลผ่านฟังก์ชันต่าง ๆ เช่น extractArrayData, dedupeByKey และ getOptionValue เพื่อให้ได้ผลลัพธ์ที่ถูกต้องและไม่ซ้ำซ้อน จากนั้นนำเอาส่วนประกอบหลัก RFAForm มาใช้งานโดยมีขนาดโค้ดรวมกว่า 600 บรรทัด", - "tags": [ - "form-component", - "rfas-module", - "data-processing" - ], - "complexity": "complex" - }, - { - "id": "file:components/rfas/list.tsx", - "type": "file", - "name": "list.tsx", - "filePath": "components/rfas/list.tsx", - "summary": "คอมโพเนนต์รายการ RFA (Request for Proposal) โดยมีฟังก์ชันหลักชื่อ RFAList ที่ใช้จัดแสดงข้อมูล RFA ในรูปแบบตารางหรือลิสต์ มีโค้ดรวมประมาณ 104 บรรทัด", - "tags": [ - "component", - "rfas", - "list" - ], - "complexity": "moderate" - }, - { - "id": "file:components/search/filters.tsx", - "type": "file", - "name": "filters.tsx", - "filePath": "components/search/filters.tsx", - "summary": "คอมโพเนนต์ SearchFilters เป็นองค์ประกอบที่ใช้สำหรับการแสดงผลและกรองข้อมูลตามเงื่อนไขต่าง ๆ โดยมีการจัดวางโครงสร้างภายในให้มีความชัดเจนและสามารถปรับแต่งได้ง่าย", - "tags": [ - "component", - "search", - "filter" - ], - "complexity": "moderate" - }, - { - "id": "file:components/search/results.tsx", - "type": "file", - "name": "results.tsx", - "filePath": "components/search/results.tsx", - "summary": "ไฟล์นี้มีหน้าที่แสดงผลลัพธ์การค้นหา โดยประกอบด้วยฟังก์ชัน getLink สำหรับสร้างลิงก์ และคอมโพเนนต์ SearchResults หลักที่ใช้ในการจัดรูปแบบข้อมูลผลลัพธ์", - "tags": [ - "search-results", - "component", - "results-display" - ], - "complexity": "moderate" - }, - { - "id": "file:components/transmittal/transmittal-form.tsx", - "type": "file", - "name": "transmittal-form.tsx", - "filePath": "components/transmittal/transmittal-form.tsx", - "summary": "คอมโพเนนต์แบบฟอร์มสำหรับการส่งเอกสาร โดยมีหน้าที่จัดวางองค์ประกอบต่าง ๆ เช่น ช่องกรอกข้อมูล เลือกไฟล์ และปุ่มยืนยัน การใช้งานโดยตรงผ่านการ export TransmittalForm", - "tags": [ - "form-component", - "document-transfer", - "react-component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/transmittal/transmittal-list.tsx", - "type": "file", - "name": "transmittal-list.tsx", - "filePath": "components/transmittal/transmittal-list.tsx", - "summary": "คอมโพเนนต์รายการส่งเอกสาร (Transmittal List) ที่แสดงข้อมูลรายการเอกสารทั้งหมดในระบบ โดยมีฟังก์ชันหลักชื่อ TransmittalList มีขนาดโค้ดประมาณ 64 บรรทัด และเป็นหน่วยงานประกอบการจัดแสดงข้อมูลสำหรับผู้ใช้งาน", - "tags": [ - "component", - "transmittal-list", - "document-management" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ui/alert-dialog.tsx", - "type": "file", - "name": "alert-dialog.tsx", - "filePath": "components/ui/alert-dialog.tsx", - "summary": "ไฟล์นี้เป็นส่วนหนึ-่งของระบบ UI ที่จัดทำ component AlertDialog โดยมีโครงสร้างประกอบด้วย header และ footer เพื่อใช้งานแสดงข้อความแจ้งเตือนหรือการยืนยันต่างๆ ในแอปพลิเคชัน มีการส่งออก (exports) เฉพาะชุดของ component ที่เกี่ยวข้องกับ AlertDialog เช่น AlertDialogHeader, AlertDialogFooter และอื่น ๆ เพื่อนำมาใช้งานในหน้าจอหลักได้โดยตรง", - "tags": [ - "component", - "ui-library", - "alert-dialog" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ui/alert.tsx", - "type": "file", - "name": "alert.tsx", - "filePath": "components/ui/alert.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน alert.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/avatar.tsx", - "type": "file", - "name": "avatar.tsx", - "filePath": "components/ui/avatar.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน avatar.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/badge.tsx", - "type": "file", - "name": "badge.tsx", - "filePath": "components/ui/badge.tsx", - "summary": "ไฟล์นี้เป็นคอมโพเนนต์ UI เบื้องต้นสำหรับการแสดงข้อมูลในรูปแบบ badge โดยมีฟังก์ชันหลักคือ Badge และการจัดแสดงโทนสีผ่าน badgeVariants ใช้งานง่ายและปรับแต่งได้ตามความต้องการ", - "tags": [ - "ui-component", - "badge", - "component" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/button.tsx", - "type": "file", - "name": "button.tsx", - "filePath": "components/ui/button.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน button.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/calendar.tsx", - "type": "file", - "name": "calendar.tsx", - "filePath": "components/ui/calendar.tsx", - "summary": "ไฟล์นี้เป็นคอมโพเนนต์ UI สำหรับการแสดงปฏิทิน โดยมีฟังก์ชันหลักชื่อ Calendar เรียงรายยาวประมาณ 42 บรรทัด มีการส่งออกเฉพาะชื่อ Calendar เพื่อนำมาใช้งานในแอปพลิเคชันได้", - "tags": [ - "ui-component", - "calendar", - "react-component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ui/card.tsx", - "type": "file", - "name": "card.tsx", - "filePath": "components/ui/card.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน card.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/checkbox.tsx", - "type": "file", - "name": "checkbox.tsx", - "filePath": "components/ui/checkbox.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน checkbox.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/command.tsx", - "type": "file", - "name": "command.tsx", - "filePath": "components/ui/command.tsx", - "summary": "ไฟล์นี้เป็นส่วนหนึ-่งของระบบ UI สำหรับการแสดงผลเมนูคำสั่ง (Command) โดยมีการสร้างคอมโพเนนต์หลายตัว เช่น Command, CommandInput, CommandList และอื่น ๆ เพื่อนำมาใช้แสดงรายการคำสั่งในแอปพลิเคชัน", - "tags": [ - "ui-component", - "command-barrel", - "react-components" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ui/dialog.tsx", - "type": "file", - "name": "dialog.tsx", - "filePath": "components/ui/dialog.tsx", - "summary": "ไฟล์นี้เป็นส่วนหนึ", - "tags": [ - "component", - "ui-library" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ui/dropdown-menu.tsx", - "type": "file", - "name": "dropdown-menu.tsx", - "filePath": "components/ui/dropdown-menu.tsx", - "summary": "ไฟล์นี้เป็นส่วนหนึ่งของระบบ UI components สำหรับการจัดวางเมนูแบบ dropdown โดยมีการสร้าง component เหล่านี้ขึ้นมาเพื่อใช้งานร่วมกันในโปรเจกต์ได้อย่างยืดหยุ่นและเข้าใจง่าย มีฟังก์ชัน DropdownMenuShortcut ที่ทำงานแคบๆ เพียงไม่กี่บรรทัด และส่งออก component ส่วนใหญ่เป็นมาตรฐาน เช่น DropdownMenu, DropdownMenuItem เป็นต้น", - "tags": [ - "ui-component", - "dropdown-menu", - "react-component" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/form.tsx", - "type": "file", - "name": "form.tsx", - "filePath": "components/ui/form.tsx", - "summary": "ไฟล์นี้เป็นส่วนหนึ", - "tags": [ - "component", - "ui-kit" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ui/hover-card.tsx", - "type": "file", - "name": "hover-card.tsx", - "filePath": "components/ui/hover-card.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน hover-card.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/input.tsx", - "type": "file", - "name": "input.tsx", - "filePath": "components/ui/input.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน input.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/label.tsx", - "type": "file", - "name": "label.tsx", - "filePath": "components/ui/label.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน label.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/popover.tsx", - "type": "file", - "name": "popover.tsx", - "filePath": "components/ui/popover.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน popover.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/progress.tsx", - "type": "file", - "name": "progress.tsx", - "filePath": "components/ui/progress.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน progress.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/scroll-area.tsx", - "type": "file", - "name": "scroll-area.tsx", - "filePath": "components/ui/scroll-area.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน scroll-area.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/select.tsx", - "type": "file", - "name": "select.tsx", - "filePath": "components/ui/select.tsx", - "summary": "ไฟล์นี้เป็นคอมโพเนนต์ UI สำหรับการเลือกค่าแบบ dropdown โดยมีองค์ประกอบหลายส่วน เช่น Select, SelectGroup, SelectValue และอื่น ๆ เพื่อใช้สร้างเมนูเลือกได้อย่างยืดหยุ่นและเข้ากันได้ง่ายในระบบที่พัฒนาขึ้น", - "tags": [ - "ui-component", - "dropdown-select", - "react-component" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ui/separator.tsx", - "type": "file", - "name": "separator.tsx", - "filePath": "components/ui/separator.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน separator.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/sheet.tsx", - "type": "file", - "name": "sheet.tsx", - "filePath": "components/ui/sheet.tsx", - "summary": "ไฟล์นี้เป็นส่วนหนึ-่งของระบบ UI components สำหรับการจัดแสดง Sheet (ช่องโต้ตอบแบบลอยตัว) โดยมีโครงสร้างพื้นฐานหลายองค์ประกอบ เช่น SheetHeader, SheetFooter และอื่น ๆ เพื่อนำมาใช้งานร่วมกันในแอปพลิเคชันได้อย่างยืดหยุ่น", - "tags": [ - "ui-component", - "sheet-modal", - "react-components" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ui/skeleton.tsx", - "type": "file", - "name": "skeleton.tsx", - "filePath": "components/ui/skeleton.tsx", - "summary": "ไฟล์นี้เป็นคอมโพเนนต์สำหรับแสดงโครงสร้างเบื้องต้น (Skeleton) ใช้งานเพื่อจำลองการแสดงผลขณะโหลดข้อมูล โดยมีฟังก์ชันหลักชื่อ Skeleton เรียบง่ายและมีขนาดเล็ก", - "tags": [ - "component", - "ui-kit", - "skeleton" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/sonner.tsx", - "type": "file", - "name": "sonner.tsx", - "filePath": "components/ui/sonner.tsx", - "summary": "ไฟล์นี้เป็นส่วนหนึ-่งของระบบแจ้งเตือน (Toaster) โดยใช้ไลบรารี Sonner เพื่อแสดงข้อความแจ้งเตือนแบบเรียลไทม์ บนหน้าเว็บ มีการสร้างคอมโพเนนต์ Toaster ที่สามารถจัดวางตำแหน่งและควบคุมการแสดงผลได้อย่างยืดหยุ่น", - "tags": [ - "toaster", - "notifications", - "sonner" - ], - "complexity": "moderate" - }, - { - "id": "file:components/ui/switch.tsx", - "type": "file", - "name": "switch.tsx", - "filePath": "components/ui/switch.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน switch.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/table.tsx", - "type": "file", - "name": "table.tsx", - "filePath": "components/ui/table.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน table.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/tabs.tsx", - "type": "file", - "name": "tabs.tsx", - "filePath": "components/ui/tabs.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน tabs.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/ui/textarea.tsx", - "type": "file", - "name": "textarea.tsx", - "filePath": "components/ui/textarea.tsx", - "summary": "ไฟล์โค้ดหน้าบ้าน textarea.tsx", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:components/workflow/integrated-banner.tsx", - "type": "file", - "name": "integrated-banner.tsx", - "filePath": "components/workflow/integrated-banner.tsx", - "summary": "ไฟล์นี้เป็นคอมโพเนนต์แสดงข้อมูลสถานะการทำงานแบบรวมกัน โดยมีฟังก์ชันสนับสนุนสำหรับการแปลงสถานะเป็นสีและรูปแบบการแสดงผล เช่น การกำหนดสีตามสถานะ และการแสดงปุ่มดำเนินการได้ ประกอบด้วยคอมโพเนนต์หลัก IntegratedBanner และ ActionButton", - "tags": [ - "component", - "workflow", - "status-display", - "action-button" - ], - "complexity": "moderate" - }, - { - "id": "file:components/workflows/dsl-editor.tsx", - "type": "file", - "name": "dsl-editor.tsx", - "filePath": "components/workflows/dsl-editor.tsx", - "summary": "คอมโพเนนต์ DSLEditor เป็นหน้าที่ใช้แสดงผลและจัดการงานดีไซน์แบบ DSL โดยมีฟังก์ชันหลักคือการสร้าง UI สำหรับแก้ไขลำดับการทำงานผ่านโครงสร้างข้อมูลเฉพาะทาง", - "tags": [ - "component", - "dsl-editor", - "workflow" - ], - "complexity": "moderate" - }, - { - "id": "file:components/workflows/visual-builder.tsx", - "type": "file", - "name": "visual-builder.tsx", - "filePath": "components/workflows/visual-builder.tsx", - "summary": "ไฟล์นี้เป็นองค์ประกอบหลักสำหรับการสร้างงานแบบสายไหล (workflow) อย่างมีภาพรวม โดยให้ผู้ใช้งานสามารถดึงโหนดและเชื่อมโยงกันได้อย่างยืดหยุ่น มีฟังก์ชันสนับสนุนหลายตัว เช่น createNode, createEdge และ parseDSL ที่ทำงานร่วมกันเพื่อแปลงโครงสร้างข้อมูลเป็นภาพประกอบ", - "tags": [ - "visual-builder", - "workflow-engine", - "dsl-parser" - ], - "complexity": "moderate" - }, - { - "id": "file:components/workflow/workflow-lifecycle.tsx", - "type": "file", - "name": "workflow-lifecycle.tsx", - "filePath": "components/workflow/workflow-lifecycle.tsx", - "summary": "ไฟล์นี้เป็นองค์ประกอบหลักสำหรับแสดงชีวิตการทำงานของระบบ (Workflow Lifecycle) โดยมีฟังก์ชันต่าง ๆ เช่น getNodeStyle และ getActionLabelKey ที่ใช้ในการกำหนดสไตลัสและป้ายกำกับการกระทำตามสถานะต่าง ๆ ในลำดับชั้นของงาน", - "tags": [ - "workflow-lifecycle", - "component", - "lifecycle-management" - ], - "complexity": "moderate" - }, - { - "id": "file:./src/eslint.config.mjs", - "type": "file", - "name": "eslint.config.mjs", - "filePath": "./src/eslint.config.mjs", - "summary": "ไฟล์กำหนดค่า ESLint เพื่อกำหนดกฎการเขียนโค้ดของโปรเจกต์ โดยไม่มีเนื้อหาหรือการนำเข้าใด ๆ ระบุไว้ในไฟล์นี้", - "tags": [ - "eslint", - "configuration", - "rules" - ], - "complexity": "simple" - }, - { - "id": "file:hooks/ai/use-intent-classification.ts", - "type": "file", - "name": "use-intent-classification.ts", - "filePath": "hooks/ai/use-intent-classification.ts", - "summary": "ไฟล์นี้เป็น hook สำหรับจัดการกับการจำแนกประเภทเจตนา (intent classification) ในระบบ AI โดยมีฟังก์ชันต่าง ๆ เช่น การดึงข้อมูลแบบกำหนดไว้ล่วงหน้า, การสร้าง/อัปเดท pattern และ definition รวมถึงการวิเคราะห์พฤติกรรมของเจตนา เพื่อให้ระบบสามารถตอบสนองตามความต้องการได้อย่างแม่นยำ", - "tags": [ - "ai", - "intent-classification", - "hook", - "use-intent-definitions", - "use-intent-patterns" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-ai-chat.ts", - "type": "file", - "name": "use-ai-chat.ts", - "filePath": "hooks/use-ai-chat.ts", - "summary": "ตัวช่วย (hook) สำหรับจัดการการสนทนาแบบ AI โดยมีฟังก์ชันหลักชื่อ useAiChat ซึ่งใช้งานได้ยาวถึง 87 บรรทัด มีหน้าที่รองรับการทำงานของระบบแชทดิสเพลย์และจัดการข้อมูลผู้ใช้ในระหว่างสนทนา", - "tags": [ - "hook", - "ai-chat" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-ai-prompts.ts", - "type": "file", - "name": "use-ai-prompts.ts", - "filePath": "hooks/use-ai-prompts.ts", - "summary": "ไฟล์นี้ประกอบด้วย hook สำหรับจัดการคำขอ AI prompts และการทำงานในสภาพแวดล้อม sandbox โดยมีฟังก์ชันหลักคือ useAiPrompts และ useSandboxRun ที่ใช้งานร่วมกับระบบบริหารจัดการ prompt และ execution ในสภาพแวดล้อมจำลอง", - "tags": [ - "hook", - "ai-prompts", - "sandbox-run" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-ai-status.ts", - "type": "file", - "name": "use-ai-status.ts", - "filePath": "hooks/use-ai-status.ts", - "summary": "ไฟล์นี้จัดเก็บชุดฟังก์ชันสำหรับการดูแลสถานะ AI โดยมีฟังก์ชันต่าง ๆ เช่น การตรวจสอบสถานะ AI ของผู้ใช้งานปัจจุบัน (useCurrentUserAiStatus), การควบคุมการทำงานของฟีเจอร์ AI (useToggleAiFeatures) และการประเมินสุขภาพระบบ AI (useAiHealth)", - "tags": [ - "hook", - "ai-status", - "user-context", - "health-check" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-audit-logs.ts", - "type": "file", - "name": "use-audit-logs.ts", - "filePath": "hooks/use-audit-logs.ts", - "summary": "ไฟล์นี้จัดเก็บ hook เกี่ยวกับการดึงประวัติการตรวจสอบ (audit logs) โดยมีฟังก์ชัน useAuditLogs ที่ใช้ในการเรียกดูข้อมูลประวัติการเข้าถึงระบบ และส่งคืน key สำหรับการจัดการ state เก็บไว้ใน cache", - "tags": [ - "hook", - "audit-logs", - "use-audit-logs" - ], - "complexity": "simple" - }, - { - "id": "file:hooks/use-circulation.ts", - "type": "file", - "name": "use-circulation.ts", - "filePath": "hooks/use-circulation.ts", - "summary": "ไฟล์นี้ประกอบด้วยตัวช่วย (hook) สำหรับจัดการข้อมูลวงจรเอกสาร เช่น การใช้งาน hook useCirculation และ useCirculationsByCorrespondence เพื่อดึงข้อมูลวงจรเอกสารตามความจำเป็นของระบบ", - "tags": [ - "hook", - "circulation", - "document-cycle" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-correspondence.ts", - "type": "file", - "name": "use-correspondence.ts", - "filePath": "hooks/use-correspondence.ts", - "summary": "ไฟล์นี้เป็นแหล่งรวมของ hook ที่เกี่ยวข้องกับการจัดการเอกสารสื่อสาร (correspondence) โดยมีฟังก์ชันต่าง ๆ เช่น การดึงรายการเอกสาร ส่งออกเอกสาร อัปเดต เปลี่ยนแปลงสถานะ และจัดการแท็กหรือแหล่งอ้างอิง รวมถึงกระบวนการไหลเวียนงาน (workflow)", - "tags": [ - "hook", - "correspondence", - "use-correspondences", - "use-create-correspondence", - "use-update-correspondence", - "use-delete-correspondence", - "use-cancel-correspondence", - "use-submit-correspondence", - "use-tags", - "use-references", - "workflow" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-dashboard.ts", - "type": "file", - "name": "use-dashboard.ts", - "filePath": "hooks/use-dashboard.ts", - "summary": "ไฟล์นี้ประกอบด้วยชุดฟังก์ชันสำหรับใช้งาน Dashboard โดยมีการสร้าง hook ต่าง ๆ เช่น useDashboardStats, useRecentActivity และ usePendingTasks เพื่อจัดเก็บและจัดการข้อมูลสถิติแดชบอร์ด กิจกรรมล่าสุด และงานที่ยังไม่ได้ดำเนินการตามลำดับ", - "tags": [ - "hook", - "dashboard", - "stats", - "activity", - "task" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-delegation.ts", - "type": "file", - "name": "use-delegation.ts", - "filePath": "hooks/use-delegation.ts", - "summary": "ไฟล์นี้จัดเก็บชุดของ hook ที่ใช้ในการจัดการหน้าที่ส่งต่อ (delegation) โดยมีฟังก์ชันหลัก ๆ เช่น useMyDelegations, useCreateDelegation และ useRevokeDelegation เพื่อให้ง่ายต่อการทำงานด้านการดึงข้อมูลและการสร้าง/ยกเลิกหน้าที่ส่งต่อ", - "tags": [ - "hook", - "delegation", - "useMyDelegations", - "useCreateDe-legation", - "useRevokeDelegation" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-distribution-matrices.ts", - "type": "file", - "name": "use-distribution-mat", - "filePath": "hooks/use-distribution-matrices.ts", - "summary": "ไฟล์นี้จัดเก็บตัวช่วย (hook) สำหรับการจัดการเมทริกซ์การกระจายสินค้า โดยมีฟังก์ชันหลัก ๆ เช่น useDistributionMatrices, useCreateDistributionMatrix และ useDeleteDistributionMatrix เพื่อให้สามารถดึงข้อมูลและดำเนินการสร้าง/ลบเมทริกซ์ได้อย่างมีประสิทธิภาพ", - "tags": [ - "hook", - "distribution-matrix", - "use-distribution-matrices" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-drawing.ts", - "type": "file", - "name": "use-drawing.ts", - "filePath": "hooks/use-drawing.ts", - "summary": "ไฟล์นี้ประกอบด้วยชุดของ hook ที่เกี่ยวข้องกับการจัดการการวาดภาพ (drawing) โดยมีฟังก์ชันต่าง ๆ เช่น useDrawings, useDrawing, useCreateDrawing และอื่น ๆ เพื่อรองรับการทำงานในระดับ component เกี่ยวกับการออกแบบหรือแก้ไขรายละเอียดของ drawing", - "tags": [ - "hook", - "drawing-management", - "use-drawings", - "contract-drawing" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-master-data.ts", - "type": "file", - "name": "use-master-data.ts", - "filePath": "hooks/use-master-data.ts", - "summary": "ไฟล์นี้เป็น hook สำหรับจัดการข้อมูลหลัก (master data) โดยมีฟังก์ชันต่าง ๆ เช่น การดึงข้อมูลองค์กร, ส่วนงาน, เรื่องราวโครงการ และสัญญา มาใช้งานได้ทันทีผ่าน React Hook API", - "tags": [ - "hook", - "master-data", - "organization", - "discipline", - "project", - "contract" - ], - "complexity": "moderate" - }, - { - "id": "file:src/hooks/use-migration-review.ts", - "type": "file", - "name": "use-migration-review.ts", - "filePath": "src/hooks/use-migration-review.ts", - "summary": "ไฟล์นี้เป็น hook สำหรับจัดการกระบวนการตรวจสอบย้ายข้อมูล (migration review) โดยมีฟังก์ชันต่าง ๆ เช่น การดึงข้อมูล, การเพิ่มเติมคำขอตรวจสอบ และการปฏิเสธคำขอตรวจสอบ มีการใช้งานร่วมกับระบบจัดเก็บสถานะและคีย์สำหรับการเรียกดูข้อมูล", - "tags": [ - "hook", - "migration-review", - "data-fetching", - "approval-flow" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-notification.ts", - "type": "file", - "name": "use-notification.ts", - "filePath": "hooks/use-notification.ts", - "summary": "ไฟล์นี้จัดเก็บระบบแจ้งเตือน (notifications) โดยมีฟังก์ชันหลักสองตัวคือ useNotifications และ useMarkNotificationRead ใช้สำหรับการดึงข้อมูลแจ้งเตือนและทำเครื่องหมายว่าอ่านแล้วตามลำดับ", - "tags": [ - "hook", - "notification", - "use-notifications" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-numbering.ts", - "type": "file", - "name": "use-numbering.ts", - "filePath": "hooks/use-numbering.ts", - "summary": "ไฟล์นี้ประกอบด้วยชุดของ custom hooks ที่จัดการกับการทำงานต่าง ๆ เรื่องเลขลำดับ (numbering) ในระบบ โดยมีฟังก์ชันเฉพาะสำหรับแต่ละภาระงาน เช่น การสร้างแบบแปลง (templates), การบันทึกข้อมูล, การตรวจสอบเมตริกส์, การจัดการประวัติการใช้งาน, การเปลี่ยนเลขลำดับแบบมือถือ, การยกเลิกเลขลำดับ และการนำเข้าจำนวนมาก ช่วยให้ระบบสามารถควบคุมและจัดการเลขลำดับได้อย่างมีประสิทธิภาพ", - "tags": [ - "custom-hooks", - "numbering-management", - "template-handling", - "audit-logs" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-projects.ts", - "type": "file", - "name": "use-projects.ts", - "filePath": "hooks/use-projects.ts", - "summary": "ไฟล์นี้จัดเก็บชุดของ hook สำหรับการจัดการโครงการต่าง ๆ โดยมีฟังก์ชันหลัก เช่น useProjects, useCreateProject, useUpdateProject และ useDeleteProject เพื่อให้สามารถดึงข้อมูลและดำเนินการ CRUD กับโครงการได้อย่างมีประสิทธิภาพ", - "tags": [ - "hook", - "project-management", - "use-projects", - "CRUD" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-reference-data.ts", - "type": "file", - "name": "use-reference-data.ts", - "filePath": "hooks/use-reference-data.ts", - "summary": "ไฟล์นี้ประกอบด้วยชุดของ hook ที่ใช้จัดการข้อมูลอ้างอิงต่าง ๆ เช่นประเภท RFA, Disciplines และประเภทการสื่อสาร โดยแต่ละ hook มีหน้าที่เฉพาะเจาะจงในการดึงหรือสร้าง/แก้ไข/ลบข้อมูลตามความจำเป็นของระบบ", - "tags": [ - "hook", - "reference-data", - "RFA-type", - "discipline", - "correspondence-type" - ], - "complexity": "moderate" - }, - { - "id": "file:src/hooks/use-reminder.ts", - "type": "file", - "name": "use-reminder.ts", - "filePath": "src/hooks/use-reminder.ts", - "summary": "ไฟล์นี้จัดเก็บชุดของ hook ที่ใช้บริหารจัดการกฎเตือนและประวัติการแจ้งเตือน โดยมีฟังก์ชันหลักๆ เช่น useReminderRules, useReminderHistory และ hook เพื่อสร้าง/แก้ไข/ลบกฎเตือน", - "tags": [ - "hook", - "reminder-rule", - "state-management" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-response-codes.ts", - "type": "file", - "name": "use-response-codes.ts", - "filePath": "hooks/use-response-codes.ts", - "summary": "ไฟล์นี้ประกอบด้วยชุดของ hook ที่ใช้จัดการรหัสคำตอบ (response codes) โดยแยกตามประเภทต่าง ๆ เช่น การแบ่งตามหมวดหมู่หรือชนิดเอกสาร เพื่อให้ง่ายต่อการเข้าถึงข้อมูลในแอปพลิเคชัน", - "tags": [ - "hook", - "response-codes", - "category-filtering", - "document-type" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-review-teams.ts", - "type": "file", - "name": "use-review-teams.ts", - "filePath": "hooks/use-review-teams.ts", - "summary": "ไฟล์นี้จัดเก็บชุดฟังก์ชันสำหรับการจัดการทีมผู้ประเมิน โดยมีฟังก์ชันต่าง ๆ เช่น การดึงข้อมูลทีมผู้ประเมิน (useReviewTeams), การสร้าง/อัปเดตทีม, และการเพิ่ม-ลบสมาชิกในทีม ใช้ร่วมกับระบบจัดการหน่วยงานและสิทธิ์พนักงาน", - "tags": [ - "hook", - "review-team", - "team-management", - "useCreateReviewTeam", - "useUpdateReviewTeam" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-rfa.ts", - "type": "file", - "name": "use-rfa.ts", - "filePath": "hooks/use-rfa.ts", - "summary": "ไฟล์นี้ประกอบด้วยชุดของ hook ที่ใช้จัดการกับการทำงานต่าง ๆ เรื่อง RFA (Request For Approval) โดยมีฟังก์ชันหลัก เช่น useRFA, useSubmitRFA, useCreateRFA และอื่นๆ เพื่อให้ง่ายต่อการดึงข้อมูลหรือส่งคำขออนุมัติจากผู้ใช้", - "tags": [ - "hook", - "rfa", - "approval workflow" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-search.ts", - "type": "file", - "name": "use-search.ts", - "filePath": "hooks/use-search.ts", - "summary": "ไฟล์นี้จัดเก็บ hook สำหรับการค้นหาข้อมูล โดยมีฟังก์ชันหลักสองตัวได้แก่ useSearch และ useSearchSuggestions ที่ใช้งานร่วมกับระบบแนะนำคำค้นหา", - "tags": [ - "hook", - "search", - "use-search" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-translations.ts", - "type": "file", - "name": "use-translations.ts", - "filePath": "hooks/use-translations.ts", - "summary": "สรุปหน้าที่ของไฟล์นี้คือการจัดการภาษาแบบใช้งานง่ายผ่าน hook โดยมีฟังก์ชัน useTranslations สำหรับดึงข้อมูลคำแปลจากแหล่งต้นทาง เช่น i18n provider และส่งกลับเป็น object ของคำแปลพร้อม key เหมาะสมสำหรับใช้งานใน component", - "tags": [ - "hook", - "i18n", - "translation" - ], - "complexity": "simple" - }, - { - "id": "file:hooks/use-transmittal.ts", - "type": "file", - "name": "use-transmittal.ts", - "filePath": "hooks/use-transmittal.ts", - "summary": "ตัวช่วย (hook) สำหรับจัดการข้อมูลการส่งเอกสาร โดยมีฟังก์ชันหลักชื่อ useTransmittal และคีย์ที่ใช้ในการจัดเก็บสถานะของเอกสารในระบบ", - "tags": [ - "use-transmittal", - "transmittal-keys" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-users.ts", - "type": "file", - "name": "use-users.ts", - "filePath": "hooks/use-users.ts", - "summary": "ไฟล์นี้จัดเก็บชุดฟังก์ชันสำหรับการจัดการผู้ใช้งาน โดยมีฟังก์ชันหลัก ๆ เช่น useUsers, useRoles, useCreateUser, useUpdateUser และ useDeleteUser ที่รองรับการทำงานต่าง ๆ เกี่ยวกับข้อมูลผู้ใช้ในระบบ", - "tags": [ - "hook", - "user-management", - "react-query" - ], - "complexity": "moderate" - }, - { - "id": "file:src/hooks/use-workflow-action.ts", - "type": "file", - "name": "use-workflow-action.ts", - "filePath": "src/hooks/use-workflow-action.ts", - "summary": "ไฟล์นี้จัดเก็บ hook สำหรับการจัดการการทำงานของ workflow โดยมีฟังก์ชัน isApiErrorResponse เพื่อตรวจสอบข้อผิดพลาดจาก API และใช้ใน hook useWorkflowAction หลักๆ เป็นตัวควบคุมสถานะและพฤติกรรมของการเรียกดูหรือดำเนินการตามลำดับของ workflow", - "tags": [ - "hook", - "workflow-action", - "api-error-check" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-workflow-history.ts", - "type": "file", - "name": "use-workflow-history.ts", - "filePath": "hooks/use-workflow-history.ts", - "summary": "ตัวช่วย (hook) เพื่อจัดการประวัติการทำงานของกระบวนการ โดยให้ค่าข้อมูลประวัติงานที่เก็บไว้อยู่ในระบบ และรองรับการดึงข้อมูลผ่าน key สำหรับใช้งานร่วมกัน", - "tags": [ - "hook", - "workflow-history" - ], - "complexity": "moderate" - }, - { - "id": "file:hooks/use-workflows.ts", - "type": "file", - "name": "use-workflows.ts", - "filePath": "hooks/use-workflows.ts", - "summary": "ไฟล์นี้จัดเก็บชุดของ custom hooks ที่ใช้ในการจัดการ workflow โดยมีฟังก์ชันต่าง ๆ เช่น การดึงข้อมูล definition, การสร้าง/อัปเดต/ลบ definition และการประเมิน workflow เพื่อนำไปใช้งานในหน้าเว็บไซต์", - "tags": [ - "custom-hooks", - "workflow-management", - "react-query", - "dsl-validation" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/api/admin.ts", - "type": "file", - "name": "admin.ts", - "filePath": "/src/lib/api/admin.ts", - "summary": "ไฟล์นี้เป็นโมดูลสำหรับจัดการ API โดยเฉพาะอย่างยิ่งใช้สำหรับระบบบริหารจัดการ (Admin) มีการส่งออกตัวแปร adminApi ซึ่งอาจเป็น instance ของ RTK Query เพื่อให้งาน CRUD และการดึงข้อมูลจาก backend เกิดขึ้นได้อย่างมีประสิทธิภาพ", - "tags": [ - "api-client", - "admin-module", - "rtk-query" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/api/ai.ts", - "type": "file", - "name": "ai.ts", - "filePath": "lib/api/ai.ts", - "summary": "ไฟล์นี้เป็นโมดูล API สำหรับการจัดการงาน AI โดยมีฟังก์ชันต่าง ๆ เช่น การส่งคำขอ Rag, การตรวจสอบสถานะงาน, การอนุมัติข้อมูล staging และการจัดการลอคของระบบ เพื่อให้ผู้ใช้งานสามารถโต้ตอบและควบคุมการทำงานของ AI ได้อย่างมีประสิทธิภาพ", - "tags": [ - "ai", - "rag-query", - "staging-queue", - "api-handler" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/api/dashboard.ts", - "type": "file", - "name": "dashboard.ts", - "filePath": "/src/lib/api/dashboard.ts", - "summary": "ไฟล์นี้เป็นโมดูลสำหรับจัดการ API dashboard โดยส่งออกตัวแปร dashboardApi ใช้ในการเรียกใช้งาน endpoint เฉพาะทางแดชบอร์ดในแอปพลิเคชัน", - "tags": [ - "api-handler", - "dashboard" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/api/drawings.ts", - "type": "file", - "name": "drawings.ts", - "filePath": "lib/api/drawings.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน drawings.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:lib/api/files.ts", - "type": "file", - "name": "files.ts", - "filePath": "lib/api/files.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน files.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:lib/api/notifications.ts", - "type": "file", - "name": "notifications.ts", - "filePath": "lib/api/notifications.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน notifications.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:lib/api/numbering.ts", - "type": "file", - "name": "numberingApi", - "filePath": "lib/api/numbering.ts", - "summary": "ไฟล์นี้เป็นโมดูล API สำหรับการจัดลำดับเลขที่ โดยส่งออกตัวแปร numberingApi เพื่อใช้งานในแอปพลิเคชันได้โดยตรง", - "tags": [ - "api-module", - "numbering" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/api/workflows.ts", - "type": "file", - "name": "workflows.ts", - "filePath": "/src/lib/api/workflows.ts", - "summary": "ไฟล์นี้เป็นโมดูลสำหรับจัดการ API เกี่ยวกับ workflow โดยส่งออกตัวแปร workflowApi ซึ่งใช้ในการเรียกดูและจัดการข้อมูล workflow ในระบบได้อย่างมีประสิทธิภาพ", - "tags": [ - "api-client", - "workflow-management" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/auth.ts", - "type": "file", - "name": "auth.ts", - "filePath": "lib/auth.ts", - "summary": "ไฟล์นี้จัดเก็บฟังก์ชันต่าง ๆ ที่เกี่ยวข้องกับการตรวจสอบและจัดการโทเคน (token) เช่น การดึงเวลาหมดอายุของ JWT, การตรวจสอบโครงสร้าง payload จาก token และการรีเฟรช accessToken โดยมีการส่งออกค่า handlers อันประกอบไปด้วยฟังก์ชัน GET และ POST เพื่อใช้งานในระบบบริหารจัดการการเข้าสู่ระบบ", - "tags": [ - "authentication", - "jwt", - "token-management", - "api-handler" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/i18n/index.ts", - "type": "file", - "name": "index.ts", - "filePath": "lib/i18n/index.ts", - "summary": "ไฟล์นี้จัดการระบบแปลภาษา (i18n) โดยสร้างฟังก์ชัน createT สำหรับใช้ในการเรียกคำแปล และส่งออกตัวแปร t เพื่อให้สามารถใช้งานได้ง่ายในโปรเจกต์", - "tags": [ - "i18n", - "translation", - "createT", - "t" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/asbuilt-drawing.service.ts", - "type": "file", - "name": "asbuilt-drawing.service.ts", - "filePath": "lib/services/asbuilt-drawing.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน asbuilt-drawing.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/audit-log.service.ts", - "type": "file", - "name": "audit-log.service.ts", - "filePath": "lib/services/audit-log.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน audit-log.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/circulation.service.ts", - "type": "file", - "name": "circulation.service.ts", - "filePath": "lib/services/circulation.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน circulation.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/contract-drawing.service.ts", - "type": "file", - "name": "contract-drawing.service.ts", - "filePath": "lib/services/contract-drawing.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน contract-drawing.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/contract.service.ts", - "type": "file", - "name": "contract.service.ts", - "filePath": "lib/services/contract.service.ts", - "summary": "ไฟล์นี้เป็นบริการสำหรับจัดการข้อมูลสัญญา โดยมีฟังก์ชัน normalizeContract ที่ใช้ปรับรูปแบบข้อมูลสัญญาให้อยู่ในมาตรฐาน และ extractContractArray เพื่อดึงข้อมูลรายการสัญญาออกมาเป็นอาร์เรย์", - "tags": [ - "service", - "contract", - "data-processing" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/correspondence.service.ts", - "type": "file", - "name": "correspondence.service.ts", - "filePath": "lib/services/correspondence.service.ts", - "summary": "บริการจัดการข้อมูลเอกสารสื่อสารภายในระบบ โดยมีหน้าที่หลักในการดึงข้อมูล สร้าง และอัปเดตเอกสารสื่อสารตามความต้องการของผู้ใช้งาน", - "tags": [ - "service", - "correspondence", - "document-management" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/dashboard.service.ts", - "type": "file", - "name": "dashboard.service.ts", - "filePath": "/src/lib/services/dashboard.service.ts", - "summary": "บริการสำหรับจัดการหน้าแดชบอร์ด โดยมีฟังก์ชันหลักในการดึงข้อมูลจากฐานข้อมูลและประมวลผลให้พร้อมใช้งานในหน้าแสดงผล", - "tags": [ - "service", - "dashboard", - "data-fetching" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/document-numbering.service.ts", - "type": "file", - "name": "document-numbering.service.ts", - "filePath": "lib/services/document-numbering.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน document-numbering.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/drawing-master-data.service.ts", - "type": "file", - "name": "drawing-master-data.service.ts", - "filePath": "lib/services/drawing-master-data.service.ts", - "summary": "บริการสำหรับจัดการข้อมูลหลักของแบบวาดภาพ โดยมีหน้าที่ดูแลการทำงานกับฐานข้อมูลและให้บริการข้อมูลแก่ component อื่น ๆ ผ่าน method เช่น การเพิ่ม เปลี่ยนแปลงหรือลบข้อมูล", - "tags": [ - "service", - "drawing-master-data" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/index.ts", - "type": "file", - "name": "index.ts", - "filePath": "lib/services/index.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน index.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/json-schema.service.ts", - "type": "file", - "name": "json-schema.service.ts", - "filePath": "lib/services/json-schema.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน json-schema.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/master-data.service.ts", - "type": "file", - "name": "master-data.service.ts", - "filePath": "/src/lib/services/master-data.service.ts", - "summary": "บริการสำหรับจัดการข้อมูลหลัก (Master Data) โดยมีฟังก์ชัน extractArrayData ใช้ในการดึงข้อมูลเป็นอาร์เรย์ และส่งค่าออกเป็นโมดูล masterDataService เพื่อให้งานต่าง ๆ เรียกดูหรือประมวลผลได้อย่างมีประสิทธิภาพ", - "tags": [ - "service", - "master-data", - "utility" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/monitoring.service.ts", - "type": "file", - "name": "monitoring.service.ts", - "filePath": "lib/services/monitoring.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน monitoring.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/notification.service.ts", - "type": "file", - "name": "notification.service.ts", - "filePath": "lib/services/notification.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน notification.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/organization.service.ts", - "type": "file", - "name": "organization.service.ts", - "filePath": "lib/services/organization.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน organization.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/project.service.ts", - "type": "file", - "name": "project.service.ts", - "filePath": "lib/services/project.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน project.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/review-team.service.ts", - "type": "file", - "name": "review-team.service.ts", - "filePath": "lib/services/review-team.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน review-team.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/rfa.service.ts", - "type": "file", - "name": "rfa.service.ts", - "filePath": "lib/services/rfa.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน rfa.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/search.service.ts", - "type": "file", - "name": "search.service.ts", - "filePath": "lib/services/search.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน search.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/session.service.ts", - "type": "file", - "name": "session.service.ts", - "filePath": "lib/services/session.service.ts", - "summary": "ไฟล์นี้เป็นบริการสำหรับจัดการเซสชัน โดยมีฟังก์ชัน extractArrayData ที่ใช้ดึงข้อมูลจากอาร์เรย์ และ transformSession เพื่อแปลงรูปแบบของข้อมูลเซสชันให้อยู่ในรูปแบบที่เหมาะสมสำหรับการตอบกลับ", - "tags": [ - "service", - "session-management" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/shop-drawing.service.ts", - "type": "file", - "name": "shop-drawing.service.ts", - "filePath": "lib/services/shop-drawing.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน shop-drawing.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/transmittal.service.ts", - "type": "file", - "name": "transmittal.service.ts", - "filePath": "lib/services/transmittal.service.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน transmittal.service.ts", - "tags": [ - "utility", - "service", - "api-handler" - ], - "complexity": "simple" - }, - { - "id": "file:lib/services/user.service.ts", - "type": "file", - "name": "user.service.ts", - "filePath": "lib/services/user.service.ts", - "summary": "ไฟล์นี้เป็นบริการสำหรับจัดการข้อมูลผู้ใช้งาน โดยมีฟังก์ชัน extractArrayData ที่ดึงข้อมูลจากอาร์เรย์ และ transformUser เพื่อแปลงรูปแบบข้อมูลผู้ใช้ให้อยู่ในรูปแบบที่ต้องการ", - "tags": [ - "service", - "user-management" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/services/workflow-engine.service.ts", - "type": "file", - "name": "workflow-engine.service.ts", - "filePath": "lib/services/workflow-engine.service.ts", - "summary": "บริการสำหรับจัดการการทำงานของระบบ workflow โดยมีฟังก์ชันต่าง ๆ เช่น การดึงข้อมูลจาก array, nested structure และ DSL definition มาใช้งานอย่างเหมาะสม มีการแปลงประเภทของ workflow ให้เข้ารูปแบบมาตรฐานเพื่อรองรับการทำงานในระบบที่หลากหลาย", - "tags": [ - "workflow-engine", - "data-extraction", - "dsl-processing", - "service-layer" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/stores/auth-store.ts", - "type": "file", - "name": "auth-store.ts", - "filePath": "lib/stores/auth-store.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน auth-store.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:lib/stores/draft-store.ts", - "type": "file", - "name": "draft-store.ts", - "filePath": "lib/stores/draft-store.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน draft-store.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:lib/stores/ui-store.ts", - "type": "file", - "name": "ui-store.ts", - "filePath": "lib/stores/ui-store.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน ui-store.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:lib/test-utils.tsx", - "type": "file", - "name": "test-utils.tsx", - "filePath": "lib/test-utils.tsx", - "summary": "ไฟล์นี้ให้เครื่องมือสำหรับการทดสอบ โดยมีฟังก์ชัน createTestQueryClient ที่สร้าง client query เพื่อใช้งานในสภาพแวดล้อมทดสอบ และ waitForQueryClient เพื่อรอให้ query client เตรียมพร้อมใช้งาน", - "tags": [ - "testing", - "query-client", - "utility" - ], - "complexity": "moderate" - }, - { - "id": "file:lib/utils.ts", - "type": "file", - "name": "utils.ts", - "filePath": "/src/lib/utils.ts", - "summary": "ไฟล์นี้มีหน้าที่จัดการกับฟังก์ชันต่าง ๆ เช่น การรวมค่าสตริงหรือการแสดงผลตามเงื่อนไข โดยเฉพาะอย่างยิ่งมีฟังก์ชัน cn ซึ่งใช้ในการควบคุมการแสดงผลขององค์ประกอบ HTML", - "tags": [ - "utility", - "string-combination" - ], - "complexity": "simple" - }, - { - "id": "file:lib/utils/uuid-guard.ts", - "type": "file", - "name": "uuid-guard.ts", - "filePath": "/src/lib/utils/uuid-guard.ts", - "summary": "ไฟล์นี้มีหน้าที่ตรวจสอบค่า UUID โดยใช้ฟังก์ชัน assertUuid ซึ่งรับพารามิเตอร์เป็นสตริงและยืนยันว่าเป็นรูปแบบของ UUID เหมือนมาตรฐาน RFC4122 หากไม่ตรงตามรูปแบบจะโยนข้อผิดพลาดออกไป", - "tags": [ - "uuid-validation", - "utility-function" - ], - "complexity": "simple" - }, - { - "id": "file:next.config.mjs", - "type": "file", - "name": "next.config.mjs", - "filePath": "next.config.mjs", - "summary": "ไฟล์โค้ดหน้าบ้าน next.config.mjs", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:.nvmrc", - "type": "file", - "name": ".nvmrc", - "filePath": ".nvmrc", - "summary": "ไฟล์โค้ดหน้าบ้าน .nvmrc", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:postcss.config.mjs", - "type": "file", - "name": "postcss.config.mjs", - "filePath": "postcss.config.mjs", - "summary": "ไฟล์โค้ดหน้าบ้าน postcss.config.mjs", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:providers/query-provider.tsx", - "type": "file", - "name": "query-provider.tsx", - "filePath": "providers/query-provider.tsx", - "summary": "ไฟล์นี้ให้บริการ QueryProvider ซึ่งเป็นโครงสร้างหลักสำหรับจัดการ query และ context ในแอปพลิเคชัน โดยมีหน้าที่รับค่าจาก hooks เช่น useQuery, useMutation เพื่อให้สามารถเข้าถึงข้อมูลได้อย่างสะดวกและมีประสิทธิภาพ", - "tags": [ - "provider", - "query-management", - "context-api" - ], - "complexity": "moderate" - }, - { - "id": "file:providers/session-provider.tsx", - "type": "file", - "name": "session-provider.tsx", - "filePath": "providers/session-provider.tsx", - "summary": "ไฟล์นี้ให้บริการ SessionProvider ซึ่งใช้สำหรับจัดการสถานะของเซสชันผู้ใช้งาน โดยมีฟังก์ชันหลักคือ SessionProvider เรียกใช้งานได้โดยตรงจากคอมโพเนนต์อื่น ๆ เพื่อดึงข้อมูลและควบคุมสถานะของผู้ใช้งาน", - "tags": [ - "provider", - "session-management" - ], - "complexity": "simple" - }, - { - "id": "file:providers/theme-provider.tsx", - "type": "file", - "name": "theme-provider.tsx", - "filePath": "providers/theme-provider.tsx", - "summary": "ไฟล์นี้เป็น provider สำหรับจัดการธีมของแอปพลิเคชัน โดยมีฟังก์ชัน ThemeProvider ที่ใช้ในการห่อหุ้มองค์ประกอบต่าง ๆ และส่งค่า theme มาให้กับ component เหล่านั้น", - "tags": [ - "provider", - "theme", - "context" - ], - "complexity": "simple" - }, - { - "id": "file:src/proxy.ts", - "type": "file", - "name": "proxy.ts", - "filePath": "src/proxy.ts", - "summary": "ไฟล์ proxy.ts มีหน้าที่ตรวจสอบเส้นทาง (path) เพื่อระบุว่าควรจะถูกบล็อกหรือไม่ โดยใช้ฟังก์ชัน isBlockedPath ซึ่งมีขนาดเล็กและตรงประเด็น เนื้อหาหลักคือการกำหนดค่า config ที่สามารถนำออกไปใช้งานได้อีกหลายจุดในโปรเจกต์", - "tags": [ - "proxy", - "security", - "path-check" - ], - "complexity": "simple" - }, - { - "id": "file:tailwind.config.ts", - "type": "file", - "name": "tailwind.config.ts", - "filePath": "tailwind.config.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน tailwind.config.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/admin.ts", - "type": "file", - "name": "admin.ts", - "filePath": "types/admin.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน admin.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/ai-chat.ts", - "type": "file", - "name": "ai-chat.ts", - "filePath": "types/ai-chat.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน ai-chat.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/api-error.ts", - "type": "file", - "name": "api-error.ts", - "filePath": "types/api-error.ts", - "summary": "ไฟล์นี้มีหน้าที่ให้บริการฟังก์ชัน getApiErrorMessage ซึ่งใช้ในการแปลข้อความข้อผิดพลาดจาก API เป็นภาษาไทยหรือภาษาอังกฤษตามต้องการ โดยมีโครงสร้างเรียบง่ายและไม่มีการนำเข้าใด ๆ เนื่องจากเป็นไฟล์ที่เก็บเฉพาะฟังก์ชันแปลข้อความผิดพลาดเท่านั้น", - "tags": [ - "api-error", - "error-handling" - ], - "complexity": "simple" - }, - { - "id": "file:src/types/circulation.ts", - "type": "file", - "name": "circulation.ts", - "filePath": "src/types/circulation.ts", - "summary": "ไฟล์นี้เป็นโครงสร้างประเภทข้อมูลสำหรับระบบการเคลื่อนย้ายสินค้าภายในโครงการ lcbp3-frontend โดยไม่มีเนื้อหาเฉพาะเจาะจงถูกระบุไว้ในชุดข้อมูลที่ให้มา", - "tags": [ - "type-definition", - "circulation-system" - ], - "complexity": "simple" - }, - { - "id": "file:types/contract.ts", - "type": "file", - "name": "contract.ts", - "filePath": "types/contract.ts", - "summary": "ไฟล์นี้มีหน้าที่จัดเก็บฟังก์ชันต่าง ๆ สำหรับดึงค่า public ID จากโปรเจกต์และคอนแท็กต์ โดยใช้เพื่อการเชื่อมโยงข้อมูลในระบบอย่างเป็นทางการ", - "tags": [ - "type-definition", - "contract-id", - "project-id" - ], - "complexity": "simple" - }, - { - "id": "file:src/types/correspondence.ts", - "type": "file", - "name": "correspondence.ts", - "filePath": "src/types/correspondence.ts", - "summary": "ไฟล์นี้เป็นโครงสร้างข้อมูลสำหรับการจัดการเอกสารสื่อสารภายในระบบ โดยกำหนดชนิดข้อมูลเฉพาะทาง เช่น ประเภทของเอกสาร สภาพะต่าง ๆ และคุณสมบัติที่เกี่ยวข้องกับการจัดเก็บและแสดงผล", - "tags": [ - "type-definition", - "correspondence-data", - "document-schema" - ], - "complexity": "simple" - }, - { - "id": "file:types/dashboard.ts", - "type": "file", - "name": "dashboard.ts", - "filePath": "types/dashboard.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน dashboard.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/drawing.ts", - "type": "file", - "name": "drawing.ts", - "filePath": "types/drawing.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน drawing.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/circulation/create-circulation.dto.ts", - "type": "file", - "name": "create-circulation.dto.ts", - "filePath": "types/dto/circulation/create-circulation.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน create-circulation.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/circulation/search-circulation.dto.ts", - "type": "file", - "name": "search-circulation.dto.ts", - "filePath": "types/dto/circulation/search-circulation.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน search-circulation.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/circulation/update-circulation-routing.dto.ts", - "type": "file", - "name": "update-circulation-routing.dto.ts", - "filePath": "types/dto/circulation/update-circulation-routing.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน update-circulation-routing.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/contract/contract.dto.ts", - "type": "file", - "name": "contract.dto.ts", - "filePath": "types/dto/contract/contract.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน contract.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/correspondence/add-reference.dto.ts", - "type": "file", - "name": "add-reference.dto.ts", - "filePath": "types/dto/correspondence/add-reference.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน add-reference.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/correspondence/create-correspondence.dto.ts", - "type": "file", - "name": "create-correspondence.dto.ts", - "filePath": "types/dto/correspondence/create-correspondence.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน create-correspondence.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/correspondence/search-correspondence.dto.ts", - "type": "file", - "name": "search-correspondence.dto.ts", - "filePath": "types/dto/correspondence/search-correspondence.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน search-correspondence.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/correspondence/submit-correspondence.dto.ts", - "type": "file", - "name": "submit-correspondence.dto.ts", - "filePath": "types/dto/correspondence/submit-correspondence.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน submit-correspondence.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/correspondence/workflow-action.dto.ts", - "type": "file", - "name": "workflow-action.dto.ts", - "filePath": "types/dto/correspondence/workflow-action.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน workflow-action.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/drawing/asbuilt-drawing.dto.ts", - "type": "file", - "name": "asbuilt-drawing.dto.ts", - "filePath": "types/dto/drawing/asbuilt-drawing.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน asbuilt-drawing.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/drawing/contract-drawing.dto.ts", - "type": "file", - "name": "contract-drawing.dto.ts", - "filePath": "types/dto/drawing/contract-drawing.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน contract-drawing.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/drawing/shop-drawing.dto.ts", - "type": "file", - "name": "shop-drawing.dto.ts", - "filePath": "types/dto/drawing/shop-drawing.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน shop-drawing.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/json-schema/json-schema.dto.ts", - "type": "file", - "name": "json-schema.dto.ts", - "filePath": "types/dto/json-schema/json-schema.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน json-schema.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/master/correspondence-type.dto.ts", - "type": "file", - "name": "correspondence-type.dto.ts", - "filePath": "types/dto/master/correspondence-type.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน correspondence-type.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/master/discipline.dto.ts", - "type": "file", - "name": "discipline.dto.ts", - "filePath": "types/dto/master/discipline.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน discipline.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/master/number-format.dto.ts", - "type": "file", - "name": "number-format.dto.ts", - "filePath": "types/dto/master/number-format.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน number-format.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/master/rfa-type.dto.ts", - "type": "file", - "name": "rfa-type.dto.ts", - "filePath": "types/dto/master/rfa-type.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน rfa-type.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/master/sub-type.dto.ts", - "type": "file", - "name": "sub-type.dto.ts", - "filePath": "types/dto/master/sub-type.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน sub-type.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/master/tag.dto.ts", - "type": "file", - "name": "tag.dto.ts", - "filePath": "types/dto/master/tag.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน tag.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/migration/migration-review.dto.ts", - "type": "file", - "name": "migration-review.dto.ts", - "filePath": "types/dto/migration/migration-review.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน migration-review.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/monitoring/set-maintenance.dto.ts", - "type": "file", - "name": "set-maintenance.dto.ts", - "filePath": "types/dto/monitoring/set-maintenance.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน set-maintenance.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/notification/notification.dto.ts", - "type": "file", - "name": "notification.dto.ts", - "filePath": "types/dto/notification/notification.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน notification.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/numbering.dto.ts", - "type": "file", - "name": "numbering.dto.ts", - "filePath": "types/dto/numbering.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน numbering.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/organization/organization.dto.ts", - "type": "file", - "name": "organization.dto.ts", - "filePath": "types/dto/organization/organization.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน organization.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/project/project.dto.ts", - "type": "file", - "name": "project.dto.ts", - "filePath": "types/dto/project/project.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน project.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/rfa/rfa.dto.ts", - "type": "file", - "name": "rfa.dto.ts", - "filePath": "types/dto/rfa/rfa.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน rfa.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/search/search-query.dto.ts", - "type": "file", - "name": "search-query.dto.ts", - "filePath": "types/dto/search/search-query.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน search-query.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/transmittal/transmittal.dto.ts", - "type": "file", - "name": "transmittal.dto.ts", - "filePath": "types/dto/transmittal/transmittal.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน transmittal.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/user/user.dto.ts", - "type": "file", - "name": "user.dto.ts", - "filePath": "types/dto/user/user.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน user.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/dto/workflow-engine/workflow-engine.dto.ts", - "type": "file", - "name": "workflow-engine.dto.ts", - "filePath": "types/dto/workflow-engine/workflow-engine.dto.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน workflow-engine.dto.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/master-data.ts", - "type": "file", - "name": "master-data.ts", - "filePath": "types/master-data.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน master-data.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/migration.ts", - "type": "file", - "name": "migration.ts", - "filePath": "types/migration.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน migration.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/next-auth.d.ts", - "type": "file", - "name": "next-auth.d.ts", - "filePath": "types/next-auth.d.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน next-auth.d.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/notification.ts", - "type": "file", - "name": "notification.ts", - "filePath": "types/notification.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน notification.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/numbering.ts", - "type": "file", - "name": "numbering.ts", - "filePath": "types/numbering.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน numbering.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/organization.ts", - "type": "file", - "name": "organization.ts", - "filePath": "types/organization.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน organization.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/react-day-picker.d.ts", - "type": "file", - "name": "react-day-picker.d.ts", - "filePath": "types/react-day-picker.d.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน react-day-picker.d.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:src/types/review-team.ts", - "type": "file", - "name": "review-team.ts", - "filePath": "src/types/review-team.ts", - "summary": "ไฟล์นี้เป็นโครงสร้างข้อมูลสำหรับการจัดการทีมผู้ตรวจสอบ (Review Team) โดยกำหนดประเภทของสมาชิกในทีมนั้น ๆ เช่น บทบาท, สิทธิภารกิจ และความสัมพันธ์ระหว่างบุคคล", - "tags": [ - "type-definition", - "review-team", - "team-structure" - ], - "complexity": "simple" - }, - { - "id": "file:src/types/rfa.ts", - "type": "file", - "name": "rfa.ts", - "filePath": "src/types/rfa.ts", - "summary": "ไฟล์นี้เป็นโครงสร้างประเภทข้อมูล (types) สำหรับระบบ RFA โดยไม่มีเนื้อหาเฉพาะเจาะจงถูกระบุไว้ในส่วนที่ให้อาหารข้อมูล กรุณาตรวจสอบรายละเอียดเพิ่มเติมจากโค้ดต้นฉบับ", - "tags": [ - "type", - "interface", - "schema" - ], - "complexity": "simple" - }, - { - "id": "file:types/search.ts", - "type": "file", - "name": "search.ts", - "filePath": "types/search.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน search.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:src/types/transmittal.ts", - "type": "file", - "name": "transmittal.ts", - "filePath": "src/types/transmittal.ts", - "summary": "ไฟล์นี้กำหนดโครงสร้างข้อมูลสำหรับเอกสารส่งมอบ (Transmittal) โดยใช้ TypeScript เพื่อให้มั่นใจว่าข้อมูลที่ถูกส่งผ่านมีรูปแบบและประเภทที่ชัดเจน", - "tags": [ - "type", - "transmittal", - "document", - "typescript" - ], - "complexity": "simple" - }, - { - "id": "file:types/user.ts", - "type": "file", - "name": "user.ts", - "filePath": "types/user.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน user.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:types/workflow.ts", - "type": "file", - "name": "workflow.ts", - "filePath": "types/workflow.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน workflow.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:.understand-anything/tmp/ua-inline-validate.cjs", - "type": "file", - "name": "ua-inline-validate.cjs", - "filePath": ".understand-anything/tmp/ua-inline-validate.cjs", - "summary": "ไฟล์โค้ดหน้าบ้าน ua-inline-validate.cjs", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:.understand-anything/.understandignore", - "type": "file", - "name": ".understandignore", - "filePath": ".understand-anything/.understandignore", - "summary": "ไฟล์โค้ดหน้าบ้าน .understandignore", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:vitest.config.ts", - "type": "file", - "name": "vitest.config.ts", - "filePath": "vitest.config.ts", - "summary": "ไฟล์โค้ดหน้าบ้าน vitest.config.ts", - "tags": [ - "utility" - ], - "complexity": "simple" - }, - { - "id": "file:src/test/setup/vitest.setup.ts", - "type": "file", - "name": "vitest.setup.ts", - "filePath": "src/test/setup/vitest.setup.ts", - "summary": "ไฟล์นี้ใช้สำหรับตั้งค่าสภาพแวดล้อมการทดสอบ โดยมีการสร้าง Mock ของ ResizeObserver เพื่อใช้งานในกรณีที่ไม่สามารถเข้าถึง DOM จริงได้ เช่น ใน environment testing", - "tags": [ - "setup-file", - "vitest", - "mock-object", - "ResizeObserver" - ], - "complexity": "simple" - } - ], - "edges": [ - { - "source": "file:lib/services/admin-ai.service.ts", - "target": "file:lib/api/client.ts", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:lib/services/admin-ai.service.ts", - "target": "file:types/ai.ts", - "type": "imports", - "direction": "forward", - "weight": 0.6 - }, - { - "source": "file:lib/services/ai-intent.service.ts", - "target": "file:lib/api/client.ts", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:lib/services/ai-intent.service.ts", - "target": "file:lib/services/ai-intent.service.ts", - "type": "exports", - "direction": "backward", - "weight": 1 - }, - { - "source": "file:lib/services/ai-prompts.service.ts", - "target": "file:lib/api/client.ts", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:lib/services/ai-prompts.service.ts", - "target": "file:types/ai-prompts.ts", - "type": "imports", - "direction": "forward", - "weight": 0.5 - }, - { - "source": "file:lib/services/ai.service.ts", - "target": "file:lib/api/client.ts", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:lib/api/client.ts", - "target": "file:lib/services/ai.service.ts", - "type": "used-by", - "direction": "backward", - "weight": 0.3 - }, - { - "source": "file:lib/services/migration.service.ts", - "target": "file:lib/api/client.ts", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:lib/services/migration.service.ts", - "target": "file:lib/services/migration.service.ts", - "type": "exports", - "direction": "backward", - "weight": 1 - }, - { - "source": "file:components/layout/header.tsx", - "target": "file:components/layout/global-search.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:components/layout/header.tsx", - "target": "file:components/layout/notifications-dropdown.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.65 - }, - { - "source": "file:components/layout/header.tsx", - "target": "file:components/layout/project-switcher.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:components/layout/header.tsx", - "target": "file:components/layout/sidebar.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.65 - }, - { - "source": "file:components/layout/header.tsx", - "target": "file:components/layout/theme-toggle.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:components/layout/header.tsx", - "target": "file:components/layout/user-menu.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.65 - }, - { - "source": "file:app/(dashboard)/rag/page.tsx", - "target": "file:components/ai/RagChatWidget.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:app/(dashboard)/rag/page.tsx", - "target": "file:lib/stores/project-store.ts", - "type": "imports", - "direction": "forward", - "weight": 0.6 - }, - { - "source": "file:app/layout.tsx", - "target": "file:app/globals.css", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:components/admin/ai/OcrSandboxPromptManager.tsx", - "target": "file:components/admin/ai/PromptVersionHistory.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:components/ai/document-comparison-view.tsx", - "target": "file:components/ai/ai-suggestion-field.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:components/ai/intent-classification/test-console-panel.tsx", - "target": "file:components/ai/intent-classification/classification-result-card.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:components/layout/navbar.tsx", - "target": "file:components/layout/user-nav.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7 - }, - { - "source": "file:components/ui/dropdown-menu.tsx", - "target": "file:components/ui/dropdown-menu.tsx", - "type": "exports", - "direction": "forward", - "weight": 0.9 - }, - { - "source": "file:components/ui/dropdown-menu.tsx", - "target": "file:components/ui/dropdown-menu.tsx", - "type": "functions", - "direction": "forward", - "weight": 1 - }, - { - "source": "file:components/ai/ai-status-banner-host.tsx", - "target": "file:components/ai/AiStatusBanner.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7, - "recoveredFromImportMap": true - }, - { - "source": "file:components/drawings/list.tsx", - "target": "file:components/drawings/columns.tsx", - "type": "imports", - "direction": "forward", - "weight": 0.7, - "recoveredFromImportMap": true - } - ], - "layers": [ - { - "id": "layer:app-router", - "name": "App Router & Pages", - "description": "โครงสร้างหน้าเว็บและเส้นทางระบบ Next.js", - "nodeIds": [ - "file:app/(dashboard)/rag/page.tsx", - "file:app/(admin)/admin/access-control/organizations/page.tsx", - "file:app/(admin)/admin/ai/intent-classification/test-console/page.tsx", - "file:app/(admin)/admin/ai/page.tsx", - "file:app/(admin)/admin/doc-control/contracts/page.tsx", - "file:app/(admin)/admin/doc-control/drawings/page.tsx", - "file:app/(admin)/admin/doc-control/projects/page.tsx", - "file:app/(admin)/admin/doc-control/reference/correspondence-types/page.tsx", - "file:app/(admin)/admin/doc-control/reference/disciplines/page.tsx", - "file:app/(admin)/admin/doc-control/reference/rfa-types/page.tsx", - "file:app/(admin)/admin/doc-control/reference/tags/page.tsx", - "file:app/(admin)/admin/migration/page.tsx", - "file:app/(admin)/admin/monitoring/sessions/page.tsx", - "file:app/(admin)/admin/monitoring/system-logs/numbering/page.tsx", - "file:app/(admin)/admin/users/page.tsx", - "file:app/(admin)/error.tsx", - "file:app/api/auth/[...nextauth]/route.ts", - "file:app/(auth)/login/page.tsx", - "file:app/(dashboard)/ai-staging/page.tsx", - "file:app/(dashboard)/circulation/new/page.tsx", - "file:app/(dashboard)/correspondences/page.tsx", - "file:app/(dashboard)/dashboard/page.tsx", - "file:app/(dashboard)/delegation/page.tsx", - "file:app/(dashboard)/distribution-matrices/page.tsx", - "file:app/(dashboard)/drawings/page.tsx", - "file:app/(dashboard)/drawings/[uuid]/page.tsx", - "file:app/(dashboard)/error.tsx", - "file:app/(dashboard)/layout.tsx", - "file:app/(dashboard)/projects/new/page.tsx", - "file:app/(dashboard)/projects/page_backup.tsx", - "file:app/(dashboard)/rfas/[uuid]/edit/page.tsx", - "file:app/(dashboard)/rfas/[uuid]/page.tsx", - "file:app/(dashboard)/search/page.tsx", - "file:app/(dashboard)/transmittals/new/page.tsx", - "file:app/(dashboard)/transmittals/page.tsx", - "file:app/(dashboard)/transmittals/[uuid]/page.tsx", - "file:app/globals.css", - "file:app/layout.tsx" - ] - }, - { - "id": "layer:global-providers", - "name": "Global Providers & Context", - "description": "โมดูลให้บริการแชร์ค่าฝั่งหน้าบ้าน", - "nodeIds": [ - "file:providers/query-provider.tsx", - "file:providers/session-provider.tsx", - "file:providers/theme-provider.tsx" - ] - }, - { - "id": "layer:custom-hooks", - "name": "Custom React Hooks", - "description": "เครื่องมือ React Hooks ควบคุม Client Logic", - "nodeIds": [ - "file:hooks/ai/use-intent-classification.ts", - "file:hooks/use-ai-chat.ts", - "file:hooks/use-ai-prompts.ts", - "file:hooks/use-ai-status.ts", - "file:hooks/use-audit-logs.ts", - "file:hooks/use-circulation.ts", - "file:hooks/use-correspondence.ts", - "file:hooks/use-dashboard.ts", - "file:hooks/use-delegation.ts", - "file:hooks/use-distribution-matrices.ts", - "file:hooks/use-drawing.ts", - "file:hooks/use-master-data.ts", - "file:hooks/use-notification.ts", - "file:hooks/use-numbering.ts", - "file:hooks/use-projects.ts", - "file:hooks/use-reference-data.ts", - "file:hooks/use-response-codes.ts", - "file:hooks/use-review-teams.ts", - "file:hooks/use-rfa.ts", - "file:hooks/use-search.ts", - "file:hooks/use-translations.ts", - "file:hooks/use-transmittal.ts", - "file:hooks/use-users.ts", - "file:hooks/use-workflow-history.ts", - "file:hooks/use-workflows.ts" - ] - }, - { - "id": "layer:ui-components", - "name": "Reusable UI Components", - "description": "คอมโพเนนต์ส่วนแสดงติดต่อผู้ใช้", - "nodeIds": [ - "file:components/layout/global-search.tsx", - "file:components/layout/header.tsx", - "file:components/layout/notifications-dropdown.tsx", - "file:components/layout/project-switcher.tsx", - "file:components/layout/sidebar.tsx", - "file:components/layout/theme-toggle.tsx", - "file:components/layout/user-menu.tsx", - "file:components/ai/RagChatWidget.tsx", - "file:components/admin/ai/OcrEngineSelector.tsx", - "file:components/admin/ai/OcrSandboxPromptManager.tsx", - "file:components/admin/ai/PromptVersionHistory.tsx", - "file:components/admin/organization-dialog.tsx", - "file:components/admin/reference/generic-crud-table.tsx", - "file:components/admin/security/rbac-matrix.tsx", - "file:components/admin/sidebar.tsx", - "file:components/admin/user-dialog.tsx", - "file:components/ai/ai-chat-input.tsx", - "file:components/ai/ai-chat-messages.tsx", - "file:components/ai/ai-chat-panel.tsx", - "file:components/ai/ai-chat-toggle.tsx", - "file:components/ai/ai-status-banner-host.tsx", - "file:components/ai/AiStatusBanner.tsx", - "file:components/ai/ai-suggestion-button.tsx", - "file:components/ai/ai-suggestion-field.tsx", - "file:components/ai/document-comparison-view.tsx", - "file:components/ai/intent-classification/analytics/analytics-summary-cards.tsx", - "file:components/ai/intent-classification/analytics/intent-breakdown-table.tsx", - "file:components/ai/intent-classification/analytics/method-breakdown-table.tsx", - "file:components/ai/intent-classification/analytics/recalibration-panel.tsx", - "file:components/ai/intent-classification/classification-result-card.tsx", - "file:components/ai/intent-classification/intent-form.tsx", - "file:components/ai/intent-classification/pattern-form.tsx", - "file:components/ai/intent-classification/test-console-panel.tsx", - "file:components/ai/processing-indicator.tsx", - "file:components/auth/auth-sync.tsx", - "file:components/circulation/circulation-list.tsx", - "file:components/common/can.tsx", - "file:components/common/confirm-dialog.tsx", - "file:components/common/data-table.tsx", - "file:components/common/error-display.tsx", - "file:components/common/file-preview-modal.tsx", - "file:components/common/pagination.tsx", - "file:components/common/status-badge.tsx", - "file:components/common/workflow-error-boundary.tsx", - "file:components/correspondences/circulation-status-card.tsx", - "file:components/correspondences/correspondences-content.tsx", - "file:components/correspondences/detail.tsx", - "file:components/correspondences/form.tsx", - "file:components/correspondences/list.tsx", - "file:components/correspondences/reference-selector.tsx", - "file:components/correspondences/revision-history.tsx", - "file:components/correspondences/tag-manager.tsx", - "file:components/correspondences/ux-flow-dialog.tsx", - "file:components/custom/file-upload-zone.tsx", - "file:components/custom/workflow-visualizer.tsx", - "file:components/dashboard/pending-tasks.tsx", - "file:components/dashboard/quick-actions.tsx", - "file:components/dashboard/recent-activity.tsx", - "file:components/dashboard/stats-cards.tsx", - "file:components/delegation/DelegationForm.tsx", - "file:components/distribution/DistributionStatus.tsx", - "file:components/documents/common/server-data-table.tsx", - "file:components/drawings/card.tsx", - "file:components/drawings/columns.tsx", - "file:components/drawings/list.tsx", - "file:components/drawings/revision-history.tsx", - "file:components/drawings/upload-form.tsx", - "file:components/layout/dashboard-shell.tsx", - "file:components/layout/navbar.tsx", - "file:components/layout/user-nav.tsx", - "file:components/migration/review-queue-table.tsx", - "file:components/numbering/audit-logs-table.tsx", - "file:components/numbering/bulk-import-form.tsx", - "file:components/numbering/cancel-number-form.tsx", - "file:components/numbering/manual-override-form.tsx", - "file:components/numbering/metrics-dashboard.tsx", - "file:components/numbering/sequence-viewer.tsx", - "file:components/numbering/template-editor.tsx", - "file:components/numbering/template-tester.tsx", - "file:components/numbering/void-replace-form.tsx", - "file:components/reminder/ReminderRuleForm.tsx", - "file:components/response-code/CodeImplications.tsx", - "file:components/response-code/MatrixEditor.tsx", - "file:components/response-code/ProjectOverrideManager.tsx", - "file:components/response-code/ResponseCodeSelector.tsx", - "file:components/review-task/CompleteReviewForm.tsx", - "file:components/review-task/DelegatedBadge.tsx", - "file:components/review-task/ParallelProgress.tsx", - "file:components/review-task/ReviewTaskInbox.tsx", - "file:components/review-task/VetoOverrideDialog.tsx", - "file:components/review-team/ReviewTeamForm.tsx", - "file:components/review-team/ReviewTeamSelector.tsx", - "file:components/review-team/TeamMemberManager.tsx", - "file:components/rfas/detail.tsx", - "file:components/rfas/form.tsx", - "file:components/rfas/list.tsx", - "file:components/search/filters.tsx", - "file:components/search/results.tsx", - "file:components/transmittal/transmittal-form.tsx", - "file:components/transmittal/transmittal-list.tsx", - "file:components/ui/alert-dialog.tsx", - "file:components/ui/alert.tsx", - "file:components/ui/avatar.tsx", - "file:components/ui/badge.tsx", - "file:components/ui/button.tsx", - "file:components/ui/calendar.tsx", - "file:components/ui/card.tsx", - "file:components/ui/checkbox.tsx", - "file:components/ui/command.tsx", - "file:components/ui/dialog.tsx", - "file:components/ui/dropdown-menu.tsx", - "file:components/ui/form.tsx", - "file:components/ui/hover-card.tsx", - "file:components/ui/input.tsx", - "file:components/ui/label.tsx", - "file:components/ui/popover.tsx", - "file:components/ui/progress.tsx", - "file:components/ui/scroll-area.tsx", - "file:components/ui/select.tsx", - "file:components/ui/separator.tsx", - "file:components/ui/sheet.tsx", - "file:components/ui/skeleton.tsx", - "file:components/ui/sonner.tsx", - "file:components/ui/switch.tsx", - "file:components/ui/table.tsx", - "file:components/ui/tabs.tsx", - "file:components/ui/textarea.tsx", - "file:components/workflow/integrated-banner.tsx", - "file:components/workflows/dsl-editor.tsx", - "file:components/workflows/visual-builder.tsx", - "file:components/workflow/workflow-lifecycle.tsx" - ] - }, - { - "id": "layer:api-services", - "name": "API Services & Utilities", - "description": "บริการดึงข้อมูลและฟังก์ชันช่วยงานระดับโปรเจกต์", - "nodeIds": [ - "file:lib/api/client.ts", - "file:lib/services/admin-ai.service.ts", - "file:lib/services/ai-intent.service.ts", - "file:lib/services/ai-prompts.service.ts", - "file:lib/services/ai.service.ts", - "file:lib/services/migration.service.ts", - "file:lib/stores/project-store.ts", - "file:lib/api/ai.ts", - "file:lib/api/drawings.ts", - "file:lib/api/files.ts", - "file:lib/api/notifications.ts", - "file:lib/api/numbering.ts", - "file:lib/auth.ts", - "file:lib/i18n/index.ts", - "file:lib/services/asbuilt-drawing.service.ts", - "file:lib/services/audit-log.service.ts", - "file:lib/services/circulation.service.ts", - "file:lib/services/contract-drawing.service.ts", - "file:lib/services/contract.service.ts", - "file:lib/services/correspondence.service.ts", - "file:lib/services/document-numbering.service.ts", - "file:lib/services/drawing-master-data.service.ts", - "file:lib/services/index.ts", - "file:lib/services/json-schema.service.ts", - "file:lib/services/monitoring.service.ts", - "file:lib/services/notification.service.ts", - "file:lib/services/organization.service.ts", - "file:lib/services/project.service.ts", - "file:lib/services/review-team.service.ts", - "file:lib/services/rfa.service.ts", - "file:lib/services/search.service.ts", - "file:lib/services/session.service.ts", - "file:lib/services/shop-drawing.service.ts", - "file:lib/services/transmittal.service.ts", - "file:lib/services/user.service.ts", - "file:lib/services/workflow-engine.service.ts", - "file:lib/stores/auth-store.ts", - "file:lib/stores/draft-store.ts", - "file:lib/stores/ui-store.ts", - "file:lib/test-utils.tsx" - ] - }, - { - "id": "layer:configs-types", - "name": "Configurations & TypeScript Types", - "description": "ตั้งค่าเครื่องมือและประมวลผลชนิดตัวแปร", - "nodeIds": [ - "file:types/ai.ts", - "file:types/ai-prompts.ts", - "service:.dockerignore", - "config:.env.example", - "document:README.md", - "config:components.json", - "config:tsconfig.json", - "file:app/(admin)/admin/access-control/roles/page.tsx", - "file:/app/(admin)/admin/access-control/users/page.tsx", - "file:app/(admin)/admin/ai/intent-classification/analytics/page.tsx", - "file:app/(admin)/admin/ai/intent-classification/[intentCode]/page.tsx", - "file:app/(admin)/admin/ai/intent-classification/page.tsx", - "file:app/(admin)/admin/audit-logs/page.tsx", - "file:app/(admin)/admin/doc-control/drawings/contract/categories/page.tsx", - "file:app/(admin)/admin/doc-control/drawings/contract/sub-categories/page.tsx", - "file:app/(admin)/admin/doc-control/drawings/contract/volumes/page.tsx", - "file:app/(admin)/admin/doc-control/drawings/shop/main-categories/page.tsx", - "file:app/(admin)/admin/doc-control/drawings/shop/sub-categories/page.tsx", - "file:app/(admin)/admin/doc-control/numbering/[id]/edit/page.tsx", - "file:app/(admin)/admin/doc-control/numbering/new/page.tsx", - "file:app/(admin)/admin/doc-control/numbering/page.tsx", - "file:app/(admin)/admin/doc-control/reference/drawing-categories/page.tsx", - "file:app/(admin)/admin/doc-control/reference/page.tsx", - "file:app/(admin)/admin/doc-control/workflows/[id]/edit/page.tsx", - "file:app/(admin)/admin/doc-control/workflows/new/page.tsx", - "file:app/(admin)/admin/doc-control/workflows/page.tsx", - "file:app/(admin)/admin/migration/errors/page.tsx", - "file:app/(admin)/admin/migration/review/[id]/page.tsx", - "file:app/(admin)/admin/monitoring/audit-logs/page.tsx", - "file:app/(admin)/admin/numbering/[id]/edit/page.tsx", - "file:app/(admin)/admin/numbering/new/page.tsx", - "file:app/(admin)/admin/numbering/page.tsx", - "file:app/(admin)/admin/organizations/page.tsx", - "file:app/(admin)/admin/page.tsx", - "file:app/(admin)/admin/settings/page.tsx", - "file:app/(admin)/admin/workflows/[id]/edit/page.tsx", - "file:app/(admin)/admin/workflows/new/page.tsx", - "file:app/(admin)/admin/workflows/page.tsx", - "file:app/(admin)/layout.tsx", - "file:app/api/ai/chat/route.ts", - "file:app/(auth)/layout.tsx", - "file:app/(dashboard)/circulation/page.tsx", - "file:/app/(dashboard)/circulation/[uuid]/page.tsx", - "file:app/(dashboard)/correspondences/new/page.tsx", - "file:app/(dashboard)/correspondences/[uuid]/edit/page.tsx", - "file:app/(dashboard)/correspondences/[uuid]/page.tsx", - "file:app/(dashboard)/drawings/upload/page.tsx", - "file:app/(dashboard)/migration/review/page.tsx", - "file:app/(dashboard)/profile/page.tsx", - "file:app/(dashboard)/projects/page.tsx", - "file:app/(dashboard)/response-codes/page.tsx", - "file:app/(dashboard)/rfa/page.tsx", - "file:app/(dashboard)/rfas/new/page.tsx", - "file:app/(dashboard)/rfas/page.tsx", - "file:app/(dashboard)/settings/delegation/page.tsx", - "file:app/(dashboard)/settings/page.tsx", - "file:app/(dashboard)/settings/reminder-rules/page.tsx", - "file:app/(dashboard)/settings/review-teams/page.tsx", - "file:app/error.tsx", - "file:app/global-error.tsx", - "file:app/page.tsx", - "file:build-map.js", - "file:components/reminder/ReminderHistory.tsx", - "file:./src/eslint.config.mjs", - "file:src/hooks/use-migration-review.ts", - "file:src/hooks/use-reminder.ts", - "file:src/hooks/use-workflow-action.ts", - "file:lib/api/admin.ts", - "file:lib/api/dashboard.ts", - "file:lib/api/workflows.ts", - "file:lib/services/dashboard.service.ts", - "file:lib/services/master-data.service.ts", - "file:lib/utils.ts", - "file:lib/utils/uuid-guard.ts", - "file:next.config.mjs", - "file:.nvmrc", - "file:postcss.config.mjs", - "file:src/proxy.ts", - "file:tailwind.config.ts", - "file:types/admin.ts", - "file:types/ai-chat.ts", - "file:types/api-error.ts", - "file:src/types/circulation.ts", - "file:types/contract.ts", - "file:src/types/correspondence.ts", - "file:types/dashboard.ts", - "file:types/drawing.ts", - "file:types/dto/circulation/create-circulation.dto.ts", - "file:types/dto/circulation/search-circulation.dto.ts", - "file:types/dto/circulation/update-circulation-routing.dto.ts", - "file:types/dto/contract/contract.dto.ts", - "file:types/dto/correspondence/add-reference.dto.ts", - "file:types/dto/correspondence/create-correspondence.dto.ts", - "file:types/dto/correspondence/search-correspondence.dto.ts", - "file:types/dto/correspondence/submit-correspondence.dto.ts", - "file:types/dto/correspondence/workflow-action.dto.ts", - "file:types/dto/drawing/asbuilt-drawing.dto.ts", - "file:types/dto/drawing/contract-drawing.dto.ts", - "file:types/dto/drawing/shop-drawing.dto.ts", - "file:types/dto/json-schema/json-schema.dto.ts", - "file:types/dto/master/correspondence-type.dto.ts", - "file:types/dto/master/discipline.dto.ts", - "file:types/dto/master/number-format.dto.ts", - "file:types/dto/master/rfa-type.dto.ts", - "file:types/dto/master/sub-type.dto.ts", - "file:types/dto/master/tag.dto.ts", - "file:types/dto/migration/migration-review.dto.ts", - "file:types/dto/monitoring/set-maintenance.dto.ts", - "file:types/dto/notification/notification.dto.ts", - "file:types/dto/numbering.dto.ts", - "file:types/dto/organization/organization.dto.ts", - "file:types/dto/project/project.dto.ts", - "file:types/dto/rfa/rfa.dto.ts", - "file:types/dto/search/search-query.dto.ts", - "file:types/dto/transmittal/transmittal.dto.ts", - "file:types/dto/user/user.dto.ts", - "file:types/dto/workflow-engine/workflow-engine.dto.ts", - "file:types/master-data.ts", - "file:types/migration.ts", - "file:types/next-auth.d.ts", - "file:types/notification.ts", - "file:types/numbering.ts", - "file:types/organization.ts", - "file:types/react-day-picker.d.ts", - "file:src/types/review-team.ts", - "file:src/types/rfa.ts", - "file:types/search.ts", - "file:src/types/transmittal.ts", - "file:types/user.ts", - "file:types/workflow.ts", - "file:.understand-anything/tmp/ua-inline-validate.cjs", - "file:.understand-anything/.understandignore", - "file:vitest.config.ts", - "file:src/test/setup/vitest.setup.ts" - ] - } - ], - "tour": [ - { - "order": 1, - "title": "โครงสร้าง Layout หลัก (Root Layout)", - "description": "โครงหน้าหลักและ Providers ส่วนกลาง", - "nodeIds": [ - "file:app/layout.tsx" - ] - }, - { - "order": 2, - "title": "หน้าควบคุมและการนำทาง (App Routing Pages)", - "description": "ระบบจัดการ Admin Console และหน้า Staging", - "nodeIds": [ - "file:app/page.tsx", - "file:app/(admin)/admin/ai/page.tsx" - ] - }, - { - "order": 3, - "title": "จัดการตรรกะและสถานะการดึงข้อมูล (Custom Hooks)", - "description": "ระบบ React Hooks ควบคุมแอนิเมชันและ AI", - "nodeIds": [ - "file:hooks/use-ai-status.ts" - ] - }, - { - "order": 4, - "title": "ระบบเชื่อมต่อ API ไป Backend (API Services)", - "description": "บริการติดต่อส่งข้อมูลผ่าน AdminAiService", - "nodeIds": [ - "file:lib/services/admin-ai.service.ts" - ] - }, - { - "order": 5, - "title": "คอมโพเนนต์นำกลับมาใช้งานใหม่ (UI Components)", - "description": "คอมโพเนนต์ควบคุมการกดอนุมัติเอกสาร RFA", - "nodeIds": [ - "file:components/ui/button.tsx" - ] - } - ] -} \ No newline at end of file diff --git a/frontend/.understand-anything/meta.json b/frontend/.understand-anything/meta.json deleted file mode 100644 index c586f519..00000000 --- a/frontend/.understand-anything/meta.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "lastAnalyzedAt": "2026-06-13T13:24:07.512Z", - "gitCommitHash": "190b9a3af5f505e9ec59ba8d447c4720b2cb7dae", - "version": "1.0.0", - "analyzedFiles": 373 -} \ No newline at end of file diff --git a/frontend/app/(admin)/admin/ai/prompt-management/page.tsx b/frontend/app/(admin)/admin/ai/prompt-management/page.tsx new file mode 100644 index 00000000..a8c44775 --- /dev/null +++ b/frontend/app/(admin)/admin/ai/prompt-management/page.tsx @@ -0,0 +1,198 @@ +// File: frontend/app/(admin)/admin/ai/prompt-management/page.tsx +// Change Log: +// - 2026-06-14: Created unified prompt management page (conforming to tasks T019, T029, T038) + +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { adminAiService } from '@/lib/services/admin-ai.service'; +import { PromptType, PromptVersion, ContextConfig } from '@/lib/types/ai-prompts'; +import PromptTypeDropdown from '@/components/admin/ai/PromptTypeDropdown'; +import VersionHistory from '@/components/admin/ai/VersionHistory'; +import PromptEditor from '@/components/admin/ai/PromptEditor'; +import ContextConfigEditor from '@/components/admin/ai/ContextConfigEditor'; +import SandboxTabs from '@/components/admin/ai/SandboxTabs'; +import RuntimeParametersPanel from '@/components/admin/ai/RuntimeParametersPanel'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { toast } from 'sonner'; +import { Brain, Sliders, Play, Settings } from 'lucide-react'; + +export default function UnifiedPromptManagementPage() { + const queryClient = useQueryClient(); + const [selectedType, setSelectedType] = useState('ocr_extraction'); + const [selectedVersion, setSelectedVersion] = useState(null); + + // ดึงข้อมูลประวัติเวอร์ชันทั้งหมดของ prompt_type ที่เลือก + const { data: versions = [], isLoading } = useQuery({ + queryKey: ['admin-ai-prompts', selectedType], + queryFn: async () => { + const res = await adminAiService.listPrompts(selectedType); + return res || []; + }, + }); + + // อัปเดต selectedVersion เมื่อเปลี่ยนประเภทหรือข้อมูลรีเฟรช + useEffect(() => { + if (versions.length > 0) { + const active = versions.find((v) => v.isActive) || versions[0]; + setSelectedVersion(active); + } else { + setSelectedVersion(null); + } + }, [versions, selectedType]); + + // สร้างเวอร์ชันใหม่ + const createMutation = useMutation({ + mutationFn: async (payload: { template: string; manualNote: string }) => { + return await adminAiService.createPrompt(selectedType, { + template: payload.template, + manualNote: payload.manualNote, + }); + }, + onSuccess: () => { + toast.success('สร้าง Prompt Version ใหม่สำเร็จ'); + queryClient.invalidateQueries({ queryKey: ['admin-ai-prompts', selectedType] }); + }, + onError: () => { + toast.error('ไม่สามารถสร้าง Prompt Version ใหม่ได้'); + }, + }); + + // เปิดใช้งานเวอร์ชัน + const activateMutation = useMutation({ + mutationFn: async (versionNumber: number) => { + return await adminAiService.activatePrompt(selectedType, versionNumber); + }, + onSuccess: () => { + toast.success('เปิดใช้งาน Prompt Version สำเร็จ'); + queryClient.invalidateQueries({ queryKey: ['admin-ai-prompts', selectedType] }); + }, + onError: () => { + toast.error('ไม่สามารถเปิดใช้งาน Prompt Version ได้'); + }, + }); + + // ลบเวอร์ชัน + const deleteMutation = useMutation({ + mutationFn: async (versionNumber: number) => { + return await adminAiService.deletePrompt(selectedType, versionNumber); + }, + onSuccess: () => { + toast.success('ลบ Prompt Version สำเร็จ'); + queryClient.invalidateQueries({ queryKey: ['admin-ai-prompts', selectedType] }); + }, + onError: (err: unknown) => { + const errorMsg = (err as { response?: { data?: { message?: string } } })?.response?.data?.message; + toast.error(errorMsg || 'ไม่สามารถลบ Prompt Version ได้'); + }, + }); + + // อัปเดตบริบทข้อมูล (Context Config) + const updateConfigMutation = useMutation({ + mutationFn: async (payload: { versionNumber: number; config: ContextConfig }) => { + return await adminAiService.updateContextConfig( + selectedType, + payload.versionNumber, + payload.config + ); + }, + onSuccess: () => { + toast.success('อัปเดตการตั้งค่าบริบทสำเร็จ'); + queryClient.invalidateQueries({ queryKey: ['admin-ai-prompts', selectedType] }); + }, + onError: (err: unknown) => { + const errorMsg = (err as { response?: { data?: { message?: string } } })?.response?.data?.message; + toast.error(errorMsg || 'ไม่สามารถอัปเดตบริบทได้'); + }, + }); + + return ( +
+
+
+

+ + ระบบจัดการ Prompt และบริบท (Prompt & Context Manager) +

+

+ จัดการเทมเพลตพรอมต์และตัวกรองข้อมูล Master Data เพื่อส่งให้ระบบ AI ประมวลผลอย่างแม่นยำ +

+
+
+ +
+
+ +
+ {/* Sidebar: รายการประวัติเวอร์ชัน */} +
+ activateMutation.mutate(v)} + onDeleteVersion={(v) => deleteMutation.mutate(v)} + isActivating={activateMutation.isPending} + isDeleting={deleteMutation.isPending} + /> +
+ + {/* Main Panel: แผงแก้ไขและทดสอบ Sandbox */} +
+ + + + + ตัวแก้ไขและบริบท (Editor & Context) + + + + บอร์ดทดลอง (3-Step Sandbox) + + + + พารามิเตอร์รันไทม์ (Runtime Params) + + + + + { + await createMutation.mutateAsync({ template: tmpl, manualNote: note }); + }} + isSaving={createMutation.isPending} + /> + {selectedVersion && ( + { + await updateConfigMutation.mutateAsync({ + versionNumber: selectedVersion.versionNumber, + config, + }); + }} + isSaving={updateConfigMutation.isPending} + /> + )} + + + + activateMutation.mutate(v)} + /> + + + + + + +
+
+
+ ); +} diff --git a/frontend/components/admin/ai/ContextConfigEditor.tsx b/frontend/components/admin/ai/ContextConfigEditor.tsx new file mode 100644 index 00000000..2cae14de --- /dev/null +++ b/frontend/components/admin/ai/ContextConfigEditor.tsx @@ -0,0 +1,244 @@ +// File: frontend/components/admin/ai/ContextConfigEditor.tsx +// Change Log: +// - 2026-06-14: Created ContextConfigEditor component with project/contract loaders and selectors (conforming to task T028) + +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { CheckCircle2, Settings } from 'lucide-react'; +import { ContextConfig } from '@/lib/types/ai-prompts'; +import { projectService } from '@/lib/services/project.service'; +import { contractService } from '@/lib/services/contract.service'; + +interface ContextConfigEditorProps { + initialConfig: ContextConfig | null; + onSave: (config: ContextConfig) => Promise; + isSaving: boolean; +} + +interface ProjectOption { + publicId: string; + projectName: string; +} + +interface ContractOption { + publicId: string; + contractName: string; + project?: { + publicId?: string; + }; +} + +/** + * คอมโพเนนต์ฟอร์มสำหรับแก้ไขบริบทข้อมูล (Context Configuration) + * จัดการตัวเลือกการกรองข้อมูลรายโครงการ (Project Filter) และรายสัญญา (Contract Filter) รวมทั้งภาษาและจำนวนประวัติการดึงข้อมูล + */ +export default function ContextConfigEditor({ + initialConfig, + onSave, + isSaving, +}: ContextConfigEditorProps) { + const [projects, setProjects] = useState([]); + const [contracts, setContracts] = useState([]); + const [filteredContracts, setFilteredContracts] = useState([]); + + // State ฟอร์ม + const [projectId, setProjectId] = useState('all'); + const [contractId, setContractId] = useState('all'); + const [pageSize, setPageSize] = useState(3); + const [language, setLanguage] = useState('th'); + const [outputLanguage, setOutputLanguage] = useState('th'); + + useEffect(() => { + const loadData = async () => { + try { + const projList = await projectService.getAll(); + setProjects( + Array.isArray(projList) + ? (projList as unknown as Record[]).map((p) => ({ + publicId: String(p.publicId || ''), + projectName: String(p.projectName || ''), + })) + : [] + ); + const contrList = await contractService.getAll(); + setContracts( + Array.isArray(contrList) + ? (contrList as unknown as Record[]).map((c) => ({ + publicId: String(c.publicId || ''), + contractName: String(c.contractName || ''), + project: c.project + ? { + publicId: String((c.project as unknown as Record).publicId || ''), + } + : undefined, + })) + : [] + ); + } catch (_err) { + // error handling silently per rules (use NestJS Logger on backend, avoid console.log on frontend) + } + }; + loadData(); + }, []); + + // พรีโหลดค่าตั้งต้น + useEffect(() => { + if (initialConfig) { + setProjectId(initialConfig.filter?.projectId || 'all'); + setContractId(initialConfig.filter?.contractId || 'all'); + setPageSize(initialConfig.pageSize || 3); + setLanguage(initialConfig.language || 'th'); + setOutputLanguage(initialConfig.outputLanguage || 'th'); + } else { + setProjectId('all'); + setContractId('all'); + setPageSize(3); + setLanguage('th'); + setOutputLanguage('th'); + } + }, [initialConfig]); + + // กรองรายการสัญญาตามโครงการที่เลือก + useEffect(() => { + if (projectId && projectId !== 'all') { + const filtered = contracts.filter((c) => c.project?.publicId === projectId); + setFilteredContracts(filtered); + // รีเซ็ตสัญญาถ้าไม่ได้ผูกกับโครงการที่เลือก + const isStillValid = filtered.some((c) => c.publicId === contractId); + if (!isStillValid && contractId !== 'all') { + setContractId('all'); + } + } else { + setFilteredContracts(contracts); + } + }, [projectId, contracts, contractId]); + + const handleSave = () => { + const config: ContextConfig = { + filter: { + projectId: projectId === 'all' ? null : projectId, + contractId: contractId === 'all' ? null : contractId, + }, + pageSize: Number(pageSize), + language, + outputLanguage, + }; + onSave(config); + }; + + return ( + + + + + การตั้งค่าบริบทข้อมูล (Context Configuration) + + + + {/* เลือกล็อคโครงการ */} +
+ + +
+ + {/* เลือกล็อคสัญญา */} +
+ + +
+ + {/* ปริมาณเอกสารอ้างอิงและภาษา */} +
+
+ + setPageSize(Math.max(1, Number(e.target.value)))} + className="bg-background/50 border-border/50 text-sm focus-visible:ring-primary/30" + /> +
+ +
+ + +
+ +
+ + +
+
+
+ + + + การตั้งค่านี้จะผูกกับเวอร์ชันของพรอมต์โดยตรง + + + +
+ ); +} diff --git a/frontend/components/admin/ai/PromptEditor.tsx b/frontend/components/admin/ai/PromptEditor.tsx new file mode 100644 index 00000000..eb264a22 --- /dev/null +++ b/frontend/components/admin/ai/PromptEditor.tsx @@ -0,0 +1,139 @@ +// File: frontend/components/admin/ai/PromptEditor.tsx +// Change Log: +// - 2026-06-14: Created PromptEditor component with live placeholder validation and save actions (conforming to task T018) + +import React, { useState, useEffect } from 'react'; +import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Textarea } from '@/components/ui/textarea'; +import { Input } from '@/components/ui/input'; +import { AlertCircle, CheckCircle, Save, HelpCircle } from 'lucide-react'; +import { PromptType } from '@/lib/types/ai-prompts'; +import { PLACEHOLDER_REQUIREMENTS } from '@/contracts/frontend-types'; + +interface PromptEditorProps { + promptType: PromptType; + initialTemplate: string; + onSave: (template: string, manualNote: string) => Promise; + isSaving: boolean; +} + +/** + * คอมโพเนนต์เครื่องมือแก้ไขเทมเพลตพรอมต์ (Prompt Editor) + * มีระบบตรวจเช็คตัวแปร/เพลสโฮลเดอร์ (Placeholder Validation) ในตัวแบบเรียลไทม์ + */ +export default function PromptEditor({ + promptType, + initialTemplate, + onSave, + isSaving, +}: PromptEditorProps) { + const [template, setTemplate] = useState(initialTemplate); + const [manualNote, setManualNote] = useState(''); + const [validationErrors, setValidationErrors] = useState([]); + + useEffect(() => { + setTemplate(initialTemplate); + setManualNote(''); + }, [initialTemplate, promptType]); + + // ตรวจสอบตัวแปรที่ต้องมีในพรอมต์เทมเพลต (Real-time Validation) + useEffect(() => { + const requirements = PLACEHOLDER_REQUIREMENTS[promptType] || []; + const missing = requirements.filter((req) => !template.includes(req)); + setValidationErrors(missing); + }, [template, promptType]); + + const handleSave = () => { + if (validationErrors.length > 0) return; + onSave(template, manualNote); + }; + + const getFriendlyTypeName = (type: PromptType) => { + switch (type) { + case 'ocr_extraction': + return 'สกัดข้อความ OCR (OCR Extraction)'; + case 'rag_query_prompt': + return 'ค้นหาข้อมูล RAG (RAG Query)'; + case 'rag_prep_prompt': + return 'เตรียมข้อมูล RAG (RAG Prep)'; + case 'classification_prompt': + return 'จำแนกประเภทเอกสาร (Classification)'; + } + }; + + return ( + + + + แก้ไขพรอมต์เทมเพลต ({getFriendlyTypeName(promptType)}) + + + +
+
+ + โครงสร้างเทมเพลต (Template Body) + + + + + 4000 ? 'text-destructive font-semibold' : 'text-muted-foreground'}> + {template.length} / 4000 อักขระ + +
+