Locked bulk delete on archives

X-Lovable-Edit-ID: edt-c5698142-6949-4748-b679-b17fa8d781c2
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
gpt-engineer-app[bot]
2026-04-19 18:28:28 +00:00
co-authored by renee-png
+55 -4
View File
@@ -19,7 +19,7 @@ import {
import { supabase } from "@/integrations/supabase/client";
import { useAuth } from "@/lib/auth";
import { toast } from "sonner";
import { Archive as ArchiveIcon, Plus, Upload, Trash2, FileSpreadsheet } from "lucide-react";
import { Archive as ArchiveIcon, Plus, Upload, Trash2, FileSpreadsheet, Lock, Unlock } from "lucide-react";
import { formatDateTime } from "@/lib/format";
export const Route = createFileRoute("/archive/")({
@@ -62,6 +62,24 @@ function ArchivePage() {
const [totalCount, setTotalCount] = useState(0);
const [uploadOpen, setUploadOpen] = useState(false);
const [newCatOpen, setNewCatOpen] = useState(false);
const [lockedTabs, setLockedTabs] = useState<Record<string, boolean>>(() => {
if (typeof window === "undefined") return {};
try {
return JSON.parse(localStorage.getItem("archive-locked-tabs") || "{}");
} catch {
return {};
}
});
const isLocked = activeId ? lockedTabs[activeId] !== false : true; // default locked
const toggleLock = () => {
if (!activeId) return;
const next = { ...lockedTabs, [activeId]: !isLocked };
setLockedTabs(next);
try {
localStorage.setItem("archive-locked-tabs", JSON.stringify(next));
} catch {}
toast.success(next[activeId] ? "Tab locked" : "Tab unlocked");
};
// Debounce search input (300ms)
useEffect(() => {
@@ -153,6 +171,10 @@ function ArchivePage() {
}, [rows]);
const deleteRow = async (id: string) => {
if (isLocked) {
toast.error("Tab is locked. Unlock it to delete records.");
return;
}
if (!confirm("Delete this record?")) return;
const { error } = await supabase.from("archive_records").delete().eq("id", id);
if (error) {
@@ -166,6 +188,10 @@ function ArchivePage() {
const deleteAllInCategory = async () => {
if (!activeCategory) return;
if (isLocked) {
toast.error("Tab is locked. Unlock it to clear records.");
return;
}
if (!confirm(`Delete ALL ${totalCount} records in "${activeCategory.label}"? This cannot be undone.`)) return;
const { error } = await supabase.from("archive_records").delete().eq("category_id", activeCategory.id);
if (error) {
@@ -232,8 +258,32 @@ function ArchivePage() {
<div className="text-xs text-muted-foreground whitespace-nowrap">
Showing {rows.length} of {totalCount} {debouncedSearch ? "matching" : ""} rows
</div>
{isAdmin && activeId && (
<Button
variant={isLocked ? "default" : "outline"}
size="sm"
onClick={toggleLock}
title={isLocked ? "Tab is locked — click to unlock" : "Tab is unlocked — click to lock"}
>
{isLocked ? (
<>
<Lock className="h-4 w-4 mr-1" /> Locked
</>
) : (
<>
<Unlock className="h-4 w-4 mr-1" /> Unlocked
</>
)}
</Button>
)}
{isAdmin && rows.length > 0 && (
<Button variant="outline" size="sm" onClick={deleteAllInCategory}>
<Button
variant="outline"
size="sm"
onClick={deleteAllInCategory}
disabled={isLocked}
title={isLocked ? "Unlock tab to clear" : "Delete all rows in this tab"}
>
<Trash2 className="h-4 w-4 mr-1" /> Clear tab
</Button>
)}
@@ -290,10 +340,11 @@ function ArchivePage() {
size="sm"
variant="ghost"
onClick={() => deleteRow(r.id)}
title="Delete row"
disabled={isLocked}
title={isLocked ? "Tab locked" : "Delete row"}
className="text-destructive hover:text-destructive h-7 w-7 p-0"
>
<Trash2 className="h-3.5 w-3.5" />
{isLocked ? <Lock className="h-3.5 w-3.5" /> : <Trash2 className="h-3.5 w-3.5" />}
</Button>
</td>
)}