mirror of
https://github.com/coleam00/Archon.git
synced 2026-01-01 04:09:08 -05:00
- Update consistent Delete Confirmation Modal
This commit is contained in:
@@ -5,7 +5,7 @@ import { ToolTestingPanel } from './ToolTestingPanel';
|
||||
import { Button } from '../ui/Button';
|
||||
import { mcpClientService, MCPClient, MCPClientConfig } from '../../services/mcpClientService';
|
||||
import { useToast } from '../../contexts/ToastContext';
|
||||
import { DeleteConfirmModal } from '../../pages/ProjectPage';
|
||||
import { DeleteConfirmModal } from '../ui/DeleteConfirmModal';
|
||||
|
||||
// Client interface (keeping for backward compatibility)
|
||||
export interface Client {
|
||||
@@ -710,18 +710,31 @@ const EditClientDrawer: React.FC<EditClientDrawerProps> = ({ client, isOpen, onC
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (confirm(`Are you sure you want to delete "${client.name}"?`)) {
|
||||
try {
|
||||
await mcpClientService.deleteClient(client.id);
|
||||
onClose();
|
||||
// Trigger a reload of the clients list
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : 'Failed to delete client');
|
||||
}
|
||||
const handleDelete = () => {
|
||||
setClientToDelete(client);
|
||||
setShowDeleteConfirm(true);
|
||||
};
|
||||
|
||||
const confirmDeleteClient = async () => {
|
||||
if (!clientToDelete) return;
|
||||
|
||||
try {
|
||||
await mcpClientService.deleteClient(clientToDelete.id);
|
||||
onClose();
|
||||
// Trigger a reload of the clients list
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
setError(error instanceof Error ? error.message : 'Failed to delete client');
|
||||
} finally {
|
||||
setShowDeleteConfirm(false);
|
||||
setClientToDelete(null);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelDeleteClient = () => {
|
||||
setShowDeleteConfirm(false);
|
||||
setClientToDelete(null);
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
@@ -853,6 +866,16 @@ const EditClientDrawer: React.FC<EditClientDrawerProps> = ({ client, isOpen, onC
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{showDeleteConfirm && clientToDelete && (
|
||||
<DeleteConfirmModal
|
||||
itemName={clientToDelete.name}
|
||||
onConfirm={confirmDeleteClient}
|
||||
onCancel={cancelDeleteClient}
|
||||
type="client"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -14,6 +14,7 @@ import { MilkdownEditor } from './MilkdownEditor';
|
||||
import { VersionHistoryModal } from './VersionHistoryModal';
|
||||
import { PRPViewer } from '../prp';
|
||||
import { DocumentCard, NewDocumentCard } from './DocumentCard';
|
||||
import { DeleteConfirmModal } from '../ui/DeleteConfirmModal';
|
||||
|
||||
|
||||
|
||||
@@ -514,6 +515,10 @@ export const DocsTab = ({
|
||||
// Document state
|
||||
const [documents, setDocuments] = useState<ProjectDoc[]>([]);
|
||||
const [selectedDocument, setSelectedDocument] = useState<ProjectDoc | null>(null);
|
||||
|
||||
// Delete confirmation modal state
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [documentToDelete, setDocumentToDelete] = useState<{ id: string; title: string } | null>(null);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -575,7 +580,14 @@ export const DocsTab = ({
|
||||
document_type: doc.document_type || 'document'
|
||||
}));
|
||||
|
||||
setDocuments(projectDocuments);
|
||||
// Merge with existing documents, preserving any temporary documents
|
||||
setDocuments(prev => {
|
||||
// Keep any temporary documents (ones with temp- prefix)
|
||||
const tempDocs = prev.filter(doc => doc.id.startsWith('temp-'));
|
||||
|
||||
// Merge temporary docs with loaded docs
|
||||
return [...projectDocuments, ...tempDocs];
|
||||
});
|
||||
|
||||
// Auto-select first document if available and no document is currently selected
|
||||
if (projectDocuments.length > 0 && !selectedDocument) {
|
||||
@@ -598,6 +610,26 @@ export const DocsTab = ({
|
||||
const template = DOCUMENT_TEMPLATES[templateKey as keyof typeof DOCUMENT_TEMPLATES];
|
||||
if (!template) return;
|
||||
|
||||
// Create a temporary document for optimistic update
|
||||
const tempDocument: ProjectDoc = {
|
||||
id: `temp-${Date.now()}`,
|
||||
title: template.name,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
content: template.content,
|
||||
document_type: template.document_type
|
||||
};
|
||||
|
||||
// Optimistically add the document to the UI immediately
|
||||
console.log('[DocsTab] Adding temporary document:', tempDocument);
|
||||
setDocuments(prev => {
|
||||
const updated = [...prev, tempDocument];
|
||||
console.log('[DocsTab] Documents after optimistic add:', updated);
|
||||
return updated;
|
||||
});
|
||||
setSelectedDocument(tempDocument);
|
||||
setShowTemplateModal(false);
|
||||
|
||||
try {
|
||||
setIsSaving(true);
|
||||
|
||||
@@ -608,15 +640,22 @@ export const DocsTab = ({
|
||||
document_type: template.document_type
|
||||
});
|
||||
|
||||
// Add to documents list
|
||||
setDocuments(prev => [...prev, newDocument]);
|
||||
// Replace temporary document with the real one
|
||||
setDocuments(prev => prev.map(doc =>
|
||||
doc.id === tempDocument.id ? newDocument : doc
|
||||
));
|
||||
setSelectedDocument(newDocument);
|
||||
|
||||
console.log('Document created successfully via API:', newDocument);
|
||||
showToast('Document created successfully', 'success');
|
||||
setShowTemplateModal(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to create document:', error);
|
||||
|
||||
// Remove the temporary document on error
|
||||
setDocuments(prev => prev.filter(doc => doc.id !== tempDocument.id));
|
||||
setSelectedDocument(null);
|
||||
setShowTemplateModal(true); // Re-open the modal
|
||||
|
||||
showToast(
|
||||
error instanceof Error ? error.message : 'Failed to create document',
|
||||
'error'
|
||||
@@ -783,6 +822,34 @@ export const DocsTab = ({
|
||||
}
|
||||
};
|
||||
|
||||
// Delete confirmation handlers
|
||||
const confirmDeleteDocument = async () => {
|
||||
if (!documentToDelete || !project?.id) return;
|
||||
|
||||
try {
|
||||
// Call API to delete from database first
|
||||
await projectService.deleteDocument(project.id, documentToDelete.id);
|
||||
|
||||
// Then remove from local state
|
||||
setDocuments(prev => prev.filter(d => d.id !== documentToDelete.id));
|
||||
if (selectedDocument?.id === documentToDelete.id) {
|
||||
setSelectedDocument(documents.find(d => d.id !== documentToDelete.id) || null);
|
||||
}
|
||||
showToast('Document deleted', 'success');
|
||||
} catch (error) {
|
||||
console.error('Failed to delete document:', error);
|
||||
showToast('Failed to delete document', 'error');
|
||||
} finally {
|
||||
setShowDeleteConfirm(false);
|
||||
setDocumentToDelete(null);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelDeleteDocument = () => {
|
||||
setShowDeleteConfirm(false);
|
||||
setDocumentToDelete(null);
|
||||
};
|
||||
|
||||
const handleProgressComplete = (data: CrawlProgressData) => {
|
||||
console.log('Crawl completed:', data);
|
||||
setProgressItems(prev => prev.filter(item => item.progressId !== data.progressId));
|
||||
@@ -935,22 +1002,11 @@ export const DocsTab = ({
|
||||
document={doc}
|
||||
isActive={selectedDocument?.id === doc.id}
|
||||
onSelect={setSelectedDocument}
|
||||
onDelete={async (docId) => {
|
||||
if (!project?.id) return;
|
||||
|
||||
try {
|
||||
// Call API to delete from database first
|
||||
await projectService.deleteDocument(project.id, docId);
|
||||
|
||||
// Then remove from local state
|
||||
setDocuments(prev => prev.filter(d => d.id !== docId));
|
||||
if (selectedDocument?.id === docId) {
|
||||
setSelectedDocument(documents.find(d => d.id !== docId) || null);
|
||||
}
|
||||
showToast('Document deleted', 'success');
|
||||
} catch (error) {
|
||||
console.error('Failed to delete document:', error);
|
||||
showToast('Failed to delete document', 'error');
|
||||
onDelete={(docId) => {
|
||||
const doc = documents.find(d => d.id === docId);
|
||||
if (doc) {
|
||||
setDocumentToDelete({ id: docId, title: doc.title });
|
||||
setShowDeleteConfirm(true);
|
||||
}
|
||||
}}
|
||||
isDarkMode={isDarkMode}
|
||||
@@ -1099,6 +1155,16 @@ export const DocsTab = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{showDeleteConfirm && documentToDelete && (
|
||||
<DeleteConfirmModal
|
||||
itemName={documentToDelete.title}
|
||||
onConfirm={confirmDeleteDocument}
|
||||
onCancel={cancelDeleteDocument}
|
||||
type="document"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -117,9 +117,7 @@ export const DocumentCard: React.FC<DocumentCardProps> = ({
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (confirm(`Delete "${document.title}"?`)) {
|
||||
onDelete(document.id);
|
||||
}
|
||||
onDelete(document.id);
|
||||
}}
|
||||
className="absolute top-2 right-2 p-1 rounded-md bg-red-500/10 hover:bg-red-500/20 text-red-600 dark:text-red-400 transition-colors"
|
||||
aria-label={`Delete ${document.title}`}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useRef, useState, useCallback } from 'react';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { useToast } from '../../contexts/ToastContext';
|
||||
import { DeleteConfirmModal } from '../../pages/ProjectPage';
|
||||
import { DeleteConfirmModal } from '../ui/DeleteConfirmModal';
|
||||
import { CheckSquare, Square, Trash2, ArrowRight } from 'lucide-react';
|
||||
import { projectService } from '../../services/projectService';
|
||||
import { Task } from './TaskTableView'; // Import Task interface
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState, useCallback, useRef, useEffect } from 'react';
|
||||
import { useDrag, useDrop } from 'react-dnd';
|
||||
import { Check, Trash2, Edit, Tag, User, Bot, Clipboard, Save, Plus } from 'lucide-react';
|
||||
import { useToast } from '../../contexts/ToastContext';
|
||||
import { DeleteConfirmModal } from '../../pages/ProjectPage';
|
||||
import { DeleteConfirmModal } from '../ui/DeleteConfirmModal';
|
||||
import { projectService } from '../../services/projectService';
|
||||
import { ItemTypes, getAssigneeIcon, getAssigneeGlow, getOrderColor, getOrderGlow } from '../../lib/task-utils';
|
||||
import { DraggableTaskCard } from './DraggableTaskCard';
|
||||
|
||||
@@ -176,7 +176,9 @@ export const TasksTab = ({
|
||||
|
||||
// Check if this is an echo of a local update
|
||||
const localUpdateTime = localUpdates[updatedTask.id];
|
||||
if (localUpdateTime && Date.now() - localUpdateTime < 2000) {
|
||||
console.log(`[Socket] Checking for echo - Task ${updatedTask.id}, localUpdateTime: ${localUpdateTime}, current time: ${Date.now()}, diff: ${localUpdateTime ? Date.now() - localUpdateTime : 'N/A'}`);
|
||||
|
||||
if (localUpdateTime && Date.now() - localUpdateTime < 5000) { // Increased window to 5 seconds
|
||||
console.log('[Socket] Skipping echo update for locally updated task:', updatedTask.id);
|
||||
// Clean up the local update marker after the echo protection window
|
||||
setTimeout(() => {
|
||||
@@ -185,9 +187,10 @@ export const TasksTab = ({
|
||||
delete newUpdates[updatedTask.id];
|
||||
return newUpdates;
|
||||
});
|
||||
}, 2000);
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
console.log('[Socket] Not an echo, applying update for task:', updatedTask.id);
|
||||
|
||||
// Skip updates while modal is open for the same task to prevent conflicts
|
||||
if (isModalOpen && editingTask?.id === updatedTask.id) {
|
||||
@@ -553,18 +556,26 @@ export const TasksTab = ({
|
||||
console.log(`[TasksTab] Moving task ${movingTask.title} from ${oldStatus} to ${newStatus} with order ${newOrder}`);
|
||||
|
||||
// OPTIMISTIC UPDATE: Update UI immediately
|
||||
console.log(`[TasksTab] Applying optimistic move for task ${taskId} to ${newStatus}`);
|
||||
setTasks(prev => {
|
||||
const updated = prev.map(task => task.id === taskId ? updatedTask : task);
|
||||
console.log(`[TasksTab] Tasks after optimistic move:`, updated);
|
||||
setTimeout(() => onTasksChange(updated), 0);
|
||||
return updated;
|
||||
});
|
||||
console.log(`[TasksTab] Optimistically updated UI for task ${taskId}`);
|
||||
|
||||
// Mark this update as local to prevent echo when socket update arrives
|
||||
setLocalUpdates(prev => ({
|
||||
...prev,
|
||||
[taskId]: Date.now()
|
||||
}));
|
||||
const updateTime = Date.now();
|
||||
console.log(`[TasksTab] Marking update as local for task ${taskId} at time ${updateTime}`);
|
||||
setLocalUpdates(prev => {
|
||||
const newUpdates = {
|
||||
...prev,
|
||||
[taskId]: updateTime
|
||||
};
|
||||
console.log('[TasksTab] LocalUpdates state:', newUpdates);
|
||||
return newUpdates;
|
||||
});
|
||||
|
||||
try {
|
||||
// Then update the backend
|
||||
@@ -698,19 +709,30 @@ export const TasksTab = ({
|
||||
const originalTask = tasks.find(t => t.id === taskId);
|
||||
|
||||
// Optimistically update the UI immediately
|
||||
setTasks(prevTasks =>
|
||||
prevTasks.map(task =>
|
||||
console.log(`[TasksTab] Applying optimistic update for task ${taskId}`, updates);
|
||||
setTasks(prevTasks => {
|
||||
const updated = prevTasks.map(task =>
|
||||
task.id === taskId
|
||||
? { ...task, ...updates }
|
||||
: task
|
||||
)
|
||||
);
|
||||
);
|
||||
console.log(`[TasksTab] Tasks after optimistic update:`, updated);
|
||||
// Notify parent of the optimistic update
|
||||
setTimeout(() => onTasksChange(updated), 0);
|
||||
return updated;
|
||||
});
|
||||
|
||||
// Mark this update as local to prevent echo when socket update arrives
|
||||
setLocalUpdates(prev => ({
|
||||
...prev,
|
||||
[taskId]: Date.now()
|
||||
}));
|
||||
const updateTime = Date.now();
|
||||
console.log(`[TasksTab] Marking update as local for task ${taskId} at time ${updateTime}`);
|
||||
setLocalUpdates(prev => {
|
||||
const newUpdates = {
|
||||
...prev,
|
||||
[taskId]: updateTime
|
||||
};
|
||||
console.log('[TasksTab] LocalUpdates state:', newUpdates);
|
||||
return newUpdates;
|
||||
});
|
||||
|
||||
try {
|
||||
const updateData: Partial<UpdateTaskRequest> = {};
|
||||
|
||||
83
archon-ui-main/src/components/ui/DeleteConfirmModal.tsx
Normal file
83
archon-ui-main/src/components/ui/DeleteConfirmModal.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
|
||||
export interface DeleteConfirmModalProps {
|
||||
itemName: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
type: 'project' | 'task' | 'client' | 'document' | 'knowledge-items' | 'feature' | 'data';
|
||||
}
|
||||
|
||||
export const DeleteConfirmModal: React.FC<DeleteConfirmModalProps> = ({ itemName, onConfirm, onCancel, type }) => {
|
||||
const getTitle = () => {
|
||||
switch (type) {
|
||||
case 'project': return 'Delete Project';
|
||||
case 'task': return 'Delete Task';
|
||||
case 'client': return 'Delete MCP Client';
|
||||
case 'document': return 'Delete Document';
|
||||
case 'knowledge-items': return 'Delete Knowledge Items';
|
||||
case 'feature': return 'Delete Feature';
|
||||
case 'data': return 'Delete Data';
|
||||
}
|
||||
};
|
||||
|
||||
const getMessage = () => {
|
||||
switch (type) {
|
||||
case 'project': return `Are you sure you want to delete the "${itemName}" project? This will also delete all associated tasks and documents and cannot be undone.`;
|
||||
case 'task': return `Are you sure you want to delete the "${itemName}" task? This action cannot be undone.`;
|
||||
case 'client': return `Are you sure you want to delete the "${itemName}" client? This will permanently remove its configuration and cannot be undone.`;
|
||||
case 'document': return `Are you sure you want to delete the "${itemName}" document? This action cannot be undone.`;
|
||||
case 'knowledge-items': return `Are you sure you want to delete ${itemName}? This will permanently remove the selected items from your knowledge base and cannot be undone.`;
|
||||
case 'feature': return `Are you sure you want to delete the "${itemName}" feature? This action cannot be undone.`;
|
||||
case 'data': return `Are you sure you want to delete this data? This action cannot be undone.`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50">
|
||||
<div className="relative p-6 rounded-md backdrop-blur-md w-full max-w-md
|
||||
bg-gradient-to-b from-white/80 to-white/60 dark:from-white/10 dark:to-black/30
|
||||
border border-gray-200 dark:border-zinc-800/50
|
||||
shadow-[0_10px_30px_-15px_rgba(0,0,0,0.1)] dark:shadow-[0_10px_30px_-15px_rgba(0,0,0,0.7)]
|
||||
before:content-[''] before:absolute before:top-0 before:left-0 before:right-0 before:h-[2px]
|
||||
before:rounded-t-[4px] before:bg-red-500
|
||||
before:shadow-[0_0_10px_2px_rgba(239,68,68,0.4)] dark:before:shadow-[0_0_20px_5px_rgba(239,68,68,0.7)]">
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
|
||||
<Trash2 className="w-6 h-6 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
{getTitle()}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
This action cannot be undone
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700 dark:text-gray-300 mb-6">
|
||||
{getMessage()}
|
||||
</p>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="px-4 py-2 text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-md transition-colors shadow-lg shadow-red-600/20"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import { KnowledgeTable } from '../components/knowledge-base/KnowledgeTable';
|
||||
import { KnowledgeItemCard } from '../components/knowledge-base/KnowledgeItemCard';
|
||||
import { GroupedKnowledgeItemCard } from '../components/knowledge-base/GroupedKnowledgeItemCard';
|
||||
import { KnowledgeGridSkeleton, KnowledgeTableSkeleton } from '../components/knowledge-base/KnowledgeItemSkeleton';
|
||||
import { DeleteConfirmModal } from '../components/ui/DeleteConfirmModal';
|
||||
import { GroupCreationModal } from '../components/knowledge-base/GroupCreationModal';
|
||||
|
||||
const extractDomain = (url: string): string => {
|
||||
@@ -70,6 +71,10 @@ export const KnowledgeBasePage = () => {
|
||||
const [isSelectionMode, setIsSelectionMode] = useState(false);
|
||||
const [lastSelectedIndex, setLastSelectedIndex] = useState<number | null>(null);
|
||||
|
||||
// Delete confirmation modal state
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [itemsToDelete, setItemsToDelete] = useState<{ count: number; items: Set<string> } | null>(null);
|
||||
|
||||
const { showToast } = useToast();
|
||||
|
||||
// Single consolidated loading function - only loads data, no filtering
|
||||
@@ -360,32 +365,43 @@ export const KnowledgeBasePage = () => {
|
||||
if (selectedItems.size === 0) return;
|
||||
|
||||
const count = selectedItems.size;
|
||||
const confirmed = window.confirm(`Are you sure you want to delete ${count} selected item${count > 1 ? 's' : ''}?`);
|
||||
|
||||
if (!confirmed) return;
|
||||
setItemsToDelete({ count, items: new Set(selectedItems) });
|
||||
setShowDeleteConfirm(true);
|
||||
};
|
||||
|
||||
const confirmDeleteItems = async () => {
|
||||
if (!itemsToDelete) return;
|
||||
|
||||
try {
|
||||
// Delete each selected item
|
||||
const deletePromises = Array.from(selectedItems).map(itemId =>
|
||||
const deletePromises = Array.from(itemsToDelete.items).map(itemId =>
|
||||
knowledgeBaseService.deleteKnowledgeItem(itemId)
|
||||
);
|
||||
|
||||
await Promise.all(deletePromises);
|
||||
|
||||
// Remove deleted items from state
|
||||
setKnowledgeItems(prev => prev.filter(item => !selectedItems.has(item.id)));
|
||||
setKnowledgeItems(prev => prev.filter(item => !itemsToDelete.items.has(item.id)));
|
||||
|
||||
// Clear selection
|
||||
setSelectedItems(new Set());
|
||||
setIsSelectionMode(false);
|
||||
|
||||
showToast(`Successfully deleted ${count} item${count > 1 ? 's' : ''}`, 'success');
|
||||
showToast(`Successfully deleted ${itemsToDelete.count} item${itemsToDelete.count > 1 ? 's' : ''}`, 'success');
|
||||
} catch (error) {
|
||||
console.error('Failed to delete selected items:', error);
|
||||
showToast('Failed to delete some items', 'error');
|
||||
} finally {
|
||||
setShowDeleteConfirm(false);
|
||||
setItemsToDelete(null);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelDeleteItems = () => {
|
||||
setShowDeleteConfirm(false);
|
||||
setItemsToDelete(null);
|
||||
};
|
||||
|
||||
// Keyboard shortcuts
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
@@ -1194,6 +1210,16 @@ export const KnowledgeBasePage = () => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{showDeleteConfirm && itemsToDelete && (
|
||||
<DeleteConfirmModal
|
||||
itemName={`${itemsToDelete.count} selected item${itemsToDelete.count > 1 ? 's' : ''}`}
|
||||
onConfirm={confirmDeleteItems}
|
||||
onCancel={cancelDeleteItems}
|
||||
type="knowledge-items"
|
||||
/>
|
||||
)}
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { TasksTab } from '../components/project-tasks/TasksTab';
|
||||
import { Button } from '../components/ui/Button';
|
||||
import { ChevronRight, ShoppingCart, Code, Briefcase, Layers, Plus, X, AlertCircle, Loader2, Heart, BarChart3, Trash2, Pin, ListTodo, Activity, CheckCircle2, Clipboard } from 'lucide-react';
|
||||
import { copyToClipboard } from '../utils/clipboard';
|
||||
import { DeleteConfirmModal } from '../components/ui/DeleteConfirmModal';
|
||||
|
||||
// Import our service layer and types
|
||||
import { projectService } from '../services/projectService';
|
||||
@@ -1062,76 +1063,3 @@ export function ProjectPage({
|
||||
);
|
||||
}
|
||||
|
||||
// Reusable Delete Confirmation Modal Component
|
||||
export interface DeleteConfirmModalProps {
|
||||
itemName: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
type: 'project' | 'task' | 'client';
|
||||
}
|
||||
|
||||
export const DeleteConfirmModal: React.FC<DeleteConfirmModalProps> = ({ itemName, onConfirm, onCancel, type }) => {
|
||||
const getTitle = () => {
|
||||
switch (type) {
|
||||
case 'project': return 'Delete Project';
|
||||
case 'task': return 'Delete Task';
|
||||
case 'client': return 'Delete MCP Client';
|
||||
}
|
||||
};
|
||||
|
||||
const getMessage = () => {
|
||||
switch (type) {
|
||||
case 'project': return `Are you sure you want to delete the "${itemName}" project? This will also delete all associated tasks and documents and cannot be undone.`;
|
||||
case 'task': return `Are you sure you want to delete the "${itemName}" task? This action cannot be undone.`;
|
||||
case 'client': return `Are you sure you want to delete the "${itemName}" client? This will permanently remove its configuration and cannot be undone.`;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50">
|
||||
<div className="relative p-6 rounded-md backdrop-blur-md w-full max-w-md
|
||||
bg-gradient-to-b from-white/80 to-white/60 dark:from-white/10 dark:to-black/30
|
||||
border border-gray-200 dark:border-zinc-800/50
|
||||
shadow-[0_10px_30px_-15px_rgba(0,0,0,0.1)] dark:shadow-[0_10px_30px_-15px_rgba(0,0,0,0.7)]
|
||||
before:content-[''] before:absolute before:top-0 before:left-0 before:right-0 before:h-[2px]
|
||||
before:rounded-t-[4px] before:bg-red-500
|
||||
before:shadow-[0_0_10px_2px_rgba(239,68,68,0.4)] dark:before:shadow-[0_0_20px_5px_rgba(239,68,68,0.7)]">
|
||||
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-12 h-12 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
|
||||
<Trash2 className="w-6 h-6 text-red-600 dark:text-red-400" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-800 dark:text-white">
|
||||
{getTitle()}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
This action cannot be undone
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-700 dark:text-gray-300 mb-6">
|
||||
{getMessage()}
|
||||
</p>
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<button
|
||||
onClick={onCancel}
|
||||
className="px-4 py-2 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors shadow-lg shadow-red-600/25 hover:shadow-red-700/25"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user