feat: Add Delete Project functionality to History Modal
This commit is contained in:
@@ -1,8 +1,31 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Search, ArrowRight, Loader2, Globe, Link as LinkIcon, Languages, Upload, FileText, X, FolderOpen, FileUp, History, Clock, Trash2 } from 'lucide-react';
|
||||
import { Language, AnalysisResult, SearchStrategy } from '../types';
|
||||
import { parseMarkdownReport } from '../utils/reportParser';
|
||||
import { listProjects, loadProject, deleteProject } from '../services/geminiService';
|
||||
|
||||
interface StepInputProps {
|
||||
onSearch: (url: string, context: string, market: string, language: Language) => void;
|
||||
onLoadReport: (strategy: SearchStrategy, results: AnalysisResult[]) => void;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const COUNTRIES = [
|
||||
"Germany", "Austria", "Switzerland", "United Kingdom", "France", "Spain", "Italy", "Netherlands", "Europe (General)", "USA"
|
||||
];
|
||||
|
||||
export const StepInput: React.FC<StepInputProps> = ({ onSearch, onLoadReport, isLoading }) => {
|
||||
const [activeMode, setActiveMode] = useState<'new' | 'load'>('new');
|
||||
const [url, setUrl] = useState('');
|
||||
const [fileContent, setFileContent] = useState('');
|
||||
const [fileName, setFileName] = useState('');
|
||||
const [market, setMarket] = useState(COUNTRIES[0]);
|
||||
const [language, setLanguage] = useState<Language>('de');
|
||||
|
||||
const [recentProjects, setRecentProjects] = useState<any[]>([]);
|
||||
const [isLoadingProjects, setIsLoadingProjects] = useState(false);
|
||||
const [showHistory, setShowHistory] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (showHistory) {
|
||||
const fetchProjects = async () => {
|
||||
setIsLoadingProjects(true);
|
||||
try {
|
||||
@@ -14,6 +37,9 @@
|
||||
setIsLoadingProjects(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (showHistory) {
|
||||
fetchProjects();
|
||||
}
|
||||
}, [showHistory]);
|
||||
@@ -21,8 +47,9 @@
|
||||
const handleProjectSelect = async (projectId: string) => {
|
||||
try {
|
||||
const projectData = await loadProject(projectId);
|
||||
if (projectData && projectData.strategy && projectData.analysisResults) {
|
||||
onLoadReport(projectData.strategy, projectData.analysisResults);
|
||||
// Check for full project data first
|
||||
if (projectData && (projectData.strategy || projectData.competitors)) {
|
||||
onLoadReport(projectData, []); // Pass full object, let App.tsx handle hydration logic
|
||||
} else {
|
||||
alert("Project data is incomplete or corrupted.");
|
||||
}
|
||||
@@ -32,7 +59,52 @@
|
||||
}
|
||||
};
|
||||
|
||||
// ... (file handlers remain same)
|
||||
const handleDeleteProject = async (e: React.MouseEvent, projectId: string) => {
|
||||
e.stopPropagation(); // Prevent loading the project
|
||||
if (confirm("Are you sure you want to delete this project?")) {
|
||||
try {
|
||||
await deleteProject(projectId);
|
||||
fetchProjects(); // Refresh list
|
||||
} catch (error) {
|
||||
console.error("Failed to delete", error);
|
||||
alert("Failed to delete project.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
setFileContent(event.target?.result as string);
|
||||
setFileName(file.name);
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadReport = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const content = event.target?.result as string;
|
||||
const parsed = parseMarkdownReport(content);
|
||||
if (parsed) {
|
||||
onLoadReport(parsed.strategy, parsed.results);
|
||||
} else {
|
||||
alert("Could not parse report. Please make sure it's a valid ProspectIntel MD file.");
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRemoveFile = () => {
|
||||
setFileContent('');
|
||||
setFileName('');
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
@@ -250,14 +322,26 @@
|
||||
<button
|
||||
key={p.id}
|
||||
onClick={() => handleProjectSelect(p.id)}
|
||||
className="w-full text-left p-4 rounded-xl bg-slate-50 hover:bg-indigo-50 border border-slate-200 hover:border-indigo-200 transition-all group relative"
|
||||
className="w-full text-left p-4 rounded-xl bg-slate-50 hover:bg-indigo-50 border border-slate-200 hover:border-indigo-200 transition-all group relative flex items-center justify-between"
|
||||
>
|
||||
<div className="font-semibold text-slate-800 group-hover:text-indigo-700 pr-8">{p.name}</div>
|
||||
<div className="flex-1 min-w-0 pr-4">
|
||||
<div className="font-semibold text-slate-800 group-hover:text-indigo-700 truncate">{p.name}</div>
|
||||
<div className="flex items-center gap-2 mt-1.5 text-xs text-slate-400">
|
||||
<Clock size={12} />
|
||||
<span>{new Date(p.updated_at).toLocaleDateString()} {new Date(p.updated_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
|
||||
</div>
|
||||
<ArrowRight className="absolute right-4 top-1/2 -translate-y-1/2 text-indigo-300 group-hover:text-indigo-600 opacity-0 group-hover:opacity-100 transition-all" size={20} />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
onClick={(e) => handleDeleteProject(e, p.id)}
|
||||
className="p-2 text-slate-400 hover:text-red-600 hover:bg-red-50 rounded-full transition-colors z-10"
|
||||
title="Delete Project"
|
||||
>
|
||||
<Trash2 size={18} />
|
||||
</span>
|
||||
<ArrowRight className="text-indigo-300 group-hover:text-indigo-600 transition-colors" size={20} />
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
) : (
|
||||
|
||||
@@ -227,6 +227,10 @@ app.get('/api/projects/:id', (req, res) => {
|
||||
runPython([dbScript, 'load', req.params.id], res);
|
||||
});
|
||||
|
||||
app.delete('/api/projects/:id', (req, res) => {
|
||||
runPython([dbScript, 'delete', req.params.id], res);
|
||||
});
|
||||
|
||||
app.post('/api/save-project', (req, res) => {
|
||||
const projectData = req.body;
|
||||
const tmpDir = path.join(__dirname, 'tmp');
|
||||
|
||||
@@ -267,6 +267,14 @@ export const loadProject = async (id: string): Promise<any> => {
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
export const deleteProject = async (id: string): Promise<any> => {
|
||||
const response = await fetch(`${API_BASE_URL}/projects/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (!response.ok) throw new Error("Failed to delete project");
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
export const saveProject = async (data: any): Promise<any> => {
|
||||
const response = await fetch(`${API_BASE_URL}/save-project`, {
|
||||
method: 'POST',
|
||||
|
||||
@@ -78,10 +78,21 @@ def load_project(project_id):
|
||||
return json.loads(project['data'])
|
||||
return None
|
||||
|
||||
def delete_project(project_id):
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
conn.execute('DELETE FROM projects WHERE id = ?', (project_id,))
|
||||
conn.commit()
|
||||
return {"status": "deleted", "id": project_id}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
# Simple CLI for Node.js bridge
|
||||
# Usage: python market_db_manager.py [init|list|save|load] [args...]
|
||||
# Usage: python market_db_manager.py [init|list|save|load|delete] [args...]
|
||||
|
||||
mode = sys.argv[1]
|
||||
|
||||
@@ -103,3 +114,7 @@ if __name__ == "__main__":
|
||||
p_id = sys.argv[2]
|
||||
result = load_project(p_id)
|
||||
print(json.dumps(result if result else {"error": "Project not found"}))
|
||||
|
||||
elif mode == "delete":
|
||||
p_id = sys.argv[2]
|
||||
print(json.dumps(delete_project(p_id)))
|
||||
|
||||
Reference in New Issue
Block a user