refactor: streamline document actions

- Simplified the RowActions component by removing unused imports and replacing the dropdown menu with tooltip buttons for editing, viewing metadata, and deleting documents.
- Updated the JsonMetadataViewer component to support controlled mode for better integration with the RowActions component.
- Adjusted CSS variables for destructive actions in globals.css for improved visual consistency.
This commit is contained in:
DESKTOP-RTLN3BA\$punk 2025-11-30 15:06:48 -08:00
parent d0c7be7eca
commit ea94c778c9
4 changed files with 135 additions and 76 deletions

View file

@ -17,9 +17,7 @@ from app.utils.rbac import check_permission
router = APIRouter() router = APIRouter()
@router.get( @router.get("/search-spaces/{search_space_id}/documents/{document_id}/editor-content")
"/search-spaces/{search_space_id}/documents/{document_id}/editor-content"
)
async def get_editor_content( async def get_editor_content(
search_space_id: int, search_space_id: int,
document_id: int, document_id: int,

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { FileText, MoreHorizontal, Pencil, Trash2 } from "lucide-react"; import { FileText, Pencil, Trash2 } from "lucide-react";
import { motion } from "motion/react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
@ -13,16 +14,9 @@ import {
AlertDialogFooter, AlertDialogFooter,
AlertDialogHeader, AlertDialogHeader,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"; } from "@/components/ui/alert-dialog";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import type { Document } from "./types"; import type { Document } from "./types";
export function RowActions({ export function RowActions({
@ -36,7 +30,8 @@ export function RowActions({
refreshDocuments: () => Promise<void>; refreshDocuments: () => Promise<void>;
searchSpaceId: string; searchSpaceId: string;
}) { }) {
const [isOpen, setIsOpen] = useState(false); const [isDeleteOpen, setIsDeleteOpen] = useState(false);
const [isMetadataOpen, setIsMetadataOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
const router = useRouter(); const router = useRouter();
@ -52,7 +47,7 @@ export function RowActions({
toast.error("Failed to delete document"); toast.error("Failed to delete document");
} finally { } finally {
setIsDeleting(false); setIsDeleting(false);
setIsOpen(false); setIsDeleteOpen(false);
} }
}; };
@ -61,64 +56,105 @@ export function RowActions({
}; };
return ( return (
<div className="flex justify-end"> <div className="flex items-center justify-end gap-1">
<DropdownMenu> {/* Edit Button */}
<DropdownMenuTrigger asChild> <Tooltip>
<Button variant="ghost" className="h-8 w-8 p-0"> <TooltipTrigger asChild>
<span className="sr-only">Open menu</span> <motion.div
<MoreHorizontal className="h-4 w-4" /> whileHover={{ scale: 1.1 }}
</Button> whileTap={{ scale: 0.95 }}
</DropdownMenuTrigger> transition={{ type: "spring", stiffness: 400, damping: 17 }}
<DropdownMenuContent align="end"> >
<DropdownMenuItem onClick={handleEdit}> <Button
<Pencil className="mr-0 h-4 w-4" /> variant="ghost"
Edit Document size="icon"
</DropdownMenuItem> className="h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted/80"
<DropdownMenuSeparator /> onClick={handleEdit}
<JsonMetadataViewer >
title={document.title} <Pencil className="h-4 w-4" />
metadata={document.document_metadata} <span className="sr-only">Edit Document</span>
trigger={ </Button>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}> </motion.div>
<FileText className="mr-0 h-4 w-4" /> </TooltipTrigger>
View Metadata <TooltipContent side="top">
</DropdownMenuItem> <p>Edit Document</p>
} </TooltipContent>
/> </Tooltip>
<DropdownMenuSeparator />
<AlertDialog open={isOpen} onOpenChange={setIsOpen}> {/* View Metadata Button */}
<AlertDialogTrigger asChild> <Tooltip>
<DropdownMenuItem <TooltipTrigger asChild>
className="text-destructive focus:text-destructive" <motion.div
onSelect={(e) => { whileHover={{ scale: 1.1 }}
e.preventDefault(); whileTap={{ scale: 0.95 }}
setIsOpen(true); transition={{ type: "spring", stiffness: 400, damping: 17 }}
}} >
> <Button
<Trash2 className="mr-0 h-4 w-4 text-destructive" /> variant="ghost"
Delete size="icon"
</DropdownMenuItem> className="h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted/80"
</AlertDialogTrigger> onClick={() => setIsMetadataOpen(true)}
<AlertDialogContent> >
<AlertDialogHeader> <FileText className="h-4 w-4" />
<AlertDialogTitle>Are you sure?</AlertDialogTitle> <span className="sr-only">View Metadata</span>
</AlertDialogHeader> </Button>
<AlertDialogFooter> </motion.div>
<AlertDialogCancel>Cancel</AlertDialogCancel> </TooltipTrigger>
<AlertDialogAction <TooltipContent side="top">
onClick={(e) => { <p>View Metadata</p>
e.preventDefault(); </TooltipContent>
handleDelete(); </Tooltip>
}} <JsonMetadataViewer
disabled={isDeleting} title={document.title}
> metadata={document.document_metadata}
{isDeleting ? "Deleting..." : "Delete"} open={isMetadataOpen}
</AlertDialogAction> onOpenChange={setIsMetadataOpen}
</AlertDialogFooter> />
</AlertDialogContent>
</AlertDialog> {/* Delete Button */}
</DropdownMenuContent> <Tooltip>
</DropdownMenu> <TooltipTrigger asChild>
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
>
<Button
variant="ghost"
size="icon"
className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
onClick={() => setIsDeleteOpen(true)}
disabled={isDeleting}
>
<Trash2 className="h-4 w-4" />
<span className="sr-only">Delete</span>
</Button>
</motion.div>
</TooltipTrigger>
<TooltipContent side="top">
<p>Delete</p>
</TooltipContent>
</Tooltip>
<AlertDialog open={isDeleteOpen} onOpenChange={setIsDeleteOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={(e) => {
e.preventDefault();
handleDelete();
}}
disabled={isDeleting}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{isDeleting ? "Deleting..." : "Delete"}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div> </div>
); );
} }

View file

@ -27,7 +27,7 @@
--accent: oklch(0.97 0 0); --accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0); --accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.922 0 0); --border: oklch(0.922 0 0);
--input: oklch(0.922 0 0); --input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0); --ring: oklch(0.708 0 0);
@ -63,8 +63,8 @@
--muted-foreground: oklch(0.708 0 0); --muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0); --accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0); --accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723); --destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.637 0.237 25.331); --destructive-foreground: oklch(0.985 0 0);
--border: oklch(0.269 0 0); --border: oklch(0.269 0 0);
--input: oklch(0.269 0 0); --input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0); --ring: oklch(0.439 0 0);

View file

@ -15,9 +15,17 @@ interface JsonMetadataViewerProps {
title: string; title: string;
metadata: any; metadata: any;
trigger?: React.ReactNode; trigger?: React.ReactNode;
open?: boolean;
onOpenChange?: (open: boolean) => void;
} }
export function JsonMetadataViewer({ title, metadata, trigger }: JsonMetadataViewerProps) { export function JsonMetadataViewer({
title,
metadata,
trigger,
open,
onOpenChange,
}: JsonMetadataViewerProps) {
// Ensure metadata is a valid object // Ensure metadata is a valid object
const jsonData = React.useMemo(() => { const jsonData = React.useMemo(() => {
if (!metadata) return {}; if (!metadata) return {};
@ -35,6 +43,23 @@ export function JsonMetadataViewer({ title, metadata, trigger }: JsonMetadataVie
} }
}, [metadata]); }, [metadata]);
// Controlled mode: when open and onOpenChange are provided
if (open !== undefined && onOpenChange !== undefined) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{title} - Metadata</DialogTitle>
</DialogHeader>
<div className="mt-4 p-4 bg-muted/30 rounded-md">
<JsonView data={jsonData} style={defaultStyles} />
</div>
</DialogContent>
</Dialog>
);
}
// Uncontrolled mode: when using trigger
return ( return (
<Dialog> <Dialog>
<DialogTrigger asChild> <DialogTrigger asChild>