357 lines
16 KiB
HTML
357 lines
16 KiB
HTML
{% load i18n %}
|
|
<section id="pdfSummaryContent" class="bg-white rounded-2xl p-4 shadow-sm border border-slate-100">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-bold text-navy flex items-center gap-2">
|
|
<i data-lucide="file-text" class="w-6 h-6"></i> {% trans "PDF Report" %}
|
|
</h3>
|
|
</div>
|
|
|
|
<p class="text-slate text-sm mb-4">
|
|
{% trans "Generate a complaint report PDF with the actual complaint and department response." %}
|
|
</p>
|
|
|
|
<!-- State: checking -->
|
|
<div id="pdfSummaryChecking" class="text-center py-10">
|
|
<div class="inline-block w-8 h-8 border-[3px] border-navy/20 border-t-navy rounded-full animate-spin"></div>
|
|
<p class="text-slate text-sm mt-4">{% trans "Checking..." %}</p>
|
|
</div>
|
|
|
|
<!-- State: empty -->
|
|
<div id="pdfSummaryEmpty" class="hidden">
|
|
<button onclick="generateSummaries()" id="generateSummariesBtn"
|
|
class="inline-flex items-center gap-2.5 px-4 py-2 text-xs bg-gradient-to-r from-navy to-blue text-white rounded-lg font-bold hover:from-blue hover:to-navy transition shadow-lg w-full justify-center">
|
|
<i data-lucide="file-text" class="w-4 h-4"></i>
|
|
{% trans "Generate Report" %}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- State: loading_ai -->
|
|
<div id="pdfSummaryLoadingAi" class="text-center py-10 hidden">
|
|
<div class="inline-block w-8 h-8 border-[3px] border-navy/20 border-t-navy rounded-full animate-spin"></div>
|
|
<p class="text-slate text-sm mt-4">{% trans "Preparing report..." %}</p>
|
|
<p class="text-slate text-xs mt-1">{% trans "Compiling complaint data" %}</p>
|
|
</div>
|
|
|
|
<!-- State: edit (read-only display) -->
|
|
<div id="pdfSummaryEdit" class="hidden">
|
|
<div class="mb-5">
|
|
<h4 class="text-sm font-bold text-navy mb-3 pb-2 border-b border-slate-200">{% trans "Complaint Info" %}</h4>
|
|
<div class="grid grid-cols-2 gap-3 mb-4">
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Name" %}</p>
|
|
<p id="fld_complainant_name" class="text-sm text-slate bg-slate-50 rounded-lg p-2.5">—</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Source" %}</p>
|
|
<p id="fld_source_name" class="text-sm text-slate bg-slate-50 rounded-lg p-2.5">—</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Submission Date" %}</p>
|
|
<p id="fld_submission_date" class="text-sm text-slate bg-slate-50 rounded-lg p-2.5">—</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Incident Date" %}</p>
|
|
<p id="fld_incident_date" class="text-sm text-slate bg-slate-50 rounded-lg p-2.5">—</p>
|
|
</div>
|
|
<div class="col-span-2">
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Department" %}</p>
|
|
<p id="fld_department_name" class="text-sm text-slate bg-slate-50 rounded-lg p-2.5">—</p>
|
|
</div>
|
|
</div>
|
|
|
|
<h4 class="text-sm font-bold text-navy mb-3 pb-2 border-b border-slate-200">{% trans "Staff Details" %}</h4>
|
|
<div class="grid grid-cols-2 gap-3 mb-4">
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Staff Name" %}</p>
|
|
<p id="fld_accused_staff_name" class="text-sm text-slate bg-slate-50 rounded-lg p-2.5">—</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Job Title" %}</p>
|
|
<p id="fld_accused_staff_title" class="text-sm text-slate bg-slate-50 rounded-lg p-2.5">—</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Sent to Dept" %}</p>
|
|
<p id="fld_sent_to_dept_date" class="text-sm text-slate bg-slate-50 rounded-lg p-2.5">—</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Response Date" %}</p>
|
|
<p id="fld_response_date" class="text-sm text-slate bg-slate-50 rounded-lg p-2.5">—</p>
|
|
</div>
|
|
</div>
|
|
|
|
<h4 class="text-sm font-bold text-navy mb-3 pb-2 border-b border-slate-200">{% trans "Content" %}</h4>
|
|
<div class="mb-3">
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Complaint" %}</p>
|
|
<p id="fld_content_summary" class="text-sm text-slate bg-slate-50 rounded-xl p-3 leading-relaxed">—</p>
|
|
</div>
|
|
<div class="mb-5">
|
|
<p class="text-xs font-semibold text-slate mb-1">{% trans "Department Response" %}</p>
|
|
<p id="fld_dept_response_summary" class="text-sm text-slate bg-slate-50 rounded-xl p-3 leading-relaxed">—</p>
|
|
</div>
|
|
|
|
<button onclick="generatePdf()" id="generatePdfBtn"
|
|
class="inline-flex items-center gap-2 px-4 py-2 text-xs bg-gradient-to-r from-navy to-blue text-white rounded-lg font-bold hover:from-blue hover:to-navy transition shadow-lg w-full justify-center">
|
|
<i data-lucide="file-text" class="w-4 h-4"></i>
|
|
{% trans "Generate PDF" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- State: loading_pdf -->
|
|
<div id="pdfSummaryLoadingPdf" class="text-center py-10 hidden">
|
|
<div class="inline-block w-8 h-8 border-[3px] border-navy/20 border-t-navy rounded-full animate-spin"></div>
|
|
<p class="text-slate text-sm mt-4">{% trans "Generating your PDF..." %}</p>
|
|
<p class="text-slate text-xs mt-1">{% trans "This may take a moment" %}</p>
|
|
</div>
|
|
|
|
<!-- State: ready -->
|
|
<div id="pdfSummaryReady" class="hidden">
|
|
<div class="bg-green-50 border border-green-200 rounded-xl p-4 flex items-start gap-3 mb-4">
|
|
<i data-lucide="check-circle" class="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5"></i>
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-green-700 text-sm font-semibold">{% trans "PDF report ready" %}</p>
|
|
<p class="text-green-600 text-xs mt-0.5">{% trans "You can download or regenerate the report" %}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border border-slate-200 rounded-xl overflow-hidden mb-4 bg-slate-50">
|
|
<iframe id="pdfSummaryIframe" class="w-full border-0" style="height:550px;"></iframe>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3">
|
|
<a id="pdfSummaryDownloadLink" href="#" download
|
|
class="inline-flex items-center gap-2 px-4 py-2 text-xs bg-gradient-to-r from-navy to-blue text-white rounded-lg font-bold hover:from-blue hover:to-navy transition shadow-lg flex-1 justify-center">
|
|
<i data-lucide="download" class="w-4 h-4"></i>
|
|
{% trans "Download PDF" %}
|
|
</a>
|
|
<button onclick="generateSummaries()"
|
|
class="inline-flex items-center gap-2 px-3 py-2 bg-slate-100 rounded-lg text-xs text-slate hover:bg-slate-200 transition font-semibold whitespace-nowrap">
|
|
<i data-lucide="refresh-cw" class="w-3.5 h-3.5"></i>
|
|
{% trans "Refresh Data" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- State: error -->
|
|
<div id="pdfSummaryError" class="hidden">
|
|
<div class="bg-red-50 border border-red-200 rounded-xl p-4 flex items-start gap-3">
|
|
<i data-lucide="alert-circle" class="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5"></i>
|
|
<div>
|
|
<p class="text-red-700 text-sm font-semibold">{% trans "Generation Failed" %}</p>
|
|
<p id="pdfSummaryErrorMsg" class="text-red-600 text-xs mt-1"></p>
|
|
</div>
|
|
</div>
|
|
<div class="text-center mt-4">
|
|
<button onclick="clearError()"
|
|
class="inline-flex items-center gap-2 px-4 py-2 bg-slate-100 rounded-lg text-sm text-slate hover:bg-slate-200 transition">
|
|
<i data-lucide="arrow-left" class="w-3.5 h-3.5"></i>
|
|
{% trans "Back" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<script>
|
|
(function() {
|
|
let _pdfBlobUrl = null;
|
|
let _fileUrl = null;
|
|
|
|
const FIELD_IDS = [
|
|
'complainant_name', 'source_name', 'submission_date', 'incident_date',
|
|
'department_name', 'accused_staff_name', 'accused_staff_title',
|
|
'sent_to_dept_date', 'response_date', 'content_summary', 'dept_response_summary'
|
|
];
|
|
|
|
function populateFields(data, prefix) {
|
|
for (const id of FIELD_IDS) {
|
|
const el = document.getElementById(prefix + id);
|
|
if (el && data[id] !== undefined) el.textContent = data[id] || '—';
|
|
}
|
|
}
|
|
|
|
function collectFields(prefix) {
|
|
const data = {};
|
|
for (const id of FIELD_IDS) {
|
|
const el = document.getElementById(prefix + id);
|
|
if (el) data[id] = el.textContent;
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function showState(state) {
|
|
document.getElementById('pdfSummaryChecking').classList.toggle('hidden', state !== 'checking');
|
|
document.getElementById('pdfSummaryEmpty').classList.toggle('hidden', state !== 'empty');
|
|
document.getElementById('pdfSummaryLoadingAi').classList.toggle('hidden', state !== 'loading_ai');
|
|
document.getElementById('pdfSummaryEdit').classList.toggle('hidden', state !== 'edit');
|
|
document.getElementById('pdfSummaryLoadingPdf').classList.toggle('hidden', state !== 'loading_pdf');
|
|
document.getElementById('pdfSummaryReady').classList.toggle('hidden', state !== 'ready');
|
|
document.getElementById('pdfSummaryError').classList.toggle('hidden', state !== 'error');
|
|
try { lucide.createIcons(); } catch(e) {}
|
|
}
|
|
|
|
function setError(msg) {
|
|
document.getElementById('pdfSummaryErrorMsg').textContent = msg;
|
|
showState('error');
|
|
}
|
|
|
|
window.clearError = function() {
|
|
document.getElementById('pdfSummaryEmpty').classList.remove('hidden');
|
|
document.getElementById('pdfSummaryError').classList.add('hidden');
|
|
};
|
|
|
|
window.toggleEditForm = function() {
|
|
const form = document.getElementById('pdfSummaryReadyEdit');
|
|
const isHidden = form.classList.contains('hidden');
|
|
form.classList.toggle('hidden');
|
|
document.getElementById('toggleEditBtn').innerHTML = isHidden
|
|
? '<i data-lucide="x" class="w-3.5 h-3.5"></i> إغلاق'
|
|
: '<i data-lucide="edit" class="w-3.5 h-3.5"></i> تعديل النص';
|
|
try { lucide.createIcons(); } catch(e) {}
|
|
};
|
|
|
|
// Check existing summary on load
|
|
fetch('/complaints/api/complaints/{{ complaint.id }}/summary_preview/', {
|
|
method: 'GET',
|
|
headers: { 'X-CSRFToken': getCSRFToken() },
|
|
credentials: 'same-origin',
|
|
})
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data.exists) {
|
|
populateFields(data, 'fld_');
|
|
populateFields(data, 'fld2_');
|
|
if (data.has_file) {
|
|
_fileUrl = data.file_url;
|
|
document.getElementById('pdfSummaryDownloadLink').href = data.file_url;
|
|
fetch(data.file_url)
|
|
.then(r => r.blob())
|
|
.then(blob => {
|
|
var blobUrl = URL.createObjectURL(blob);
|
|
document.getElementById('pdfSummaryIframe').src = blobUrl;
|
|
});
|
|
showState('ready');
|
|
} else {
|
|
showState('edit');
|
|
}
|
|
} else {
|
|
// Populate with computed DB values even without saved summary
|
|
populateFields(data, 'fld_');
|
|
populateFields(data, 'fld2_');
|
|
showState('empty');
|
|
}
|
|
})
|
|
.catch(() => {
|
|
showState('empty');
|
|
});
|
|
|
|
// Generate AI summaries (POST summary_preview)
|
|
window.generateSummaries = function() {
|
|
showState('loading_ai');
|
|
|
|
fetch('/complaints/api/complaints/{{ complaint.id }}/summary_preview/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': getCSRFToken(),
|
|
},
|
|
credentials: 'same-origin',
|
|
body: JSON.stringify({ lang: 'ar' })
|
|
})
|
|
.then(r => {
|
|
if (!r.ok) return r.json().then(d => { throw new Error(d.error || 'Request failed'); });
|
|
return r.json();
|
|
})
|
|
.then(data => {
|
|
populateFields(data, 'fld_');
|
|
populateFields(data, 'fld2_');
|
|
showState('edit');
|
|
})
|
|
.catch(err => {
|
|
setError(err.message || '{% trans "An unexpected error occurred" %}');
|
|
});
|
|
};
|
|
|
|
// Generate PDF (POST generate_summary_pdf)
|
|
function doGeneratePdf(prefix) {
|
|
showState('loading_pdf');
|
|
|
|
if (_pdfBlobUrl) {
|
|
window.URL.revokeObjectURL(_pdfBlobUrl);
|
|
_pdfBlobUrl = null;
|
|
}
|
|
|
|
const body = {
|
|
lang: 'ar',
|
|
...collectFields(prefix),
|
|
};
|
|
|
|
fetch('/complaints/api/complaints/{{ complaint.id }}/generate_summary_pdf/', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': getCSRFToken(),
|
|
},
|
|
credentials: 'same-origin',
|
|
body: JSON.stringify(body)
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
return response.json().then(data => {
|
|
throw new Error(data.error || 'Request failed');
|
|
}).catch(e => {
|
|
if (e instanceof SyntaxError) {
|
|
throw new Error('Server returned an error (' + response.status + ')');
|
|
}
|
|
throw e;
|
|
});
|
|
}
|
|
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
let filename = 'complaint_report.pdf';
|
|
if (disposition && disposition.indexOf('filename=') !== -1) {
|
|
const matches = disposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
|
|
if (matches && matches[1]) {
|
|
filename = matches[1].replace(/['"]/g, '');
|
|
}
|
|
}
|
|
|
|
return response.blob().then(blob => ({ blob, filename }));
|
|
})
|
|
.then(({ blob, filename }) => {
|
|
_pdfBlobUrl = window.URL.createObjectURL(blob);
|
|
|
|
document.getElementById('pdfSummaryIframe').src = _pdfBlobUrl;
|
|
document.getElementById('pdfSummaryDownloadLink').href = _pdfBlobUrl;
|
|
document.getElementById('pdfSummaryDownloadLink').download = filename;
|
|
|
|
const a = document.createElement('a');
|
|
a.href = _pdfBlobUrl;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
|
|
// Sync the other form set
|
|
if (prefix === 'fld_') {
|
|
populateFields(collectFields('fld_'), 'fld2_');
|
|
} else {
|
|
populateFields(collectFields('fld2_'), 'fld_');
|
|
}
|
|
|
|
showState('ready');
|
|
})
|
|
.catch(err => {
|
|
setError(err.message || '{% trans "An unexpected error occurred" %}');
|
|
});
|
|
}
|
|
|
|
window.generatePdf = function() {
|
|
doGeneratePdf('fld_');
|
|
};
|
|
|
|
window.generatePdfFromReady = function() {
|
|
doGeneratePdf('fld2_');
|
|
};
|
|
})();
|
|
</script>
|