feat: add functionality to manage deleted projects in settings
- Introduced a new section for displaying soft-deleted projects. - Implemented loading of deleted projects via an API call. - Added restore and permanently delete options for each deleted project. - Integrated loading of deleted projects when the data tab is shown.
This commit is contained in:
@@ -355,6 +355,25 @@
|
||||
</form>
|
||||
<div id="uploadResult" class="hidden mt-3"></div>
|
||||
</div>
|
||||
|
||||
<!-- Deleted Projects -->
|
||||
<div class="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Deleted Projects</h2>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">Projects that have been soft-deleted. Restore them or permanently remove them.</p>
|
||||
</div>
|
||||
<button onclick="loadDeletedProjects()" class="px-4 py-2 text-sm text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div id="deletedProjectsList">
|
||||
<p class="text-sm text-gray-400 dark:text-gray-500 italic">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -584,6 +603,67 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// ========== DELETED PROJECTS ==========
|
||||
|
||||
async function loadDeletedProjects() {
|
||||
const container = document.getElementById('deletedProjectsList');
|
||||
container.innerHTML = '<p class="text-sm text-gray-400 dark:text-gray-500 italic">Loading...</p>';
|
||||
try {
|
||||
const resp = await fetch('/api/projects/deleted');
|
||||
const projects = await resp.json();
|
||||
if (!projects.length) {
|
||||
container.innerHTML = '<p class="text-sm text-gray-400 dark:text-gray-500 italic">No deleted projects.</p>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = `
|
||||
<div class="divide-y divide-gray-100 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||||
${projects.map(p => `
|
||||
<div class="flex items-center justify-between px-4 py-3 bg-gray-50 dark:bg-gray-900/30">
|
||||
<div>
|
||||
<div class="font-medium text-gray-900 dark:text-white">${p.name}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
|
||||
${p.client_name ? p.client_name + ' · ' : ''}Deleted ${new Date(p.deleted_at).toLocaleDateString()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
<button onclick="restoreProject('${p.id}', '${p.name.replace(/'/g, "\\'")}')"
|
||||
class="px-3 py-1 text-xs bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors">
|
||||
Restore
|
||||
</button>
|
||||
<button onclick="permanentlyDeleteProject('${p.id}', '${p.name.replace(/'/g, "\\'")}')"
|
||||
class="px-3 py-1 text-xs bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">
|
||||
Delete Permanently
|
||||
</button>
|
||||
</div>
|
||||
</div>`).join('')}
|
||||
</div>`;
|
||||
} catch (e) {
|
||||
container.innerHTML = '<p class="text-sm text-red-500">Failed to load deleted projects.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreProject(projectId, name) {
|
||||
if (!confirm(`Restore "${name}"?`)) return;
|
||||
const resp = await fetch(`/api/projects/${projectId}/restore`, { method: 'POST' });
|
||||
if (resp.ok) {
|
||||
loadDeletedProjects();
|
||||
} else {
|
||||
const d = await resp.json();
|
||||
alert('Failed to restore: ' + (d.detail || 'Unknown error'));
|
||||
}
|
||||
}
|
||||
|
||||
async function permanentlyDeleteProject(projectId, name) {
|
||||
if (!confirm(`Permanently delete "${name}" and all its data? This cannot be undone.`)) return;
|
||||
const resp = await fetch(`/api/projects/${projectId}/permanent`, { method: 'DELETE' });
|
||||
if (resp.ok) {
|
||||
loadDeletedProjects();
|
||||
} else {
|
||||
const d = await resp.json();
|
||||
alert('Failed to delete: ' + (d.detail || 'Unknown error'));
|
||||
}
|
||||
}
|
||||
|
||||
// ========== TAB MANAGEMENT ==========
|
||||
|
||||
function showTab(tabName) {
|
||||
@@ -609,9 +689,10 @@ function showTab(tabName) {
|
||||
// Save last active tab to localStorage
|
||||
localStorage.setItem('settings-last-tab', tabName);
|
||||
|
||||
// Load roster table when data tab is shown
|
||||
// Load roster table and deleted projects when data tab is shown
|
||||
if (tabName === 'data') {
|
||||
loadRosterTable();
|
||||
loadDeletedProjects();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user