Files
lcbp3/frontend/components/admin/ai/ContextConfigEditor.tsx
T
admin 152caa25e9
CI / CD Pipeline / build (push) Failing after 3m57s
CI / CD Pipeline / deploy (push) Has been skipped
fix(frontend): remove console.log from comment to fix linter false positive
2026-06-14 20:06:20 +07:00

245 lines
9.9 KiB
TypeScript

// 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<void>;
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<ProjectOption[]>([]);
const [contracts, setContracts] = useState<ContractOption[]>([]);
const [filteredContracts, setFilteredContracts] = useState<ContractOption[]>([]);
// State ฟอร์ม
const [projectId, setProjectId] = useState<string>('all');
const [contractId, setContractId] = useState<string>('all');
const [pageSize, setPageSize] = useState<number>(3);
const [language, setLanguage] = useState<string>('th');
const [outputLanguage, setOutputLanguage] = useState<string>('th');
useEffect(() => {
const loadData = async () => {
try {
const projList = await projectService.getAll();
setProjects(
Array.isArray(projList)
? (projList as unknown as Record<string, unknown>[]).map((p) => ({
publicId: String(p.publicId || ''),
projectName: String(p.projectName || ''),
}))
: []
);
const contrList = await contractService.getAll();
setContracts(
Array.isArray(contrList)
? (contrList as unknown as Record<string, unknown>[]).map((c) => ({
publicId: String(c.publicId || ''),
contractName: String(c.contractName || ''),
project: c.project
? {
publicId: String((c.project as unknown as Record<string, unknown>).publicId || ''),
}
: undefined,
}))
: []
);
} catch (_err) {
// Error handling silently - backend logs via NestJS Logger
}
};
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 (
<Card className="border border-border/50 bg-background/30 backdrop-blur-md transition-all duration-300 hover:shadow-md">
<CardHeader className="pb-3 border-b border-border/10">
<CardTitle className="flex items-center gap-2 text-sm font-semibold tracking-wide text-foreground">
<Settings className="h-4 w-4 text-primary" />
(Context Configuration)
</CardTitle>
</CardHeader>
<CardContent className="pt-4 space-y-4">
{/* เลือกล็อคโครงการ */}
<div className="space-y-1.5">
<label className="text-xs font-medium text-muted-foreground">
(Project Filter)
</label>
<Select value={projectId} onValueChange={setProjectId}>
<SelectTrigger className="w-full bg-background/50 border-border/50 backdrop-blur-sm">
<SelectValue placeholder="เลือกโครงการ..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"> ( / Global)</SelectItem>
{projects.map((p) => (
<SelectItem key={p.publicId} value={p.publicId}>
{p.projectName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* เลือกล็อคสัญญา */}
<div className="space-y-1.5">
<label className="text-xs font-medium text-muted-foreground">
(Contract Filter)
</label>
<Select value={contractId} onValueChange={setContractId}>
<SelectTrigger className="w-full bg-background/50 border-border/50 backdrop-blur-sm">
<SelectValue placeholder="เลือกสัญญา..." />
</SelectTrigger>
<SelectContent>
<SelectItem value="all"> ( / Global)</SelectItem>
{filteredContracts.map((c) => (
<SelectItem key={c.publicId} value={c.publicId}>
{c.contractName}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* ปริมาณเอกสารอ้างอิงและภาษา */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3.5">
<div className="space-y-1.5">
<label className="text-xs font-medium text-muted-foreground">
(Page Size)
</label>
<Input
type="number"
min={1}
max={20}
value={pageSize}
onChange={(e) => setPageSize(Math.max(1, Number(e.target.value)))}
className="bg-background/50 border-border/50 text-sm focus-visible:ring-primary/30"
/>
</div>
<div className="space-y-1.5">
<label className="text-xs font-medium text-muted-foreground">
(Language)
</label>
<Select value={language} onValueChange={setLanguage}>
<SelectTrigger className="bg-background/50 border-border/50 backdrop-blur-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="th"> (TH)</SelectItem>
<SelectItem value="en">English (EN)</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<label className="text-xs font-medium text-muted-foreground">
(Output)
</label>
<Select value={outputLanguage} onValueChange={setOutputLanguage}>
<SelectTrigger className="bg-background/50 border-border/50 backdrop-blur-sm">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="th"> (TH)</SelectItem>
<SelectItem value="en">English (EN)</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
<CardFooter className="flex items-center justify-between border-t border-border/10 pt-4 bg-muted/5 rounded-b-xl">
<span className="text-[11px] text-muted-foreground italic flex items-center gap-1">
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500" />
</span>
<Button
onClick={handleSave}
disabled={isSaving}
size="sm"
className="bg-primary hover:bg-primary/95 font-semibold text-xs"
>
{isSaving ? 'กำลังบันทึก...' : 'บันทึกบริบท (Save Config)'}
</Button>
</CardFooter>
</Card>
);
}