Files
protoc-frontend/src/component/TemplateManager/TemplateManager.tsx
2025-07-24 05:10:56 +03:00

359 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useTemplateContext } from '@/entitiy/template/model/TemplateContext'
import { Template } from '@/type/template'
import { Circle, CircleCheck, FileText, Plus, Trash2 } from 'lucide-react'
import React, { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import TemplateCard from '../../widget/template/ui/TemplateCard'
import { Button } from '../ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '../ui/dialog'
import { Input } from '../ui/input'
import { TemplateEditor } from './TemplateEditor'
interface TemplateManagerProps {
template?: Template
}
export const TemplateManager: React.FC<TemplateManagerProps> = ({
template: initialTemplate,
}) => {
const navigate = useNavigate()
const {
templates,
isLoading,
error,
addTemplate,
updateTemplate,
deleteTemplate,
refetchTemplates,
} = useTemplateContext()
const [selectedTemplate, setSelectedTemplate] = useState<Template | null>(
null
)
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [isEditMode, setIsEditMode] = useState(false)
const [newTemplateName, setNewTemplateName] = useState('')
const [newTemplateDescription, setNewTemplateDescription] = useState('')
const [isSelectionMode, setIsSelectionMode] = useState(false)
const [selectedTemplates, setSelectedTemplates] = useState<Set<string>>(
new Set()
)
const [templatesToDelete, setTemplatesToDelete] = useState<Template[]>([])
const [isCreating, setIsCreating] = useState(false)
// Автоматически открываем редактирование шаблона, если передан templateId
useEffect(() => {
if (initialTemplate) {
setSelectedTemplate(initialTemplate)
setIsEditMode(true)
}
}, [initialTemplate])
const handleCreateTemplate = async () => {
if (!newTemplateName.trim()) return
setIsCreating(true)
try {
await addTemplate({
name: newTemplateName.trim(),
description: newTemplateDescription.trim(),
elements: [],
layoutSettings: undefined,
})
setIsCreateDialogOpen(false)
setNewTemplateName('')
setNewTemplateDescription('')
await refetchTemplates()
const created = templates.find(t => t.name === newTemplateName.trim())
if (created) {
setSelectedTemplate(created)
setIsEditMode(true)
}
} catch (err) {
console.error('Error creating template:', err)
alert('Ошибка при создании шаблона. Попробуйте еще раз.')
} finally {
setIsCreating(false)
}
}
const handleDeleteTemplates = () => {
templatesToDelete.forEach(t => deleteTemplate(t.id))
setTemplatesToDelete([])
setIsSelectionMode(false)
setSelectedTemplates(new Set())
}
const toggleTemplateSelection = (id: UUID) => {
const next = new Set(selectedTemplates)
next.has(id) ? next.delete(id) : next.add(id)
setSelectedTemplates(next)
}
const handleSelectionModeToggle = () => {
if (isSelectionMode) {
setIsSelectionMode(false)
setSelectedTemplates(new Set())
setTemplatesToDelete([])
} else {
setIsSelectionMode(true)
}
}
const handleDeleteSelected = () => {
const toDelete = templates.filter(t => selectedTemplates.has(t.id))
setTemplatesToDelete(toDelete)
}
const handleRetry = () => {
refetchTemplates()
}
if (isEditMode && selectedTemplate) {
return (
<TemplateEditor
template={selectedTemplate}
onBack={() => {
setIsEditMode(false)
setSelectedTemplate(null)
navigate('/templates')
}}
/>
)
}
// Показываем ошибку, если есть
if (error && !isLoading) {
return (
<div className="flex h-screen items-center justify-center">
<div className="text-center">
<FileText className="mx-auto mb-4 h-16 w-16 text-red-400" />
<h3 className="mb-2 text-lg font-medium text-gray-900">
Ошибка загрузки шаблонов
</h3>
<p className="mb-4 text-gray-600">{error.message}</p>
<Button onClick={handleRetry}>Попробовать снова</Button>
</div>
</div>
)
}
return (
<div className="mx-auto max-w-7xl p-6">
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900">Шаблоны протоколов</h1>
<div className="flex gap-3">
{isSelectionMode ? (
<>
<Button
variant="outline"
onClick={handleSelectionModeToggle}
className="flex items-center gap-2"
>
Отменить выбор
</Button>
<Button
variant="destructive"
onClick={handleDeleteSelected}
disabled={selectedTemplates.size === 0}
className="flex items-center gap-2"
>
<Trash2 className="h-4 w-4" />
Удалить выбранные ({selectedTemplates.size})
</Button>
</>
) : (
<>
<Button
variant="outline"
onClick={handleSelectionModeToggle}
disabled={templates.length === 0}
className="flex items-center gap-2"
>
<Circle className="h-4 w-4" />
Выбрать
</Button>
<Dialog
open={isCreateDialogOpen}
onOpenChange={setIsCreateDialogOpen}
>
<DialogTrigger asChild>
<Button className="flex items-center gap-2">
<Plus className="h-4 w-4" />
Создать шаблон
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Создать новый шаблон</DialogTitle>
<DialogDescription>
Введите основную информацию для нового шаблона протокола
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div>
<label className="mb-1 block text-sm font-medium">
Название шаблона
</label>
<Input
placeholder="Например: Протокол испытаний"
value={newTemplateName}
onChange={e => setNewTemplateName(e.target.value)}
onKeyDown={e => {
if (e.key === 'Enter') {
handleCreateTemplate()
}
}}
/>
</div>
<div>
<label className="mb-1 block text-sm font-medium">
Описание (необязательно)
</label>
<Input
placeholder="Краткое описание назначения шаблона"
value={newTemplateDescription}
onChange={e =>
setNewTemplateDescription(e.target.value)
}
onKeyDown={e => {
if (e.key === 'Enter') {
handleCreateTemplate()
}
}}
/>
</div>
<div className="flex justify-end gap-2">
<Button
variant="outline"
onClick={() => setIsCreateDialogOpen(false)}
disabled={isCreating}
>
Отменить
</Button>
<Button
onClick={handleCreateTemplate}
disabled={!newTemplateName.trim() || isCreating}
>
{isCreating ? 'Создание...' : 'Создать'}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</>
)}
</div>
</div>
{/* Показываем индикатор загрузки */}
{isLoading && (
<div className="flex h-64 items-center justify-center">
<div className="text-center">
<div className="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-4 border-blue-600 border-t-transparent"></div>
<p className="text-gray-600">Загрузка шаблонов...</p>
</div>
</div>
)}
{/* Список шаблонов */}
{!isLoading && (
<>
{templates.length === 0 ? (
<div className="flex h-64 items-center justify-center">
<div className="text-center">
<FileText className="mx-auto mb-4 h-16 w-16 text-gray-400" />
<h3 className="mb-2 text-lg font-medium text-gray-900">
Нет шаблонов
</h3>
<p className="mb-4 text-gray-600">
Создайте первый шаблон для начала работы
</p>
<Dialog
open={isCreateDialogOpen}
onOpenChange={setIsCreateDialogOpen}
>
<DialogTrigger asChild>
<Button>
<Plus className="mr-2 h-4 w-4" />
Создать шаблон
</Button>
</DialogTrigger>
</Dialog>
</div>
</div>
) : (
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
{templates.map(template => (
<div key={template.id} className="relative">
{isSelectionMode && (
<div className="absolute right-2 top-2 z-10">
<button
onClick={() => toggleTemplateSelection(template.id)}
className="rounded-full bg-white p-1 shadow-md"
>
{selectedTemplates.has(template.id) ? (
<CircleCheck className="h-5 w-5 text-blue-600" />
) : (
<Circle className="h-5 w-5 text-gray-400" />
)}
</button>
</div>
)}
<TemplateCard
template={template}
onDelete={() => deleteTemplate(template.id)}
isSelected={selectedTemplates.has(template.id)}
/>
</div>
))}
</div>
)}
</>
)}
{/* Диалог подтверждения удаления */}
{templatesToDelete.length > 0 && (
<Dialog
open={templatesToDelete.length > 0}
onOpenChange={() => setTemplatesToDelete([])}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Подтверждение удаления</DialogTitle>
<DialogDescription>
Вы действительно хотите удалить выбранные шаблоны? Это действие
нельзя отменить.
</DialogDescription>
</DialogHeader>
<div className="space-y-2">
{templatesToDelete.map(template => (
<div key={template.id} className="text-sm text-gray-600">
{template.name}
</div>
))}
</div>
<div className="flex justify-end gap-2">
<Button
variant="outline"
onClick={() => setTemplatesToDelete([])}
>
Отменить
</Button>
<Button variant="destructive" onClick={handleDeleteTemplates}>
Удалить
</Button>
</div>
</DialogContent>
</Dialog>
)}
</div>
)
}