переименования папок

This commit is contained in:
2025-07-21 15:02:10 +03:00
parent d0c79bd334
commit 84f0c9c6aa
72 changed files with 245 additions and 249 deletions

View 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>
)
}