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);
|
const [showHistory, setShowHistory] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (showHistory) {
|
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
setIsLoadingProjects(true);
|
setIsLoadingProjects(true);
|
||||||
try {
|
try {
|
||||||
@@ -14,6 +37,9 @@
|
|||||||
setIsLoadingProjects(false);
|
setIsLoadingProjects(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showHistory) {
|
||||||
fetchProjects();
|
fetchProjects();
|
||||||
}
|
}
|
||||||
}, [showHistory]);
|
}, [showHistory]);
|
||||||
@@ -21,8 +47,9 @@
|
|||||||
const handleProjectSelect = async (projectId: string) => {
|
const handleProjectSelect = async (projectId: string) => {
|
||||||
try {
|
try {
|
||||||
const projectData = await loadProject(projectId);
|
const projectData = await loadProject(projectId);
|
||||||
if (projectData && projectData.strategy && projectData.analysisResults) {
|
// Check for full project data first
|
||||||
onLoadReport(projectData.strategy, projectData.analysisResults);
|
if (projectData && (projectData.strategy || projectData.competitors)) {
|
||||||
|
onLoadReport(projectData, []); // Pass full object, let App.tsx handle hydration logic
|
||||||
} else {
|
} else {
|
||||||
alert("Project data is incomplete or corrupted.");
|
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) => {
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -250,14 +322,26 @@
|
|||||||
<button
|
<button
|
||||||
key={p.id}
|
key={p.id}
|
||||||
onClick={() => handleProjectSelect(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">
|
<div className="flex items-center gap-2 mt-1.5 text-xs text-slate-400">
|
||||||
<Clock size={12} />
|
<Clock size={12} />
|
||||||
<span>{new Date(p.updated_at).toLocaleDateString()} {new Date(p.updated_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
|
<span>{new Date(p.updated_at).toLocaleDateString()} {new Date(p.updated_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
|
||||||
</div>
|
</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>
|
</button>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -227,6 +227,10 @@ app.get('/api/projects/:id', (req, res) => {
|
|||||||
runPython([dbScript, 'load', req.params.id], 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) => {
|
app.post('/api/save-project', (req, res) => {
|
||||||
const projectData = req.body;
|
const projectData = req.body;
|
||||||
const tmpDir = path.join(__dirname, 'tmp');
|
const tmpDir = path.join(__dirname, 'tmp');
|
||||||
|
|||||||
@@ -267,6 +267,14 @@ export const loadProject = async (id: string): Promise<any> => {
|
|||||||
return await response.json();
|
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> => {
|
export const saveProject = async (data: any): Promise<any> => {
|
||||||
const response = await fetch(`${API_BASE_URL}/save-project`, {
|
const response = await fetch(`${API_BASE_URL}/save-project`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -78,10 +78,21 @@ def load_project(project_id):
|
|||||||
return json.loads(project['data'])
|
return json.loads(project['data'])
|
||||||
return None
|
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__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
# Simple CLI for Node.js bridge
|
# 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]
|
mode = sys.argv[1]
|
||||||
|
|
||||||
@@ -103,3 +114,7 @@ if __name__ == "__main__":
|
|||||||
p_id = sys.argv[2]
|
p_id = sys.argv[2]
|
||||||
result = load_project(p_id)
|
result = load_project(p_id)
|
||||||
print(json.dumps(result if result else {"error": "Project not found"}))
|
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