Files
shop-server/client/src/pages/admin-info/ui/AdminInfoPage.tsx
T
2026-05-13 22:07:46 +05:00

217 lines
7.3 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 Alert from '@mui/material/Alert'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import Dialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent'
import DialogTitle from '@mui/material/DialogTitle'
import FormControlLabel from '@mui/material/FormControlLabel'
import Stack from '@mui/material/Stack'
import Switch from '@mui/material/Switch'
import Table from '@mui/material/Table'
import TableBody from '@mui/material/TableBody'
import TableCell from '@mui/material/TableCell'
import TableHead from '@mui/material/TableHead'
import TableRow from '@mui/material/TableRow'
import TextField from '@mui/material/TextField'
import Typography from '@mui/material/Typography'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Controller, useForm } from 'react-hook-form'
import {
createInfoBlock,
deleteInfoBlock,
fetchAdminInfoBlocks,
type InfoPageBlock,
updateInfoBlock,
} from '@/entities/info'
import { getErrorMessage } from '@/shared/lib/get-error-message'
import { invalidateQueryKeys } from '@/shared/lib/invalidate-query-keys'
import { useEditDialogState } from '@/shared/lib/use-edit-dialog-state'
import { EntityRowActions } from '@/shared/ui/EntityRowActions'
type FormState = {
key: string
title: string
body: string
sort: string
published: boolean
}
const emptyForm = (): FormState => ({ key: '', title: '', body: '', sort: '0', published: true })
export function AdminInfoPage() {
const qc = useQueryClient()
const { dialogOpen, editing, openCreateDialog, openEditDialog, closeDialog } = useEditDialogState<InfoPageBlock>()
const form = useForm<FormState>({ defaultValues: emptyForm(), mode: 'onChange' })
const blocksQuery = useQuery({
queryKey: ['admin', 'info-page', 'blocks'],
queryFn: fetchAdminInfoBlocks,
})
const saveMut = useMutation({
mutationFn: async () => {
const values = form.getValues()
const payload = {
key: values.key.trim(),
title: values.title.trim(),
body: values.body.trim(),
sort: Number(values.sort || 0),
published: values.published,
}
if (editing) return updateInfoBlock(editing.id, payload)
return createInfoBlock(payload)
},
onSuccess: async () => {
closeDialog()
form.reset(emptyForm())
await invalidateQueryKeys(qc, [
['admin', 'info-page', 'blocks'],
['info-page', 'public', 'blocks'],
])
},
})
const deleteMut = useMutation({
mutationFn: (id: string) => deleteInfoBlock(id),
onSuccess: async () => {
await invalidateQueryKeys(qc, [
['admin', 'info-page', 'blocks'],
['info-page', 'public', 'blocks'],
])
},
})
const openCreate = () => {
form.reset(emptyForm())
openCreateDialog()
}
const openEdit = (item: InfoPageBlock) => {
openEditDialog(item)
form.reset({
key: item.key,
title: item.title,
body: item.body,
sort: String(item.sort),
published: item.published,
})
}
const items = blocksQuery.data?.items ?? []
const err = saveMut.error ?? deleteMut.error
return (
<Box>
<Stack direction="row" sx={{ mb: 2, alignItems: 'center', justifyContent: 'space-between' }}>
<Typography variant="h4">Информационная страница</Typography>
<Button variant="contained" onClick={openCreate}>
Новый блок
</Button>
</Stack>
<Typography color="text.secondary" sx={{ mb: 2 }}>
Управление блоками страницы с процессом покупки, оплаты и доставки.
</Typography>
{blocksQuery.isError && <Alert severity="error">Не удалось загрузить блоки.</Alert>}
{err && (
<Alert severity="error" sx={{ mb: 2 }}>
{getErrorMessage(err)}
</Alert>
)}
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Key</TableCell>
<TableCell>Заголовок</TableCell>
<TableCell>Порядок</TableCell>
<TableCell>Опубликован</TableCell>
<TableCell align="right">Действия</TableCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item) => (
<TableRow key={item.id} hover>
<TableCell>{item.key}</TableCell>
<TableCell>{item.title}</TableCell>
<TableCell>{item.sort}</TableCell>
<TableCell>{item.published ? 'Да' : 'Нет'}</TableCell>
<TableCell align="right">
<EntityRowActions
onEdit={() => openEdit(item)}
onDelete={() => deleteMut.mutate(item.id)}
deleteDisabled={deleteMut.isPending}
/>
</TableCell>
</TableRow>
))}
{items.length === 0 && !blocksQuery.isLoading && (
<TableRow>
<TableCell colSpan={5} sx={{ color: 'text.secondary' }}>
Блоков пока нет.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
<Dialog open={dialogOpen} onClose={closeDialog} fullWidth maxWidth="md">
<DialogTitle>{editing ? 'Редактировать блок' : 'Новый блок'}</DialogTitle>
<DialogContent>
<Stack spacing={2} sx={{ mt: 1 }}>
<Controller
control={form.control}
name="key"
render={({ field }) => <TextField label="Key (латиница, цифры, _-)" fullWidth required {...field} />}
/>
<Controller
control={form.control}
name="title"
render={({ field }) => <TextField label="Заголовок" fullWidth required {...field} />}
/>
<Controller
control={form.control}
name="body"
render={({ field }) => (
<TextField label="Содержимое" fullWidth multiline minRows={5} required {...field} />
)}
/>
<Controller
control={form.control}
name="sort"
render={({ field }) => <TextField label="Порядок сортировки" fullWidth {...field} />}
/>
<Controller
control={form.control}
name="published"
render={({ field }) => (
<FormControlLabel
control={<Switch checked={field.value} onChange={(_, v) => field.onChange(v)} />}
label="Показывать на публичной странице"
/>
)}
/>
</Stack>
</DialogContent>
<DialogActions>
<Button onClick={closeDialog}>Отмена</Button>
<Button
variant="contained"
onClick={() => saveMut.mutate()}
disabled={
saveMut.isPending ||
!form.watch('key').trim() ||
!form.watch('title').trim() ||
!form.watch('body').trim()
}
>
{editing ? 'Сохранить' : 'Создать'}
</Button>
</DialogActions>
</Dialog>
</Box>
)
}