Enhance show deletion functionality to support media removal option
ci / build-backend (push) Successful in 1m32s
ci / build-frontend (push) Successful in 1m7s
ci / tests (push) Successful in 1m45s
ci / sonar (push) Failing after 31s

Updated the DeleteShowCommand and its handler to include an optional parameter for media deletion, allowing users to remove associated media files when deleting a show. Refactored related components and API calls to accommodate this new feature, ensuring a seamless user experience. Additionally, updated localization strings to reflect the new functionality and adjusted tests to verify the correct behavior of the deletion process with and without media.
This commit is contained in:
Leonid Pershin
2026-07-28 11:06:52 +03:00
parent 6ca629c13a
commit 2e76fa1452
16 changed files with 368 additions and 71 deletions
@@ -0,0 +1,77 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import type { ShowSummaryDto } from '@/shared/api/types'
import { Button } from '@/shared/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/shared/ui/dialog'
/**
* Подтверждение удаления шоу с выбором, что делать с его файлами.
*
* Галочка нужна потому, что медиа переживает шоу: серия ссылается на ассет по значению, внешнего
* ключа между ними нет, и после удаления файлы остались бы в библиотеке ничьими, занимая место.
* Спросить об этом здесь дешевле, чем потом искать осиротевшие гигабайты в списке медиа.
*/
export function DeleteShowDialog({
show,
pending,
onConfirm,
onClose,
}: Readonly<{
show: ShowSummaryDto
pending: boolean
onConfirm: (withMedia: boolean) => void
onClose: () => void
}>) {
const { t } = useTranslation()
const [withMedia, setWithMedia] = useState(false)
const hasMedia = show.episodeCount > 0
return (
<Dialog open onOpenChange={(open) => !open && onClose()}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('admin.shows.deleteTitle')}</DialogTitle>
<DialogDescription>{t('admin.shows.deleteHint', { name: show.name })}</DialogDescription>
</DialogHeader>
{hasMedia && (
<label className="flex items-start gap-2 text-sm">
<input
type="checkbox"
className="mt-1"
checked={withMedia}
onChange={(e) => setWithMedia(e.target.checked)}
/>
<span>
{t('admin.shows.deleteWithMedia', { count: show.episodeCount })}
<span className="block text-xs text-muted-foreground">
{t('admin.shows.deleteWithMediaHint')}
</span>
</span>
</label>
)}
<DialogFooter>
<Button size="sm" variant="outline" onClick={onClose}>
{t('common.cancel')}
</Button>
<Button
size="sm"
variant="destructive"
disabled={pending}
onClick={() => onConfirm(withMedia)}
>
{t('common.delete')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}