переименования папок
This commit is contained in:
390
src/component/TemplateManager/TemplateManager.tsx
Normal file
390
src/component/TemplateManager/TemplateManager.tsx
Normal file
@@ -0,0 +1,390 @@
|
||||
import { Circle, CircleCheck, FileText, Plus, Trash2 } from 'lucide-react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { useTemplateContext } from '../../context/TemplateContext'
|
||||
import { Template } from '../../type/template'
|
||||
import { Button } from '../ui/button'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '../ui/dialog'
|
||||
import { Input } from '../ui/input'
|
||||
import TemplateCard from './TemplateCard'
|
||||
import { TemplateEditor } from './TemplateEditor'
|
||||
|
||||
interface TemplateManagerProps {
|
||||
onTemplateSelect?: (template: Template) => void
|
||||
templateId?: string
|
||||
}
|
||||
|
||||
export const TemplateManager: React.FC<TemplateManagerProps> = ({
|
||||
templateId,
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
const {
|
||||
templates,
|
||||
isLoading,
|
||||
error,
|
||||
addTemplate,
|
||||
updateTemplate,
|
||||
deleteTemplate,
|
||||
loadTemplates,
|
||||
} = 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 (templateId) {
|
||||
const template = templates.find(t => t.id === templateId)
|
||||
if (template) {
|
||||
setSelectedTemplate(template)
|
||||
setIsEditMode(true)
|
||||
}
|
||||
}
|
||||
}, [templateId, templates])
|
||||
|
||||
const handleCreateTemplate = async () => {
|
||||
if (!newTemplateName.trim()) return
|
||||
|
||||
setIsCreating(true)
|
||||
try {
|
||||
await addTemplate({
|
||||
name: newTemplateName.trim(),
|
||||
description: newTemplateDescription.trim(),
|
||||
elements: [],
|
||||
layoutSettings: undefined,
|
||||
})
|
||||
|
||||
// Находим созданный шаблон и открываем его для редактирования
|
||||
// Поскольку addTemplate может занять время, используем небольшую задержку
|
||||
setTimeout(() => {
|
||||
const newTemplate = templates.find(
|
||||
t => t.name === newTemplateName.trim()
|
||||
)
|
||||
if (newTemplate) {
|
||||
setSelectedTemplate(newTemplate)
|
||||
setIsEditMode(true)
|
||||
}
|
||||
}, 100)
|
||||
|
||||
setIsCreateDialogOpen(false)
|
||||
setNewTemplateName('')
|
||||
setNewTemplateDescription('')
|
||||
} catch (error) {
|
||||
console.error('Ошибка при создании шаблона:', error)
|
||||
// Здесь можно добавить уведомление пользователю об ошибке
|
||||
alert('Ошибка при создании шаблона. Попробуйте еще раз.')
|
||||
} finally {
|
||||
setIsCreating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleTemplateUpdate = async (updatedTemplate: Template) => {
|
||||
try {
|
||||
await updateTemplate(updatedTemplate)
|
||||
setSelectedTemplate(updatedTemplate)
|
||||
} catch (error) {
|
||||
console.error('Ошибка при обновлении шаблона:', error)
|
||||
alert('Ошибка при обновлении шаблона. Попробуйте еще раз.')
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteTemplates = () => {
|
||||
templatesToDelete.forEach(template => {
|
||||
deleteTemplate(template.id)
|
||||
})
|
||||
setTemplatesToDelete([])
|
||||
setIsSelectionMode(false)
|
||||
setSelectedTemplates(new Set())
|
||||
}
|
||||
|
||||
const toggleTemplateSelection = (templateId: string) => {
|
||||
const newSelected = new Set(selectedTemplates)
|
||||
if (newSelected.has(templateId)) {
|
||||
newSelected.delete(templateId)
|
||||
} else {
|
||||
newSelected.add(templateId)
|
||||
}
|
||||
setSelectedTemplates(newSelected)
|
||||
}
|
||||
|
||||
const handleSelectionModeToggle = () => {
|
||||
if (isSelectionMode) {
|
||||
setIsSelectionMode(false)
|
||||
setSelectedTemplates(new Set())
|
||||
setTemplatesToDelete([])
|
||||
} else {
|
||||
setIsSelectionMode(true)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteSelected = () => {
|
||||
const selectedTemplatesList = templates.filter(t =>
|
||||
selectedTemplates.has(t.id)
|
||||
)
|
||||
setTemplatesToDelete(selectedTemplatesList)
|
||||
}
|
||||
|
||||
const handleRetry = () => {
|
||||
loadTemplates()
|
||||
}
|
||||
|
||||
if (isEditMode && selectedTemplate) {
|
||||
return (
|
||||
<TemplateEditor
|
||||
template={selectedTemplate}
|
||||
onBack={() => {
|
||||
setIsEditMode(false)
|
||||
setSelectedTemplate(null)
|
||||
if (templateId) {
|
||||
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}</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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user