feat: add comprehensive GitHub workflow and development tools
This commit is contained in:
551
app/templates/stiftung/dokument_management.html
Normal file
551
app/templates/stiftung/dokument_management.html
Normal file
@@ -0,0 +1,551 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Dokumentenverwaltung - Stiftung{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1><i class="fas fa-folder-open me-2"></i>Dokumentenverwaltung</h1>
|
||||
<div>
|
||||
<a href="{% url 'stiftung:dokument_list' %}" class="btn btn-outline-primary">
|
||||
<i class="fas fa-list me-1"></i>Alle Dokumente anzeigen
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="statusMessages"></div>
|
||||
|
||||
<!-- Filter -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-filter me-2"></i>Filter
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Kategorie</label>
|
||||
<select id="filterCategory" class="form-select">
|
||||
<option value="all">Alle</option>
|
||||
<option value="destinaere">Destinatäre</option>
|
||||
<option value="land">Ländereien</option>
|
||||
<option value="admin">Administration</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Suche im Titel</label>
|
||||
<input id="filterQuery" class="form-control" placeholder="Titel enthält..." />
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button id="refreshDocuments" class="btn btn-primary w-100">
|
||||
<i class="fas fa-sync me-1"></i>Aktualisieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dokumente-Liste -->
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div><i class="fas fa-file-alt me-2"></i>Dokumente</div>
|
||||
<small class="text-muted" id="counts"></small>
|
||||
</div>
|
||||
<div class="card-body" id="documentsContainer">
|
||||
<div class="text-center py-5 text-muted" id="loadingState">
|
||||
<div class="spinner-border" role="status"></div>
|
||||
<p class="mt-2">Lade Dokumente...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Re-Link Modal -->
|
||||
<div class="modal fade" id="relinkModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Dokument neu verknüpfen</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-3"><strong id="relinkDocTitle"></strong></div>
|
||||
<div id="currentLinks" class="mb-3"></div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Kategorie</label>
|
||||
<select id="relinkCategory" class="form-select">
|
||||
<option value="destinataer">Destinatäre</option>
|
||||
<option value="land">Ländereien</option>
|
||||
<option value="paechter">Pächter</option>
|
||||
<option value="verpachtung">Verpachtungen</option>
|
||||
<option value="rentmeister">Rentmeister</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="form-label">Suche</label>
|
||||
<div class="input-group">
|
||||
<input id="relinkQuery" class="form-control" placeholder="Name, Ort, E-Mail, Telefon, Adresse..." />
|
||||
<button id="relinkSearch" class="btn btn-outline-secondary"><i class="fas fa-search"></i></button>
|
||||
</div>
|
||||
<small class="form-text text-muted">Durchsucht Name, Adresse, E-Mail, Telefon und weitere Felder</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3" id="relinkResults" style="max-height: 400px; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 0.375rem; padding: 0.5rem;"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.search-result-item:hover {
|
||||
background-color: #f8f9fa !important;
|
||||
border-color: #0d6efd !important;
|
||||
}
|
||||
#relinkResults::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
#relinkResults::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
#relinkResults::-webkit-scrollbar-thumb {
|
||||
background: #c1c1c1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
#relinkResults::-webkit-scrollbar-thumb:hover {
|
||||
background: #a8a8a8;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script>
|
||||
let allDocuments = [];
|
||||
let linksByPaperlessId = new Map();
|
||||
let currentRelink = { linkId: null, paperlessId: null };
|
||||
|
||||
function showMessage(message, type) {
|
||||
const statusDiv = document.getElementById('statusMessages');
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
alert.innerHTML = `${message}<button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
|
||||
statusDiv.appendChild(alert);
|
||||
setTimeout(() => alert.remove(), 5000);
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
console.log('Fetching updated document data...'); // Debug log
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
if (loadingState) {
|
||||
loadingState.style.display = 'block';
|
||||
}
|
||||
|
||||
console.log('Making API calls to:', [
|
||||
'/api/paperless/documents/?poll=1',
|
||||
'/api/link-document/list/'
|
||||
]);
|
||||
|
||||
Promise.all([
|
||||
fetch('/api/paperless/documents/?poll=1').then(r => {
|
||||
console.log('Paperless API response status:', r.status, r.ok);
|
||||
if (!r.ok) {
|
||||
throw new Error(`Paperless API failed: ${r.status} ${r.statusText}`);
|
||||
}
|
||||
return r.json();
|
||||
}),
|
||||
fetch('/api/link-document/list/').then(r => {
|
||||
console.log('Link API response status:', r.status, r.ok);
|
||||
if (!r.ok) {
|
||||
throw new Error(`Link API failed: ${r.status} ${r.statusText}`);
|
||||
}
|
||||
return r.json();
|
||||
})
|
||||
]).then(([docs, linksResp]) => {
|
||||
console.log('Data fetched successfully:', { docs: docs.documents?.length, links: linksResp.links?.length }); // Debug log
|
||||
console.log('Full docs response:', docs); // Debug the full response
|
||||
console.log('Full links response:', linksResp); // Debug the full response
|
||||
allDocuments = docs.documents || [];
|
||||
linksByPaperlessId = new Map();
|
||||
// Handle new grouped links format
|
||||
(linksResp.links || []).forEach(docLinks => {
|
||||
console.log(`Setting linksByPaperlessId for document ${docLinks.paperless_id}:`, docLinks);
|
||||
linksByPaperlessId.set(docLinks.paperless_id, docLinks);
|
||||
});
|
||||
console.log('Final linksByPaperlessId Map:', linksByPaperlessId);
|
||||
renderDocuments();
|
||||
const countsElement = document.getElementById('counts');
|
||||
if (countsElement) {
|
||||
countsElement.textContent = `Gesamt: ${docs.total_all} | Destinatäre: ${docs.total_destinaere} | Land: ${docs.total_land} | Admin: ${docs.total_admin}`;
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('Error fetching data:', err);
|
||||
console.error('Error details:', {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
name: err.name
|
||||
});
|
||||
showMessage('Fehler beim Laden der Daten: ' + err.message, 'danger');
|
||||
|
||||
// Show error in the container
|
||||
const container = document.getElementById('documentsContainer');
|
||||
if (container) {
|
||||
container.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
<h6><i class="fas fa-exclamation-triangle me-2"></i>Fehler beim Laden der Dokumente</h6>
|
||||
<p class="mb-0">${err.message}</p>
|
||||
<button class="btn btn-primary mt-2" onclick="fetchData()">
|
||||
<i class="fas fa-sync me-1"></i>Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}).finally(() => {
|
||||
const loadingState = document.getElementById('loadingState');
|
||||
if (loadingState) {
|
||||
loadingState.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderDocuments() {
|
||||
const container = document.getElementById('documentsContainer');
|
||||
const category = document.getElementById('filterCategory').value;
|
||||
const query = document.getElementById('filterQuery').value.toLowerCase();
|
||||
let filtered = allDocuments.slice();
|
||||
if (category !== 'all') {
|
||||
filtered = filtered.filter(d => d.tag_category === category);
|
||||
}
|
||||
if (query) {
|
||||
filtered = filtered.filter(d => (d.title || '').toLowerCase().includes(query));
|
||||
}
|
||||
if (!filtered.length) {
|
||||
container.innerHTML = '<p class="text-muted">Keine Dokumente gefunden.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="table-responsive"><table class="table table-striped align-middle"><thead><tr><th>Titel</th><th>Kategorie</th><th>Verknüpft mit</th><th>Aktionen</th></tr></thead><tbody>';
|
||||
filtered.forEach(doc => {
|
||||
const linkData = linksByPaperlessId.get(doc.id);
|
||||
let linkedTo = '<span class="text-muted">nicht verknüpft</span>';
|
||||
let hasLinks = false;
|
||||
|
||||
if (linkData && linkData.links && linkData.links.length > 0) {
|
||||
console.log(`Document ${doc.id} (${doc.title}) has ${linkData.links.length} links:`, linkData.links);
|
||||
hasLinks = true;
|
||||
const linkItems = linkData.links.map(link => {
|
||||
const obj = link.linked_object;
|
||||
if (!obj) {
|
||||
console.warn('Link missing linked_object:', link);
|
||||
return `<div class="mb-1"><span class="badge bg-warning me-1">Fehler</span> Fehlerhafter Link</div>`;
|
||||
}
|
||||
// Generate the appropriate detail URL based on link type
|
||||
let detailUrl = '#';
|
||||
if (link.link_type === 'destinataer') {
|
||||
detailUrl = `/destinataere/${obj.id}/`;
|
||||
} else if (link.link_type === 'land') {
|
||||
detailUrl = `/laendereien/${obj.id}/`;
|
||||
} else if (link.link_type === 'paechter') {
|
||||
detailUrl = `/paechter/${obj.id}/`;
|
||||
} else if (link.link_type === 'verpachtung') {
|
||||
detailUrl = `/verpachtungen/${obj.id}/`;
|
||||
} else if (link.link_type === 'rentmeister') {
|
||||
detailUrl = `/geschaeftsfuehrung/rentmeister/${obj.id}/`;
|
||||
}
|
||||
|
||||
return `<div class="mb-1 d-flex align-items-center justify-content-between">
|
||||
<div class="flex-grow-1">
|
||||
<span class="badge bg-info me-1">${obj?.type || 'Unbekannt'}</span>
|
||||
<a href="${detailUrl}" class="text-decoration-none small text-primary" title="Zu ${obj?.type || 'Entität'} navigieren">
|
||||
${obj?.name || 'Unbekannt'}
|
||||
<i class="fas fa-external-link-alt ms-1" style="font-size: 0.7em;"></i>
|
||||
</a>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline-danger ms-2" onclick="onDeleteLink('${link.id}')" title="Diese Verknüpfung löschen">
|
||||
<i class="fas fa-times" style="font-size: 0.7em;"></i>
|
||||
</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
linkedTo = `<div class="small">${linkItems}</div>`;
|
||||
console.log(`Final linkedTo HTML for doc ${doc.id}:`, linkedTo);
|
||||
}
|
||||
|
||||
const openUrl = `/api/paperless/documents/${doc.id}/`;
|
||||
html += `
|
||||
<tr>
|
||||
<td><strong>${doc.title || 'Ohne Titel'}</strong><br><small class="text-muted">Paperless-ID: ${doc.id}</small></td>
|
||||
<td><span class="badge bg-secondary">${doc.tag_category}</span></td>
|
||||
<td>${linkedTo}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm btn-outline-primary" href="${openUrl}" target="_blank" title="In Paperless öffnen"><i class="fas fa-external-link-alt"></i></a>
|
||||
${hasLinks ? `<button class="btn btn-sm btn-outline-danger" onclick="onDeleteAllLinks(${doc.id})" title="Alle Verknüpfungen löschen"><i class="fas fa-trash"></i></button>` : ''}
|
||||
<button class="btn btn-sm btn-outline-success" onclick="onRelink('', ${doc.id}, '${(doc.title||'').replace(/'/g, "'")}')" title="Neu verknüpfen"><i class="fas fa-link"></i></button>
|
||||
</td>
|
||||
</tr>`;
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
function onRelink(linkId, paperlessId, title) {
|
||||
currentRelink = { linkId, paperlessId };
|
||||
document.getElementById('relinkDocTitle').textContent = title;
|
||||
|
||||
// Show current links in modal
|
||||
const linkData = linksByPaperlessId.get(paperlessId);
|
||||
const currentLinksDiv = document.getElementById('currentLinks');
|
||||
if (linkData && linkData.links && linkData.links.length > 0) {
|
||||
let currentLinksHtml = '<div class="alert alert-info"><strong>Aktuell verknüpft mit:</strong><ul class="mb-0 mt-2">';
|
||||
linkData.links.forEach(link => {
|
||||
const obj = link.linked_object;
|
||||
currentLinksHtml += `<li><span class="badge bg-secondary me-1">${obj?.type || 'Unbekannt'}</span> ${obj?.name || 'Unbekannt'}</li>`;
|
||||
});
|
||||
currentLinksHtml += '</ul></div>';
|
||||
currentLinksDiv.innerHTML = currentLinksHtml;
|
||||
} else {
|
||||
currentLinksDiv.innerHTML = '<div class="alert alert-warning">Dieses Dokument ist noch nicht verknüpft.</div>';
|
||||
}
|
||||
|
||||
document.getElementById('relinkResults').innerHTML = '';
|
||||
const modal = new bootstrap.Modal(document.getElementById('relinkModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
document.getElementById('relinkSearch').addEventListener('click', function() {
|
||||
const category = document.getElementById('relinkCategory').value;
|
||||
const q = document.getElementById('relinkQuery').value || 'all';
|
||||
const target = document.getElementById('relinkResults');
|
||||
target.innerHTML = '<div class="d-flex align-items-center"><div class="spinner-border spinner-border-sm me-2" role="status"></div>Suche...</div>';
|
||||
fetch(`/api/link-document/search/?q=${encodeURIComponent(q)}&category=${category}`)
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
let items = data[category] || [];
|
||||
if (!items.length) {
|
||||
target.innerHTML = '<div class="text-center py-3 text-muted"><i class="fas fa-search me-2"></i>Keine Treffer gefunden.</div>';
|
||||
return;
|
||||
}
|
||||
let html = `<div class="mb-2"><small class="text-muted">${items.length} Treffer gefunden</small></div>`;
|
||||
|
||||
// Get current links for this document to mark already linked entities
|
||||
const linkData = linksByPaperlessId.get(currentRelink.paperlessId);
|
||||
const currentlyLinkedIds = new Set();
|
||||
if (linkData && linkData.links) {
|
||||
linkData.links.forEach(link => {
|
||||
if (link.linked_object && link.linked_object.id) {
|
||||
currentlyLinkedIds.add(link.linked_object.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
items.forEach(it => {
|
||||
const isLinked = currentlyLinkedIds.has(it.id);
|
||||
const linkClass = isLinked ? 'border-success bg-light' : 'border';
|
||||
const buttonClass = isLinked ? 'btn-success' : 'btn-outline-primary';
|
||||
const buttonIcon = isLinked ? 'fas fa-check-circle' : 'fas fa-plus';
|
||||
const buttonText = isLinked ? 'Bereits verknüpft' : 'Verknüpfen';
|
||||
|
||||
html += `<div class="d-flex justify-content-between align-items-start ${linkClass} rounded p-3 mb-2 search-result-item" style="transition: all 0.2s;">
|
||||
<div class="flex-grow-1">
|
||||
<div class="fw-bold mb-1">${it.name}</div>
|
||||
<div class="text-muted small mb-1">${it.details || ''}</div>
|
||||
${isLinked ? '<span class="badge bg-success mt-1"><i class="fas fa-check-circle me-1"></i>Aktuell verknüpft</span>' : ''}
|
||||
</div>
|
||||
<button class="btn btn-sm ${buttonClass} ms-3" onclick="confirmRelink('${it.id}', '${category}')" title="${buttonText}" ${isLinked ? 'disabled' : ''}>
|
||||
<i class="${buttonIcon} me-1"></i>${isLinked ? 'Verknüpft' : 'Auswählen'}
|
||||
</button>
|
||||
</div>`;
|
||||
});
|
||||
target.innerHTML = html;
|
||||
})
|
||||
.catch(() => { target.innerHTML = '<div class="text-center py-3 text-danger"><i class="fas fa-exclamation-triangle me-2"></i>Fehler bei der Suche</div>'; });
|
||||
});
|
||||
|
||||
function onDeleteAllLinks(paperlessId) {
|
||||
const linkData = linksByPaperlessId.get(paperlessId);
|
||||
if (!linkData || !linkData.links || linkData.links.length === 0) {
|
||||
showMessage('Keine Verknüpfungen zum Löschen gefunden', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`Möchten Sie wirklich alle ${linkData.links.length} Verknüpfung(en) für dieses Dokument löschen?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Deleting all ${linkData.links.length} links for document ${paperlessId}:`, linkData.links);
|
||||
|
||||
// Delete all links for this document with proper CSRF token
|
||||
const deletePromises = linkData.links.map(link => {
|
||||
console.log(`Deleting link ${link.id}`);
|
||||
return fetch(`/api/link-document/delete/${link.id}/`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-CSRFToken': getCookie('csrftoken') }
|
||||
});
|
||||
});
|
||||
|
||||
Promise.all(deletePromises).then(responses => {
|
||||
console.log('All delete responses:', responses.map(r => ({ status: r.status, ok: r.ok })));
|
||||
const allSuccessful = responses.every(r => r.ok);
|
||||
const failedCount = responses.filter(r => !r.ok).length;
|
||||
|
||||
if (allSuccessful) {
|
||||
showMessage('Alle Verknüpfungen erfolgreich gelöscht', 'success');
|
||||
setTimeout(() => {
|
||||
fetchData();
|
||||
}, 300);
|
||||
} else {
|
||||
console.error(`${failedCount} of ${responses.length} delete requests failed`);
|
||||
showMessage(`Fehler beim Löschen von ${failedCount} Verknüpfung(en)`, 'danger');
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('Error during bulk delete:', err);
|
||||
showMessage('Fehler beim Löschen der Verknüpfungen', 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
// Add Enter key support for search
|
||||
document.getElementById('relinkQuery').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
document.getElementById('relinkSearch').click();
|
||||
}
|
||||
});
|
||||
|
||||
function confirmRelink(targetId, category) {
|
||||
console.log('confirmRelink called:', { targetId, category, currentRelink });
|
||||
|
||||
const isUpdate = !!currentRelink.linkId;
|
||||
const url = isUpdate ? '/api/link-document/update/' : '/api/link-document/create/';
|
||||
const payload = isUpdate ? {
|
||||
link_id: currentRelink.linkId,
|
||||
link_type: category,
|
||||
link_id_target: targetId
|
||||
} : {
|
||||
paperless_id: currentRelink.paperlessId,
|
||||
paperless_title: document.getElementById('relinkDocTitle').textContent,
|
||||
paperless_url: `/api/paperless/documents/${currentRelink.paperlessId}/`,
|
||||
link_type: category,
|
||||
link_id: targetId
|
||||
};
|
||||
|
||||
console.log('Sending request:', { url, payload });
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') },
|
||||
body: JSON.stringify(payload)
|
||||
}).then(async r => {
|
||||
console.log('Response status:', r.status, r.ok);
|
||||
|
||||
let resp = {};
|
||||
try {
|
||||
resp = await r.json();
|
||||
console.log('Response data:', resp);
|
||||
} catch (e) {
|
||||
console.log('No JSON response, treating as success if status OK');
|
||||
if (r.ok) {
|
||||
resp = { success: true };
|
||||
} else {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
}
|
||||
|
||||
if (r.ok && (resp.success || resp.message)) {
|
||||
console.log('Success! Showing message and refreshing...');
|
||||
showMessage(resp.message || 'Verknüpfung gespeichert', 'success');
|
||||
|
||||
// Close modal first, then refresh data
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('relinkModal'));
|
||||
if (modal) {
|
||||
console.log('Closing modal...');
|
||||
modal.hide();
|
||||
}
|
||||
|
||||
// Clear search results to prevent confusion
|
||||
document.getElementById('relinkResults').innerHTML = '';
|
||||
document.getElementById('relinkQuery').value = '';
|
||||
|
||||
// Try immediate refresh first
|
||||
console.log('Calling fetchData() immediately...');
|
||||
fetchData();
|
||||
|
||||
// Also schedule a backup refresh to be sure
|
||||
console.log('Scheduling backup refresh in 500ms...');
|
||||
setTimeout(() => {
|
||||
console.log('Backup fetchData() call...');
|
||||
fetchData();
|
||||
}, 500);
|
||||
} else {
|
||||
console.log('Error response:', resp);
|
||||
showMessage(resp.error || 'Fehler beim Speichern', 'danger');
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('Relink error:', err);
|
||||
showMessage('Fehler beim Speichern', 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function onDeleteLink(linkId) {
|
||||
if (!confirm('Diese Verknüpfung wirklich löschen?')) return;
|
||||
|
||||
console.log('Deleting individual link:', linkId);
|
||||
|
||||
fetch(`/api/link-document/delete/${linkId}/`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'X-CSRFToken': getCookie('csrftoken') }
|
||||
})
|
||||
.then(async r => {
|
||||
console.log('Delete response status:', r.status, r.ok);
|
||||
let data = {};
|
||||
try {
|
||||
data = await r.json();
|
||||
console.log('Delete response data:', data);
|
||||
} catch (_) {
|
||||
console.log('No JSON response, treating as success if status OK');
|
||||
}
|
||||
|
||||
if (r.ok && (data.success === undefined || data.success === true)) {
|
||||
showMessage('Verknüpfung gelöscht', 'success');
|
||||
setTimeout(() => {
|
||||
fetchData();
|
||||
}, 300);
|
||||
} else {
|
||||
showMessage((data && data.error) || 'Fehler beim Löschen', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Delete error:', err);
|
||||
showMessage('Fehler beim Löschen', 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
document.getElementById('refreshDocuments').addEventListener('click', fetchData);
|
||||
document.getElementById('filterCategory').addEventListener('change', renderDocuments);
|
||||
document.getElementById('filterQuery').addEventListener('input', () => { renderDocuments(); });
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Dokumentenverwaltung page loaded, calling fetchData()...');
|
||||
fetchData();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user