192 lines
7.6 KiB
React
192 lines
7.6 KiB
React
"use client";
|
|
|
|
import React, { useState, useTransition, useCallback, useEffect } from "react";
|
|
import Link from "next/link";
|
|
import {
|
|
AcademicCapIcon,
|
|
PlusIcon,
|
|
PencilSquareIcon,
|
|
TrashIcon,
|
|
EyeIcon,
|
|
DocumentDuplicateIcon,
|
|
} from "@heroicons/react/24/outline";
|
|
import ActionBtn from "@/app/(protected)/dashboard/components/ActionBtn";
|
|
import Card from "@/app/(protected)/dashboard/components/Card";
|
|
import SectionHeader from "@/app/(protected)/dashboard/components/SectionHeader";
|
|
import Label from "@/app/(protected)/components/shared/Label";
|
|
import { deleteExamTemplate, duplicateExamTemplate } from "@/app/lib/actions/examActions";
|
|
|
|
export default function TemplateList({
|
|
templates: initialTemplates = [],
|
|
setTemplates: setTemplatesProp
|
|
}) {
|
|
const [templates, setTemplates] = useState(initialTemplates);
|
|
const [isPending, startTransition] = useTransition();
|
|
const [pendingDeleteId, setPendingDeleteId] = useState(null);
|
|
|
|
// Sync with parent prop when it changes
|
|
useEffect(() => {
|
|
setTemplates(initialTemplates);
|
|
}, [initialTemplates]);
|
|
|
|
// Wrapper to update both local and parent state
|
|
const updateTemplates = useCallback((updater) => {
|
|
setTemplates(updater);
|
|
if (setTemplatesProp) {
|
|
setTemplatesProp(typeof updater === 'function' ? updater(templates) : updater);
|
|
}
|
|
}, [setTemplatesProp, templates]);
|
|
|
|
const handleDelete = useCallback(async (templateId) => {
|
|
if (!confirm("Tem certeza que deseja excluir este template?")) {
|
|
return;
|
|
}
|
|
|
|
// Set pending state for UI feedback
|
|
setPendingDeleteId(templateId);
|
|
|
|
startTransition(() => {
|
|
// Perform the delete operation
|
|
deleteExamTemplate(templateId).then((result) => {
|
|
if (result.success) {
|
|
updateTemplates((prev) => prev.filter((t) => t._id !== templateId));
|
|
} else {
|
|
alert(result.error || "Erro ao excluir template");
|
|
}
|
|
}).catch((error) => {
|
|
console.error("Erro ao excluir template:", error);
|
|
alert("Erro ao excluir template");
|
|
}).finally(() => {
|
|
setPendingDeleteId(null);
|
|
});
|
|
});
|
|
}, [updateTemplates]);
|
|
|
|
const handleDuplicate = useCallback(async (templateId) => {
|
|
startTransition(() => {
|
|
duplicateExamTemplate(templateId).then((result) => {
|
|
if (result.success && result.data) {
|
|
// Ensure the duplicated template has a unique _id
|
|
const newTemplate = result.data;
|
|
console.log('Duplicated template _id:', newTemplate._id);
|
|
updateTemplates((prev) => {
|
|
// Check if _id already exists
|
|
if (prev.some(t => t._id === newTemplate._id)) {
|
|
console.warn('Duplicate _id detected, forcing refresh');
|
|
window.location.reload();
|
|
return prev;
|
|
}
|
|
return [newTemplate, ...prev];
|
|
});
|
|
} else {
|
|
alert(result.error || "Erro ao duplicar template");
|
|
}
|
|
}).catch((error) => {
|
|
console.error("Erro ao duplicar template:", error);
|
|
alert("Erro ao duplicar template");
|
|
});
|
|
});
|
|
}, [updateTemplates]);
|
|
|
|
// Use templates for rendering
|
|
const displayTemplates = templates;
|
|
|
|
return (
|
|
<Card>
|
|
<div className="flex items-center justify-between flex-wrap gap-2">
|
|
<SectionHeader title="Meus Templates" icon={<AcademicCapIcon className="w-5 h-5" />} />
|
|
<ActionBtn
|
|
icon={<PlusIcon className="w-5 h-5" />}
|
|
label="Novo Template"
|
|
onClick={() => {
|
|
// This will be handled by the parent component
|
|
window.dispatchEvent(new CustomEvent('open-template-form', { detail: null }));
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div className="mt-4 space-y-3">
|
|
{displayTemplates.length === 0 ? (
|
|
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
|
Nenhum template criado ainda.
|
|
</p>
|
|
) : (
|
|
displayTemplates.map((template) => (
|
|
<div
|
|
key={template._id}
|
|
className={`hover-card-light p-4 border border-neutral-200 dark:border-neutral-800 rounded-lg ${(isPending || pendingDeleteId === template._id) ? 'opacity-70' : ''}`}
|
|
>
|
|
<div className="flex items-start justify-between gap-4">
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-3 flex-wrap">
|
|
<h3 key={`${template._id}-title`} className="text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
|
{template.title}
|
|
</h3>
|
|
{template.isPublic && (
|
|
<Label key={`${template._id}-public`} color="blue" size="sm">Público</Label>
|
|
)}
|
|
{template.category && (
|
|
<Label key={`${template._id}-category`} color="gray" size="sm">{template.category}</Label>
|
|
)}
|
|
</div>
|
|
{template.description && (
|
|
<p className="mt-1 text-sm text-neutral-600 dark:text-neutral-400 line-clamp-1">
|
|
{template.description}
|
|
</p>
|
|
)}
|
|
<div className="mt-2 flex items-center gap-4 text-xs text-neutral-500 dark:text-neutral-400">
|
|
<span key={`${template._id}-questions`}>{template.questions?.length || 0} questões</span>
|
|
<span key={`${template._id}-points`}>{template.totalPoints || 0} pontos</span>
|
|
{template.timeLimit && <span key={`${template._id}-time`}>{template.timeLimit} min</span>}
|
|
<span key={`${template._id}-usage`}>Usado {template.usageCount || 0}x</span>
|
|
</div>
|
|
{template.tags && template.tags.length > 0 && (
|
|
<div className="mt-2 flex gap-1 flex-wrap">
|
|
{template.tags.map((tag, idx) => (
|
|
<span key={`${template._id}-tag-${idx}`} className="text-xs px-2 py-0.5 rounded-full bg-neutral-100 dark:bg-neutral-800 text-neutral-600 dark:text-neutral-400">
|
|
{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-wrap items-center gap-2 flex-shrink-0">
|
|
<ActionBtn
|
|
key={`${template._id}-duplicate`}
|
|
icon={<DocumentDuplicateIcon className="w-4 h-4" />}
|
|
label="Duplicar"
|
|
onClick={() => handleDuplicate(template._id)}
|
|
variant="secondary"
|
|
small
|
|
disabled={isPending || pendingDeleteId === template._id}
|
|
/>
|
|
<ActionBtn
|
|
key={`${template._id}-edit`}
|
|
icon={<PencilSquareIcon className="w-4 h-4" />}
|
|
label="Editar"
|
|
onClick={() => {
|
|
// This will be handled by the parent component
|
|
window.dispatchEvent(new CustomEvent('open-template-form', { detail: template }));
|
|
}}
|
|
variant="secondary"
|
|
small
|
|
/>
|
|
<ActionBtn
|
|
key={`${template._id}-delete`}
|
|
icon={<TrashIcon className="w-4 h-4" />}
|
|
label="Excluir"
|
|
onClick={() => handleDelete(template._id)}
|
|
variant="secondary"
|
|
small
|
|
disabled={isPending || pendingDeleteId === template._id}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|