feat: Add Delete Project functionality to History Modal

This commit is contained in:
2025-12-29 15:28:04 +00:00
parent 7359a3ff88
commit 259faea53d
4 changed files with 133 additions and 22 deletions

View File

@@ -1,19 +1,45 @@
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);
const fetchProjects = async () => {
setIsLoadingProjects(true);
try {
const projects = await listProjects();
setRecentProjects(projects);
} catch (e) {
console.error("Failed to load projects", e);
} finally {
setIsLoadingProjects(false);
}
};
useEffect(() => {
if (showHistory) {
const fetchProjects = async () => {
setIsLoadingProjects(true);
try {
const projects = await listProjects();
setRecentProjects(projects);
} catch (e) {
console.error("Failed to load projects", e);
} finally {
setIsLoadingProjects(false);
}
};
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 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 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>
</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>
<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} />
</button>
))
) : (
@@ -273,4 +357,4 @@
</div>
);
};
};

View File

@@ -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');

View File

@@ -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',

View File

@@ -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)))