HH/templates/organizations/staff_import.html
ismail c5f76b3855
Some checks are pending
Build and Push Docker Image / build (push) Waiting to run
updates
2026-05-11 14:45:30 +03:00

377 lines
21 KiB
HTML

{% extends "layouts/base.html" %}
{% load i18n %}
{% block title %}{% trans "Import Staff" %} - PX360{% endblock %}
{% block extra_css %}
<style>
.page-header-gradient {
background: linear-gradient(135deg, #005696 0%, #0069a8 50%, #007bbd 100%);
color: white; padding: 1.5rem 2rem; border-radius: 1rem; margin-bottom: 1.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 86, 150, 0.2);
}
.drop-zone {
border: 2px dashed #cbd5e1; border-radius: 1rem; padding: 2.5rem;
text-align: center; transition: all 0.3s ease; cursor: pointer;
}
.drop-zone:hover, .drop-zone.drag-over {
border-color: #005696; background: #f0f7ff;
}
.stat-card {
background: white; border-radius: 1rem; border: 2px solid #e2e8f0; padding: 1.25rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.05); transition: all 0.3s ease;
}
.stat-card:hover { transform: translateY(-2px); box-shadow: 0 8px 15px rgba(0,0,0,0.1); }
</style>
{% endblock %}
{% block content %}
<div class="p-6">
<!-- Breadcrumb -->
<div class="flex items-center gap-2 text-sm text-slate-500 mb-4">
<a href="{% url 'organizations:staff_list' %}" class="hover:text-navy transition">{% trans "Staff" %}</a>
<i data-lucide="chevron-right" class="w-4 h-4"></i>
<span class="text-navy font-semibold">{% trans "Import" %}</span>
</div>
<!-- Header -->
<div class="page-header-gradient">
<div class="flex justify-between items-center">
<div>
<h1 class="text-2xl font-bold mb-1">{% trans "Import Staff" %}</h1>
<p class="text-blue-100 text-sm">{% trans "Upload a CSV file to bulk-import staff into" %} {{ hospital.name }}</p>
</div>
<a href="{% url 'organizations:staff_import_sample_csv' %}"
class="inline-flex items-center gap-2 px-4 py-2.5 bg-white text-navy font-medium rounded-xl hover:bg-blue-50 transition">
<i data-lucide="download" class="w-4 h-4"></i>{% trans "Sample CSV" %}
</a>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Upload Form -->
<div class="lg:col-span-2">
<form method="post" enctype="multipart/form-data" id="importForm">
{% csrf_token %}
<!-- File Upload -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 p-6 mb-6">
<h2 class="text-lg font-bold text-navy mb-4 flex items-center gap-2">
<i data-lucide="upload" class="w-5 h-5"></i> {% trans "Upload CSV File" %}
</h2>
<div class="drop-zone" id="dropZone" onclick="document.getElementById('csvFile').click()">
<i data-lucide="file-up" class="w-12 h-12 mx-auto text-slate-300 mb-3"></i>
<p class="text-slate-600 font-semibold mb-1">{% trans "Click to upload or drag and drop" %}</p>
<p class="text-slate-400 text-sm">{% trans "CSV files only. UTF-8 encoding recommended." %}</p>
<input type="file" name="csv_file" id="csvFile" accept=".csv" class="hidden" onchange="showFileName(this)">
<p class="mt-3 text-sm font-semibold text-navy hidden" id="fileName"></p>
</div>
</div>
<!-- Options -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 p-6 mb-6">
<h2 class="text-lg font-bold text-navy mb-4 flex items-center gap-2">
<i data-lucide="settings" class="w-5 h-5"></i> {% trans "Import Options" %}
</h2>
<div class="mb-4">
<label class="block text-sm font-semibold text-slate mb-2">{% trans "Default Staff Type" %}</label>
<select name="staff_type" class="w-full px-4 py-3 border-2 border-slate-200 rounded-xl focus:outline-none focus:border-navy text-sm">
<option value="physician">{% trans "Physician" %}</option>
<option value="nurse">{% trans "Nurse" %}</option>
<option value="admin">{% trans "Administrative" %}</option>
<option value="other" selected>{% trans "Other" %}</option>
</select>
</div>
<div class="space-y-3">
<label class="flex items-center gap-3 p-3 border-2 border-slate-100 rounded-xl hover:border-navy/20 transition cursor-pointer">
<input type="checkbox" name="update_existing" class="w-4 h-4 accent-navy">
<div>
<p class="font-semibold text-navy text-sm">{% trans "Update existing staff" %}</p>
<p class="text-slate-400 text-xs">{% trans "When Staff ID matches, update the record instead of skipping" %}</p>
</div>
</label>
<label class="flex items-center gap-3 p-3 border-2 border-slate-100 rounded-xl hover:border-navy/20 transition cursor-pointer">
<input type="checkbox" name="create_departments" class="w-4 h-4 accent-navy">
<div>
<p class="font-semibold text-navy text-sm">{% trans "Auto-create departments" %}</p>
<p class="text-slate-400 text-xs">{% trans "Create departments, sections, and subsections that don't exist yet" %}</p>
</div>
</label>
<label class="flex items-center gap-3 p-3 border-2 border-amber-200 bg-amber-50/50 rounded-xl hover:border-amber-300 transition cursor-pointer">
<input type="checkbox" name="dry_run" class="w-4 h-4 accent-amber-500" checked>
<div>
<p class="font-semibold text-amber-700 text-sm">{% trans "Dry run (preview only)" %}</p>
<p class="text-amber-600 text-xs">{% trans "Preview results without making any changes to the database" %}</p>
</div>
</label>
<label class="flex items-center gap-3 p-3 border-2 border-red-100 bg-red-50/30 rounded-xl hover:border-red-200 transition cursor-pointer">
<input type="checkbox" name="deactivate_missing" class="w-4 h-4 accent-red-500">
<div>
<p class="font-semibold text-red-700 text-sm">{% trans "Deactivate staff not in CSV" %}</p>
<p class="text-red-500 text-xs">{% trans "Active staff in the hospital whose ID is not in the CSV will be set to inactive" %}</p>
</div>
</label>
</div>
</div>
<!-- Submit -->
<div class="flex gap-3">
<button type="submit" class="flex-1 bg-navy text-white px-6 py-3 rounded-xl font-semibold hover:bg-blue transition flex items-center justify-center gap-2">
<i data-lucide="upload" class="w-4 h-4"></i>
<span id="submitLabel">{% trans "Preview Import" %}</span>
</button>
<a href="{% url 'organizations:staff_list' %}" class="px-6 py-3 border-2 border-slate-200 text-slate rounded-xl font-semibold hover:bg-slate-50 transition">
{% trans "Cancel" %}
</a>
</div>
</form>
</div>
<!-- Sidebar -->
<div>
<!-- CSV Format Reference -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 p-6 mb-6">
<h3 class="text-sm font-bold text-navy mb-3 flex items-center gap-2">
<i data-lucide="info" class="w-4 h-4"></i> {% trans "Expected Columns" %}
</h3>
<div class="space-y-1.5 text-xs">
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-green-100 text-green-700 rounded font-bold">Required</span>
<span class="text-slate-700 font-mono">Staff ID, Name</span>
</div>
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded font-bold">Optional</span>
<span class="text-slate-500 font-mono">Name_ar</span>
</div>
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded font-bold">Optional</span>
<span class="text-slate-500 font-mono">Manager, Manager_ar</span>
</div>
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded font-bold">Optional</span>
<span class="text-slate-500 font-mono">Civil Identity Number</span>
</div>
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded font-bold">Optional</span>
<span class="text-slate-500 font-mono">Location, Location_ar</span>
</div>
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded font-bold">Optional</span>
<span class="text-slate-500 font-mono">Department, Department_ar</span>
</div>
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded font-bold">Optional</span>
<span class="text-slate-500 font-mono">Section, Section_ar</span>
</div>
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded font-bold">Optional</span>
<span class="text-slate-500 font-mono">Subsection, Subsection_ar</span>
</div>
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded font-bold">Optional</span>
<span class="text-slate-500 font-mono">AlHammadi Job Title, ..._ar</span>
</div>
<div class="flex items-center gap-2">
<span class="px-1.5 py-0.5 bg-slate-100 text-slate-600 rounded font-bold">Optional</span>
<span class="text-slate-500 font-mono">Country, Country_ar</span>
</div>
</div>
<div class="mt-4 pt-4 border-t border-slate-100">
<p class="text-xs text-slate-400">
<i data-lucide="lightbulb" class="w-3 h-3 inline"></i>
{% trans "Download the sample CSV for the exact format with an example row." %}
</p>
</div>
</div>
<!-- Tips -->
<div class="bg-blue-50 border border-blue-100 rounded-2xl p-6">
<h3 class="text-sm font-bold text-blue-800 mb-3 flex items-center gap-2">
<i data-lucide="lightbulb" class="w-4 h-4"></i> {% trans "Tips" %}
</h3>
<ul class="text-xs text-blue-700 space-y-2">
<li class="flex items-start gap-2">
<i data-lucide="check-circle" class="w-3.5 h-3.5 mt-0.5 shrink-0"></i>
{% trans "Start with a dry run to preview results before importing." %}
</li>
<li class="flex items-start gap-2">
<i data-lucide="check-circle" class="w-3.5 h-3.5 mt-0.5 shrink-0"></i>
{% trans "Staff ID must be unique within the hospital." %}
</li>
<li class="flex items-start gap-2">
<i data-lucide="check-circle" class="w-3.5 h-3.5 mt-0.5 shrink-0"></i>
{% trans "Manager format: \"EMP001 - Name\" (ID dash Name)." %}
</li>
<li class="flex items-start gap-2">
<i data-lucide="check-circle" class="w-3.5 h-3.5 mt-0.5 shrink-0"></i>
{% trans "Save your CSV as UTF-8 for Arabic text support." %}
</li>
<li class="flex items-start gap-2">
<i data-lucide="check-circle" class="w-3.5 h-3.5 mt-0.5 shrink-0"></i>
{% trans "Use 'Deactivate missing' to sync: staff not in the CSV will be set to inactive." %}
</li>
</ul>
</div>
</div>
</div>
<!-- Results Section -->
{% if results %}
<div class="mt-8">
<h2 class="text-xl font-bold text-navy mb-4 flex items-center gap-2">
<i data-lucide="bar-chart-3" class="w-5 h-5"></i>
{% if results.dry_run %}{% trans "Preview Results" %}{% else %}{% trans "Import Results" %}{% endif %}
</h2>
<!-- Stats Cards -->
<div class="grid grid-cols-2 md:grid-cols-6 gap-4 mb-6">
<div class="stat-card">
<p class="text-xs font-bold text-slate uppercase">{% trans "Total Rows" %}</p>
<p class="text-2xl font-bold text-navy mt-1">{{ results.total_rows }}</p>
</div>
<div class="stat-card" style="border-color: #dcfce7;">
<p class="text-xs font-bold text-green-600 uppercase">{% trans "Created" %}</p>
<p class="text-2xl font-bold text-green-600 mt-1">{{ results.created_count }}</p>
</div>
<div class="stat-card" style="border-color: #dbeafe;">
<p class="text-xs font-bold text-blue-600 uppercase">{% trans "Updated" %}</p>
<p class="text-2xl font-bold text-blue-600 mt-1">{{ results.updated_count }}</p>
</div>
<div class="stat-card" style="border-color: #fef9c3;">
<p class="text-xs font-bold text-amber-600 uppercase">{% trans "Skipped" %}</p>
<p class="text-2xl font-bold text-amber-600 mt-1">{{ results.skipped_count }}</p>
</div>
<div class="stat-card" style="border-color: #e9d5ff;">
<p class="text-xs font-bold text-purple-600 uppercase">{% trans "Deactivated" %}</p>
<p class="text-2xl font-bold text-purple-600 mt-1">{{ results.deactivated_count }}</p>
</div>
<div class="stat-card" style="border-color: #fee2e2;">
<p class="text-xs font-bold text-red-600 uppercase">{% trans "Errors" %}</p>
<p class="text-2xl font-bold text-red-600 mt-1">{{ results.error_count }}</p>
</div>
</div>
<!-- Results Table -->
<div class="bg-white rounded-2xl shadow-sm border border-slate-100 overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
<thead class="bg-slate-50 border-b">
<tr>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Row" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Staff ID" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Name" %}</th>
<th class="px-4 py-3 text-left text-[10px] font-bold text-slate uppercase">{% trans "Status" %}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
{% for r in results.created %}
<tr class="hover:bg-green-50/50">
<td class="px-4 py-3 text-sm text-slate">{{ r.row }}</td>
<td class="px-4 py-3 text-sm font-mono text-navy">{{ r.id }}</td>
<td class="px-4 py-3 text-sm">{{ r.name }}</td>
<td class="px-4 py-3"><span class="px-2 py-1 bg-green-100 text-green-700 rounded text-xs font-bold">{{ r.message }}</span></td>
</tr>
{% endfor %}
{% for r in results.updated %}
<tr class="hover:bg-blue-50/50">
<td class="px-4 py-3 text-sm text-slate">{{ r.row }}</td>
<td class="px-4 py-3 text-sm font-mono text-navy">{{ r.id }}</td>
<td class="px-4 py-3 text-sm">{{ r.name }}</td>
<td class="px-4 py-3"><span class="px-2 py-1 bg-blue-100 text-blue-700 rounded text-xs font-bold">{{ r.message }}</span></td>
</tr>
{% endfor %}
{% for r in results.skipped %}
<tr class="hover:bg-amber-50/50">
<td class="px-4 py-3 text-sm text-slate">{{ r.row }}</td>
<td class="px-4 py-3 text-sm font-mono text-navy">{{ r.id }}</td>
<td class="px-4 py-3 text-sm">{{ r.name }}</td>
<td class="px-4 py-3"><span class="px-2 py-1 bg-amber-100 text-amber-700 rounded text-xs font-bold">{{ r.message }}</span></td>
</tr>
{% endfor %}
{% for r in results.errors %}
<tr class="hover:bg-red-50/50">
<td class="px-4 py-3 text-sm text-slate">{{ r.row }}</td>
<td class="px-4 py-3 text-sm font-mono text-navy">{{ r.id }}</td>
<td class="px-4 py-3 text-sm">{{ r.name }}</td>
<td class="px-4 py-3"><span class="px-2 py-1 bg-red-100 text-red-700 rounded text-xs font-bold">{{ r.message }}</span></td>
</tr>
{% endfor %}
{% for r in results.deactivated %}
<tr class="hover:bg-purple-50/50">
<td class="px-4 py-3 text-sm text-slate">{{ r.row }}</td>
<td class="px-4 py-3 text-sm font-mono text-navy">{{ r.id }}</td>
<td class="px-4 py-3 text-sm">{{ r.name }}</td>
<td class="px-4 py-3"><span class="px-2 py-1 bg-purple-100 text-purple-700 rounded text-xs font-bold">{{ r.message }}</span></td>
</tr>
{% endfor %}
{% if results.total_rows == 0 %}
<tr><td colspan="4" class="px-4 py-8 text-center text-slate">{% trans "No rows found in the CSV file." %}</td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
<!-- Re-import with actual data -->
{% if results.dry_run and results.error_count == 0 and results.total_rows > 0 %}
<div class="mt-4 p-4 bg-green-50 border border-green-200 rounded-xl flex items-center justify-between">
<p class="text-green-700 text-sm font-semibold">
<i data-lucide="check-circle" class="w-4 h-4 inline"></i>
{% trans "Preview looks good! Uncheck 'Dry run' and submit again to apply the import." %}
{% if results.deactivated_count > 0 %}
<span class="text-purple-600 ml-2">({{ results.deactivated_count }} {% trans "staff would be deactivated" %})</span>
{% endif %}
</p>
</div>
{% endif %}
</div>
{% endif %}
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
lucide.createIcons();
});
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('csvFile');
if (dropZone) {
dropZone.addEventListener('dragover', function(e) {
e.preventDefault();
dropZone.classList.add('drag-over');
});
dropZone.addEventListener('dragleave', function() {
dropZone.classList.remove('drag-over');
});
dropZone.addEventListener('drop', function(e) {
e.preventDefault();
dropZone.classList.remove('drag-over');
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
showFileName(fileInput);
}
});
}
function showFileName(input) {
const label = document.getElementById('fileName');
if (input.files.length) {
label.textContent = input.files[0].name;
label.classList.remove('hidden');
}
}
const dryRunCheckbox = document.querySelector('input[name="dry_run"]');
const submitLabel = document.getElementById('submitLabel');
if (dryRunCheckbox && submitLabel) {
dryRunCheckbox.addEventListener('change', function() {
submitLabel.textContent = this.checked ? '{% trans "Preview Import" %}' : '{% trans "Import Staff" %}';
});
}
</script>
{% endblock %}