pif / app /templates /admin /client_executives.html
pramodmisra's picture
Separate Client Executive list from Producers
3c9f702
Raw
History Blame Contribute Delete
11.5 kB
{% extends "base.html" %}
{% block title %}Manage Client Executives — Admin{% endblock %}
{% block content %}
<div class="space-y-4">
<a href="/admin/?token={{ token }}" class="text-sm text-gray-400 hover:text-gray-600">← Admin Panel</a>
<div class="flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold sw-blue-text">Manage Client Executives</h1>
<p class="text-gray-500 text-sm mt-1">
The CE list shown on the intake form. Separate from producers. Add, edit, or
deactivate CEs — changes persist in the database (survive redeploys).
</p>
</div>
{% if user.role == 'superadmin' %}
<button onclick="openAddModal()"
class="bg-green-600 text-white px-4 py-2 rounded-md text-sm font-semibold hover:bg-green-700">
+ New Client Executive
</button>
{% endif %}
</div>
<!-- Filters -->
<div class="bg-white rounded-lg shadow p-4 flex items-center justify-between gap-4">
<input type="text" id="searchBox" placeholder="Search by name or code…"
oninput="filterRows()"
class="flex-1 border border-gray-200 rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400">
<label class="flex items-center text-sm text-gray-600 cursor-pointer">
<input type="checkbox" id="show_inactive" onchange="toggleInactive()"
{% if show_inactive %}checked{% endif %} class="mr-2">
Show inactive
</label>
</div>
<!-- CE table -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<table class="w-full text-sm">
<thead>
<tr class="sw-blue text-white text-left">
<th class="px-4 py-3 w-[5%]">#</th>
<th class="px-4 py-3 w-[14%]">Code</th>
<th class="px-4 py-3 w-[24%]">Name</th>
<th class="px-4 py-3 w-[28%]">Email</th>
<th class="px-4 py-3 w-[10%] text-center">Status</th>
<th class="px-4 py-3 w-[19%] text-center">Action</th>
</tr>
</thead>
<tbody id="ceTable">
{% for ce in ces %}
<tr class="border-b hover:bg-gray-50 ce-row {% if not ce.is_active %}opacity-60{% endif %}"
data-id="{{ ce.id }}"
data-search="{{ ce.name|lower }} {{ ce.code|lower }}">
<td class="px-4 py-3 text-gray-400">{{ loop.index }}</td>
<td class="px-4 py-3 font-mono">
<input type="text" id="code_{{ ce.id }}" value="{{ ce.code }}"
class="w-24 border border-gray-200 rounded px-2 py-1 text-xs font-mono uppercase">
</td>
<td class="px-4 py-3">
<input type="text" id="name_{{ ce.id }}" value="{{ ce.name }}"
class="w-full border border-gray-200 rounded px-2 py-1 text-sm">
</td>
<td class="px-4 py-3">
<input type="email" id="email_{{ ce.id }}" value="{{ ce.email or '' }}"
placeholder="name@snellingswalters.com"
class="w-full border border-gray-200 rounded px-2 py-1 text-sm {% if ce.email %}bg-green-50{% endif %}">
</td>
<td class="px-4 py-3 text-center">
<span class="px-2 py-0.5 rounded text-xs font-semibold {% if ce.is_active %}bg-green-100 text-green-700{% else %}bg-red-100 text-red-700{% endif %}">
{{ 'Active' if ce.is_active else 'Inactive' }}
</span>
</td>
<td class="px-4 py-3 text-center space-x-2">
{% if user.role == 'superadmin' %}
<button onclick="saveRow({{ ce.id }})"
class="bg-blue-600 text-white px-3 py-1 rounded text-xs font-semibold hover:bg-blue-700">
Save
</button>
<button onclick="toggleCE({{ ce.id }})"
class="text-amber-600 hover:underline text-xs">
{{ 'Deactivate' if ce.is_active else 'Activate' }}
</button>
{% if not ce.is_active %}
<button onclick="deleteCE({{ ce.id }}, '{{ ce.name }}')"
class="text-red-600 hover:underline text-xs">
Delete
</button>
{% endif %}
{% else %}
<span class="text-gray-300 text-xs">View only</span>
{% endif %}
</td>
</tr>
{% endfor %}
{% if not ces %}
<tr><td colspan="6" class="px-4 py-6 text-center text-gray-400">
No client executives yet. Add one above, or paste a list below.
</td></tr>
{% endif %}
</tbody>
</table>
</div>
{% if user.role == 'superadmin' %}
<!-- Bulk paste -->
<div class="bg-white rounded-lg shadow p-5">
<h3 class="font-semibold sw-blue-text mb-2">Bulk Add / Update</h3>
<p class="text-gray-400 text-xs mb-3">Paste one CE per line: <code>Full Name, email@example.com</code> (email optional). Codes auto-generate. Existing names get their email updated.</p>
<textarea id="bulkInput" rows="5" placeholder="Jane Doe, jdoe@snellingswalters.com&#10;John Smith, jsmith@snellingswalters.com"
class="w-full border border-gray-200 rounded-lg px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400"></textarea>
<button onclick="bulkImport()"
class="mt-2 bg-green-600 text-white px-6 py-2 rounded-lg text-sm font-semibold hover:bg-green-700 transition">
Import All
</button>
<span id="bulkResult" class="ml-3 text-sm text-gray-500"></span>
</div>
<!-- Add CE Modal -->
<div id="addModal" class="fixed inset-0 bg-black/50 hidden flex items-center justify-center z-50">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md p-6">
<h3 class="text-lg font-bold sw-blue-text mb-4">New Client Executive</h3>
<div class="space-y-3">
<div>
<label class="text-xs text-gray-500">Full Name</label>
<input id="new_name" type="text" class="w-full border rounded-md px-3 py-2 text-sm"
placeholder="Jane Doe">
</div>
<div>
<label class="text-xs text-gray-500">Code (optional — auto-generated if blank)</label>
<input id="new_code" type="text" class="w-full border rounded-md px-3 py-2 text-sm font-mono uppercase"
placeholder="DOEJA1">
</div>
<div>
<label class="text-xs text-gray-500">Email</label>
<input id="new_email" type="email" class="w-full border rounded-md px-3 py-2 text-sm"
placeholder="jdoe@snellingswalters.com">
</div>
</div>
<div class="flex justify-end space-x-3 mt-5">
<button onclick="closeAddModal()" class="px-4 py-2 text-sm text-gray-600 hover:text-gray-800">Cancel</button>
<button onclick="createCE()" class="bg-green-600 text-white px-4 py-2 rounded-md text-sm font-semibold hover:bg-green-700">Create</button>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<script>
const authToken = '{{ token }}';
const authHeaders = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + authToken};
function filterRows() {
const q = document.getElementById('searchBox').value.toLowerCase();
document.querySelectorAll('.ce-row').forEach(r => {
r.style.display = r.dataset.search.includes(q) ? '' : 'none';
});
}
function toggleInactive() {
const url = new URL(window.location);
if (document.getElementById('show_inactive').checked) {
url.searchParams.set('show_inactive', '1');
} else {
url.searchParams.delete('show_inactive');
}
window.location = url.toString();
}
async function saveRow(id) {
const code = document.getElementById('code_' + id).value.trim().toUpperCase();
const name = document.getElementById('name_' + id).value.trim();
const email = document.getElementById('email_' + id).value.trim();
const resp = await fetch('/admin/api/ces/' + id, {
method: 'POST', headers: authHeaders,
body: JSON.stringify({ code, name, email }),
});
const data = await resp.json();
if (data.error) { alert(data.error); return; }
flashRow(id, data.success);
}
async function toggleCE(id) {
if (!confirm('Toggle active status for this client executive?')) return;
await fetch('/admin/api/ces/' + id + '/toggle', {
method: 'POST', headers: {'Authorization': 'Bearer ' + authToken},
});
location.reload();
}
async function deleteCE(id, name) {
if (!confirm('Permanently delete ' + name + '? This cannot be undone.')) return;
const resp = await fetch('/admin/api/ces/' + id, {
method: 'DELETE', headers: {'Authorization': 'Bearer ' + authToken},
});
const data = await resp.json();
if (data.success) location.reload();
else alert(data.error || 'Failed to delete');
}
function flashRow(id, ok) {
const input = document.getElementById('email_' + id);
if (input) {
input.style.outline = ok ? '2px solid #27ae60' : '2px solid #c0392b';
setTimeout(() => { input.style.outline = ''; }, 1000);
}
}
function openAddModal() { document.getElementById('addModal').classList.remove('hidden'); }
function closeAddModal() { document.getElementById('addModal').classList.add('hidden'); }
async function createCE() {
const body = {
code: document.getElementById('new_code').value.trim().toUpperCase(),
name: document.getElementById('new_name').value.trim(),
email: document.getElementById('new_email').value.trim(),
};
const resp = await fetch('/admin/api/ces/new', {
method: 'POST', headers: authHeaders,
body: JSON.stringify(body),
});
const data = await resp.json();
if (data.success) { location.reload(); }
else { alert(data.error || 'Failed to create CE'); }
}
async function bulkImport() {
const text = document.getElementById('bulkInput').value.trim();
if (!text) return;
const resp = await fetch('/admin/api/ces/bulk', {
method: 'POST', headers: authHeaders,
body: JSON.stringify({ text }),
});
const data = await resp.json();
if (data.success) {
document.getElementById('bulkResult').textContent =
'✅ ' + data.added + ' added, ' + data.updated + ' updated' +
(data.skipped ? ', ' + data.skipped + ' skipped' : '');
setTimeout(() => location.reload(), 1500);
} else {
document.getElementById('bulkResult').textContent = '❌ ' + (data.error || 'Failed');
}
}
</script>
{% endblock %}