Add a delete button to the library detail page
Staff and uploaders (for their own items) get a Delete action on /detail/J-<id> with an inline confirmation; on success it returns to the library and refreshes files, duplicates and storage.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { ArrowLeft, Download } from "lucide-react";
|
import { ArrowLeft, Download, Trash2 } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
@@ -296,8 +296,10 @@ function CustomDataCard({ item, canEdit }: { item: MediaItem; canEdit: boolean }
|
|||||||
|
|
||||||
export function LibraryDetail({ jId }: { jId: string }) {
|
export function LibraryDetail({ jId }: { jId: string }) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const ordering = useUi((state) => state.ordering);
|
const ordering = useUi((state) => state.ordering);
|
||||||
const user = useAuth((state) => state.user);
|
const user = useAuth((state) => state.user);
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
const rawUrl = `/api/files/${jId}/raw/`;
|
const rawUrl = `/api/files/${jId}/raw/`;
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
@@ -313,6 +315,20 @@ export function LibraryDetail({ jId }: { jId: string }) {
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: () =>
|
||||||
|
api<{ deleted: string[]; errors: { j_id?: string; error: string }[] }>(
|
||||||
|
"/api/delete/",
|
||||||
|
{ method: "POST", json: { j_ids: [jId] } },
|
||||||
|
),
|
||||||
|
onSuccess: () => {
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ["files"] });
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ["duplicates-md5"] });
|
||||||
|
void queryClient.invalidateQueries({ queryKey: ["storage"] });
|
||||||
|
navigate("/");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
||||||
@@ -361,6 +377,12 @@ export function LibraryDetail({ jId }: { jId: string }) {
|
|||||||
user.is_staff ||
|
user.is_staff ||
|
||||||
user.role === "staff"),
|
user.role === "staff"),
|
||||||
);
|
);
|
||||||
|
const canDelete = Boolean(
|
||||||
|
user &&
|
||||||
|
(user.is_superuser ||
|
||||||
|
user.role === "staff" ||
|
||||||
|
user.id === item.uploaded_by_id),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DetailShell
|
<DetailShell
|
||||||
@@ -381,6 +403,42 @@ export function LibraryDetail({ jId }: { jId: string }) {
|
|||||||
<a href={`${rawUrl}?download=1`} className={linkButtonClass}>
|
<a href={`${rawUrl}?download=1`} className={linkButtonClass}>
|
||||||
<Download className="h-4 w-4" /> Download
|
<Download className="h-4 w-4" /> Download
|
||||||
</a>
|
</a>
|
||||||
|
{canDelete ? (
|
||||||
|
confirmDelete ? (
|
||||||
|
<>
|
||||||
|
<span className="text-xs text-ctp-subtext0">
|
||||||
|
Delete this item and all its copies?
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
disabled={deleteMutation.isPending}
|
||||||
|
onClick={() => deleteMutation.mutate()}
|
||||||
|
>
|
||||||
|
{deleteMutation.isPending ? "Deleting…" : "Confirm delete"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => setConfirmDelete(false)}
|
||||||
|
disabled={deleteMutation.isPending}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="danger"
|
||||||
|
onClick={() => setConfirmDelete(true)}
|
||||||
|
title="Delete this item from the library"
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4" /> Delete
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
) : null}
|
||||||
|
{deleteMutation.isError ? (
|
||||||
|
<p className="text-sm text-ctp-red">
|
||||||
|
{errorMessage(deleteMutation.error)}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
media={
|
media={
|
||||||
|
|||||||
Reference in New Issue
Block a user