changes
This commit is contained in:
@@ -254,6 +254,83 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Closure Checklist -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6 md:col-span-2" id="closureChecklistCard" style="display: none;">
|
||||
<div class="flex items-center justify-between pb-2 mb-3 border-b border-neutral-200 dark:border-neutral-700">
|
||||
<h6 class="mb-0 font-semibold">Closure Checklist</h6>
|
||||
<div class="flex items-center gap-2">
|
||||
<input type="text" id="newChecklistName" class="px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" placeholder="Add checklist item...">
|
||||
<label class="flex items-center gap-1 text-sm"><input type="checkbox" id="newChecklistRequired" class="mr-1"> Required</label>
|
||||
<button type="button" class="px-3 py-2 bg-success-600 text-white rounded-lg hover:bg-success-700" id="addChecklistBtn">
|
||||
<i class="fa-solid fa-plus mr-1"></i> Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="checklistItems" class="space-y-2"></ul>
|
||||
</div>
|
||||
|
||||
<!-- File Alerts -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6 md:col-span-2" id="fileAlertsCard" style="display: none;">
|
||||
<div class="pb-2 mb-3 border-b border-neutral-200 dark:border-neutral-700">
|
||||
<h6 class="mb-0 font-semibold">File Alerts</h6>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-2 mb-3">
|
||||
<div>
|
||||
<input type="text" id="alertType" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" placeholder="Type (e.g., follow_up)">
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<input type="text" id="alertTitle" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" placeholder="Title">
|
||||
</div>
|
||||
<div>
|
||||
<input type="date" id="alertDate" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg">
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label class="flex items-center gap-1 text-sm"><input type="checkbox" id="alertNotifyAttorney" checked> Attorney</label>
|
||||
<label class="flex items-center gap-1 text-sm"><input type="checkbox" id="alertNotifyAdmin"> Admin</label>
|
||||
</div>
|
||||
<div class="md:col-span-4">
|
||||
<input type="text" id="alertMessage" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" placeholder="Message">
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="w-full px-3 py-2 bg-success-600 text-white rounded-lg hover:bg-success-700" id="createAlertBtn">
|
||||
<i class="fa-solid fa-bell mr-1"></i> Create
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="alertsList" class="space-y-2"></ul>
|
||||
</div>
|
||||
|
||||
<!-- File Relationships -->
|
||||
<div class="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6 md:col-span-2" id="fileRelationshipsCard" style="display: none;">
|
||||
<div class="pb-2 mb-3 border-b border-neutral-200 dark:border-neutral-700">
|
||||
<h6 class="mb-0 font-semibold">File Relationships</h6>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-5 gap-2 mb-3">
|
||||
<div>
|
||||
<input type="text" id="relTargetFileNo" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" placeholder="Target File #">
|
||||
</div>
|
||||
<div>
|
||||
<select id="relType" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg">
|
||||
<option value="related">related</option>
|
||||
<option value="parent">parent</option>
|
||||
<option value="child">child</option>
|
||||
<option value="duplicate">duplicate</option>
|
||||
<option value="conflict">conflict</option>
|
||||
<option value="referral">referral</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="md:col-span-3">
|
||||
<input type="text" id="relNotes" class="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg" placeholder="Notes (optional)">
|
||||
</div>
|
||||
<div class="md:col-span-5">
|
||||
<button type="button" class="px-3 py-2 bg-success-600 text-white rounded-lg hover:bg-success-700" id="addRelationshipBtn">
|
||||
<i class="fa-solid fa-link mr-1"></i> Link Files
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul id="relationshipsList" class="space-y-2"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -430,6 +507,12 @@ function setupEventListeners() {
|
||||
document.getElementById('deleteFileBtn').addEventListener('click', deleteFile);
|
||||
document.getElementById('closeFileBtn').addEventListener('click', closeFile);
|
||||
document.getElementById('reopenFileBtn').addEventListener('click', reopenFile);
|
||||
// Checklist
|
||||
document.getElementById('addChecklistBtn').addEventListener('click', addChecklistItem);
|
||||
// Alerts
|
||||
document.getElementById('createAlertBtn').addEventListener('click', createAlert);
|
||||
// Relationships
|
||||
document.getElementById('addRelationshipBtn').addEventListener('click', addRelationship);
|
||||
|
||||
// Other buttons
|
||||
document.getElementById('statsBtn').addEventListener('click', showStats);
|
||||
@@ -676,6 +759,9 @@ async function editFile(fileNo) {
|
||||
document.getElementById('fileActions').style.display = 'block';
|
||||
document.getElementById('financialSummaryCard').style.display = 'block';
|
||||
document.getElementById('documentsCard').style.display = 'block'; // Show documents card for editing
|
||||
document.getElementById('closureChecklistCard').style.display = 'block';
|
||||
document.getElementById('fileAlertsCard').style.display = 'block';
|
||||
document.getElementById('fileRelationshipsCard').style.display = 'block';
|
||||
document.getElementById('fileNo').readOnly = true;
|
||||
|
||||
// Show/hide close/reopen buttons based on status
|
||||
@@ -686,6 +772,9 @@ async function editFile(fileNo) {
|
||||
// Load financial summary
|
||||
loadFinancialSummary(fileNo);
|
||||
loadDocuments(fileNo); // Load documents for editing
|
||||
loadClosureChecklist(fileNo);
|
||||
loadAlerts(fileNo);
|
||||
loadRelationships(fileNo);
|
||||
|
||||
openModal('fileModal');
|
||||
|
||||
@@ -1214,5 +1303,342 @@ async function updateDocumentDescription(docId, description) {
|
||||
showAlert('Error updating description: ' + error.message, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// Closure Checklist
|
||||
async function loadClosureChecklist(fileNo) {
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/${encodeURIComponent(fileNo)}/closure-checklist`);
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to load checklist');
|
||||
const items = await res.json();
|
||||
const list = document.getElementById('checklistItems');
|
||||
list.innerHTML = '';
|
||||
if (!items || items.length === 0) {
|
||||
list.innerHTML = '<li class="text-neutral-500 text-sm">No checklist items yet.</li>';
|
||||
return;
|
||||
}
|
||||
items.forEach(item => {
|
||||
const li = document.createElement('li');
|
||||
li.dataset.itemId = item.id;
|
||||
li.className = 'flex items-center justify-between gap-2 border border-neutral-200 dark:border-neutral-700 rounded-md px-3 py-2';
|
||||
li.innerHTML = `
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" ${item.is_completed ? 'checked' : ''} onchange="toggleChecklistItem(${item.id}, this.checked)" />
|
||||
<div>
|
||||
<div class="font-medium">${_escapeHtml(item.item_name)}</div>
|
||||
<div class="text-xs text-neutral-500">${_escapeHtml(item.item_description || '')}</div>
|
||||
</div>
|
||||
${item.is_required ? '<span class="text-xs px-2 py-0.5 rounded bg-red-100 text-red-700">Required</span>' : ''}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded" onclick="editChecklistItem(${item.id})">Edit</button>
|
||||
<button class="px-2 py-1 text-xs border border-red-600 text-red-700 rounded" onclick="deleteChecklistItem(${item.id})">Delete</button>
|
||||
</div>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
});
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error loading checklist'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function addChecklistItem() {
|
||||
const name = (document.getElementById('newChecklistName').value || '').trim();
|
||||
const isRequired = !!document.getElementById('newChecklistRequired').checked;
|
||||
if (!editingFileNo) return;
|
||||
if (!name) {
|
||||
showAlert('Enter a checklist item name', 'warning');
|
||||
return;
|
||||
}
|
||||
// optimistic add
|
||||
const tempId = 'temp-' + Date.now();
|
||||
const list = document.getElementById('checklistItems');
|
||||
const li = document.createElement('li');
|
||||
li.dataset.itemId = tempId;
|
||||
li.className = 'flex items-center justify-between gap-2 border border-neutral-200 dark:border-neutral-700 rounded-md px-3 py-2 opacity-60';
|
||||
li.innerHTML = `
|
||||
<div class="flex items-center gap-3">
|
||||
<input type="checkbox" />
|
||||
<div>
|
||||
<div class="font-medium">${_escapeHtml(name)}</div>
|
||||
</div>
|
||||
${isRequired ? '<span class="text-xs px-2 py-0.5 rounded bg-red-100 text-red-700">Required</span>' : ''}
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-neutral-500">Saving...</span>
|
||||
</div>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/${encodeURIComponent(editingFileNo)}/closure-checklist`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ item_name: name, is_required: isRequired })
|
||||
});
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to add item');
|
||||
const saved = await res.json();
|
||||
document.getElementById('newChecklistName').value = '';
|
||||
document.getElementById('newChecklistRequired').checked = true;
|
||||
// Refresh list for clean state
|
||||
loadClosureChecklist(editingFileNo);
|
||||
} catch (err) {
|
||||
li.remove();
|
||||
showAlert(window.http.formatAlert(err, 'Error adding item'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleChecklistItem(itemId, checked) {
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/closure-checklist/${itemId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ is_completed: !!checked })
|
||||
});
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to update item');
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error updating item'), 'danger');
|
||||
loadClosureChecklist(editingFileNo);
|
||||
}
|
||||
}
|
||||
|
||||
function editChecklistItem(itemId) {
|
||||
const newName = prompt('Update item name (leave blank to skip):');
|
||||
if (newName === null) return;
|
||||
const newNotes = prompt('Notes (optional, leave blank to skip):');
|
||||
updateChecklistItem(itemId, newName, newNotes || undefined);
|
||||
}
|
||||
|
||||
async function updateChecklistItem(itemId, newName, notes) {
|
||||
const payload = {};
|
||||
if (newName && newName.trim()) payload.item_name = newName.trim();
|
||||
if (notes !== undefined) payload.notes = notes;
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/closure-checklist/${itemId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to update item');
|
||||
loadClosureChecklist(editingFileNo);
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error updating item'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteChecklistItem(itemId) {
|
||||
if (!confirm('Delete this checklist item?')) return;
|
||||
const li = document.querySelector(`li[data-item-id="${itemId}"]`);
|
||||
if (li) li.remove();
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/closure-checklist/${itemId}`, { method: 'DELETE' });
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to delete item');
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error deleting item'), 'danger');
|
||||
loadClosureChecklist(editingFileNo);
|
||||
}
|
||||
}
|
||||
|
||||
// Alerts
|
||||
async function loadAlerts(fileNo) {
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/${encodeURIComponent(fileNo)}/alerts?active_only=true&upcoming_only=false&limit=100`);
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to load alerts');
|
||||
const alerts = await res.json();
|
||||
const list = document.getElementById('alertsList');
|
||||
list.innerHTML = '';
|
||||
if (!alerts || alerts.length === 0) {
|
||||
list.innerHTML = '<li class="text-neutral-500 text-sm">No alerts yet.</li>';
|
||||
return;
|
||||
}
|
||||
alerts.forEach(a => {
|
||||
const li = document.createElement('li');
|
||||
li.dataset.alertId = a.id;
|
||||
li.className = 'flex items-center justify-between gap-2 border border-neutral-200 dark:border-neutral-700 rounded-md px-3 py-2';
|
||||
li.innerHTML = `
|
||||
<div>
|
||||
<div class="font-medium">${_escapeHtml(a.title)} <span class="text-xs text-neutral-500">(${_escapeHtml(a.alert_type)})</span></div>
|
||||
<div class="text-xs text-neutral-500">${formatDate(a.alert_date)} • ${_escapeHtml(a.message || '')}</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
${a.is_acknowledged ? '<span class="text-xs text-green-700">Acknowledged</span>' : `<button class="px-2 py-1 text-xs border border-success-600 text-success-700 rounded" onclick="ackAlert(${a.id})">Acknowledge</button>`}
|
||||
<button class="px-2 py-1 text-xs border border-neutral-300 dark:border-neutral-600 rounded" onclick="editAlert(${a.id})">Edit</button>
|
||||
<button class="px-2 py-1 text-xs border border-red-600 text-red-700 rounded" onclick="deleteAlert(${a.id})">Delete</button>
|
||||
</div>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
});
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error loading alerts'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function createAlert() {
|
||||
if (!editingFileNo) return;
|
||||
const alert_type = (document.getElementById('alertType').value || '').trim();
|
||||
const title = (document.getElementById('alertTitle').value || '').trim();
|
||||
const message = (document.getElementById('alertMessage').value || '').trim();
|
||||
const alert_date = document.getElementById('alertDate').value;
|
||||
const notify_attorney = !!document.getElementById('alertNotifyAttorney').checked;
|
||||
const notify_admin = !!document.getElementById('alertNotifyAdmin').checked;
|
||||
if (!alert_type || !title || !alert_date) {
|
||||
showAlert('Type, title, and date are required', 'warning');
|
||||
return;
|
||||
}
|
||||
// optimistic row
|
||||
const tempId = 'temp-' + Date.now();
|
||||
const list = document.getElementById('alertsList');
|
||||
const li = document.createElement('li');
|
||||
li.dataset.alertId = tempId;
|
||||
li.className = 'flex items-center justify-between gap-2 border border-neutral-200 dark:border-neutral-700 rounded-md px-3 py-2 opacity-60';
|
||||
li.innerHTML = `
|
||||
<div>
|
||||
<div class="font-medium">${_escapeHtml(title)} <span class="text-xs text-neutral-500">(${_escapeHtml(alert_type)})</span></div>
|
||||
<div class="text-xs text-neutral-500">${_escapeHtml(alert_date)} • ${_escapeHtml(message)}</div>
|
||||
</div>
|
||||
<div class="text-xs text-neutral-500">Saving...</div>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/${encodeURIComponent(editingFileNo)}/alerts`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ alert_type, title, message, alert_date, notify_attorney, notify_admin })
|
||||
});
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to create alert');
|
||||
document.getElementById('alertType').value = '';
|
||||
document.getElementById('alertTitle').value = '';
|
||||
document.getElementById('alertMessage').value = '';
|
||||
document.getElementById('alertDate').value = '';
|
||||
document.getElementById('alertNotifyAttorney').checked = true;
|
||||
document.getElementById('alertNotifyAdmin').checked = false;
|
||||
loadAlerts(editingFileNo);
|
||||
} catch (err) {
|
||||
li.remove();
|
||||
showAlert(window.http.formatAlert(err, 'Error creating alert'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function ackAlert(alertId) {
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/alerts/${alertId}/acknowledge`, { method: 'POST' });
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to acknowledge alert');
|
||||
loadAlerts(editingFileNo);
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error acknowledging alert'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
function editAlert(alertId) {
|
||||
const newTitle = prompt('New title (leave blank to skip):');
|
||||
if (newTitle === null) return;
|
||||
const newMessage = prompt('New message (leave blank to skip):');
|
||||
updateAlert(alertId, newTitle, newMessage);
|
||||
}
|
||||
|
||||
async function updateAlert(alertId, title, message) {
|
||||
const payload = {};
|
||||
if (title && title.trim()) payload.title = title.trim();
|
||||
if (message && message.trim()) payload.message = message.trim();
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/alerts/${alertId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to update alert');
|
||||
loadAlerts(editingFileNo);
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error updating alert'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteAlert(alertId) {
|
||||
if (!confirm('Delete this alert?')) return;
|
||||
const li = document.querySelector(`li[data-alert-id="${alertId}"]`);
|
||||
if (li) li.remove();
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/alerts/${alertId}`, { method: 'DELETE' });
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to delete alert');
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error deleting alert'), 'danger');
|
||||
loadAlerts(editingFileNo);
|
||||
}
|
||||
}
|
||||
|
||||
// Relationships
|
||||
async function loadRelationships(fileNo) {
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/${encodeURIComponent(fileNo)}/relationships`);
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to load relationships');
|
||||
const rels = await res.json();
|
||||
const list = document.getElementById('relationshipsList');
|
||||
list.innerHTML = '';
|
||||
if (!rels || rels.length === 0) {
|
||||
list.innerHTML = '<li class="text-neutral-500 text-sm">No relationships yet.</li>';
|
||||
return;
|
||||
}
|
||||
rels.forEach(r => {
|
||||
const li = document.createElement('li');
|
||||
li.dataset.relationshipId = r.id;
|
||||
li.className = 'flex items-center justify-between gap-2 border border-neutral-200 dark:border-neutral-700 rounded-md px-3 py-2';
|
||||
li.innerHTML = `
|
||||
<div>
|
||||
<div class="font-medium">${_escapeHtml(r.relationship_type)} → ${_escapeHtml(r.other_file_no)}</div>
|
||||
<div class="text-xs text-neutral-500">${_escapeHtml(r.notes || '')}</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="px-2 py-1 text-xs border border-red-600 text-red-700 rounded" onclick="deleteRelationship(${r.id})">Remove</button>
|
||||
</div>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
});
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error loading relationships'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function addRelationship() {
|
||||
const target = (document.getElementById('relTargetFileNo').value || '').trim();
|
||||
const relationship_type = document.getElementById('relType').value;
|
||||
const notes = (document.getElementById('relNotes').value || '').trim();
|
||||
if (!editingFileNo) return;
|
||||
if (!target) { showAlert('Enter a target file #', 'warning'); return; }
|
||||
// optimistic
|
||||
const tempId = 'temp-' + Date.now();
|
||||
const list = document.getElementById('relationshipsList');
|
||||
const li = document.createElement('li');
|
||||
li.dataset.relationshipId = tempId;
|
||||
li.className = 'flex items-center justify-between gap-2 border border-neutral-200 dark:border-neutral-700 rounded-md px-3 py-2 opacity-60';
|
||||
li.innerHTML = `
|
||||
<div>
|
||||
<div class="font-medium">${_escapeHtml(relationship_type)} → ${_escapeHtml(target)}</div>
|
||||
<div class="text-xs text-neutral-500">${_escapeHtml(notes)}</div>
|
||||
</div>
|
||||
<div class="text-xs text-neutral-500">Saving...</div>
|
||||
`;
|
||||
list.appendChild(li);
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/${encodeURIComponent(editingFileNo)}/relationships`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ target_file_no: target, relationship_type, notes })
|
||||
});
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to link files');
|
||||
document.getElementById('relTargetFileNo').value = '';
|
||||
document.getElementById('relNotes').value = '';
|
||||
loadRelationships(editingFileNo);
|
||||
} catch (err) {
|
||||
li.remove();
|
||||
showAlert(window.http.formatAlert(err, 'Error linking files'), 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRelationship(id) {
|
||||
if (!confirm('Remove this relationship?')) return;
|
||||
const li = document.querySelector(`li[data-relationship-id="${id}"]`);
|
||||
if (li) li.remove();
|
||||
try {
|
||||
const res = await window.http.wrappedFetch(`/api/file-management/relationships/${id}`, { method: 'DELETE' });
|
||||
if (!res.ok) throw await window.http.toError(res, 'Failed to remove relationship');
|
||||
} catch (err) {
|
||||
showAlert(window.http.formatAlert(err, 'Error removing relationship'), 'danger');
|
||||
loadRelationships(editingFileNo);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user