Add firm admin bulk user import with template
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
<a href="/firm-admin/users/new?role=partner" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">+ Add Partner</a>
|
||||
<a href="/firm-admin/users/new?role=manager" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">+ Add Manager</a>
|
||||
<a href="/firm-admin/users/new?role=staff" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">+ Add Staff</a>
|
||||
<a href="/firm-admin/users/import" class="rounded-xl bg-emerald-600 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-700">Import Users</a>
|
||||
<a href="/firm-admin/users/import/template" class="rounded-xl border border-emerald-200 px-4 py-2 text-sm font-semibold text-emerald-700 hover:bg-emerald-50">Template</a>
|
||||
<a href="/system-settings/users" class="rounded-xl border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Manage Users</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<section class="mx-auto max-w-4xl space-y-6">
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-soft">
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-brand-600">Firm Admin Bulk Onboarding</p>
|
||||
<h1 class="mt-2 text-2xl font-bold text-slate-900">Import Partner / Manager / Staff</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Upload the Excel template to create user accounts, map roles and branches, create/link employee masters and generate invite links in bulk.</p>
|
||||
</div>
|
||||
<a href="/firm-admin/dashboard?tab=users" class="rounded-xl border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Back to Users</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if flash %}
|
||||
<div class="rounded-3xl border border-red-200 bg-red-50 p-5 text-sm font-semibold text-red-800 shadow-soft">{{ flash }}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid gap-5 md:grid-cols-2">
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-soft">
|
||||
<h2 class="text-lg font-bold text-slate-900">1. Download template</h2>
|
||||
<p class="mt-2 text-sm text-slate-500">The template includes role values, sample rows and your active firm's branch list.</p>
|
||||
<a href="/firm-admin/users/import/template" class="mt-5 inline-flex rounded-xl bg-emerald-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-emerald-700">Download Excel Template</a>
|
||||
</div>
|
||||
|
||||
<form method="post" action="/firm-admin/users/import" enctype="multipart/form-data" class="rounded-3xl border border-slate-200 bg-white p-6 shadow-soft">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
|
||||
<h2 class="text-lg font-bold text-slate-900">2. Upload completed file</h2>
|
||||
<p class="mt-2 text-sm text-slate-500">Allowed roles: <b>partner</b>, <b>manager</b>, <b>staff</b>. Manager will be assigned your existing Branch Manager role.</p>
|
||||
<label class="mt-5 block">
|
||||
<span class="text-sm font-semibold text-slate-700">Excel File <span class="text-red-600">*</span></span>
|
||||
<input type="file" name="import_file" accept=".xlsx,.xlsm" required class="mt-1 w-full rounded-xl border border-slate-300 px-3 py-2 text-sm focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-100" />
|
||||
</label>
|
||||
<button class="mt-5 rounded-xl bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white shadow-soft hover:bg-brand-700">Import Users</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="rounded-3xl border border-amber-200 bg-amber-50 p-5 text-sm text-amber-900 shadow-soft">
|
||||
<div class="font-bold">Important</div>
|
||||
<p class="mt-1">Do not keep the sample rows unless you want them imported. Email and employee code must be unique. If email sending fails, the fallback invite links will still be shown after import.</p>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,54 @@
|
||||
{% extends "ui/templates/base/layout.html" %}
|
||||
{% block content %}
|
||||
<section class="mx-auto max-w-6xl space-y-6">
|
||||
<div class="rounded-3xl border border-slate-200 bg-white p-6 shadow-soft">
|
||||
<div class="flex flex-col gap-3 md:flex-row md:items-start md:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.24em] text-brand-600">Firm Admin Bulk Onboarding</p>
|
||||
<h1 class="mt-2 text-2xl font-bold text-slate-900">Import Result</h1>
|
||||
<p class="mt-1 text-sm text-slate-500">Created {{ import_result.summary.created }} of {{ import_result.summary.total }} rows. Failed {{ import_result.summary.failed }} rows.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a href="/firm-admin/users/import" class="rounded-xl border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50">Import More</a>
|
||||
<a href="/firm-admin/dashboard?tab=users" class="rounded-xl bg-brand-600 px-4 py-2 text-sm font-semibold text-white hover:bg-brand-700">Back to Users</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if import_result.invite_links %}
|
||||
<div class="rounded-3xl border border-emerald-200 bg-emerald-50 p-5 shadow-soft">
|
||||
<h2 class="text-lg font-bold text-emerald-900">Invite link fallback</h2>
|
||||
<p class="mt-1 text-sm text-emerald-800">Copy these links if automatic email is not configured or failed for any user.</p>
|
||||
<div class="mt-4 space-y-3">
|
||||
{% for item in import_result.invite_links %}
|
||||
<div class="rounded-2xl border border-emerald-100 bg-white p-4">
|
||||
<div class="text-sm font-bold text-slate-900">Row {{ item.row_no }} — {{ item.name }} / {{ item.email }} / {{ item.role }}</div>
|
||||
<div class="mt-1 text-xs font-semibold text-slate-500">Email status: {{ item.email_status or 'not_attempted' }}</div>
|
||||
<input value="{{ item.invite_url }}" readonly onclick="this.select()" class="mt-2 w-full rounded-xl border border-slate-200 bg-slate-50 px-3 py-2 text-xs text-slate-700" />
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="overflow-x-auto rounded-3xl border border-slate-200 bg-white shadow-soft">
|
||||
<table class="min-w-full divide-y divide-slate-100 text-sm">
|
||||
<thead class="bg-slate-50 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">
|
||||
<tr><th class="px-5 py-3">Row</th><th class="px-5 py-3">Status</th><th class="px-5 py-3">Name</th><th class="px-5 py-3">Email</th><th class="px-5 py-3">Role</th><th class="px-5 py-3">Message</th></tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100">
|
||||
{% for row in import_result.rows %}
|
||||
<tr class="hover:bg-slate-50">
|
||||
<td class="px-5 py-3">{{ row.row_no }}</td>
|
||||
<td class="px-5 py-3"><span class="rounded-full px-2 py-1 text-xs font-semibold {% if row.status == 'created' %}bg-emerald-50 text-emerald-700{% else %}bg-red-50 text-red-700{% endif %}">{{ row.status }}</span></td>
|
||||
<td class="px-5 py-3 text-slate-700">{{ row.name or '-' }}</td>
|
||||
<td class="px-5 py-3 text-slate-700">{{ row.email or '-' }}</td>
|
||||
<td class="px-5 py-3 text-slate-700">{{ row.role or '-' }}</td>
|
||||
<td class="px-5 py-3 text-slate-600">{{ row.message }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user