рефакторинг

This commit is contained in:
2025-07-21 17:07:03 +03:00
parent 84f0c9c6aa
commit 5737e37386
35 changed files with 526 additions and 579 deletions

View File

@@ -0,0 +1,198 @@
export interface CreateTemplateInput {
name: string
description?: string
elements: Record<string, any>
}
export interface CreateTemplateOutput {
id: UUID
}
export interface GetTemplatesOutput {
templates: ApiTemplate[]
}
export interface GetTemplateOutput {
template: ApiTemplate | null
}
export interface PatchTemplateInput {
name?: string | null
description?: string | null
elements?: Record<string, any> | null
}
export interface PatchTemplateOutput {
id: string
}
export interface ApiTemplate {
id: string
name: string
description?: string | null
elements: Record<string, any>
created_at: string
updated_at: string
deleted_at?: string | null
}
// Импортируем типы для преобразования
import { getDefaultLayoutSettings, migrateTemplateElement } from '@/lib/utils'
import { Template, TemplateElement } from '@/type/template'
// Утилитарные функции для преобразования данных
// Преобразование API шаблона в внутренний формат
export function apiTemplateToTemplate(apiTemplate: ApiTemplate): Template {
// Преобразуем elements из Record<string, any> в TemplateElement[]
const elements: TemplateElement[] = Object.values(apiTemplate.elements || {})
.map(el => migrateTemplateElement(el))
.sort((a, b) => (a.order || 0) - (b.order || 0))
return {
id: apiTemplate.id,
name: apiTemplate.name,
description: apiTemplate.description || undefined,
elements,
layoutSettings: getDefaultLayoutSettings(),
createdAt: new Date(apiTemplate.created_at),
updatedAt: new Date(apiTemplate.updated_at),
}
}
// Преобразование внутреннего шаблона в API формат для создания
export function templateToCreateInput(
template: Omit<Template, 'id' | 'createdAt' | 'updatedAt'>
): CreateTemplateInput {
// Преобразуем элементы в Record<string, any>
const elements: Record<string, any> = {}
template.elements.forEach((element, index) => {
elements[element.id] = element
})
return {
name: template.name,
description: template.description,
elements,
}
}
// Преобразование внутреннего шаблона в API формат для обновления
export function templateToPatchInput(template: Template): PatchTemplateInput {
// Преобразуем элементы в Record<string, any>
const elements: Record<string, any> = {}
template.elements.forEach((element, index) => {
elements[element.id] = element
})
return {
name: template.name,
description: template.description,
elements,
}
}
// API создание шаблона
export async function createTemplateApi(
template: CreateTemplateInput
): Promise<CreateTemplateOutput> {
try {
const response = await fetch('/api/templates/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(template),
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result: CreateTemplateOutput = await response.json()
return result
} catch (error) {
console.error('Ошибка при создании шаблона:', error)
throw new Error('Ошибка при создании шаблона на сервере')
}
}
// API получение всех шаблонов
export async function getTemplatesApi(): Promise<GetTemplatesOutput> {
try {
const response = await fetch('/api/templates/', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result: GetTemplatesOutput = await response.json()
return result
} catch (error) {
console.error('Ошибка при получении шаблонов:', error)
throw new Error('Ошибка при получении шаблонов с сервера')
}
}
// API получение конкретного шаблона
export async function getTemplateApi(
templateId: UUID
): Promise<GetTemplateOutput> {
try {
const response = await fetch(`/api/templates/${templateId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result: GetTemplateOutput = await response.json()
return result
} catch (error) {
console.error('Ошибка при получении шаблона:', error)
throw new Error('Ошибка при получении шаблона с сервера')
}
}
// API обновление шаблона
export async function patchTemplateApi(
templateId: string,
template: PatchTemplateInput
): Promise<PatchTemplateOutput> {
try {
const response = await fetch(`/api/templates/${templateId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(template),
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const result: PatchTemplateOutput = await response.json()
return result
} catch (error) {
console.error('Ошибка при обновлении шаблона:', error)
throw new Error('Ошибка при обновлении шаблона на сервере')
}
}
// Алиас для удобства использования
export async function updateTemplate(
template: Template
): Promise<PatchTemplateOutput> {
const patchInput = templateToPatchInput(template)
return patchTemplateApi(template.id, patchInput)
}

View File

@@ -0,0 +1,143 @@
import {
apiTemplateToTemplate,
createTemplateApi,
getTemplateApi,
getTemplatesApi,
patchTemplateApi,
templateToCreateInput,
templateToPatchInput,
} from '@/entitiy/template/api/templateApiService'
import { Template } from '@/type/template'
import {
QueryClient,
useMutation,
useQuery,
useQueryClient,
} from '@tanstack/react-query'
import React, { createContext, ReactNode, useContext, useMemo } from 'react'
export const queryClient = new QueryClient()
interface TemplateContextType {
templates: Template[]
isLoading: boolean
error: Error | null
refetchTemplates: () => void
addTemplate: (
data: Omit<Template, 'id' | 'createdAt' | 'updatedAt'>
) => Promise<Template>
updateTemplate: (template: Template) => Promise<Template>
deleteTemplate: (id: UUID) => Promise<string>
}
const TemplateContext = createContext<TemplateContextType | undefined>(
undefined
)
export const TemplateProvider: React.FC<{ children: ReactNode }> = ({
children,
}) => {
const client = useQueryClient()
const {
data: templates = [],
status: listStatus,
error: errorList,
refetch,
} = useQuery<Template[], Error>({
queryKey: ['templates'],
queryFn: async () => {
const { templates: apiList } = await getTemplatesApi()
return apiList.map(apiTemplateToTemplate)
},
})
const createMutation = useMutation({
mutationFn: async (
newTemplate: Omit<Template, 'id' | 'createdAt' | 'updatedAt'>
) => {
const input = templateToCreateInput(newTemplate)
const { id } = await createTemplateApi(input)
const { template: apiTpl } = await getTemplateApi(id)
if (!apiTpl) throw new Error('Template not found')
return apiTemplateToTemplate(apiTpl)
},
onSuccess: () => {
client.invalidateQueries({ queryKey: ['templates'] })
},
})
const updateMutation = useMutation({
mutationFn: async (template: Template) => {
const input = templateToPatchInput(template)
await patchTemplateApi(template.id, input)
return template
},
onSuccess: updated => {
client.setQueryData<Template[]>(
['templates'],
old =>
old?.map((t: Template) => (t.id === updated.id ? updated : t)) ?? []
)
},
})
const deleteMutation = useMutation({
mutationFn: async (id: string) => {
// TODO: deleteTemplateApi
return id
},
onSuccess: id => {
client.setQueryData<Template[]>(
['templates'],
old => old?.filter((t: Template) => t.id !== id) ?? []
)
},
})
const value = useMemo(
() => ({
templates,
isLoading:
listStatus === 'pending' ||
createMutation.status === 'pending' ||
updateMutation.status === 'pending' ||
deleteMutation.status === 'pending',
error:
(errorList as Error) ||
(createMutation.error as Error) ||
(updateMutation.error as Error) ||
(deleteMutation.error as Error) ||
null,
refetchTemplates: refetch,
addTemplate: createMutation.mutateAsync,
updateTemplate: updateMutation.mutateAsync,
deleteTemplate: deleteMutation.mutateAsync,
}),
[
templates,
listStatus,
errorList,
createMutation.status,
createMutation.error,
updateMutation.status,
updateMutation.error,
deleteMutation.status,
deleteMutation.error,
refetch,
]
)
return (
<TemplateContext.Provider value={value}>
{children}
</TemplateContext.Provider>
)
}
export const useTemplateContext = (): TemplateContextType => {
const ctx = useContext(TemplateContext)
if (!ctx)
throw new Error('useTemplateContext must be used within TemplateProvider')
return ctx
}