feat: add context menu support to DocumentList, FAQList, and KnowledgeBaseList for improved user interaction
This commit is contained in:
@@ -152,6 +152,7 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL
|
|||||||
const [moving, setMoving] = useState(false);
|
const [moving, setMoving] = useState(false);
|
||||||
const [bulkMoveOpen, setBulkMoveOpen] = useState(false);
|
const [bulkMoveOpen, setBulkMoveOpen] = useState(false);
|
||||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||||
|
const [contextMenuDocumentId, setContextMenuDocumentId] = useState<number | null>(null);
|
||||||
const [selectedDirectoryId, setSelectedDirectoryId] = useState<number | null>(null);
|
const [selectedDirectoryId, setSelectedDirectoryId] = useState<number | null>(null);
|
||||||
const [actionLoadingMap, setActionLoadingMap] = useState<Record<number, { rebuildIndex: boolean; delete: boolean }>>({});
|
const [actionLoadingMap, setActionLoadingMap] = useState<Record<number, { rebuildIndex: boolean; delete: boolean }>>({});
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
@@ -484,10 +485,16 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL
|
|||||||
) : null}
|
) : null}
|
||||||
{documents.results.map((item) => (
|
{documents.results.map((item) => (
|
||||||
viewMode === "grid" ? (
|
viewMode === "grid" ? (
|
||||||
<ContextMenu key={item.id}>
|
<ContextMenu
|
||||||
|
key={item.id}
|
||||||
|
onOpenChange={(open) => setContextMenuDocumentId(open ? item.id : null)}
|
||||||
|
>
|
||||||
<ContextMenuTrigger className="w-full">
|
<ContextMenuTrigger className="w-full">
|
||||||
<div
|
<div
|
||||||
className="bg-background p-3 transition-colors hover:bg-accent w-full"
|
className={cn(
|
||||||
|
"bg-background p-3 transition-colors hover:bg-accent w-full",
|
||||||
|
contextMenuDocumentId === item.id && "bg-accent text-accent-foreground",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-2">
|
<div className="flex items-start justify-between gap-2">
|
||||||
<div className="flex min-w-0 flex-1 items-start gap-2">
|
<div className="flex min-w-0 flex-1 items-start gap-2">
|
||||||
@@ -569,10 +576,16 @@ export function DocumentList({ knowledgeBaseId, onActionStateChange }: DocumentL
|
|||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
) : (
|
) : (
|
||||||
<ContextMenu key={item.id}>
|
<ContextMenu
|
||||||
|
key={item.id}
|
||||||
|
onOpenChange={(open) => setContextMenuDocumentId(open ? item.id : null)}
|
||||||
|
>
|
||||||
<ContextMenuTrigger className="w-full">
|
<ContextMenuTrigger className="w-full">
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-3 bg-background p-2 transition-colors hover:bg-accent w-full"
|
className={cn(
|
||||||
|
"flex items-center gap-3 bg-background p-2 transition-colors hover:bg-accent w-full",
|
||||||
|
contextMenuDocumentId === item.id && "bg-accent text-accent-foreground",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedIds.includes(item.id)}
|
checked={selectedIds.includes(item.id)}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import {
|
|||||||
} from "@/lib/api/admin";
|
} from "@/lib/api/admin";
|
||||||
import { KnowledgeDocumentIndexStatus } from "@/lib/generated/enums";
|
import { KnowledgeDocumentIndexStatus } from "@/lib/generated/enums";
|
||||||
import { useI18n } from "@/i18n/provider";
|
import { useI18n } from "@/i18n/provider";
|
||||||
import { formatDateTime } from "@/lib/utils";
|
import { cn, formatDateTime } from "@/lib/utils";
|
||||||
import { FAQEditDialog } from "./faq-edit";
|
import { FAQEditDialog } from "./faq-edit";
|
||||||
import { FAQImportDialog } from "./faq-import-dialog";
|
import { FAQImportDialog } from "./faq-import-dialog";
|
||||||
import { KnowledgeBulkMoveDialog } from "./knowledge-bulk-move-dialog";
|
import { KnowledgeBulkMoveDialog } from "./knowledge-bulk-move-dialog";
|
||||||
@@ -141,6 +141,7 @@ export function FAQList({
|
|||||||
const [moving, setMoving] = useState(false);
|
const [moving, setMoving] = useState(false);
|
||||||
const [bulkMoveOpen, setBulkMoveOpen] = useState(false);
|
const [bulkMoveOpen, setBulkMoveOpen] = useState(false);
|
||||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||||
|
const [contextMenuFAQId, setContextMenuFAQId] = useState<number | null>(null);
|
||||||
const [importing, setImporting] = useState(false);
|
const [importing, setImporting] = useState(false);
|
||||||
const [importDialogOpen, setImportDialogOpen] = useState(false);
|
const [importDialogOpen, setImportDialogOpen] = useState(false);
|
||||||
const [selectedDirectoryId, setSelectedDirectoryId] = useState<number | null>(null);
|
const [selectedDirectoryId, setSelectedDirectoryId] = useState<number | null>(null);
|
||||||
@@ -429,9 +430,17 @@ export function FAQList({
|
|||||||
</label>
|
</label>
|
||||||
) : null}
|
) : null}
|
||||||
{faqs.results.map((item) => (
|
{faqs.results.map((item) => (
|
||||||
<ContextMenu key={item.id}>
|
<ContextMenu
|
||||||
|
key={item.id}
|
||||||
|
onOpenChange={(open) => setContextMenuFAQId(open ? item.id : null)}
|
||||||
|
>
|
||||||
<ContextMenuTrigger className="w-full">
|
<ContextMenuTrigger className="w-full">
|
||||||
<div className="flex items-center gap-3 bg-background p-2 transition-colors hover:bg-accent w-full">
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-3 bg-background p-2 transition-colors hover:bg-accent w-full",
|
||||||
|
contextMenuFAQId === item.id && "bg-accent text-accent-foreground",
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedIds.includes(item.id)}
|
checked={selectedIds.includes(item.id)}
|
||||||
onClick={(event) => event.stopPropagation()}
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
|||||||
@@ -82,7 +82,9 @@ function getStatusOptions(t: TFunction) {
|
|||||||
type SortableKnowledgeBaseCardProps = {
|
type SortableKnowledgeBaseCardProps = {
|
||||||
item: KnowledgeBase;
|
item: KnowledgeBase;
|
||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
|
isContextMenuOpen: boolean;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
|
onContextMenuOpenChange: (open: boolean) => void;
|
||||||
onSelect: () => void;
|
onSelect: () => void;
|
||||||
onEdit: () => void;
|
onEdit: () => void;
|
||||||
onDelete: () => void;
|
onDelete: () => void;
|
||||||
@@ -95,7 +97,9 @@ type SortableKnowledgeBaseCardProps = {
|
|||||||
function SortableKnowledgeBaseCard({
|
function SortableKnowledgeBaseCard({
|
||||||
item,
|
item,
|
||||||
isSelected,
|
isSelected,
|
||||||
|
isContextMenuOpen,
|
||||||
disabled,
|
disabled,
|
||||||
|
onContextMenuOpenChange,
|
||||||
onSelect,
|
onSelect,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
@@ -122,13 +126,13 @@ function SortableKnowledgeBaseCard({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu>
|
<ContextMenu onOpenChange={onContextMenuOpenChange}>
|
||||||
<ContextMenuTrigger
|
<ContextMenuTrigger
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group flex items-center gap-1 rounded mx-2 px-2 py-1.5 text-sm transition-colors hover:bg-accent cursor-pointer",
|
"group flex items-center gap-1 rounded mx-2 px-2 py-1.5 text-sm transition-colors hover:bg-accent cursor-pointer",
|
||||||
isSelected && "bg-accent text-accent-foreground",
|
(isSelected || isContextMenuOpen) && "bg-accent text-accent-foreground",
|
||||||
isDragging && "bg-muted/60 shadow-sm opacity-80",
|
isDragging && "bg-muted/60 shadow-sm opacity-80",
|
||||||
)}
|
)}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
@@ -248,6 +252,7 @@ export function KnowledgeBaseList({
|
|||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [editingItemId, setEditingItemId] = useState<number | null>(null);
|
const [editingItemId, setEditingItemId] = useState<number | null>(null);
|
||||||
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([]);
|
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([]);
|
||||||
|
const [contextMenuKnowledgeBaseId, setContextMenuKnowledgeBaseId] = useState<number | null>(null);
|
||||||
const statusOptions = useMemo(() => getStatusOptions(t), [t]);
|
const statusOptions = useMemo(() => getStatusOptions(t), [t]);
|
||||||
|
|
||||||
const sensors = useSensors(
|
const sensors = useSensors(
|
||||||
@@ -487,7 +492,11 @@ export function KnowledgeBaseList({
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
item={item}
|
item={item}
|
||||||
isSelected={selectedKnowledgeBaseId === item.id}
|
isSelected={selectedKnowledgeBaseId === item.id}
|
||||||
|
isContextMenuOpen={contextMenuKnowledgeBaseId === item.id}
|
||||||
disabled={loading || sorting}
|
disabled={loading || sorting}
|
||||||
|
onContextMenuOpenChange={(open) =>
|
||||||
|
setContextMenuKnowledgeBaseId(open ? item.id : null)
|
||||||
|
}
|
||||||
onSelect={() => onSelectKnowledgeBase(item)}
|
onSelect={() => onSelectKnowledgeBase(item)}
|
||||||
onEdit={() => openEditDialog(item)}
|
onEdit={() => openEditDialog(item)}
|
||||||
onDelete={() => void handleDelete(item)}
|
onDelete={() => void handleDelete(item)}
|
||||||
|
|||||||
Reference in New Issue
Block a user