mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-10 16:22:38 +02:00
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:
parent
d0c7be7eca
commit
ea94c778c9
4 changed files with 135 additions and 76 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue