// File: frontend/components/admin/ai/VersionHistory.tsx // Change Log: // - 2026-06-14: Created VersionHistory component with type filtering and nice badges (conforming to task T017) // - 2026-06-15: Added All Types view grouped by prompt type (T065) // - 2026-06-15: Added pagination (20 versions/page) (T075) // - 2026-06-15: เปลี่ยน button pagination เป็น infinite scroll ตาม spec FR (T075) import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { CheckCircle2, Trash2, BookOpen, Clock, StickyNote, Folder } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { PromptVersion } from '@/lib/types/ai-prompts'; import { cn } from '@/lib/utils'; interface VersionHistoryProps { versions: PromptVersion[]; isLoading: boolean; onLoadTemplate: (version: PromptVersion) => void; onActivateVersion: (versionNumber: number) => void; onDeleteVersion: (versionNumber: number) => void; isActivating: boolean; isDeleting: boolean; showAllTypes?: boolean; } /** * คอมโพเนนต์แสดงประวัติเวอร์ชันของพรอมต์ตามประเภทที่กรองไว้ * หรือแสดงทุกประเภทแบบจัดกลุ่ม (T065) * แสดงรายการเวอร์ชันพร้อมปุ่มพรีโหลด เปิดใช้งาน และลบเวอร์ชันที่ไม่ต้องการ */ export default function VersionHistory({ versions, isLoading, onLoadTemplate, onActivateVersion, onDeleteVersion, isActivating, isDeleting, showAllTypes = false, }: VersionHistoryProps) { const { t } = useTranslation('ai'); const PAGE_SIZE = 20; const [visibleCount, setVisibleCount] = useState(PAGE_SIZE); const sentinelRef = useRef(null); // Group versions by prompt type when showing all types const groupedVersions = showAllTypes ? versions.reduce((acc, version) => { const type = version.promptType; if (!acc[type]) { acc[type] = []; } acc[type].push(version); return acc; }, {} as Record) : null; const getPromptTypeLabel = (type: string): string => { const labels: Record = { ocr_extraction: 'สกัดข้อความ OCR (OCR Extraction)', rag_query_prompt: 'ค้นหาข้อมูล RAG (RAG Query)', rag_prep_prompt: 'เตรียมข้อมูล RAG (RAG Prep)', classification_prompt: 'จำแนกประเภทเอกสาร (Classification)', }; return labels[type] || type; }; // รีเซ็ต visible count เมื่อ versions เปลี่ยน (เช่น เปลี่ยน prompt type) useEffect(() => { setVisibleCount(PAGE_SIZE); }, [versions, PAGE_SIZE]); // Infinite scroll ด้วย IntersectionObserver const handleObserver = useCallback( (entries: IntersectionObserverEntry[]) => { const target = entries[0]; if (target.isIntersecting && visibleCount < versions.length) { setVisibleCount((prev) => Math.min(prev + PAGE_SIZE, versions.length)); } }, [visibleCount, versions.length, PAGE_SIZE] ); useEffect(() => { const sentinel = sentinelRef.current; if (!sentinel) return; const observer = new IntersectionObserver(handleObserver, { threshold: 0.1 }); observer.observe(sentinel); return () => observer.disconnect(); }, [handleObserver]); const visibleVersions = versions.slice(0, visibleCount); if (isLoading) { return (
{t('prompt_management.version_history')}...
); } return ( {showAllTypes ? `${t('prompt_management.version_history')} (${t('prompt_management.all_types')})` : t('prompt_management.version_history')} {versions.length === 0 ? (
{t('prompt_management.no_versions')}
) : showAllTypes && groupedVersions ? ( // Grouped view by prompt type — infinite scroll applies across all groups Object.entries(groupedVersions).map(([promptType, typeVersions]) => { const visibleGroupVersions = typeVersions.slice(0, visibleCount); return (
{getPromptTypeLabel(promptType)}
{visibleGroupVersions.map((version) => { const isActive = version.isActive === true; return (
v{version.versionNumber} {isActive ? ( {t('prompt_management.is_active')} ) : ( ร่าง (Inactive) )}
สร้าง: {new Date(version.createdAt).toLocaleString('th-TH')}
{!isActive && ( <> )}
{version.manualNote && (

{version.manualNote}

)}
); })}
); }) ) : ( // Single type view — infinite scroll (T075) visibleVersions.map((version) => { const isActive = version.isActive === true; return (
v{version.versionNumber} {isActive ? ( {t('prompt_management.is_active')} ) : ( ร่าง (Inactive) )}
สร้าง: {new Date(version.createdAt).toLocaleString('th-TH')}
{!isActive && ( <> )}
{version.manualNote && (

{version.manualNote}

)}
); }) )} {/* Sentinel สำหรับ infinite scroll — IntersectionObserver จะโหลดเพิ่มเมื่อ scroll ถึง */}
{visibleCount < versions.length && (
แสดง {visibleCount} จาก {versions.length} เวอร์ชัน
)} ); }