фильтры

This commit is contained in:
2025-07-27 07:39:01 +03:00
parent f5b2a5e6ee
commit bfaba2723c
5 changed files with 736 additions and 77 deletions

View File

@@ -23,30 +23,82 @@ import {
SidebarTrigger,
} from '@/component/ui/sidebar'
import { Textarea } from '@/component/ui/textarea'
import {
apiTemplateWithAttributesToTemplate,
getTemplateWithAttributesApi,
} from '@/entitiy/template/api/templateApiService'
import { useCategoryContext } from '@/entitiy/template/model/CategoryContext'
import { useTemplateContext } from '@/entitiy/template/model/TemplateContext'
import { Template, TemplateAttributeDetail } from '@/type/template'
import TemplateCard from '@/widget/template/ui/TemplateCard'
import clsx from 'clsx'
import { CheckSquare, Plus, Square, Trash2 } from 'lucide-react'
import { useCallback, useMemo, useState } from 'react'
interface AttributeValue {
attributeId: string
value: string | null
}
// Тип для CategoryFieldsSelector
interface CategoryAttributeValue {
attributeId: string
value: string | number
}
// Адаптеры для преобразования типов
const toCategoryAttributeValues = (
values: AttributeValue[]
): CategoryAttributeValue[] => {
return values.map(v => ({
attributeId: v.attributeId,
value: v.value || '',
}))
}
const fromCategoryAttributeValues = (
values: CategoryAttributeValue[]
): AttributeValue[] => {
return values.map(v => ({
attributeId: v.attributeId,
value: v.value?.toString() || null,
}))
}
export const TemplatesOverviewPage = () => {
const { templates, deleteTemplate, addTemplate } = useTemplateContext()
const {
templates,
deleteTemplate,
addTemplate,
copyTemplate,
updateTemplate,
} = useTemplateContext()
const { isLoading: attributesLoading } = useCategoryContext()
const [selected, setSelected] = useState<Set<string>>(new Set())
const [selectionMode, setSelectionMode] = useState(false)
const [isDialogOpen, setIsDialogOpen] = useState(false)
const [isCopyDialogOpen, setIsCopyDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [templateToCopy, setTemplateToCopy] = useState<string | null>(null)
const [templateToEdit, setTemplateToEdit] = useState<string | null>(null)
const [newName, setNewName] = useState('')
const [newDescription, setNewDescription] = useState('')
const [newAttributeValues, setNewAttributeValues] = useState<
AttributeValue[]
>([])
const [copyName, setCopyName] = useState('')
const [copyDescription, setCopyDescription] = useState('')
const [copyAttributeValues, setCopyAttributeValues] = useState<
AttributeValue[]
>([])
const [editName, setEditName] = useState('')
const [editDescription, setEditDescription] = useState('')
const [editAttributeValues, setEditAttributeValues] = useState<
AttributeValue[]
>([])
const [templateAttributesLoading, setTemplateAttributesLoading] =
useState(false)
const [filters, setFilters] = useState<TemplateFilters>({
name: '',
description: '',
@@ -61,6 +113,29 @@ export const TemplatesOverviewPage = () => {
})
}, [])
// Функция для загрузки атрибутов шаблона
const loadTemplateAttributes = async (templateId: string) => {
try {
setTemplateAttributesLoading(true)
const { template } = await getTemplateWithAttributesApi(templateId)
if (template) {
const templateWithAttrs = apiTemplateWithAttributesToTemplate(template)
const attributeValues: AttributeValue[] =
templateWithAttrs.attributes.map(attr => ({
attributeId: attr.attribute_id,
value: attr.value,
}))
return attributeValues
}
return []
} catch (error) {
console.error('Ошибка при загрузке атрибутов шаблона:', error)
return []
} finally {
setTemplateAttributesLoading(false)
}
}
const removeSelected = () => {
selected.forEach(deleteTemplate)
setSelected(new Set())
@@ -82,6 +157,87 @@ export const TemplatesOverviewPage = () => {
}
}
const handleCopy = (templateId: string) => {
const template = templates.find(t => t.id === templateId)
if (template) {
setTemplateToCopy(templateId)
setCopyName(`${template.name} (копия)`)
setCopyDescription(template.description || '')
// Загружаем атрибуты шаблона
loadTemplateAttributes(templateId).then(attributes => {
setCopyAttributeValues(attributes)
})
setIsCopyDialogOpen(true)
}
}
const handleCopyConfirm = async () => {
if (templateToCopy && copyName.trim()) {
try {
await copyTemplate(
templateToCopy,
copyName,
copyDescription,
copyAttributeValues
)
setCopyName('')
setCopyDescription('')
setCopyAttributeValues([])
setTemplateToCopy(null)
setIsCopyDialogOpen(false)
} catch (error) {
console.error('Ошибка при копировании шаблона:', error)
}
}
}
const handleEdit = (templateId: string) => {
const template = templates.find(t => t.id === templateId)
if (template) {
setTemplateToEdit(templateId)
setEditName(template.name)
setEditDescription(template.description || '')
// Загружаем атрибуты шаблона
loadTemplateAttributes(templateId).then(attributes => {
setEditAttributeValues(attributes)
})
setIsEditDialogOpen(true)
}
}
const handleEditConfirm = async () => {
if (templateToEdit && editName.trim()) {
try {
const template = templates.find(t => t.id === templateToEdit)
if (template) {
const updatedTemplate = {
...template,
name: editName,
description: editDescription,
}
await updateTemplate(updatedTemplate, editAttributeValues)
setEditName('')
setEditDescription('')
setEditAttributeValues([])
setTemplateToEdit(null)
setIsEditDialogOpen(false)
}
} catch (error) {
console.error('Ошибка при редактировании шаблона:', error)
}
}
}
const handleDelete = (templateId: string) => {
if (confirm('Вы уверены, что хотите удалить этот шаблон?')) {
deleteTemplate(templateId)
}
}
const toggleSelectionMode = () => {
setSelectionMode(v => {
if (v) setSelected(new Set())
@@ -111,9 +267,40 @@ export const TemplatesOverviewPage = () => {
return false
}
// Примечание: Фильтрация по атрибутам пока отключена,
// так как атрибуты теперь хранятся отдельно от шаблонов
// и требуют отдельного запроса к API для каждого шаблона
// Фильтрация по атрибутам
if (Object.keys(filters.attributes).length > 0) {
// Проверяем есть ли атрибуты у шаблона
const templateWithAttrs = template as Template & {
attributes?: TemplateAttributeDetail[]
}
if (!templateWithAttrs.attributes) {
return false
}
// Проверяем соответствуют ли атрибуты фильтрам
for (const [attributeId, filterValue] of Object.entries(
filters.attributes
)) {
if (!filterValue) continue // пропускаем пустые фильтры
const templateAttribute = templateWithAttrs.attributes.find(
(attr: TemplateAttributeDetail) => attr.attribute_id === attributeId
)
if (!templateAttribute || !templateAttribute.value) {
return false // атрибут не найден или пустой
}
// Проверяем содержится ли значение фильтра в значении атрибута
if (
!templateAttribute.value
.toLowerCase()
.includes(filterValue.toLowerCase())
) {
return false
}
}
}
return true
})
@@ -222,8 +409,14 @@ export const TemplatesOverviewPage = () => {
<div className="col-span-3">
{!attributesLoading && (
<CategoryFieldsSelector
selectedValues={newAttributeValues}
onValuesChange={setNewAttributeValues}
selectedValues={toCategoryAttributeValues(
newAttributeValues
)}
onValuesChange={values =>
setNewAttributeValues(
fromCategoryAttributeValues(values)
)
}
/>
)}
</div>
@@ -237,6 +430,154 @@ export const TemplatesOverviewPage = () => {
</DialogFooter>
</DialogContent>
</Dialog>
{/* Диалог копирования */}
<Dialog
open={isCopyDialogOpen}
onOpenChange={setIsCopyDialogOpen}
>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Копировать шаблон</DialogTitle>
<DialogDescription>
Укажите название и описание для копии шаблона
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="copy-name" className="text-right">
Название
</Label>
<Input
id="copy-name"
className="col-span-3"
value={copyName}
onChange={e => setCopyName(e.target.value)}
placeholder="Введите название"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label
htmlFor="copy-description"
className="text-right"
>
Описание
</Label>
<Textarea
id="copy-description"
className="col-span-3"
value={copyDescription}
onChange={e => setCopyDescription(e.target.value)}
placeholder="Введите описание"
/>
</div>
<div className="grid grid-cols-4 gap-4">
<Label className="self-start pt-2 text-right">
Атрибуты
</Label>
<div className="col-span-3">
{!attributesLoading && (
<CategoryFieldsSelector
selectedValues={toCategoryAttributeValues(
copyAttributeValues
)}
onValuesChange={values =>
setCopyAttributeValues(
fromCategoryAttributeValues(values)
)
}
/>
)}
</div>
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Отмена</Button>
</DialogClose>
<Button
onClick={handleCopyConfirm}
disabled={!copyName.trim()}
>
Копировать
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Диалог редактирования */}
<Dialog
open={isEditDialogOpen}
onOpenChange={setIsEditDialogOpen}
>
<DialogContent className="max-w-md">
<DialogHeader>
<DialogTitle>Редактировать шаблон</DialogTitle>
<DialogDescription>
Измените название и описание шаблона
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="edit-name" className="text-right">
Название
</Label>
<Input
id="edit-name"
className="col-span-3"
value={editName}
onChange={e => setEditName(e.target.value)}
placeholder="Введите название"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label
htmlFor="edit-description"
className="text-right"
>
Описание
</Label>
<Textarea
id="edit-description"
className="col-span-3"
value={editDescription}
onChange={e => setEditDescription(e.target.value)}
placeholder="Введите описание"
/>
</div>
<div className="grid grid-cols-4 gap-4">
<Label className="self-start pt-2 text-right">
Атрибуты
</Label>
<div className="col-span-3">
{!attributesLoading && (
<CategoryFieldsSelector
selectedValues={toCategoryAttributeValues(
editAttributeValues
)}
onValuesChange={values =>
setEditAttributeValues(
fromCategoryAttributeValues(values)
)
}
/>
)}
</div>
</div>
</div>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline">Отмена</Button>
</DialogClose>
<Button
onClick={handleEditConfirm}
disabled={!editName.trim()}
>
Сохранить
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
)}
</div>
@@ -274,6 +615,10 @@ export const TemplatesOverviewPage = () => {
selectionMode={selectionMode}
isSelected={selected.has(t.id)}
onToggleSelect={toggleSelect}
onCopy={handleCopy}
onEdit={handleEdit}
onDelete={handleDelete}
activeFilters={filters.attributes}
/>
))}
</section>