Prepare ERP source for Gitea deployment
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Email Audit Dashboard</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Monitor SMTP delivery, retry queue, incoming IMAP messages, template activity and email-to-work mapping health.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/email/settings" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Settings</a>
|
||||
<a href="/email/logs" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Logs</a>
|
||||
<a href="/email/queue" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Queue</a>
|
||||
<a href="/email/inbox" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Inbox</a>
|
||||
<a href="/email/templates" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Templates</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div class="rounded-2xl bg-white p-5 shadow-card ring-1 ring-slate-200">
|
||||
<div class="text-sm font-medium text-slate-500">SMTP Status</div>
|
||||
<div class="mt-3 flex items-center justify-between gap-3">
|
||||
<div class="text-2xl font-semibold text-slate-900">{{ 'Ready' if smtp_configured else 'Pending' }}</div>
|
||||
{% if smtp_configured %}
|
||||
<span class="rounded-full bg-emerald-50 px-3 py-1 text-xs font-semibold text-emerald-700">Configured</span>
|
||||
{% else %}
|
||||
<span class="rounded-full bg-amber-50 px-3 py-1 text-xs font-semibold text-amber-700">Needs setup</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-slate-500">{{ setting.smtp_host if setting and setting.smtp_host else 'SMTP host not configured' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl bg-white p-5 shadow-card ring-1 ring-slate-200">
|
||||
<div class="text-sm font-medium text-slate-500">Sent in 24 Hours</div>
|
||||
<div class="mt-3 text-3xl font-semibold text-slate-900">{{ summary.sent_24h }}</div>
|
||||
<p class="mt-2 text-xs text-slate-500">Last 7 days sent: {{ summary.sent_7d }}</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl bg-white p-5 shadow-card ring-1 ring-slate-200">
|
||||
<div class="text-sm font-medium text-slate-500">Failed in 24 Hours</div>
|
||||
<div class="mt-3 text-3xl font-semibold {% if summary.failed_24h %}text-red-700{% else %}text-slate-900{% endif %}">{{ summary.failed_24h }}</div>
|
||||
<p class="mt-2 text-xs text-slate-500">Last 7 days failed: {{ summary.failed_7d }}</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl bg-white p-5 shadow-card ring-1 ring-slate-200">
|
||||
<div class="text-sm font-medium text-slate-500">Queue Pending</div>
|
||||
<div class="mt-3 text-3xl font-semibold {% if summary.pending_now %}text-amber-700{% else %}text-slate-900{% endif %}">{{ summary.pending_now }}</div>
|
||||
<p class="mt-2 text-xs text-slate-500">Retryable: {{ summary.queue_retryable }} | Exhausted: {{ summary.queue_exhausted }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<div class="rounded-2xl bg-white p-5 shadow-card ring-1 ring-slate-200">
|
||||
<div class="text-sm font-medium text-slate-500">IMAP Status</div>
|
||||
<div class="mt-3 flex items-center justify-between gap-3">
|
||||
<div class="text-2xl font-semibold text-slate-900">{{ 'Ready' if imap_configured else 'Pending' }}</div>
|
||||
{% if imap_configured %}
|
||||
<span class="rounded-full bg-emerald-50 px-3 py-1 text-xs font-semibold text-emerald-700">Configured</span>
|
||||
{% else %}
|
||||
<span class="rounded-full bg-amber-50 px-3 py-1 text-xs font-semibold text-amber-700">Needs setup</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-slate-500">{{ setting.imap_host if setting and setting.imap_host else 'IMAP host not configured' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl bg-white p-5 shadow-card ring-1 ring-slate-200">
|
||||
<div class="text-sm font-medium text-slate-500">Incoming Emails</div>
|
||||
<div class="mt-3 text-3xl font-semibold text-slate-900">{{ summary.incoming_7d }}</div>
|
||||
<p class="mt-2 text-xs text-slate-500">Fetched in last 7 days</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl bg-white p-5 shadow-card ring-1 ring-slate-200">
|
||||
<div class="text-sm font-medium text-slate-500">Unmapped Incoming</div>
|
||||
<div class="mt-3 text-3xl font-semibold {% if summary.incoming_unmapped %}text-amber-700{% else %}text-slate-900{% endif %}">{{ summary.incoming_unmapped }}</div>
|
||||
<p class="mt-2 text-xs text-slate-500">Mapped: {{ summary.incoming_mapped }}</p>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl bg-white p-5 shadow-card ring-1 ring-slate-200">
|
||||
<div class="text-sm font-medium text-slate-500">Templates</div>
|
||||
<div class="mt-3 text-3xl font-semibold text-slate-900">{{ summary.active_templates }}</div>
|
||||
<p class="mt-2 text-xs text-slate-500">Inactive: {{ summary.inactive_templates }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-2">
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<h3 class="text-base font-semibold text-slate-900">7-Day Delivery Summary</h3>
|
||||
<span class="text-xs text-slate-500">Generated: {{ generated_at }}</span>
|
||||
</div>
|
||||
<div class="mt-4 overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead><tr class="text-left text-xs uppercase tracking-wide text-slate-500"><th class="py-2 pr-4">Status</th><th class="py-2 pr-4 text-right">Count</th></tr></thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in status_summary %}
|
||||
<tr><td class="py-3 pr-4"><span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold text-slate-700">{{ row.status }}</span></td><td class="py-3 pr-4 text-right font-semibold">{{ row.count }}</td></tr>
|
||||
{% else %}
|
||||
<tr><td colspan="2" class="py-6 text-center text-slate-500">No email activity in the last 7 days.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<h3 class="text-base font-semibold text-slate-900">Template Activity</h3>
|
||||
<div class="mt-4 overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead><tr class="text-left text-xs uppercase tracking-wide text-slate-500"><th class="py-2 pr-4">Template</th><th class="py-2 pr-4 text-right">Sent</th><th class="py-2 pr-4 text-right">Failed</th><th class="py-2 pr-4 text-right">Pending</th><th class="py-2 pr-4 text-right">Total</th></tr></thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in template_summary_rows %}
|
||||
<tr>
|
||||
<td class="py-3 pr-4 font-medium text-slate-800">{{ row.template_code }}</td>
|
||||
<td class="py-3 pr-4 text-right text-emerald-700">{{ row.SENT }}</td>
|
||||
<td class="py-3 pr-4 text-right text-red-700">{{ row.FAILED }}</td>
|
||||
<td class="py-3 pr-4 text-right text-amber-700">{{ row.PENDING }}</td>
|
||||
<td class="py-3 pr-4 text-right font-semibold">{{ row.total }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="py-6 text-center text-slate-500">No template-wise activity found.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-3">
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex items-center justify-between gap-2"><h3 class="text-base font-semibold text-slate-900">Recent Failed Emails</h3><a href="/email/logs" class="text-xs font-semibold text-brand-700 hover:underline">View logs</a></div>
|
||||
<div class="mt-4 space-y-3">
|
||||
{% for log in recent_failed %}
|
||||
<div class="rounded-xl border border-red-100 bg-red-50 p-3">
|
||||
<div class="text-sm font-semibold text-slate-900">{{ log.recipient_email }}</div>
|
||||
<div class="mt-1 truncate text-xs text-slate-700">{{ log.subject }}</div>
|
||||
<div class="mt-1 line-clamp-2 text-xs text-red-700">{{ log.error_message or 'No error message stored.' }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="rounded-xl border border-emerald-100 bg-emerald-50 p-4 text-sm text-emerald-800">No recent failed emails.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex items-center justify-between gap-2"><h3 class="text-base font-semibold text-slate-900">Queue Attention</h3><a href="/email/queue" class="text-xs font-semibold text-brand-700 hover:underline">Process queue</a></div>
|
||||
<div class="mt-4 space-y-3">
|
||||
{% for log in queue_due %}
|
||||
<div class="rounded-xl border border-amber-100 bg-amber-50 p-3">
|
||||
<div class="flex justify-between gap-2"><span class="text-sm font-semibold text-slate-900">{{ log.status }}</span><span class="text-xs text-slate-500">{{ log.attempt_count }}/{{ log.max_attempts }}</span></div>
|
||||
<div class="mt-1 truncate text-xs text-slate-700">{{ log.recipient_email }}</div>
|
||||
<div class="mt-1 truncate text-xs text-slate-500">{{ log.subject }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="rounded-xl border border-emerald-100 bg-emerald-50 p-4 text-sm text-emerald-800">No pending retry items.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex items-center justify-between gap-2"><h3 class="text-base font-semibold text-slate-900">Incoming Mapping Attention</h3><a href="/email/inbox" class="text-xs font-semibold text-brand-700 hover:underline">Open inbox</a></div>
|
||||
<div class="mt-4 space-y-3">
|
||||
{% for mail in incoming_attention %}
|
||||
<a href="/email/inbox/{{ mail.id }}" class="block rounded-xl border border-slate-200 p-3 hover:bg-slate-50">
|
||||
<div class="flex justify-between gap-2"><span class="truncate text-sm font-semibold text-slate-900">{{ mail.sender_email or '-' }}</span><span class="text-xs text-amber-700">{{ mail.mapping_status }}</span></div>
|
||||
<div class="mt-1 truncate text-xs text-slate-700">{{ mail.subject or '(No subject)' }}</div>
|
||||
<div class="mt-1 text-xs text-slate-500">Attachments: {{ mail.attachment_count }}</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<div class="rounded-xl border border-emerald-100 bg-emerald-50 p-4 text-sm text-emerald-800">No unmapped incoming emails needing attention.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,107 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Incoming Emails</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Fetch unread replies from the configured IMAP mailbox and map tracking codes to engagements, tasks or invoices.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/email/settings" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Settings</a>
|
||||
<a href="/email/logs" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Logs</a>
|
||||
</div>
|
||||
</div>
|
||||
{% if flash %}
|
||||
<div class="mt-4 rounded-xl border border-blue-200 bg-blue-50 px-4 py-3 text-sm text-blue-800">{{ flash }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="grid gap-4 lg:grid-cols-2">
|
||||
<form method="post" action="/email/inbox/fetch" class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<h3 class="text-base font-semibold text-slate-900">Fetch from IMAP</h3>
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-2">
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-slate-700">Folder</span>
|
||||
<input name="folder" value="INBOX" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2">
|
||||
</label>
|
||||
<label class="block">
|
||||
<span class="text-sm font-medium text-slate-700">Limit</span>
|
||||
<input type="number" min="1" max="100" name="limit" value="25" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2">
|
||||
</label>
|
||||
<label class="flex items-center gap-2 rounded-xl border border-slate-200 px-3 py-2">
|
||||
<input type="checkbox" name="unread_only" value="1" checked>
|
||||
<span class="text-sm text-slate-700">Unread only</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 rounded-xl border border-slate-200 px-3 py-2">
|
||||
<input type="checkbox" name="mark_seen" value="1">
|
||||
<span class="text-sm text-slate-700">Mark seen after fetch</span>
|
||||
</label>
|
||||
</div>
|
||||
<button class="mt-4 rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white hover:bg-slate-800">Fetch Emails</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/email/inbox/map-all" class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<h3 class="text-base font-semibold text-slate-900">Map Existing Emails</h3>
|
||||
<p class="mt-2 text-sm text-slate-500">Scans unmapped emails for tracking codes such as <span class="font-mono">[AF-ENG-123]</span>, <span class="font-mono">[AF-TASK-123]</span> and <span class="font-mono">[AF-INV-INV-001]</span>.</p>
|
||||
<label class="mt-4 block">
|
||||
<span class="text-sm font-medium text-slate-700">Limit</span>
|
||||
<input type="number" min="1" max="500" name="limit" value="100" class="mt-1 w-36 rounded-xl border border-slate-300 px-3 py-2">
|
||||
</label>
|
||||
<button class="mt-4 rounded-xl border border-slate-300 px-4 py-2 text-sm font-semibold text-slate-800 hover:bg-slate-50">Auto Map Emails</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded-2xl bg-white shadow-card ring-1 ring-slate-200">
|
||||
<div class="border-b border-slate-200 px-6 py-4">
|
||||
<h3 class="text-base font-semibold text-slate-900">Fetched Emails</h3>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead class="bg-slate-50 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">
|
||||
<tr>
|
||||
<th class="px-4 py-3">Received</th>
|
||||
<th class="px-4 py-3">From</th>
|
||||
<th class="px-4 py-3">Subject</th>
|
||||
<th class="px-4 py-3">Sender Match</th>
|
||||
<th class="px-4 py-3">Work Mapping</th>
|
||||
<th class="px-4 py-3">Attachments</th>
|
||||
<th class="px-4 py-3 text-right">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in incoming_rows %}
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="px-4 py-3 text-slate-600">{{ row.received_at_utc or row.fetched_at_utc }}</td>
|
||||
<td class="px-4 py-3"><div class="font-medium text-slate-900">{{ row.sender_name or '-' }}</div><div class="text-xs text-slate-500">{{ row.sender_email or '-' }}</div></td>
|
||||
<td class="px-4 py-3 text-slate-800">{{ row.subject or '(No subject)' }}</td>
|
||||
<td class="px-4 py-3">
|
||||
{% if row.matched_client_id %}<span class="rounded-full bg-emerald-50 px-2 py-1 text-xs font-medium text-emerald-700">Client</span>{% endif %}
|
||||
{% if row.matched_consultant_id %}<span class="rounded-full bg-indigo-50 px-2 py-1 text-xs font-medium text-indigo-700">Consultant</span>{% endif %}
|
||||
{% if row.matched_user_id and not row.matched_client_id and not row.matched_consultant_id %}<span class="rounded-full bg-blue-50 px-2 py-1 text-xs font-medium text-blue-700">User</span>{% endif %}
|
||||
{% if not row.matched_client_id and not row.matched_consultant_id and not row.matched_user_id %}<span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-medium text-slate-600">Unmatched</span>{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
{% if row.mapping_status in ['AUTO_MAPPED','MANUAL_MAPPED'] %}
|
||||
<div><span class="rounded-full bg-emerald-50 px-2 py-1 text-xs font-medium text-emerald-700">{{ row.mapping_status }}</span></div>
|
||||
<div class="mt-1 text-xs text-slate-500">{{ row.related_module or '-' }} #{{ row.related_id or '-' }}</div>
|
||||
{% elif row.mapping_status == 'ERROR' %}
|
||||
<span class="rounded-full bg-red-50 px-2 py-1 text-xs font-medium text-red-700">Error</span>
|
||||
{% else %}
|
||||
<span class="rounded-full bg-amber-50 px-2 py-1 text-xs font-medium text-amber-700">{{ row.mapping_status or 'UNMAPPED' }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-slate-600">{{ row.attachment_count }}</td>
|
||||
<td class="px-4 py-3 text-right"><a href="/email/inbox/{{ row.id }}" class="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50">Open</a></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="7" class="px-4 py-8 text-center text-slate-500">No incoming emails fetched yet.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,110 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
<div>
|
||||
<a href="/email/inbox" class="text-sm font-medium text-blue-700 hover:underline">← Back to inbox</a>
|
||||
<h2 class="mt-2 text-xl font-semibold text-slate-900">{{ row.subject or '(No subject)' }}</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">From {{ row.sender_name or row.sender_email or '-' }} <{{ row.sender_email or '-' }}></p>
|
||||
</div>
|
||||
<div class="flex flex-col items-start gap-2 md:items-end">
|
||||
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-semibold text-slate-700">{{ row.status }}</span>
|
||||
<span class="rounded-full bg-blue-50 px-3 py-1 text-xs font-semibold text-blue-700">{{ row.mapping_status or 'UNMAPPED' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 grid gap-3 text-sm md:grid-cols-2">
|
||||
<div><span class="font-medium text-slate-700">Received:</span> {{ row.received_at_utc or '-' }}</div>
|
||||
<div><span class="font-medium text-slate-700">Mailbox:</span> {{ row.mailbox_email }}</div>
|
||||
<div><span class="font-medium text-slate-700">To:</span> {{ row.recipient_emails or '-' }}</div>
|
||||
<div><span class="font-medium text-slate-700">CC:</span> {{ row.cc_emails or '-' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-3">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200 lg:col-span-2">
|
||||
<h3 class="text-base font-semibold text-slate-900">Message</h3>
|
||||
{% if row.body_text %}
|
||||
<pre class="mt-4 max-h-[560px] overflow-auto whitespace-pre-wrap rounded-xl bg-slate-50 p-4 text-sm leading-6 text-slate-800">{{ row.body_text }}</pre>
|
||||
{% elif row.body_html %}
|
||||
<div class="mt-4 rounded-xl border border-slate-200 p-4 text-sm text-slate-700">HTML-only message stored. Full HTML body is available in database.</div>
|
||||
{% else %}
|
||||
<div class="mt-4 rounded-xl border border-slate-200 p-4 text-sm text-slate-500">No readable body found.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<h3 class="text-base font-semibold text-slate-900">Matched Sender</h3>
|
||||
<div class="mt-4 space-y-2 text-sm text-slate-700">
|
||||
<div>Client ID: <span class="font-medium">{{ row.matched_client_id or '-' }}</span></div>
|
||||
<div>Consultant ID: <span class="font-medium">{{ row.matched_consultant_id or '-' }}</span></div>
|
||||
<div>User ID: <span class="font-medium">{{ row.matched_user_id or '-' }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<h3 class="text-base font-semibold text-slate-900">Work Mapping</h3>
|
||||
<div class="mt-4 space-y-2 text-sm text-slate-700">
|
||||
<div>Tracking Code: <span class="font-medium">{{ row.tracking_code or '-' }}</span></div>
|
||||
<div>Related Module: <span class="font-medium">{{ row.related_module or '-' }}</span></div>
|
||||
<div>Related ID: <span class="font-medium">{{ row.related_id or '-' }}</span></div>
|
||||
<div>Engagement ID: <span class="font-medium">{{ row.matched_engagement_id or '-' }}</span></div>
|
||||
<div>Task ID: <span class="font-medium">{{ row.matched_task_id or '-' }}</span></div>
|
||||
<div>Invoice ID: <span class="font-medium">{{ row.matched_invoice_id or '-' }}</span></div>
|
||||
{% if row.mapping_notes %}<div class="rounded-xl bg-slate-50 p-3 text-xs text-slate-600">{{ row.mapping_notes }}</div>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/email/inbox/{{ row.id }}/map" class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<h3 class="text-base font-semibold text-slate-900">Manual Mapping</h3>
|
||||
<p class="mt-1 text-xs text-slate-500">Select only one. Task mapping also adds the email to the task communication timeline.</p>
|
||||
<label class="mt-4 block">
|
||||
<span class="text-sm font-medium text-slate-700">Task</span>
|
||||
<select name="task_id" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">-- Select task --</option>
|
||||
{% for task in tasks %}
|
||||
<option value="{{ task.id }}">#{{ task.id }} - {{ task.task_name }}{% if task.financial_year %} ({{ task.financial_year }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="mt-3 block">
|
||||
<span class="text-sm font-medium text-slate-700">Engagement / Assignment</span>
|
||||
<select name="engagement_id" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">-- Select engagement --</option>
|
||||
{% for eng in engagements %}
|
||||
<option value="{{ eng.id }}">#{{ eng.id }} - Service {{ eng.service_catalogue_id }}{% if eng.financial_year %} ({{ eng.financial_year }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<label class="mt-3 block">
|
||||
<span class="text-sm font-medium text-slate-700">Invoice</span>
|
||||
<select name="invoice_id" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm">
|
||||
<option value="">-- Select invoice --</option>
|
||||
{% for inv in invoices %}
|
||||
<option value="{{ inv.id }}">#{{ inv.id }} - {{ inv.invoice_no }} - ₹{{ inv.total_amount }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<button class="mt-4 w-full rounded-xl bg-slate-900 px-4 py-2 text-sm font-semibold text-white hover:bg-slate-800">Save Mapping</button>
|
||||
</form>
|
||||
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<h3 class="text-base font-semibold text-slate-900">Attachments</h3>
|
||||
<div class="mt-4 space-y-3">
|
||||
{% for item in attachments %}
|
||||
<div class="rounded-xl border border-slate-200 p-3 text-sm">
|
||||
<div class="font-medium text-slate-800">{{ item.filename or 'attachment' }}</div>
|
||||
<div class="text-xs text-slate-500">{{ item.content_type or '-' }} • {{ item.size_bytes }} bytes</div>
|
||||
<div class="mt-1 break-all text-xs text-slate-400">{{ item.storage_path or '' }}</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-sm text-slate-500">No attachments.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,7 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex items-center justify-between gap-3"><div><h2 class="text-xl font-semibold text-slate-900">Email Logs</h2><p class="mt-1 text-sm text-slate-500">Last 100 outgoing email attempts.</p></div><a href="/email/settings" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Settings</a></div>
|
||||
<div class="mt-6 overflow-x-auto"><table class="min-w-full divide-y divide-slate-200 text-sm"><thead><tr class="text-left text-xs uppercase tracking-wide text-slate-500"><th class="py-2">Created</th><th>Sent</th><th>To</th><th>Template</th><th>Subject</th><th>Status</th><th>Error</th></tr></thead><tbody class="divide-y divide-slate-100">{% for log in logs %}<tr><td class="py-2 text-slate-500">{{ log.created_at_utc }}</td><td class="text-slate-500">{{ log.sent_at or '-' }}</td><td>{{ log.recipient_email }}</td><td>{{ log.template_code or '-' }}</td><td class="max-w-sm truncate">{{ log.subject }}</td><td><span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold">{{ log.status }}</span></td><td class="max-w-sm truncate text-red-600">{{ log.error_message or '' }}</td></tr>{% else %}<tr><td colspan="7" class="py-8 text-center text-slate-500">No email logs found.</td></tr>{% endfor %}</tbody></table></div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,60 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">Email Queue & Retry</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Pending and failed emails that can be retried without disturbing the original business transaction.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<a href="/email/settings" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Settings</a>
|
||||
<a href="/email/logs" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Logs</a>
|
||||
<form method="post" action="/email/queue/process" class="flex items-center gap-2">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<input type="number" name="limit" value="25" min="1" max="100" class="w-20 rounded-xl border border-slate-300 px-3 py-2 text-sm" />
|
||||
<button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Process Queue</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% if flash %}
|
||||
<div class="mt-4 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-800">{{ flash }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
||||
<thead>
|
||||
<tr class="text-left text-xs uppercase tracking-wide text-slate-500">
|
||||
<th class="py-2 pr-4">Queued</th>
|
||||
<th class="py-2 pr-4">Next Retry</th>
|
||||
<th class="py-2 pr-4">Attempts</th>
|
||||
<th class="py-2 pr-4">To</th>
|
||||
<th class="py-2 pr-4">Template</th>
|
||||
<th class="py-2 pr-4">Subject</th>
|
||||
<th class="py-2 pr-4">Status</th>
|
||||
<th class="py-2 pr-4">Error</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for log in queue_rows %}
|
||||
<tr class="align-top">
|
||||
<td class="py-3 pr-4 text-slate-500 whitespace-nowrap">{{ log.queued_at or log.created_at_utc }}</td>
|
||||
<td class="py-3 pr-4 text-slate-500 whitespace-nowrap">{{ log.next_retry_at or 'Ready' }}</td>
|
||||
<td class="py-3 pr-4 whitespace-nowrap">{{ log.attempt_count or 0 }} / {{ log.max_attempts or 3 }}</td>
|
||||
<td class="py-3 pr-4">{{ log.recipient_email }}</td>
|
||||
<td class="py-3 pr-4 text-slate-600">{{ log.template_code or '-' }}</td>
|
||||
<td class="py-3 pr-4 max-w-sm truncate">{{ log.subject }}</td>
|
||||
<td class="py-3 pr-4"><span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold">{{ log.status }}</span></td>
|
||||
<td class="py-3 pr-4 max-w-md truncate text-red-600">{{ log.error_message or '' }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="8" class="py-10 text-center text-slate-500">No pending or failed retryable emails.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,125 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold text-slate-900">SMTP & IMAP Settings</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Use Hostinger, Dovecot, Zoho, Gmail Workspace or any SMTP/IMAP provider. Password values are retained if left blank.</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<a href="/email/inbox" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Inbox</a>
|
||||
<a href="/email/queue" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Queue</a>
|
||||
<a href="/email/templates" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Templates</a>
|
||||
<a href="/email/logs" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Logs</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/email/settings" class="space-y-6">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<h3 class="text-base font-semibold text-slate-900">Outgoing SMTP</h3>
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-3">
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">SMTP Host</span><input name="smtp_host" value="{{ setting.smtp_host or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="smtp.hostinger.com"></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">SMTP Port</span><input type="number" name="smtp_port" value="{{ setting.smtp_port or 465 }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">Security</span><select name="smtp_security" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="SSL" {% if setting.smtp_security=='SSL' %}selected{% endif %}>SSL</option><option value="STARTTLS" {% if setting.smtp_security=='STARTTLS' %}selected{% endif %}>STARTTLS / TLS</option><option value="NONE" {% if setting.smtp_security=='NONE' %}selected{% endif %}>None</option></select></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">SMTP Username</span><input name="smtp_username" value="{{ setting.smtp_username or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="notifications@yourdomain.com"></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">SMTP Password</span><input type="password" name="smtp_password" value="" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="Leave blank to keep existing"></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">From Email</span><input name="from_email" value="{{ setting.from_email or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">From Name</span><input name="from_name" value="{{ setting.from_name or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="A R R R & ASSOCIATES"></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">Reply-to Email</span><input name="reply_to_email" value="{{ setting.reply_to_email or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<h3 class="text-base font-semibold text-slate-900">Incoming IMAP</h3>
|
||||
<p class="mt-1 text-sm text-slate-500">Use Hostinger IMAP, Dovecot IMAP or any standard IMAP mailbox. Incoming replies can be fetched from Email → Inbox.</p>
|
||||
<div class="mt-4 grid gap-4 md:grid-cols-3">
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">IMAP Host</span><input name="imap_host" value="{{ setting.imap_host or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="imap.hostinger.com"></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">IMAP Port</span><input type="number" name="imap_port" value="{{ setting.imap_port or 993 }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">Security</span><select name="imap_security" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"><option value="SSL" {% if setting.imap_security=='SSL' %}selected{% endif %}>SSL</option><option value="STARTTLS" {% if setting.imap_security=='STARTTLS' %}selected{% endif %}>STARTTLS / TLS</option><option value="NONE" {% if setting.imap_security=='NONE' %}selected{% endif %}>None</option></select></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">IMAP Username</span><input name="imap_username" value="{{ setting.imap_username or '' }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></label>
|
||||
<label class="block"><span class="text-sm font-medium text-slate-700">IMAP Password</span><input type="password" name="imap_password" value="" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2" placeholder="Leave blank to keep existing"></label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h3 class="text-base font-semibold text-slate-900">Notification Preferences</h3>
|
||||
<p class="text-sm text-slate-500">Enable only the email categories your firm wants. In-app alerts and popup alerts will continue separately.</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 grid gap-3 md:grid-cols-2">
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="is_active" value="1" class="mt-1" {% if setting.is_active %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Email integration active</span><span class="text-xs text-slate-500">Master switch for SMTP email sending.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_auth_emails" value="1" class="mt-1" {% if setting.send_auth_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Authentication / OTP emails</span><span class="text-xs text-slate-500">Login OTP, password reset OTP, password reset link, invite and password-change emails.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_alert_emails" value="1" class="mt-1" {% if setting.send_alert_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">General alert emails</span><span class="text-xs text-slate-500">Allows email copy of important in-app alerts.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_task_emails" value="1" class="mt-1" {% if setting.send_task_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Task and work emails</span><span class="text-xs text-slate-500">Task assigned, due today, overdue and status update emails.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_client_emails" value="1" class="mt-1" {% if setting.send_client_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Client communication emails</span><span class="text-xs text-slate-500">Client clarification, portal welcome and client-facing updates.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_document_emails" value="1" class="mt-1" {% if setting.send_document_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Document request / receipt emails</span><span class="text-xs text-slate-500">Document request and document received notifications.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_billing_emails" value="1" class="mt-1" {% if setting.send_billing_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Billing emails</span><span class="text-xs text-slate-500">Master switch for invoice, reminder and receipt emails.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_invoice_emails" value="1" class="mt-1" {% if setting.send_invoice_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Invoice and payment reminder emails</span><span class="text-xs text-slate-500">Invoice generated and payment reminder emails.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_payment_emails" value="1" class="mt-1" {% if setting.send_payment_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Payment / receipt emails</span><span class="text-xs text-slate-500">Payment received, receipt and online payment status emails.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_online_payment_emails" value="1" class="mt-1" {% if setting.send_online_payment_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Online payment emails</span><span class="text-xs text-slate-500">Cashfree / PayU success and failure emails.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_partner_review_emails" value="1" class="mt-1" {% if setting.send_partner_review_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Partner review emails</span><span class="text-xs text-slate-500">Partner review required and rework assigned emails.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_consultant_emails" value="1" class="mt-1" {% if setting.send_consultant_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Consultant emails</span><span class="text-xs text-slate-500">Consultant assignments, clarifications and submissions.</span></span>
|
||||
</label>
|
||||
<label class="flex items-start gap-3 rounded-xl border border-slate-200 p-3">
|
||||
<input type="checkbox" name="send_leave_attendance_emails" value="1" class="mt-1" {% if setting.send_leave_attendance_emails %}checked{% endif %}>
|
||||
<span><span class="block font-medium text-slate-800">Leave and attendance emails</span><span class="text-xs text-slate-500">Leave requests, approvals, rejections and punch missing alerts.</span></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end"><button class="rounded-xl bg-brand-600 px-5 py-2.5 text-sm font-semibold text-white hover:bg-brand-700">Save Email Settings</button></div>
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<h3 class="text-base font-semibold text-slate-900">Send Test Email</h3>
|
||||
<form method="post" action="/email/settings/test" class="mt-4 flex flex-col gap-3 sm:flex-row">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<input name="test_email" value="{{ current_user.email }}" class="min-w-0 flex-1 rounded-xl border border-slate-300 px-3 py-2">
|
||||
<button class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Send Test</button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<h3 class="text-base font-semibold text-slate-900">Recent Email Logs</h3>
|
||||
<div class="mt-4 overflow-x-auto"><table class="min-w-full divide-y divide-slate-200 text-sm"><thead><tr class="text-left text-xs uppercase tracking-wide text-slate-500"><th class="py-2">Time</th><th>To</th><th>Subject</th><th>Status</th><th>Error</th></tr></thead><tbody class="divide-y divide-slate-100">{% for log in recent_logs %}<tr><td class="py-2 text-slate-500">{{ log.created_at_utc }}</td><td>{{ log.recipient_email }}</td><td>{{ log.subject }}</td><td><span class="rounded-full bg-slate-100 px-2 py-1 text-xs font-semibold">{{ log.status }}</span></td><td class="max-w-xs truncate text-red-600">{{ log.error_message or '' }}</td></tr>{% else %}<tr><td colspan="5" class="py-4 text-center text-slate-500">No emails logged yet.</td></tr>{% endfor %}</tbody></table></div>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
<div class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<div class="flex items-center justify-between gap-3"><div><h2 class="text-xl font-semibold text-slate-900">Email Templates</h2><p class="mt-1 text-sm text-slate-500">Use tokens like {{ '{{ firm_name }}' }}, {{ '{{ user_name }}' }}, {{ '{{ otp_code }}' }}, {{ '{{ reset_link }}' }}, {{ '{{ invite_link }}' }}, {{ '{{ expiry_minutes }}' }}, {{ '{{ expiry_hours }}' }} and {{ '{{ client_name }}' }}.</p></div><a href="/email/settings" class="rounded-xl border border-slate-300 px-4 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50">Settings</a></div>
|
||||
</div>
|
||||
{% for tpl in templates_rows %}
|
||||
<form method="post" action="/email/templates/{{ tpl.id }}" class="rounded-2xl bg-white p-6 shadow-card ring-1 ring-slate-200">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"><div><h3 class="font-semibold text-slate-900">{{ tpl.template_name }}</h3><div class="text-xs text-slate-500">{{ tpl.template_code }}</div></div><label class="flex items-center gap-2 text-sm"><input type="checkbox" name="is_active" value="1" {% if tpl.is_active %}checked{% endif %}> Active</label></div>
|
||||
<label class="mt-4 block"><span class="text-sm font-medium text-slate-700">Subject</span><input name="subject_template" value="{{ tpl.subject_template }}" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2"></label>
|
||||
<label class="mt-4 block"><span class="text-sm font-medium text-slate-700">Body</span><textarea name="body_template" rows="8" class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 font-mono text-sm">{{ tpl.body_template }}</textarea></label>
|
||||
<div class="mt-4 flex justify-end"><button class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Save Template</button></div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="rounded-2xl bg-white p-8 text-center text-slate-500 shadow-card ring-1 ring-slate-200">No templates found. Open Email Settings once to seed defaults.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user