Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/features/collections/components/CollectionTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ const CollectionTab: React.FC<CollectionTabProps> = ({
const [jsonEditData, setJsonEditData] = useState('');
const [jsonHasChanges, setJsonHasChanges] = useState(false);
const [createDialogOpen, setCreateDialogOpen] = useState(false);
const [duplicateDialogOpen, setDuplicateDialogOpen] = useState(false);
const [newDocId, setNewDocId] = useState('');
const [newDocData, setNewDocData] = useState('{}');
const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
Expand Down Expand Up @@ -703,6 +704,45 @@ const CollectionTab: React.FC<CollectionTabProps> = ({
}
}, [newDocId, newDocData, dispatch, project, collectionPath, firestoreDatabaseId, showMessage, showError]);

// Duplicate the single selected table document using the create flow.
const handleDuplicateDocument = useCallback(() => {
if (selectedRows.length !== 1) return;
const source = documents.find((doc) => doc.id === selectedRows[0]);
if (!source) return;

const existingIds = new Set(documents.map((doc) => doc.id));
const baseId = `${source.id}_copy`;
let duplicateId = baseId;
let suffix = 2;
while (existingIds.has(duplicateId)) duplicateId = `${baseId}_${suffix++}`;

setNewDocId(duplicateId);
setNewDocData(JSON.stringify(source.data, null, 2));
setDuplicateDialogOpen(true);
}, [documents, selectedRows]);

const handleDuplicateCreate = useCallback(async () => {
try {
JSON.parse(newDocData);
const createdId = await dispatch(
addDocument({
project,
collection: collectionPath,
docId: newDocId,
docData: newDocData,
firestoreDatabaseId,
}),
).unwrap();
setDuplicateDialogOpen(false);
setNewDocId('');
setNewDocData('{}');
setSelectedRows([]);
showMessage?.(`Duplicated document as ${createdId || newDocId}`, 'success');
} catch (error: unknown) {
showError(error);
}
}, [newDocData, newDocId, dispatch, project, collectionPath, firestoreDatabaseId, showMessage, showError]);

// Delete Selected Documents Handler
const handleDeleteSelected = useCallback(async () => {
if (selectedRows.length === 0) return;
Expand Down Expand Up @@ -923,6 +963,7 @@ const CollectionTab: React.FC<CollectionTabProps> = ({
onImport={handleImportDocuments}
onAdd={() => setCreateDialogOpen(true)}
onDelete={() => setDeleteDialogOpen(true)}
onDuplicate={handleDuplicateDocument}
allFields={allFields}
visibleFields={visibleFields}
hiddenColumns={hiddenColumns}
Expand Down Expand Up @@ -1009,6 +1050,19 @@ const CollectionTab: React.FC<CollectionTabProps> = ({
onCreate={handleCreateDocument}
/>

{/* Duplicate Document Dialog */}
<CreateDocumentDialog
open={duplicateDialogOpen}
onClose={() => setDuplicateDialogOpen(false)}
newDocId={newDocId}
setNewDocId={setNewDocId}
newDocData={newDocData}
setNewDocData={setNewDocData}
onCreate={handleDuplicateCreate}
title="Duplicate Document"
actionLabel="Duplicate"
/>

{/* Add Field Dialog (Tree view) */}
<AddFieldDialog
open={Boolean(addFieldTarget)}
Expand Down
6 changes: 6 additions & 0 deletions src/features/collections/components/CreateDocumentDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ interface CreateDocumentDialogProps {
newDocData: string;
setNewDocData: (data: string) => void;
onCreate: () => void;
title?: string;
actionLabel?: string;
}

const CreateDocumentDialog: React.FC<CreateDocumentDialogProps> = ({
Expand All @@ -19,10 +21,13 @@ const CreateDocumentDialog: React.FC<CreateDocumentDialogProps> = ({
newDocData,
setNewDocData,
onCreate,
title = 'Create Document',
actionLabel = 'Create',
}) => {
return (
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
<DialogTitle>Create Document</DialogTitle>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<TextField
fullWidth
Expand All @@ -46,6 +51,7 @@ const CreateDocumentDialog: React.FC<CreateDocumentDialogProps> = ({
<Button onClick={onClose}>Cancel</Button>
<Button variant="contained" onClick={onCreate}>
Create
{actionLabel}
</Button>
</DialogActions>
</Dialog>
Expand Down
10 changes: 10 additions & 0 deletions src/features/collections/components/ViewTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
FileUpload as ImportIcon,
ViewColumn as ColumnsIcon,
Delete as DeleteIcon,
ContentCopy as DuplicateIcon,
} from '@mui/icons-material';

interface ViewTabsProps {
Expand All @@ -21,6 +22,7 @@ interface ViewTabsProps {
onImport: () => void;
onAdd: () => void;
onDelete: () => void;
onDuplicate: () => void;
allFields: string[];
visibleFields: string[];
hiddenColumns: Record<string, boolean>;
Expand All @@ -37,6 +39,7 @@ const ViewTabs: React.FC<ViewTabsProps> = ({
onImport,
onAdd,
onDelete,
onDuplicate,
allFields,
visibleFields,
hiddenColumns,
Expand Down Expand Up @@ -167,6 +170,13 @@ const ViewTabs: React.FC<ViewTabsProps> = ({
</Button>
</Tooltip>
)}
{selectedRowsCount === 1 && (
<Tooltip title="Duplicate selected document">
<IconButton size="small" onClick={onDuplicate}>
<DuplicateIcon sx={{ fontSize: 18 }} />
</IconButton>
</Tooltip>
)}
<Tooltip title="Refresh">
<IconButton size="small" onClick={onRefresh}>
<RefreshIcon sx={{ fontSize: 18 }} />
Expand Down