mirror of
https://github.com/willchen96/mike.git
synced 2026-06-20 21:18:07 +02:00
Update document UI, tabular reviews, and storage caching
This commit is contained in:
parent
2bbb628891
commit
4f3384334a
26 changed files with 856 additions and 341 deletions
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { forwardRef, useImperativeHandle, useRef } from "react";
|
||||
import { Plus, Table2 } from "lucide-react";
|
||||
import { Loader2, Plus, Table2, Upload } from "lucide-react";
|
||||
import type { ColumnConfig, MikeDocument, TabularCell } from "../shared/types";
|
||||
import { TabularCell as TabularCellComponent } from "./TabularCell";
|
||||
import { TREditColumnMenu } from "./TREditColumnMenu";
|
||||
|
|
@ -30,6 +30,8 @@ interface Props {
|
|||
savingColumn: boolean;
|
||||
savingColumnsConfig: boolean;
|
||||
selectedDocIds: string[];
|
||||
uploadingFilenames?: string[];
|
||||
dragOverFiles?: boolean;
|
||||
highlightedCell?: { colIdx: number; rowIdx: number } | null;
|
||||
onSelectionChange: (ids: string[]) => void;
|
||||
onExpand: (cell: TabularCell) => void;
|
||||
|
|
@ -49,6 +51,8 @@ export const TRTable = forwardRef<TRTableHandle, Props>(function TRTable(
|
|||
savingColumn,
|
||||
savingColumnsConfig,
|
||||
selectedDocIds,
|
||||
uploadingFilenames = [],
|
||||
dragOverFiles = false,
|
||||
highlightedCell,
|
||||
onSelectionChange,
|
||||
onExpand,
|
||||
|
|
@ -165,7 +169,11 @@ export const TRTable = forwardRef<TRTableHandle, Props>(function TRTable(
|
|||
);
|
||||
}
|
||||
|
||||
if (columns.length === 0 && documents.length === 0) {
|
||||
if (
|
||||
columns.length === 0 &&
|
||||
documents.length === 0 &&
|
||||
uploadingFilenames.length === 0
|
||||
) {
|
||||
return (
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<div className="flex items-center border-b border-gray-200">
|
||||
|
|
@ -177,28 +185,33 @@ export const TRTable = forwardRef<TRTableHandle, Props>(function TRTable(
|
|||
</div>
|
||||
<div className="flex-1" />
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col items-start justify-center w-full max-w-xs mx-auto">
|
||||
<Table2 className="h-8 w-8 text-gray-300 mb-4" />
|
||||
<p className="text-2xl font-medium font-serif text-gray-900">
|
||||
Tabular Review
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-gray-400 text-left">
|
||||
Add columns and documents to get started.
|
||||
</p>
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
<button
|
||||
onClick={onAddColumn}
|
||||
className="inline-flex items-center gap-1 rounded-full bg-gray-900 px-3 py-1 text-xs font-medium text-white transition-colors hover:bg-gray-700 shadow-md"
|
||||
>
|
||||
+ Add Columns
|
||||
</button>
|
||||
<button
|
||||
onClick={onAddDocuments}
|
||||
className="inline-flex items-center gap-1.5 rounded-full border border-gray-200 bg-white px-3 py-1 text-xs font-medium text-gray-600 hover:bg-gray-50 transition-colors shadow-sm"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Add Documents
|
||||
</button>
|
||||
<div className="relative flex min-h-0 flex-1">
|
||||
{dragOverFiles && (
|
||||
<div className="absolute inset-0 z-[90] border-2 border-blue-400 bg-blue-50/40 pointer-events-none" />
|
||||
)}
|
||||
<div className="flex flex-1 flex-col items-start justify-center w-full max-w-xs mx-auto">
|
||||
<Table2 className="h-8 w-8 text-gray-300 mb-4" />
|
||||
<p className="text-2xl font-medium font-serif text-gray-900">
|
||||
Tabular Review
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-gray-400 text-left">
|
||||
Add columns and documents to get started.
|
||||
</p>
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
<button
|
||||
onClick={onAddColumn}
|
||||
className="inline-flex items-center gap-1 rounded-full bg-gray-900 px-3 py-1 text-xs font-medium text-white transition-colors hover:bg-gray-700 shadow-md"
|
||||
>
|
||||
+ Add Columns
|
||||
</button>
|
||||
<button
|
||||
onClick={onAddDocuments}
|
||||
className="inline-flex items-center gap-1.5 rounded-full border border-gray-200 bg-white px-3 py-1 text-xs font-medium text-gray-600 hover:bg-gray-50 transition-colors shadow-sm"
|
||||
>
|
||||
<Upload className="h-3.5 w-3.5" />
|
||||
Add Documents
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -206,7 +219,10 @@ export const TRTable = forwardRef<TRTableHandle, Props>(function TRTable(
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-auto" ref={scrollContainerRef}>
|
||||
<div
|
||||
className="flex flex-1 flex-col overflow-auto"
|
||||
ref={scrollContainerRef}
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
className="sticky top-0 z-20 flex bg-white h-8"
|
||||
|
|
@ -258,69 +274,114 @@ export const TRTable = forwardRef<TRTableHandle, Props>(function TRTable(
|
|||
</div>
|
||||
|
||||
{/* Rows */}
|
||||
{documents.map((doc, docIdx) => {
|
||||
const rowBg = selectedDocIds.includes(doc.id)
|
||||
? "bg-gray-100"
|
||||
: docIdx % 2 === 0
|
||||
? "bg-white"
|
||||
: "bg-gray-50";
|
||||
return (
|
||||
<div className="relative min-h-0 flex-1">
|
||||
{dragOverFiles && (
|
||||
<div className="absolute inset-0 z-[90] border-2 border-blue-400 bg-blue-50/40 pointer-events-none" />
|
||||
)}
|
||||
{uploadingFilenames.map((filename) => (
|
||||
<div
|
||||
key={doc.id}
|
||||
className={`flex ${rowBg}`}
|
||||
key={`uploading-${filename}`}
|
||||
className="flex bg-white"
|
||||
style={{ minWidth: totalContentWidth }}
|
||||
>
|
||||
<div
|
||||
className={`sticky left-0 z-[60] ${CHECK_W} border-b border-r border-gray-200 p-2 flex items-center justify-center ${rowBg}`}
|
||||
className={`sticky left-0 z-[60] ${CHECK_W} border-b border-r border-gray-200 p-2 flex items-center justify-center bg-white`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedDocIds.includes(doc.id)}
|
||||
onChange={() => toggleDoc(doc.id)}
|
||||
className="h-2.5 w-2.5 shrink-0 rounded border-gray-200 cursor-pointer accent-black"
|
||||
disabled
|
||||
className="h-2.5 w-2.5 shrink-0 rounded border-gray-200 cursor-default accent-black disabled:opacity-100"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`sticky left-8 z-[60] ${COL_W} border-b border-r border-gray-200 p-2 text-xs text-gray-800 flex items-center ${rowBg}`}
|
||||
className={`sticky left-8 z-[60] ${COL_W} border-b border-r border-gray-200 p-2 text-xs text-gray-400 flex items-center gap-2 bg-white`}
|
||||
>
|
||||
<span className="line-clamp-1" title={doc.filename}>
|
||||
{doc.filename}
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin shrink-0" />
|
||||
<span className="line-clamp-1" title={filename}>
|
||||
{filename}
|
||||
</span>
|
||||
</div>
|
||||
{columns.map((col) => {
|
||||
const cell = getCell(doc.id, col.index);
|
||||
const colPos = sortedColumns.findIndex(
|
||||
(c) => c.index === col.index,
|
||||
);
|
||||
const isHighlighted =
|
||||
highlightedCell?.colIdx === colPos &&
|
||||
highlightedCell?.rowIdx === docIdx;
|
||||
return (
|
||||
<div
|
||||
key={col.index}
|
||||
className={`${COL_W} border-b border-r border-gray-200 transition-colors ${isHighlighted ? "bg-blue-200" : ""}`}
|
||||
>
|
||||
{cell && (
|
||||
<TabularCellComponent
|
||||
cell={cell}
|
||||
column={col}
|
||||
onExpand={() => onExpand(cell)}
|
||||
onCitationClick={(page, quote) =>
|
||||
onCitationClick(
|
||||
cell,
|
||||
page,
|
||||
quote,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{sortedColumns.map((col) => (
|
||||
<div
|
||||
key={col.index}
|
||||
className={`${COL_W} border-b border-r border-gray-200 p-2`}
|
||||
>
|
||||
<div className="h-4 w-20 rounded bg-gray-100 animate-pulse" />
|
||||
</div>
|
||||
))}
|
||||
<div className="flex-1 border-b border-gray-200 min-h-8 min-w-8" />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
))}
|
||||
{documents.map((doc, docIdx) => {
|
||||
const baseRowBg =
|
||||
docIdx % 2 === 0 ? "bg-white" : "bg-gray-50";
|
||||
const rowBg = selectedDocIds.includes(doc.id)
|
||||
? "bg-gray-100"
|
||||
: baseRowBg;
|
||||
return (
|
||||
<div
|
||||
key={doc.id}
|
||||
className={`flex ${rowBg}`}
|
||||
style={{ minWidth: totalContentWidth }}
|
||||
>
|
||||
<div
|
||||
className={`sticky left-0 z-[60] ${CHECK_W} border-b border-r border-gray-200 p-2 flex items-center justify-center ${rowBg}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedDocIds.includes(doc.id)}
|
||||
onChange={() => toggleDoc(doc.id)}
|
||||
className="h-2.5 w-2.5 shrink-0 rounded border-gray-200 cursor-pointer accent-black"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`sticky left-8 z-[60] ${COL_W} border-b border-r border-gray-200 p-2 text-xs text-gray-800 flex items-center ${baseRowBg}`}
|
||||
>
|
||||
<span
|
||||
className="line-clamp-1"
|
||||
title={doc.filename}
|
||||
>
|
||||
{doc.filename}
|
||||
</span>
|
||||
</div>
|
||||
{columns.map((col) => {
|
||||
const cell = getCell(doc.id, col.index);
|
||||
const colPos = sortedColumns.findIndex(
|
||||
(c) => c.index === col.index,
|
||||
);
|
||||
const isHighlighted =
|
||||
highlightedCell?.colIdx === colPos &&
|
||||
highlightedCell?.rowIdx === docIdx;
|
||||
return (
|
||||
<div
|
||||
key={col.index}
|
||||
className={`${COL_W} border-b border-r border-gray-200 transition-colors ${isHighlighted ? "bg-blue-200" : ""}`}
|
||||
>
|
||||
{cell && (
|
||||
<TabularCellComponent
|
||||
cell={cell}
|
||||
column={col}
|
||||
onExpand={() => onExpand(cell)}
|
||||
onCitationClick={(
|
||||
page,
|
||||
quote,
|
||||
) =>
|
||||
onCitationClick(
|
||||
cell,
|
||||
page,
|
||||
quote,
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="flex-1 border-b border-gray-200 min-h-8 min-w-8" />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { Plus, Loader2, Play, ChevronDown, MessageSquare, Download, Users } from "lucide-react";
|
||||
import { Plus, Loader2, Play, ChevronDown, MessageSquare, Download, Users, Upload } from "lucide-react";
|
||||
import { HeaderSearchBtn } from "../shared/HeaderSearchBtn";
|
||||
|
||||
import {
|
||||
|
|
@ -13,6 +13,7 @@ import {
|
|||
regenerateTabularCell,
|
||||
streamTabularGeneration,
|
||||
updateTabularReview,
|
||||
uploadReviewDocument,
|
||||
} from "@/app/lib/mikeApi";
|
||||
import type {
|
||||
ColumnConfig,
|
||||
|
|
@ -70,6 +71,10 @@ export function TRView({ reviewId, projectId }: Props) {
|
|||
const [selectedDocIds, setSelectedDocIds] = useState<string[]>([]);
|
||||
const [actionsOpen, setActionsOpen] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [dragOverReviewFiles, setDragOverReviewFiles] = useState(false);
|
||||
const [uploadingDroppedFilenames, setUploadingDroppedFilenames] = useState<
|
||||
string[]
|
||||
>([]);
|
||||
const searchParams = useSearchParams();
|
||||
const initialChatParamRef = useRef<string | null>(
|
||||
searchParams.get("chat"),
|
||||
|
|
@ -188,6 +193,33 @@ export function TRView({ reviewId, projectId }: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
function hasFilePayload(dt: DataTransfer): boolean {
|
||||
return Array.from(dt.types).includes("Files");
|
||||
}
|
||||
|
||||
async function handleDropReviewFiles(files: File[]) {
|
||||
if (files.length === 0) return;
|
||||
setUploadingDroppedFilenames(files.map((file) => file.name));
|
||||
try {
|
||||
const uploaded: MikeDocument[] = [];
|
||||
const documentIds = documents.map((document) => document.id);
|
||||
for (const file of files) {
|
||||
const document = await uploadReviewDocument(reviewId, file, {
|
||||
projectId,
|
||||
documentIds,
|
||||
columnsConfig: columns,
|
||||
});
|
||||
uploaded.push(document);
|
||||
documentIds.push(document.id);
|
||||
}
|
||||
await handleAddDocuments(uploaded);
|
||||
} catch (err) {
|
||||
console.error("Tabular review document drop upload failed", err);
|
||||
} finally {
|
||||
setUploadingDroppedFilenames([]);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleRegenerateCell(docId: string, colIndex: number) {
|
||||
if (apiKeys && !isModelAvailable(tabularModel, apiKeys)) {
|
||||
setApiKeyModalProvider(getModelProvider(tabularModel));
|
||||
|
|
@ -441,19 +473,30 @@ export function TRView({ reviewId, projectId }: Props) {
|
|||
}
|
||||
|
||||
async function handleDeleteDocuments() {
|
||||
const idsToDelete = [...selectedDocIds];
|
||||
if (idsToDelete.length === 0) return;
|
||||
const previousDocuments = documents;
|
||||
const previousCells = cells;
|
||||
const remaining = documents.filter(
|
||||
(d) => !selectedDocIds.includes(d.id),
|
||||
(d) => !idsToDelete.includes(d.id),
|
||||
);
|
||||
setDocuments(remaining);
|
||||
setCells((prev) =>
|
||||
prev.filter((c) => !selectedDocIds.includes(c.document_id)),
|
||||
prev.filter((c) => !idsToDelete.includes(c.document_id)),
|
||||
);
|
||||
setSelectedDocIds([]);
|
||||
setActionsOpen(false);
|
||||
await updateTabularReview(reviewId, {
|
||||
document_ids: remaining.map((d) => d.id),
|
||||
columns_config: columns,
|
||||
});
|
||||
try {
|
||||
await updateTabularReview(reviewId, {
|
||||
document_ids: remaining.map((d) => d.id),
|
||||
columns_config: columns,
|
||||
});
|
||||
} catch (err) {
|
||||
setDocuments(previousDocuments);
|
||||
setCells(previousCells);
|
||||
setSelectedDocIds(idsToDelete);
|
||||
console.error("Failed to delete tabular review documents", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleClearResults() {
|
||||
|
|
@ -486,7 +529,7 @@ export function TRView({ reviewId, projectId }: Props) {
|
|||
<div className="flex h-full overflow-hidden bg-white">
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="bg-white px-8 py-4 flex items-start justify-between shrink-0 gap-4">
|
||||
<div className="mb-1 bg-white px-4 py-3 md:px-10 flex items-start justify-between shrink-0 gap-4">
|
||||
<div className="flex items-center gap-1.5 text-2xl font-medium font-serif">
|
||||
{projectId && (
|
||||
<>
|
||||
|
|
@ -614,7 +657,7 @@ export function TRView({ reviewId, projectId }: Props) {
|
|||
</div>
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center h-10 px-8 border-b border-gray-200 gap-4">
|
||||
<div className="flex items-center h-10 px-4 md:px-10 border-b border-gray-200 gap-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
if (!chatOpen) setSidebarOpen(false);
|
||||
|
|
@ -631,8 +674,14 @@ export function TRView({ reviewId, projectId }: Props) {
|
|||
<MessageSquare className="h-3.5 w-3.5" />
|
||||
Assistant in Tabular Review
|
||||
</button>
|
||||
<div className="ml-auto flex items-center gap-4">
|
||||
{selectedDocIds.length > 0 && (
|
||||
<div className="ml-auto flex items-center gap-5">
|
||||
{loading ? (
|
||||
<>
|
||||
<div className="h-3 w-24 rounded bg-gray-100 animate-pulse" />
|
||||
<div className="h-3 w-20 rounded bg-gray-100 animate-pulse" />
|
||||
</>
|
||||
) : null}
|
||||
{!loading && selectedDocIds.length > 0 && (
|
||||
<div ref={actionsRef} className="relative">
|
||||
<button
|
||||
onClick={() => setActionsOpen((v) => !v)}
|
||||
|
|
@ -659,32 +708,34 @@ export function TRView({ reviewId, projectId }: Props) {
|
|||
)}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => setAddDocsOpen(true)}
|
||||
disabled={loading || savingColumnsConfig}
|
||||
className={`flex items-center gap-1 text-xs font-medium transition-colors ${
|
||||
loading || savingColumnsConfig
|
||||
? "text-gray-300 cursor-default"
|
||||
: "text-gray-700 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Add Documents
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setAddColOpen(true)}
|
||||
disabled={
|
||||
loading || savingColumn || savingColumnsConfig
|
||||
}
|
||||
className={`flex items-center gap-1 text-xs font-medium transition-colors ${
|
||||
loading || savingColumn || savingColumnsConfig
|
||||
? "text-gray-300 cursor-default"
|
||||
: "text-gray-700 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Add Columns
|
||||
</button>
|
||||
{!loading && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => setAddDocsOpen(true)}
|
||||
disabled={savingColumnsConfig}
|
||||
className={`flex items-center gap-1 text-xs font-medium transition-colors ${
|
||||
savingColumnsConfig
|
||||
? "text-gray-300 cursor-default"
|
||||
: "text-gray-700 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
<Upload className="h-3.5 w-3.5" />
|
||||
Add Documents
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setAddColOpen(true)}
|
||||
disabled={savingColumn || savingColumnsConfig}
|
||||
className={`flex items-center gap-1 text-xs font-medium transition-colors ${
|
||||
savingColumn || savingColumnsConfig
|
||||
? "text-gray-300 cursor-default"
|
||||
: "text-gray-700 hover:text-gray-900"
|
||||
}`}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" />
|
||||
Add Columns
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -706,30 +757,60 @@ export function TRView({ reviewId, projectId }: Props) {
|
|||
onChatIdChange={setSelectedChatId}
|
||||
/>
|
||||
)}
|
||||
<TRTable
|
||||
ref={tableRef}
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
documents={filteredDocuments}
|
||||
cells={cells}
|
||||
highlightedCell={highlightedCell}
|
||||
savingColumn={savingColumn}
|
||||
savingColumnsConfig={savingColumnsConfig}
|
||||
selectedDocIds={selectedDocIds}
|
||||
onSelectionChange={setSelectedDocIds}
|
||||
onExpand={(cell) => {
|
||||
setExpandedCell(cell);
|
||||
setExpandedCellCitation(undefined);
|
||||
<div
|
||||
className="relative flex flex-1 overflow-hidden"
|
||||
onDragOver={(e) => {
|
||||
if (!hasFilePayload(e.dataTransfer)) return;
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "copy";
|
||||
setDragOverReviewFiles(true);
|
||||
}}
|
||||
onCitationClick={(cell, page, quote) => {
|
||||
setExpandedCell(cell);
|
||||
setExpandedCellCitation({ quote, page });
|
||||
onDragLeave={(e) => {
|
||||
if (
|
||||
!e.currentTarget.contains(
|
||||
e.relatedTarget as Node,
|
||||
)
|
||||
) {
|
||||
setDragOverReviewFiles(false);
|
||||
}
|
||||
}}
|
||||
onUpdateColumn={handleUpdateColumn}
|
||||
onDeleteColumn={handleDeleteColumn}
|
||||
onAddColumn={() => setAddColOpen(true)}
|
||||
onAddDocuments={() => setAddDocsOpen(true)}
|
||||
/>
|
||||
onDrop={(e) => {
|
||||
if (!hasFilePayload(e.dataTransfer)) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragOverReviewFiles(false);
|
||||
void handleDropReviewFiles(
|
||||
Array.from(e.dataTransfer.files),
|
||||
);
|
||||
}}
|
||||
>
|
||||
<TRTable
|
||||
ref={tableRef}
|
||||
loading={loading}
|
||||
columns={columns}
|
||||
documents={filteredDocuments}
|
||||
cells={cells}
|
||||
highlightedCell={highlightedCell}
|
||||
savingColumn={savingColumn}
|
||||
savingColumnsConfig={savingColumnsConfig}
|
||||
selectedDocIds={selectedDocIds}
|
||||
uploadingFilenames={uploadingDroppedFilenames}
|
||||
dragOverFiles={dragOverReviewFiles}
|
||||
onSelectionChange={setSelectedDocIds}
|
||||
onExpand={(cell) => {
|
||||
setExpandedCell(cell);
|
||||
setExpandedCellCitation(undefined);
|
||||
}}
|
||||
onCitationClick={(cell, page, quote) => {
|
||||
setExpandedCell(cell);
|
||||
setExpandedCellCitation({ quote, page });
|
||||
}}
|
||||
onUpdateColumn={handleUpdateColumn}
|
||||
onDeleteColumn={handleDeleteColumn}
|
||||
onAddColumn={() => setAddColOpen(true)}
|
||||
onAddDocuments={() => setAddDocsOpen(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue