Add maintenance management features: implement maintenance endpoints in the API, enhance media upload handling to skip duplicate files, and update frontend routes and translations for maintenance operations.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AlertTriangle } from 'lucide-react'
|
||||
import { HttpError } from '@/shared/api/client'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/shared/ui/card'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import { toast } from '@/shared/ui/toast-store'
|
||||
import { listShows } from '@/features/admin/shows/api'
|
||||
import { clearAllMedia, deleteAllShows, deleteShowMedia } from './api'
|
||||
|
||||
export function MaintenancePanel() {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const [showId, setShowId] = useState('')
|
||||
|
||||
const { data: shows } = useQuery({ queryKey: ['admin', 'shows'], queryFn: listShows })
|
||||
|
||||
const onError = (error: unknown) =>
|
||||
toast.error(error instanceof HttpError ? error.detail : t('common.error'))
|
||||
|
||||
// Затрагиваются медиа/шоу/каналы — сбрасываем все связанные кэши.
|
||||
const invalidateAll = () => {
|
||||
for (const key of [['admin', 'media'], ['admin', 'shows'], ['admin', 'channels']])
|
||||
void queryClient.invalidateQueries({ queryKey: key })
|
||||
}
|
||||
|
||||
const clearMedia = useMutation({
|
||||
mutationFn: clearAllMedia,
|
||||
onSuccess: (count) => {
|
||||
toast.success(t('admin.maintenance.doneCount', { count }))
|
||||
invalidateAll()
|
||||
},
|
||||
onError,
|
||||
})
|
||||
|
||||
const clearShows = useMutation({
|
||||
mutationFn: deleteAllShows,
|
||||
onSuccess: (count) => {
|
||||
toast.success(t('admin.maintenance.doneCount', { count }))
|
||||
invalidateAll()
|
||||
},
|
||||
onError,
|
||||
})
|
||||
|
||||
const clearShowMedia = useMutation({
|
||||
mutationFn: () => deleteShowMedia(showId),
|
||||
onSuccess: (count) => {
|
||||
setShowId('')
|
||||
toast.success(t('admin.maintenance.doneCount', { count }))
|
||||
invalidateAll()
|
||||
},
|
||||
onError,
|
||||
})
|
||||
|
||||
const confirmed = (message: string, run: () => void) => {
|
||||
if (window.confirm(message)) run()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5 text-red-500" />
|
||||
<h2 className="crt-glow text-xl font-semibold">{t('admin.maintenance.title')}</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{t('admin.maintenance.warning')}</p>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('admin.maintenance.clearMedia')}</CardTitle>
|
||||
<CardDescription>{t('admin.maintenance.clearMediaHint')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={clearMedia.isPending}
|
||||
onClick={() => confirmed(t('admin.maintenance.confirmClearMedia'), () => clearMedia.mutate())}
|
||||
>
|
||||
{t('admin.maintenance.clearMedia')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('admin.maintenance.clearShowMedia')}</CardTitle>
|
||||
<CardDescription>{t('admin.maintenance.clearShowMediaHint')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-wrap items-center gap-2">
|
||||
<Select value={showId} onValueChange={setShowId}>
|
||||
<SelectTrigger className="max-w-xs">
|
||||
<SelectValue placeholder={t('admin.maintenance.pickShow')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{shows?.map((s) => (
|
||||
<SelectItem key={s.id} value={s.id}>
|
||||
{s.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={!showId || clearShowMedia.isPending}
|
||||
onClick={() =>
|
||||
confirmed(t('admin.maintenance.confirmClearShowMedia'), () => clearShowMedia.mutate())
|
||||
}
|
||||
>
|
||||
{t('admin.maintenance.clearShowMedia')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('admin.maintenance.deleteShows')}</CardTitle>
|
||||
<CardDescription>{t('admin.maintenance.deleteShowsHint')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button
|
||||
variant="destructive"
|
||||
disabled={clearShows.isPending}
|
||||
onClick={() => confirmed(t('admin.maintenance.confirmDeleteShows'), () => clearShows.mutate())}
|
||||
>
|
||||
{t('admin.maintenance.deleteShows')}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user