import React, { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { Plus, Edit2, Trash2, FileText, Loader2, X, Sparkles, Sliders, Hash, Type, Brain, Copy, Check } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { useLanguage } from '../../contexts/LanguageContext'; import { useToast } from '../../contexts/ToastContext'; import { useConfirm } from '../../contexts/ConfirmContext'; import { templateService } from '../../services/templateService'; import { knowledgeGroupService } from '../../services/knowledgeGroupService'; import { AssessmentTemplate, CreateTemplateData, UpdateTemplateData, KnowledgeGroup, AssessmentDimension } from '../../types'; export const AssessmentTemplateManager: React.FC = () => { const { t } = useLanguage(); const { showSuccess, showError } = useToast(); const { confirm } = useConfirm(); const [templates, setTemplates] = useState([]); const [groups, setGroups] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isSaving, setIsSaving] = useState(false); const [showModal, setShowModal] = useState(false); const [editingTemplate, setEditingTemplate] = useState(null); // UI state uses strings for easy input const [formData, setFormData] = useState({ name: '', description: '', keywords: '', questionCount: 5, difficultyDistribution: 'Basic: 30%, Intermediate: 40%, Advanced: 30%', style: 'Professional', knowledgeGroupId: '', passingScore: 6, totalTimeLimit: 1800, perQuestionTimeLimit: 300, attemptLimit: 1, scheduledStart: '', scheduledEnd: '', reviewMode: 'none', shuffleQuestions: true, }); const [copiedId, setCopiedId] = useState(null); const [dimensions, setDimensions] = useState([]); const fetchTemplates = async () => { setIsLoading(true); try { const data = await templateService.getAll(); setTemplates(data); } catch (error) { console.error('Failed to fetch templates:', error); showError(t('actionFailed')); } finally { setIsLoading(false); } }; const fetchGroups = async () => { try { const data = await knowledgeGroupService.getGroups(); setGroups(data); } catch (error) { console.error('Failed to fetch groups:', error); } }; useEffect(() => { fetchTemplates(); fetchGroups(); }, []); const handleOpenModal = (template?: AssessmentTemplate) => { if (template) { setEditingTemplate(template); setFormData({ name: template.name, description: template.description || '', keywords: Array.isArray(template.keywords) ? template.keywords.join(', ') : '', questionCount: template.questionCount, difficultyDistribution: typeof template.difficultyDistribution === 'object' ? JSON.stringify(template.difficultyDistribution) : (template.difficultyDistribution || ''), style: template.style || 'Professional', knowledgeGroupId: template.knowledgeGroupId || '', passingScore: template.passingScore !== null && template.passingScore !== undefined ? template.passingScore / 10 : 6, totalTimeLimit: template.totalTimeLimit ?? 1800, perQuestionTimeLimit: template.perQuestionTimeLimit ?? 300, attemptLimit: template.attemptLimit ?? 1, scheduledStart: template.scheduledStart || '', scheduledEnd: template.scheduledEnd || '', reviewMode: template.reviewMode || 'none', shuffleQuestions: template.shuffleQuestions ?? true, }); setDimensions(template.dimensions || []); } else { setEditingTemplate(null); setFormData({ name: '', description: '', keywords: '', questionCount: 5, difficultyDistribution: '{"Basic": 3, "Intermediate": 4, "Advanced": 3}', style: 'Professional', knowledgeGroupId: '', passingScore: 6, totalTimeLimit: 1800, perQuestionTimeLimit: 300, }); setDimensions([]); } setShowModal(true); }; const handleSave = async (e: React.FormEvent) => { e.preventDefault(); setIsSaving(true); try { // Convert UI strings back to required types const keywordsArray = formData.keywords.split(',').map(k => k.trim()).filter(k => k !== ''); let diffDist: any = formData.difficultyDistribution; if (typeof diffDist === 'string' && diffDist.trim().startsWith('{')) { try { diffDist = JSON.parse(diffDist); } catch (e) { diffDist = undefined; } } if (typeof diffDist !== 'object' || diffDist === null) diffDist = undefined; const payload: CreateTemplateData = { name: formData.name, description: formData.description, keywords: keywordsArray, questionCount: formData.questionCount, difficultyDistribution: diffDist, style: formData.style, knowledgeGroupId: formData.knowledgeGroupId || undefined, dimensions: dimensions.length > 0 ? dimensions : undefined, passingScore: formData.passingScore * 10, totalTimeLimit: formData.totalTimeLimit, perQuestionTimeLimit: formData.perQuestionTimeLimit, attemptLimit: formData.attemptLimit, scheduledStart: formData.scheduledStart || null, scheduledEnd: formData.scheduledEnd || null, reviewMode: formData.reviewMode, shuffleQuestions: formData.shuffleQuestions, }; if (editingTemplate) { await templateService.update(editingTemplate.id, payload as UpdateTemplateData); showSuccess(t('featureUpdated')); } else { await templateService.create(payload); showSuccess(t('confirm')); } setShowModal(false); fetchTemplates(); } catch (error: any) { console.error('Save failed:', error); const msg = error?.message; showError(msg && msg !== 'Request failed' ? msg : t('actionFailed')); } finally { setIsSaving(false); } }; const handleCopyId = async (id: string) => { try { await navigator.clipboard.writeText(id); setCopiedId(id); showSuccess(t('copySuccess') || 'ID copied to clipboard'); setTimeout(() => setCopiedId(null), 2000); } catch (err) { showError(t('actionFailed')); } }; const handleDimensionChange = (index: number, field: 'name' | 'label' | 'weight', value: string | number) => { const updated = [...dimensions]; updated[index] = { ...updated[index], [field]: value }; setDimensions(updated); }; const handleAddDimension = () => { setDimensions([...dimensions, { name: '', label: '', weight: 1 }]); }; const handleRemoveDimension = (index: number) => { setDimensions(dimensions.filter((_, i) => i !== index)); }; const handleDelete = async (id: string) => { if (!(await confirm(t('confirmTitle')))) return; try { await templateService.delete(id); showSuccess(t('confirm')); fetchTemplates(); } catch (error) { showError(t('actionFailed')); } }; const renderDifficulty = (dist: any) => { if (typeof dist === 'string') return dist; if (typeof dist === 'object' && dist !== null) { return Object.entries(dist).map(([k, v]) => `${k}: ${v}`).join(', '); } return ''; }; return (

{t('assessmentTemplates')}

{t('assessmentTemplatesSubtitle')}

{isLoading ? (
) : templates.length === 0 ? (

{t('mmEmpty')}

) : (
{templates.map((template) => (

{template.name}

{template.description || t('noDescription')}

{t('questionCount')} {template.questionCount}
{t('difficultyDistribution')} {renderDifficulty(template.difficultyDistribution)}
Template ID {template.id}
{template.knowledgeGroup?.name || t('selectKnowledgeGroup')}
{Array.isArray(template.dimensions) && template.dimensions.length > 0 && (
{template.dimensions.map((dim, i) => ( {dim.label} ({dim.weight}%) ))}
)}
{Array.isArray(template.keywords) && template.keywords.map((kw, i) => ( {kw} ))} {(!template.keywords || template.keywords.length === 0) && No keywords}
))}
)} {createPortal( {showModal && (
setShowModal(false)} className="absolute inset-0 bg-slate-900/40 backdrop-blur-sm" />
{editingTemplate ? : }

{editingTemplate ? t('editTemplate') : t('createTemplate')}

setFormData({ ...formData, name: e.target.value })} placeholder="e.g. Senior Frontend Engineer Technical Interview" />
setFormData({ ...formData, keywords: e.target.value })} placeholder={t('keywordsHint')} />
setFormData({ ...formData, questionCount: parseInt(e.target.value) })} />
setFormData({ ...formData, difficultyDistribution: e.target.value })} placeholder='{"Basic": 3, "Inter": 4, "Adv": 3}' />
setFormData({ ...formData, style: e.target.value })} />
setFormData({ ...formData, passingScore: parseFloat(e.target.value) || 0 })} />
setFormData({ ...formData, totalTimeLimit: parseInt(e.target.value) || 1800 })} />
setFormData({ ...formData, perQuestionTimeLimit: parseInt(e.target.value) || 300 })} />
{/* P2: Attempt limit, Review mode, Shuffle */}
{/* P2: Scheduled window */}
setFormData({ ...formData, scheduledStart: e.target.value })} />
setFormData({ ...formData, scheduledEnd: e.target.value })} />
{dimensions.length === 0 && (

{t('mmEmpty')}

)} {dimensions.map((dim, index) => (
handleDimensionChange(index, 'name', e.target.value)} placeholder={t('dimensionName')} /> handleDimensionChange(index, 'label', e.target.value)} placeholder={t('dimensionLabel')} /> handleDimensionChange(index, 'weight', parseInt(e.target.value) || 0)} min={0} max={100} placeholder={t('dimensionWeight')} />
))}
)}
, document.body )}
); };