mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-26 09:16:22 +02:00
chore: ran linting
This commit is contained in:
parent
00a617ef17
commit
aa66928154
44 changed files with 2025 additions and 1658 deletions
|
|
@ -38,7 +38,9 @@ export function DocumentTypeChip({ type, className }: { type: string; className?
|
|||
className={`inline-flex items-center gap-1.5 rounded bg-muted/40 px-2 py-1 text-xs text-muted-foreground max-w-full overflow-hidden ${className ?? ""}`}
|
||||
>
|
||||
<span className="opacity-80 flex-shrink-0">{icon}</span>
|
||||
<span ref={textRef} className="truncate min-w-0">{fullLabel}</span>
|
||||
<span ref={textRef} className="truncate min-w-0">
|
||||
{fullLabel}
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -68,9 +68,7 @@ export function DocumentsFilters({
|
|||
const filteredTypes = useMemo(() => {
|
||||
if (!typeSearchQuery.trim()) return uniqueTypes;
|
||||
const query = typeSearchQuery.toLowerCase();
|
||||
return uniqueTypes.filter((type) =>
|
||||
getDocumentTypeLabel(type).toLowerCase().includes(query)
|
||||
);
|
||||
return uniqueTypes.filter((type) => getDocumentTypeLabel(type).toLowerCase().includes(query));
|
||||
}, [uniqueTypes, typeSearchQuery]);
|
||||
|
||||
const typeCounts = useMemo(() => {
|
||||
|
|
@ -156,94 +154,95 @@ export function DocumentsFilters({
|
|||
|
||||
{/* Filter Buttons Group */}
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{/* Type Filter */}
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-9 gap-2 border-dashed border-border/60 text-muted-foreground hover:text-foreground hover:border-border"
|
||||
>
|
||||
<FileType size={14} className="text-muted-foreground" />
|
||||
<span className="hidden sm:inline">Type</span>
|
||||
{activeTypes.length > 0 && (
|
||||
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground">
|
||||
{activeTypes.length}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-64 !p-0 overflow-hidden" align="end">
|
||||
<div>
|
||||
{/* Search input */}
|
||||
<div className="p-2 border-b border-border/50">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-0.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search types..."
|
||||
value={typeSearchQuery}
|
||||
onChange={(e) => setTypeSearchQuery(e.target.value)}
|
||||
className="h-6 pl-6 text-sm bg-transparent border-0 focus-visible:ring-0"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-h-[300px] overflow-y-auto overflow-x-hidden py-1.5 px-1.5">
|
||||
{filteredTypes.length === 0 ? (
|
||||
<div className="py-6 text-center text-sm text-muted-foreground">
|
||||
No types found
|
||||
{/* Type Filter */}
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-9 gap-2 border-dashed border-border/60 text-muted-foreground hover:text-foreground hover:border-border"
|
||||
>
|
||||
<FileType size={14} className="text-muted-foreground" />
|
||||
<span className="hidden sm:inline">Type</span>
|
||||
{activeTypes.length > 0 && (
|
||||
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground">
|
||||
{activeTypes.length}
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-64 !p-0 overflow-hidden" align="end">
|
||||
<div>
|
||||
{/* Search input */}
|
||||
<div className="p-2 border-b border-border/50">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-0.5 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search types..."
|
||||
value={typeSearchQuery}
|
||||
onChange={(e) => setTypeSearchQuery(e.target.value)}
|
||||
className="h-6 pl-6 text-sm bg-transparent border-0 focus-visible:ring-0"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
filteredTypes.map((value: DocumentTypeEnum, i) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2.5 py-2 px-3 rounded-md hover:bg-muted/50 transition-colors cursor-pointer text-left"
|
||||
onClick={() => onToggleType(value, !activeTypes.includes(value))}
|
||||
</div>
|
||||
|
||||
<div className="max-h-[300px] overflow-y-auto overflow-x-hidden py-1.5 px-1.5">
|
||||
{filteredTypes.length === 0 ? (
|
||||
<div className="py-6 text-center text-sm text-muted-foreground">
|
||||
No types found
|
||||
</div>
|
||||
) : (
|
||||
filteredTypes.map((value: DocumentTypeEnum, i) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
className="flex w-full items-center gap-2.5 py-2 px-3 rounded-md hover:bg-muted/50 transition-colors cursor-pointer text-left"
|
||||
onClick={() => onToggleType(value, !activeTypes.includes(value))}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted/50 text-foreground/80">
|
||||
{getDocumentTypeIcon(value, "h-4 w-4")}
|
||||
</div>
|
||||
{/* Text content */}
|
||||
<div className="flex flex-col min-w-0 flex-1 gap-0.5">
|
||||
<span className="text-[13px] font-medium text-foreground truncate leading-tight">
|
||||
{getDocumentTypeLabel(value)}
|
||||
</span>
|
||||
<span className="text-[11px] text-muted-foreground leading-tight">
|
||||
{typeCounts.get(value)} document
|
||||
{(typeCounts.get(value) ?? 0) !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
{/* Checkbox */}
|
||||
<Checkbox
|
||||
id={`${id}-${i}`}
|
||||
checked={activeTypes.includes(value)}
|
||||
onCheckedChange={(checked: boolean) => onToggleType(value, !!checked)}
|
||||
className="h-4 w-4 shrink-0 rounded border-muted-foreground/30 data-[state=checked]:bg-primary data-[state=checked]:border-primary"
|
||||
/>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
{activeTypes.length > 0 && (
|
||||
<div className="px-3 pt-1.5 pb-1.5 border-t border-border/50">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full h-7 text-[11px] text-muted-foreground hover:text-foreground"
|
||||
onClick={() => {
|
||||
activeTypes.forEach((t) => {
|
||||
onToggleType(t, false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{/* Icon */}
|
||||
<div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-md bg-muted/50 text-foreground/80">
|
||||
{getDocumentTypeIcon(value, "h-4 w-4")}
|
||||
</div>
|
||||
{/* Text content */}
|
||||
<div className="flex flex-col min-w-0 flex-1 gap-0.5">
|
||||
<span className="text-[13px] font-medium text-foreground truncate leading-tight">
|
||||
{getDocumentTypeLabel(value)}
|
||||
</span>
|
||||
<span className="text-[11px] text-muted-foreground leading-tight">
|
||||
{typeCounts.get(value)} document{(typeCounts.get(value) ?? 0) !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
{/* Checkbox */}
|
||||
<Checkbox
|
||||
id={`${id}-${i}`}
|
||||
checked={activeTypes.includes(value)}
|
||||
onCheckedChange={(checked: boolean) => onToggleType(value, !!checked)}
|
||||
className="h-4 w-4 shrink-0 rounded border-muted-foreground/30 data-[state=checked]:bg-primary data-[state=checked]:border-primary"
|
||||
/>
|
||||
</button>
|
||||
))
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{activeTypes.length > 0 && (
|
||||
<div className="px-3 pt-1.5 pb-1.5 border-t border-border/50">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full h-7 text-[11px] text-muted-foreground hover:text-foreground"
|
||||
onClick={() => {
|
||||
activeTypes.forEach((t) => {
|
||||
onToggleType(t, false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
{/* Bulk Delete Button */}
|
||||
{selectedIds.size > 0 && (
|
||||
|
|
@ -255,22 +254,14 @@ export function DocumentsFilters({
|
|||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
>
|
||||
{/* Mobile: icon with count */}
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="h-9 gap-1.5 px-2.5 md:hidden"
|
||||
>
|
||||
<Button variant="destructive" size="sm" className="h-9 gap-1.5 px-2.5 md:hidden">
|
||||
<Trash size={14} />
|
||||
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-destructive-foreground/20 text-[10px] font-medium">
|
||||
{selectedIds.size}
|
||||
</span>
|
||||
</Button>
|
||||
{/* Desktop: full button */}
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="h-9 gap-2 hidden md:flex"
|
||||
>
|
||||
<Button variant="destructive" size="sm" className="h-9 gap-2 hidden md:flex">
|
||||
<Trash size={14} />
|
||||
Delete
|
||||
<span className="flex h-5 w-5 items-center justify-center rounded-full bg-destructive-foreground/20 text-[10px] font-medium">
|
||||
|
|
@ -288,9 +279,12 @@ export function DocumentsFilters({
|
|||
<CircleAlert size={18} strokeWidth={2} />
|
||||
</div>
|
||||
<AlertDialogHeader className="flex-1">
|
||||
<AlertDialogTitle>Delete {selectedIds.size} document{selectedIds.size !== 1 ? "s" : ""}?</AlertDialogTitle>
|
||||
<AlertDialogTitle>
|
||||
Delete {selectedIds.size} document{selectedIds.size !== 1 ? "s" : ""}?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the selected {selectedIds.size === 1 ? "document" : "documents"} from your search space.
|
||||
This action cannot be undone. This will permanently delete the selected{" "}
|
||||
{selectedIds.size === 1 ? "document" : "documents"} from your search space.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,20 @@
|
|||
"use client";
|
||||
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { AlertCircle, Calendar, CheckCircle2, ChevronDown, ChevronUp, Clock, FileText, FileX, Loader2, Network, Plus, User } from "lucide-react";
|
||||
import {
|
||||
AlertCircle,
|
||||
Calendar,
|
||||
CheckCircle2,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Clock,
|
||||
FileText,
|
||||
FileX,
|
||||
Loader2,
|
||||
Network,
|
||||
Plus,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
import { motion } from "motion/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import React, { useRef, useState, useEffect, useCallback } from "react";
|
||||
|
|
@ -10,12 +23,7 @@ import { JsonMetadataViewer } from "@/components/json-metadata-viewer";
|
|||
import { MarkdownViewer } from "@/components/markdown-viewer";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import {
|
||||
|
|
@ -35,7 +43,7 @@ import type { ColumnVisibility, Document, DocumentStatus } from "./types";
|
|||
// Status indicator component for document processing status
|
||||
function StatusIndicator({ status }: { status?: DocumentStatus }) {
|
||||
const state = status?.state ?? "ready";
|
||||
|
||||
|
||||
switch (state) {
|
||||
case "pending":
|
||||
return (
|
||||
|
|
@ -176,12 +184,10 @@ function SortableHeader({
|
|||
>
|
||||
{icon && <span className="opacity-60">{icon}</span>}
|
||||
{children}
|
||||
<span className={`transition-opacity ${isActive ? "opacity-100" : "opacity-0 group-hover:opacity-50"}`}>
|
||||
{isActive && sortDesc ? (
|
||||
<ChevronDown size={14} />
|
||||
) : (
|
||||
<ChevronUp size={14} />
|
||||
)}
|
||||
<span
|
||||
className={`transition-opacity ${isActive ? "opacity-100" : "opacity-0 group-hover:opacity-50"}`}
|
||||
>
|
||||
{isActive && sortDesc ? <ChevronDown size={14} /> : <ChevronUp size={14} />}
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
|
|
@ -300,8 +306,10 @@ export function DocumentsTableShell({
|
|||
|
||||
// Only consider selectable documents for "select all" logic
|
||||
const selectableDocs = sorted.filter(isSelectable);
|
||||
const allSelectedOnPage = selectableDocs.length > 0 && selectableDocs.every((d) => selectedIds.has(d.id));
|
||||
const someSelectedOnPage = selectableDocs.some((d) => selectedIds.has(d.id)) && !allSelectedOnPage;
|
||||
const allSelectedOnPage =
|
||||
selectableDocs.length > 0 && selectableDocs.every((d) => selectedIds.has(d.id));
|
||||
const someSelectedOnPage =
|
||||
selectableDocs.some((d) => selectedIds.has(d.id)) && !allSelectedOnPage;
|
||||
|
||||
const toggleAll = (checked: boolean) => {
|
||||
const next = new Set(selectedIds);
|
||||
|
|
@ -388,10 +396,7 @@ export function DocumentsTableShell({
|
|||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="w-[35%] py-2.5 max-w-0 border-r border-border/40">
|
||||
<Skeleton
|
||||
className="h-4"
|
||||
style={{ width: `${widthPercent}%` }}
|
||||
/>
|
||||
<Skeleton className="h-4" style={{ width: `${widthPercent}%` }} />
|
||||
</TableCell>
|
||||
{columnVisibility.document_type && (
|
||||
<TableCell className="w-[20%] min-w-[120px] max-w-[200px] py-2.5 border-r border-border/40 overflow-hidden">
|
||||
|
|
@ -429,24 +434,15 @@ export function DocumentsTableShell({
|
|||
<div className="flex items-start gap-3">
|
||||
<Skeleton className="h-4 w-4 mt-0.5 rounded" />
|
||||
<div className="flex-1 min-w-0 space-y-2">
|
||||
<Skeleton
|
||||
className="h-4"
|
||||
style={{ width: `${widthPercent}%` }}
|
||||
/>
|
||||
<Skeleton className="h-4" style={{ width: `${widthPercent}%` }} />
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Skeleton className="h-5 w-20 rounded" />
|
||||
{columnVisibility.created_by && (
|
||||
<Skeleton className="h-3 w-14" />
|
||||
)}
|
||||
{columnVisibility.created_at && (
|
||||
<Skeleton className="h-3 w-20" />
|
||||
)}
|
||||
{columnVisibility.created_by && <Skeleton className="h-3 w-14" />}
|
||||
{columnVisibility.created_at && <Skeleton className="h-3 w-20" />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{columnVisibility.status && (
|
||||
<Skeleton className="h-5 w-5 rounded-full" />
|
||||
)}
|
||||
{columnVisibility.status && <Skeleton className="h-5 w-5 rounded-full" />}
|
||||
<Skeleton className="h-7 w-7 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -549,9 +545,7 @@ export function DocumentsTableShell({
|
|||
)}
|
||||
{columnVisibility.status && (
|
||||
<TableHead className="w-20 text-center">
|
||||
<span className="text-sm font-medium text-muted-foreground/70">
|
||||
Status
|
||||
</span>
|
||||
<span className="text-sm font-medium text-muted-foreground/70">Status</span>
|
||||
</TableHead>
|
||||
)}
|
||||
<TableHead className="w-10">
|
||||
|
|
@ -580,9 +574,7 @@ export function DocumentsTableShell({
|
|||
},
|
||||
}}
|
||||
className={`border-b border-border/40 transition-colors ${
|
||||
isSelected
|
||||
? "bg-primary/5 hover:bg-primary/8"
|
||||
: "hover:bg-muted/30"
|
||||
isSelected ? "bg-primary/5 hover:bg-primary/8" : "hover:bg-muted/30"
|
||||
}`}
|
||||
>
|
||||
<TableCell className="w-8 px-0 py-2.5 text-center">
|
||||
|
|
@ -591,7 +583,9 @@ export function DocumentsTableShell({
|
|||
checked={isSelected}
|
||||
onCheckedChange={(v) => canSelect && toggleOne(doc.id, !!v)}
|
||||
disabled={!canSelect}
|
||||
aria-label={canSelect ? "Select row" : "Cannot select while processing"}
|
||||
aria-label={
|
||||
canSelect ? "Select row" : "Cannot select while processing"
|
||||
}
|
||||
className={`border-foreground data-[state=checked]:bg-primary data-[state=checked]:border-primary ${!canSelect ? "opacity-40 cursor-not-allowed" : ""}`}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -639,7 +633,9 @@ export function DocumentsTableShell({
|
|||
<TableCell className="w-32 py-2.5 text-sm text-foreground border-r border-border/40">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="cursor-default">{formatRelativeDate(doc.created_at)}</span>
|
||||
<span className="cursor-default">
|
||||
{formatRelativeDate(doc.created_at)}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
{formatAbsoluteDate(doc.created_at)}
|
||||
|
|
@ -720,9 +716,7 @@ export function DocumentsTableShell({
|
|||
<div className="flex flex-wrap items-center gap-2">
|
||||
<DocumentTypeChip type={doc.document_type} />
|
||||
{columnVisibility.created_by && doc.created_by_name && (
|
||||
<span className="text-xs text-foreground">
|
||||
{doc.created_by_name}
|
||||
</span>
|
||||
<span className="text-xs text-foreground">{doc.created_by_name}</span>
|
||||
)}
|
||||
{columnVisibility.created_at && (
|
||||
<Tooltip>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ export function RowActions({
|
|||
);
|
||||
|
||||
// Documents in "pending" or "processing" state should show disabled delete
|
||||
const isBeingProcessed = document.status?.state === "pending" || document.status?.state === "processing";
|
||||
const isBeingProcessed =
|
||||
document.status?.state === "pending" || document.status?.state === "processing";
|
||||
|
||||
// SURFSENSE_DOCS are system-managed and should not show delete at all
|
||||
const shouldShowDelete = !NON_DELETABLE_DOCUMENT_TYPES.includes(
|
||||
|
|
@ -67,8 +68,9 @@ export function RowActions({
|
|||
} catch (error: unknown) {
|
||||
console.error("Error deleting document:", error);
|
||||
// Check for 409 Conflict (document started processing after UI loaded)
|
||||
const status = (error as { response?: { status?: number } })?.response?.status
|
||||
?? (error as { status?: number })?.status;
|
||||
const status =
|
||||
(error as { response?: { status?: number } })?.response?.status ??
|
||||
(error as { status?: number })?.status;
|
||||
if (status === 409) {
|
||||
toast.error("Document is now being processed. Please try again later.");
|
||||
} else {
|
||||
|
|
@ -92,7 +94,11 @@ export function RowActions({
|
|||
// Editable documents: show 3-dot dropdown with edit + delete
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted/80">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 text-muted-foreground hover:text-foreground hover:bg-muted/80"
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">Open menu</span>
|
||||
</Button>
|
||||
|
|
@ -101,7 +107,9 @@ export function RowActions({
|
|||
<DropdownMenuItem
|
||||
onClick={() => !isEditDisabled && handleEdit()}
|
||||
disabled={isEditDisabled}
|
||||
className={isEditDisabled ? "text-muted-foreground cursor-not-allowed opacity-50" : ""}
|
||||
className={
|
||||
isEditDisabled ? "text-muted-foreground cursor-not-allowed opacity-50" : ""
|
||||
}
|
||||
>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
<span>Edit</span>
|
||||
|
|
@ -110,7 +118,11 @@ export function RowActions({
|
|||
<DropdownMenuItem
|
||||
onClick={() => !isDeleteDisabled && setIsDeleteOpen(true)}
|
||||
disabled={isDeleteDisabled}
|
||||
className={isDeleteDisabled ? "text-muted-foreground cursor-not-allowed opacity-50" : "text-destructive focus:text-destructive"}
|
||||
className={
|
||||
isDeleteDisabled
|
||||
? "text-muted-foreground cursor-not-allowed opacity-50"
|
||||
: "text-destructive focus:text-destructive"
|
||||
}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
<span>Delete</span>
|
||||
|
|
@ -150,7 +162,9 @@ export function RowActions({
|
|||
<DropdownMenuItem
|
||||
onClick={() => !isEditDisabled && handleEdit()}
|
||||
disabled={isEditDisabled}
|
||||
className={isEditDisabled ? "text-muted-foreground cursor-not-allowed opacity-50" : ""}
|
||||
className={
|
||||
isEditDisabled ? "text-muted-foreground cursor-not-allowed opacity-50" : ""
|
||||
}
|
||||
>
|
||||
<Pencil className="mr-2 h-4 w-4" />
|
||||
<span>Edit</span>
|
||||
|
|
@ -159,7 +173,11 @@ export function RowActions({
|
|||
<DropdownMenuItem
|
||||
onClick={() => !isDeleteDisabled && setIsDeleteOpen(true)}
|
||||
disabled={isDeleteDisabled}
|
||||
className={isDeleteDisabled ? "text-muted-foreground cursor-not-allowed opacity-50" : "text-destructive focus:text-destructive"}
|
||||
className={
|
||||
isDeleteDisabled
|
||||
? "text-muted-foreground cursor-not-allowed opacity-50"
|
||||
: "text-destructive focus:text-destructive"
|
||||
}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
<span>Delete</span>
|
||||
|
|
|
|||
|
|
@ -116,13 +116,15 @@ export default function DocumentsTable() {
|
|||
created_by_id: item.created_by_id ?? null,
|
||||
created_by_name: item.created_by_name ?? null,
|
||||
created_at: item.created_at,
|
||||
status: (item as { status?: { state: "ready" | "pending" | "processing" | "failed"; reason?: string } }).status ?? { state: "ready" as const },
|
||||
status: (
|
||||
item as {
|
||||
status?: { state: "ready" | "pending" | "processing" | "failed"; reason?: string };
|
||||
}
|
||||
).status ?? { state: "ready" as const },
|
||||
}))
|
||||
: paginatedRealtimeDocuments;
|
||||
|
||||
const displayTotal = isSearchMode
|
||||
? searchResponse?.total || 0
|
||||
: sortedRealtimeDocuments.length;
|
||||
const displayTotal = isSearchMode ? searchResponse?.total || 0 : sortedRealtimeDocuments.length;
|
||||
|
||||
const loading = isSearchMode ? isSearchLoading : realtimeLoading;
|
||||
const error = isSearchMode ? searchError : realtimeError;
|
||||
|
|
@ -149,13 +151,13 @@ export default function DocumentsTable() {
|
|||
// Filter out pending/processing documents - they cannot be deleted
|
||||
// For real-time mode, use sortedRealtimeDocuments (which has status)
|
||||
// For search mode, use searchResponse items (need to safely access status)
|
||||
const allDocs = isSearchMode
|
||||
? (searchResponse?.items || []).map(item => ({
|
||||
id: item.id,
|
||||
status: (item as { status?: { state: string } }).status,
|
||||
}))
|
||||
: sortedRealtimeDocuments.map(doc => ({ id: doc.id, status: doc.status }));
|
||||
|
||||
const allDocs = isSearchMode
|
||||
? (searchResponse?.items || []).map((item) => ({
|
||||
id: item.id,
|
||||
status: (item as { status?: { state: string } }).status,
|
||||
}))
|
||||
: sortedRealtimeDocuments.map((doc) => ({ id: doc.id, status: doc.status }));
|
||||
|
||||
const selectedDocs = allDocs.filter((doc) => selectedIds.has(doc.id));
|
||||
const deletableIds = selectedDocs
|
||||
.filter((doc) => doc.status?.state !== "pending" && doc.status?.state !== "processing")
|
||||
|
|
@ -163,7 +165,9 @@ export default function DocumentsTable() {
|
|||
const inProgressCount = selectedIds.size - deletableIds.length;
|
||||
|
||||
if (inProgressCount > 0) {
|
||||
toast.warning(`${inProgressCount} document(s) are pending or processing and cannot be deleted.`);
|
||||
toast.warning(
|
||||
`${inProgressCount} document(s) are pending or processing and cannot be deleted.`
|
||||
);
|
||||
}
|
||||
|
||||
if (deletableIds.length === 0) {
|
||||
|
|
@ -180,8 +184,9 @@ export default function DocumentsTable() {
|
|||
await deleteDocumentMutation({ id });
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
const status = (error as { response?: { status?: number } })?.response?.status
|
||||
?? (error as { status?: number })?.status;
|
||||
const status =
|
||||
(error as { response?: { status?: number } })?.response?.status ??
|
||||
(error as { status?: number })?.status;
|
||||
if (status === 409) conflictCount++;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -195,13 +200,13 @@ export default function DocumentsTable() {
|
|||
} else {
|
||||
toast.error(t("delete_partial_failed"));
|
||||
}
|
||||
|
||||
|
||||
// If in search mode, refetch search results to reflect deletion
|
||||
if (isSearchMode) {
|
||||
await refetchSearch();
|
||||
}
|
||||
// Real-time mode: Electric will sync the deletion automatically
|
||||
|
||||
|
||||
setSelectedIds(new Set());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
@ -210,21 +215,24 @@ export default function DocumentsTable() {
|
|||
};
|
||||
|
||||
// Single document delete handler for RowActions
|
||||
const handleDeleteDocument = useCallback(async (id: number): Promise<boolean> => {
|
||||
try {
|
||||
await deleteDocumentMutation({ id });
|
||||
toast.success(t("delete_success") || "Document deleted");
|
||||
// If in search mode, refetch search results to reflect deletion
|
||||
if (isSearchMode) {
|
||||
await refetchSearch();
|
||||
const handleDeleteDocument = useCallback(
|
||||
async (id: number): Promise<boolean> => {
|
||||
try {
|
||||
await deleteDocumentMutation({ id });
|
||||
toast.success(t("delete_success") || "Document deleted");
|
||||
// If in search mode, refetch search results to reflect deletion
|
||||
if (isSearchMode) {
|
||||
await refetchSearch();
|
||||
}
|
||||
// Real-time mode: Electric will sync the deletion automatically
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Error deleting document:", e);
|
||||
return false;
|
||||
}
|
||||
// Real-time mode: Electric will sync the deletion automatically
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("Error deleting document:", e);
|
||||
return false;
|
||||
}
|
||||
}, [deleteDocumentMutation, isSearchMode, refetchSearch, t]);
|
||||
},
|
||||
[deleteDocumentMutation, isSearchMode, refetchSearch, t]
|
||||
);
|
||||
|
||||
const handleSortChange = useCallback((key: SortKey) => {
|
||||
setSortKey((currentKey) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue