feat: add admin notification settings page
This commit is contained in:
@@ -15,7 +15,7 @@ import Typography from '@mui/material/Typography'
|
|||||||
import useMediaQuery from '@mui/material/useMediaQuery'
|
import useMediaQuery from '@mui/material/useMediaQuery'
|
||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { useUnit } from 'effector-react'
|
import { useUnit } from 'effector-react'
|
||||||
import { FileText, Image, LayoutGrid, ListOrdered, MessageSquare, Store, Users } from 'lucide-react'
|
import { Bell, FileText, Image, LayoutGrid, ListOrdered, MessageSquare, Store, Users } from 'lucide-react'
|
||||||
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
import { Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { fetchAdminOrdersSummary } from '@/entities/order/api/admin-order-api'
|
import { fetchAdminOrdersSummary } from '@/entities/order/api/admin-order-api'
|
||||||
import { AdminCategoriesPage } from '@/pages/admin-categories'
|
import { AdminCategoriesPage } from '@/pages/admin-categories'
|
||||||
@@ -26,6 +26,7 @@ import { AdminProductsPage } from '@/pages/admin-products'
|
|||||||
import { AdminReviewsPage } from '@/pages/admin-reviews'
|
import { AdminReviewsPage } from '@/pages/admin-reviews'
|
||||||
import { AdminUsersPage } from '@/pages/admin-users'
|
import { AdminUsersPage } from '@/pages/admin-users'
|
||||||
import { $user } from '@/shared/model/auth'
|
import { $user } from '@/shared/model/auth'
|
||||||
|
import { AdminNotificationsPage } from './AdminNotificationsPage'
|
||||||
|
|
||||||
type NavItem = {
|
type NavItem = {
|
||||||
to: string
|
to: string
|
||||||
@@ -61,6 +62,7 @@ export function AdminLayoutPage() {
|
|||||||
{ to: '/admin/reviews', label: 'Отзывы', icon: <MessageSquare /> },
|
{ to: '/admin/reviews', label: 'Отзывы', icon: <MessageSquare /> },
|
||||||
{ to: '/admin/users', label: 'Пользователи', icon: <Users /> },
|
{ to: '/admin/users', label: 'Пользователи', icon: <Users /> },
|
||||||
{ to: '/admin/info', label: 'Инфо-страница', icon: <FileText /> },
|
{ to: '/admin/info', label: 'Инфо-страница', icon: <FileText /> },
|
||||||
|
{ to: '/admin/notifications', label: 'Оповещения', icon: <Bell /> },
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
)
|
)
|
||||||
@@ -188,6 +190,7 @@ export function AdminLayoutPage() {
|
|||||||
<Route path="reviews" element={<AdminReviewsPage />} />
|
<Route path="reviews" element={<AdminReviewsPage />} />
|
||||||
<Route path="users" element={<AdminUsersPage />} />
|
<Route path="users" element={<AdminUsersPage />} />
|
||||||
<Route path="info" element={<AdminInfoPage />} />
|
<Route path="info" element={<AdminInfoPage />} />
|
||||||
|
<Route path="notifications" element={<AdminNotificationsPage />} />
|
||||||
<Route path="*" element={<Navigate to="/admin" replace />} />
|
<Route path="*" element={<Navigate to="/admin" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import Alert from '@mui/material/Alert'
|
||||||
|
import Box from '@mui/material/Box'
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel'
|
||||||
|
import Stack from '@mui/material/Stack'
|
||||||
|
import Switch from '@mui/material/Switch'
|
||||||
|
import TextField from '@mui/material/TextField'
|
||||||
|
import Typography from '@mui/material/Typography'
|
||||||
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
import {
|
||||||
|
fetchAdminNotificationSettings,
|
||||||
|
updateAdminNotificationSettings,
|
||||||
|
} from '@/entities/notification/api/notifications-api'
|
||||||
|
|
||||||
|
export function AdminNotificationsPage() {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [success, setSuccess] = useState(false)
|
||||||
|
|
||||||
|
const { data, isLoading } = useQuery({
|
||||||
|
queryKey: ['admin', 'notifications', 'settings'],
|
||||||
|
queryFn: fetchAdminNotificationSettings,
|
||||||
|
})
|
||||||
|
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: updateAdminNotificationSettings,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['admin', 'notifications', 'settings'] })
|
||||||
|
setSuccess(true)
|
||||||
|
setTimeout(() => setSuccess(false), 3000)
|
||||||
|
},
|
||||||
|
onError: (err: { response?: { data?: { error?: string } } }) => {
|
||||||
|
setError(err.response?.data?.error || 'Ошибка сохранения')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isLoading) return <Typography>Загрузка...</Typography>
|
||||||
|
|
||||||
|
const s = data?.settings
|
||||||
|
if (!s) return <Alert severity="error">Не удалось загрузить настройки</Alert>
|
||||||
|
|
||||||
|
const save = (updates: Record<string, unknown>) => {
|
||||||
|
setError(null)
|
||||||
|
mutation.mutate(updates)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
Оповещения
|
||||||
|
</Typography>
|
||||||
|
<Typography color="text.secondary" sx={{ mb: 3 }}>
|
||||||
|
Настройка оповещений администратора.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{success && (
|
||||||
|
<Alert severity="success" sx={{ mb: 2 }}>
|
||||||
|
Настройки сохранены
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Stack spacing={3} sx={{ maxWidth: 560 }}>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Email
|
||||||
|
</Typography>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={s.emailEnabled} onChange={(e) => save({ emailEnabled: e.target.checked })} />}
|
||||||
|
label="Получать уведомления на почту"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Telegram
|
||||||
|
</Typography>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch checked={s.telegramEnabled} onChange={(e) => save({ telegramEnabled: e.target.checked })} />
|
||||||
|
}
|
||||||
|
label="Получать уведомления в Telegram"
|
||||||
|
/>
|
||||||
|
{s.telegramEnabled && (
|
||||||
|
<Box sx={{ mt: 1, ml: 4 }}>
|
||||||
|
<TextField
|
||||||
|
label="Telegram Chat ID"
|
||||||
|
value={s.telegramChatId || ''}
|
||||||
|
onChange={(e) => save({ telegramChatId: e.target.value })}
|
||||||
|
helperText="Заполняется автоматически при /start бота"
|
||||||
|
fullWidth
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Типы уведомлений
|
||||||
|
</Typography>
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={s.newOrder} onChange={(e) => save({ newOrder: e.target.checked })} />}
|
||||||
|
label="Новый заказ"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch checked={s.newOrderMessage} onChange={(e) => save({ newOrderMessage: e.target.checked })} />
|
||||||
|
}
|
||||||
|
label="Сообщение в заказе"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={<Switch checked={s.newReview} onChange={(e) => save({ newReview: e.target.checked })} />}
|
||||||
|
label="Новый отзыв"
|
||||||
|
/>
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Switch checked={s.authCodeDuplicate} onChange={(e) => save({ authCodeDuplicate: e.target.checked })} />
|
||||||
|
}
|
||||||
|
label="Дублировать код входа в Telegram"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user