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,44 +56,86 @@ 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 }}
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-foreground hover:bg-muted/80"
onClick={handleEdit}
>
<Pencil className="h-4 w-4" />
<span className="sr-only">Edit Document</span>
</Button> </Button>
</DropdownMenuTrigger> </motion.div>
<DropdownMenuContent align="end"> </TooltipTrigger>
<DropdownMenuItem onClick={handleEdit}> <TooltipContent side="top">
<Pencil className="mr-0 h-4 w-4" /> <p>Edit Document</p>
Edit Document </TooltipContent>
</DropdownMenuItem> </Tooltip>
<DropdownMenuSeparator />
{/* View Metadata Button */}
<Tooltip>
<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-foreground hover:bg-muted/80"
onClick={() => setIsMetadataOpen(true)}
>
<FileText className="h-4 w-4" />
<span className="sr-only">View Metadata</span>
</Button>
</motion.div>
</TooltipTrigger>
<TooltipContent side="top">
<p>View Metadata</p>
</TooltipContent>
</Tooltip>
<JsonMetadataViewer <JsonMetadataViewer
title={document.title} title={document.title}
metadata={document.document_metadata} metadata={document.document_metadata}
trigger={ open={isMetadataOpen}
<DropdownMenuItem onSelect={(e) => e.preventDefault()}> onOpenChange={setIsMetadataOpen}
<FileText className="mr-0 h-4 w-4" />
View Metadata
</DropdownMenuItem>
}
/> />
<DropdownMenuSeparator />
<AlertDialog open={isOpen} onOpenChange={setIsOpen}> {/* Delete 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 }}
}}
> >
<Trash2 className="mr-0 h-4 w-4 text-destructive" /> <Button
Delete variant="ghost"
</DropdownMenuItem> size="icon"
</AlertDialogTrigger> 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> <AlertDialogContent>
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>Are you sure?</AlertDialogTitle> <AlertDialogTitle>Are you sure?</AlertDialogTitle>
@ -111,14 +148,13 @@ export function RowActions({
handleDelete(); handleDelete();
}} }}
disabled={isDeleting} disabled={isDeleting}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
> >
{isDeleting ? "Deleting..." : "Delete"} {isDeleting ? "Deleting..." : "Delete"}
</AlertDialogAction> </AlertDialogAction>
</AlertDialogFooter> </AlertDialogFooter>
</AlertDialogContent> </AlertDialogContent>
</AlertDialog> </AlertDialog>
</DropdownMenuContent>
</DropdownMenu>
</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>