feat: async email slanje s cache PDF fallbackom i admin logom

Prebaci slanje emaila putnog naloga u pozadinski task bez blokiranja UI-a i dodaj fallback kad queue nije dostupna. Kod greške slanja spremi generirane PDF-ove u cache i pošalji notifikaciju s download linkovima.

Dodaj EmailDispatchLog model i Django admin pregled za praćenje kome je poslano, što je poslano, status i detalje privitaka.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
mariomitte
2026-07-21 14:41:45 +02:00
parent 42fcd836ed
commit 5945423346
11 changed files with 1338 additions and 85 deletions

View File

@@ -24,6 +24,7 @@ function detectNotificationKind(notification) {
const title = String(notification?.title || '').toLowerCase();
if (title.includes('servisni kontekst')) return 'service_context';
if (title.includes('pdf')) return 'work_order_pdf';
if (title.includes('email')) return 'work_order_email';
if (title.includes('putni nalog kreiran')) return 'work_order_created';
if (title.includes('novi putni nalog')) return 'work_order_assigned';
if (title.includes('putni nalog zatvoren')) return 'work_order_closed';
@@ -167,6 +168,9 @@ export default function NotificationDetailModal({ open, notification, onClose })
notification?.metadata?.stage === 'completed' &&
notification?.metadata?.work_order_id
);
const failedEmailCachedPdfs = Array.isArray(notification?.metadata?.cached_pdfs)
? notification.metadata.cached_pdfs
: [];
if (!open || !notification) {
return null;
@@ -217,6 +221,23 @@ export default function NotificationDetailModal({ open, notification, onClose })
{action.label}
</button>
)}
{failedEmailCachedPdfs.length > 0 && (
<div className="mt-3 space-y-2">
<p className="text-xs font-semibold uppercase tracking-wide text-text-muted">
Cached PDF datoteke
</p>
{failedEmailCachedPdfs.map((item, index) => (
<button
key={`${item.generated_pdf_id || index}-${item.filename || 'pdf'}`}
type="button"
onClick={() => downloadGeneratedPdfByUrl(item.download_url, item.filename || `work-order-${index + 1}.pdf`)}
className="block text-left text-sm font-medium text-indigo-600 underline underline-offset-2 hover:text-indigo-700"
>
{item.filename || `PDF ${index + 1}`}
</button>
))}
</div>
)}
</div>
<div className="rounded-lg border border-border-hairline bg-canvas-base p-3">