438 lines
20 KiB
HTML
438 lines
20 KiB
HTML
{% extends "layouts/public_base.html" %}
|
|
{% load i18n static %}
|
|
|
|
{% block title %}{% trans "Track Your Submission" %} - PX360{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
header.glass-card {
|
|
display: none !important;
|
|
}
|
|
|
|
.lang-switcher {
|
|
position: fixed;
|
|
top: 1rem;
|
|
right: 1rem;
|
|
z-index: 100;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.lang-switcher a {
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 0.75rem;
|
|
border: 2px solid rgba(255,255,255,0.3);
|
|
background: rgba(255,255,255,0.1);
|
|
backdrop-filter: blur(10px);
|
|
color: white;
|
|
font-weight: 600;
|
|
font-size: 0.875rem;
|
|
transition: all 0.2s ease;
|
|
text-decoration: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.lang-switcher a:hover {
|
|
background: rgba(255,255,255,0.2);
|
|
border-color: rgba(255,255,255,0.5);
|
|
}
|
|
|
|
.lang-switcher a.active {
|
|
background: white;
|
|
color: #005696;
|
|
border-color: white;
|
|
}
|
|
|
|
[dir="rtl"] .lang-switcher {
|
|
right: auto;
|
|
left: 1rem;
|
|
}
|
|
|
|
.selection-card {
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.selection-card:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 12px 24px -6px rgba(0, 86, 150, 0.2);
|
|
}
|
|
|
|
.selection-card.active {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 12px 24px -6px rgba(0, 86, 150, 0.3);
|
|
}
|
|
|
|
.timeline-dot::after {
|
|
content: '';
|
|
position: absolute;
|
|
width: 2px;
|
|
height: 100%;
|
|
background: #e2e8f0;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
top: 24px;
|
|
z-index: 0;
|
|
}
|
|
|
|
.timeline-item:last-child .timeline-dot::after {
|
|
display: none;
|
|
}
|
|
|
|
@keyframes slideUp {
|
|
from { opacity: 0; transform: translateY(30px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.animate-slide-up {
|
|
animation: slideUp 0.5s ease-out forwards;
|
|
}
|
|
|
|
@keyframes subtle-float {
|
|
0% { transform: translateY(0px); }
|
|
50% { transform: translateY(-5px); }
|
|
100% { transform: translateY(0px); }
|
|
}
|
|
.float-icon { animation: subtle-float 3s ease-in-out infinite; }
|
|
|
|
.spinner {
|
|
display: inline-block;
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
border: 3px solid rgba(255,255,255,0.3);
|
|
border-radius: 50%;
|
|
border-top-color: white;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Language Switcher -->
|
|
<div class="lang-switcher">
|
|
{% get_available_languages as LANGUAGES %}
|
|
{% get_current_language as LANGUAGE_CODE %}
|
|
{% for lang_code, lang_name in LANGUAGES %}
|
|
<a href="{% url 'core:set_language' %}?language={{ lang_code }}"
|
|
class="{% if LANGUAGE_CODE == lang_code %}active{% endif %}">
|
|
<span>{% if lang_code == 'en' %}🇬🇧{% elif lang_code == 'ar' %}🇸🇦{% endif %}</span>
|
|
<span>{{ lang_name }}</span>
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<div class="max-w-4xl mx-auto px-4 py-8 md:py-12">
|
|
<!-- Logo Banner -->
|
|
<div class="rounded-3xl shadow-2xl overflow-hidden mb-8 text-center animate-fade-in">
|
|
<div class="bg-white w-full py-8 px-6 flex items-center justify-center">
|
|
<img src="{% static 'img/hh-logo.png' %}" alt="Al Hammadi Hospital" class="max-h-16 w-auto object-contain">
|
|
</div>
|
|
<div class="bg-white p-8">
|
|
<h1 class="text-2xl font-bold text-navy mb-3">{% trans "Track Your Submission" %}</h1>
|
|
<p class="text-slate text-base max-w-xl mx-auto">
|
|
{% trans "Select a category below and enter your reference number to check the status." %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Selection Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8" id="selectionCards">
|
|
<!-- Complaint Card -->
|
|
<div class="selection-card bg-white rounded-2xl shadow-xl border-2 border-transparent p-6 text-center relative overflow-hidden group"
|
|
data-type="complaint" onclick="selectType('complaint')">
|
|
<div class="absolute top-0 left-0 right-0 h-2 bg-gradient-to-r from-red-400 to-red-600"></div>
|
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-red-50 to-red-100 mx-auto mb-3 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
|
|
<i data-lucide="alert-circle" class="w-7 h-7 text-red-600"></i>
|
|
</div>
|
|
<h3 class="text-lg font-bold text-navy mb-1">{% trans "Complaint" %}</h3>
|
|
<p class="text-slate text-xs">{% trans "e.g., CMP-20240101-123456" %}</p>
|
|
</div>
|
|
|
|
<!-- Inquiry Card -->
|
|
<div class="selection-card bg-white rounded-2xl shadow-xl border-2 border-transparent p-6 text-center relative overflow-hidden group"
|
|
data-type="inquiry" onclick="selectType('inquiry')">
|
|
<div class="absolute top-0 left-0 right-0 h-2 bg-gradient-to-r from-amber-400 to-amber-600"></div>
|
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-amber-50 to-amber-100 mx-auto mb-3 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
|
|
<i data-lucide="help-circle" class="w-7 h-7 text-amber-600"></i>
|
|
</div>
|
|
<h3 class="text-lg font-bold text-navy mb-1">{% trans "Inquiry" %}</h3>
|
|
<p class="text-slate text-xs">{% trans "e.g., INQ-20260428-123456" %}</p>
|
|
</div>
|
|
|
|
<!-- Observation Card -->
|
|
<div class="selection-card bg-white rounded-2xl shadow-xl border-2 border-transparent p-6 text-center relative overflow-hidden group"
|
|
data-type="observation" onclick="selectType('observation')">
|
|
<div class="absolute top-0 left-0 right-0 h-2 bg-gradient-to-r from-purple-400 to-purple-600"></div>
|
|
<div class="w-14 h-14 rounded-2xl bg-gradient-to-br from-purple-50 to-purple-100 mx-auto mb-3 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
|
|
<i data-lucide="eye" class="w-7 h-7 text-purple-600"></i>
|
|
</div>
|
|
<h3 class="text-lg font-bold text-navy mb-1">{% trans "Observation" %}</h3>
|
|
<p class="text-slate text-xs">{% trans "e.g., OBS-ABC123" %}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Search Form (hidden until type selected) -->
|
|
<div id="searchForm" class="glass-card rounded-3xl shadow-2xl p-8 mb-8 hidden">
|
|
<form id="trackForm" class="max-w-lg mx-auto">
|
|
{% csrf_token %}
|
|
<input type="hidden" id="trackType" name="type" value="">
|
|
<div class="relative group">
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
<i data-lucide="hash" class="w-5 h-5 text-slate/40 group-focus-within:text-blue transition-colors"></i>
|
|
</div>
|
|
<input type="text" id="referenceInput" name="reference"
|
|
class="w-full pl-12 pr-6 py-5 border-2 border-slate-100 rounded-2xl text-navy text-lg focus:ring-4 focus:ring-blue/10 focus:border-blue transition-all duration-300 bg-white/80 placeholder:text-slate/30"
|
|
placeholder="{% trans 'Enter your reference number' %}"
|
|
required>
|
|
</div>
|
|
<button type="submit" id="trackBtn" class="w-full mt-4 bg-navy hover:bg-navy/90 text-white px-8 py-5 rounded-2xl font-bold text-lg transition-all duration-300 shadow-lg shadow-navy/20 hover:shadow-xl hover:-translate-y-1 flex items-center justify-center gap-3">
|
|
<i data-lucide="crosshair" class="w-5 h-5"></i>
|
|
{% trans "Track Status" %}
|
|
</button>
|
|
</form>
|
|
<p class="text-center text-slate/50 text-xs mt-6 uppercase tracking-widest font-semibold">
|
|
<i data-lucide="info" class="w-3 h-3 inline mr-1"></i>
|
|
{% trans "Found in your confirmation email or SMS" %}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Error Message -->
|
|
<div id="errorBox" class="bg-white rounded-3xl shadow-lg p-6 mb-8 hidden flex items-center gap-4">
|
|
<div class="w-12 h-12 bg-rose-100 rounded-xl flex items-center justify-center text-rose-600 shrink-0">
|
|
<i data-lucide="alert-circle" class="w-6 h-6"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-rose-900">{% trans "Not Found" %}</h3>
|
|
<p id="errorText" class="text-rose-700/80 text-sm"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading -->
|
|
<div id="loadingBox" class="text-center py-16 hidden">
|
|
<div class="inline-block w-14 h-14 border-4 border-slate-200 border-t-blue rounded-full animate-spin mb-4"></div>
|
|
<p class="text-slate text-sm font-medium">{% trans "Looking up your submission..." %}</p>
|
|
</div>
|
|
|
|
<!-- Results -->
|
|
<div id="resultsBox" class="hidden animate-slide-up">
|
|
<!-- Status Header -->
|
|
<div class="bg-white rounded-3xl shadow-2xl p-6 md:p-8 mb-6">
|
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6">
|
|
<div>
|
|
<span class="text-xs font-bold text-slate/40 uppercase tracking-widest block mb-1" id="resultRefLabel"></span>
|
|
<h2 class="text-3xl font-black text-navy" id="resultReference"></h2>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<div class="text-right hidden md:block">
|
|
<span class="text-xs font-bold text-slate/40 uppercase tracking-widest block mb-1">{% trans "Current Status" %}</span>
|
|
<p class="font-bold text-navy" id="resultStatusText"></p>
|
|
</div>
|
|
<div id="resultStatusBadge" class="px-6 py-3 rounded-2xl text-sm font-black uppercase tracking-wider shadow-sm border-b-4"></div>
|
|
<div id="resultEscalated" class="px-4 py-2 rounded-2xl text-xs font-bold uppercase tracking-wider bg-red-50 text-red-700 border border-red-200 flex items-center gap-2 hidden">
|
|
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
|
|
{% trans "Escalated" %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mt-8 h-2 w-full bg-slate-100 rounded-full overflow-hidden">
|
|
<div id="resultProgressBar" class="h-full bg-navy transition-all duration-1000" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info Cards -->
|
|
<div id="resultInfoCards" class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10"></div>
|
|
|
|
<!-- Timeline -->
|
|
<div class="bg-white rounded-3xl shadow-2xl p-8 md:p-10">
|
|
<h3 class="text-2xl font-bold text-navy mb-10 flex items-center gap-3">
|
|
<div class="p-2 bg-navy text-white rounded-lg">
|
|
<i data-lucide="list-checks" class="w-5 h-5"></i>
|
|
</div>
|
|
{% trans "Resolution Journey" %}
|
|
</h3>
|
|
<div id="resultTimeline" class="space-y-1"></div>
|
|
<div id="resultNoTimeline" class="text-center py-12 hidden">
|
|
<div class="w-20 h-20 bg-slate-50 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<i data-lucide="loader" class="w-8 h-8 text-slate/30 animate-spin"></i>
|
|
</div>
|
|
<p class="text-slate/60 font-medium">{% trans "Your submission is being reviewed. Updates will appear here." %}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Privacy Note -->
|
|
<div class="bg-white/80 backdrop-blur rounded-2xl p-5 mt-6 flex items-start gap-3 border border-white/50">
|
|
<i data-lucide="shield" class="w-5 h-5 text-navy mt-0.5 shrink-0"></i>
|
|
<p class="text-sm text-slate/60">{% trans "For privacy reasons, detailed notes and internal communications are not shown here." %}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Back Link -->
|
|
<div class="text-center mt-8">
|
|
<a href="{% url 'core:public_submit_landing' %}" class="text-sm text-blue hover:text-blue-700 transition font-medium">
|
|
<i data-lucide="arrow-left" class="w-4 h-4 inline mr-1"></i>
|
|
{% trans "Back to Submit Feedback" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
|
|
|
var selectedType = null;
|
|
|
|
window.selectType = function(type) {
|
|
selectedType = type;
|
|
document.getElementById('trackType').value = type;
|
|
document.getElementById('searchForm').classList.remove('hidden');
|
|
document.getElementById('errorBox').classList.add('hidden');
|
|
document.getElementById('resultsBox').classList.add('hidden');
|
|
document.getElementById('loadingBox').classList.add('hidden');
|
|
|
|
var placeholders = {
|
|
complaint: "{% trans 'e.g., CMP-20240101-123456' %}",
|
|
inquiry: "{% trans 'e.g., INQ-20260428-123456' %}",
|
|
observation: "{% trans 'e.g., OBS-ABC123' %}"
|
|
};
|
|
document.getElementById('referenceInput').placeholder = placeholders[type] || '';
|
|
document.getElementById('referenceInput').focus();
|
|
|
|
document.querySelectorAll('.selection-card').forEach(function(card) {
|
|
var isActive = card.getAttribute('data-type') === type;
|
|
card.classList.toggle('active', isActive);
|
|
card.style.borderColor = isActive ? (type === 'complaint' ? '#ef4444' : type === 'inquiry' ? '#f59e0b' : '#a855f7') : 'transparent';
|
|
});
|
|
};
|
|
|
|
document.getElementById('trackForm').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
var reference = document.getElementById('referenceInput').value.trim();
|
|
if (!reference || !selectedType) return;
|
|
|
|
document.getElementById('errorBox').classList.add('hidden');
|
|
document.getElementById('resultsBox').classList.add('hidden');
|
|
document.getElementById('loadingBox').classList.remove('hidden');
|
|
|
|
var btn = document.getElementById('trackBtn');
|
|
var originalBtn = btn.innerHTML;
|
|
btn.disabled = true;
|
|
btn.innerHTML = '<span class="spinner"></span> {% trans "Searching..." %}';
|
|
|
|
var url = '{% url "core:public_track_api" %}?type=' + encodeURIComponent(selectedType) + '&reference=' + encodeURIComponent(reference);
|
|
|
|
fetch(url)
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalBtn;
|
|
document.getElementById('loadingBox').classList.add('hidden');
|
|
|
|
if (data.found) {
|
|
renderResults(data);
|
|
} else {
|
|
document.getElementById('errorText').textContent = data.error || "{% trans 'No submission found with this reference number.' %}";
|
|
document.getElementById('errorBox').classList.remove('hidden');
|
|
document.getElementById('errorBox').style.display = 'flex';
|
|
lucide.createIcons();
|
|
}
|
|
})
|
|
.catch(function() {
|
|
btn.disabled = false;
|
|
btn.innerHTML = originalBtn;
|
|
document.getElementById('loadingBox').classList.add('hidden');
|
|
document.getElementById('errorText').textContent = "{% trans 'An error occurred. Please try again.' %}";
|
|
document.getElementById('errorBox').classList.remove('hidden');
|
|
document.getElementById('errorBox').style.display = 'flex';
|
|
lucide.createIcons();
|
|
});
|
|
});
|
|
|
|
function renderResults(data) {
|
|
var refLabels = {
|
|
complaint: "{% trans 'Case Reference' %}",
|
|
inquiry: "{% trans 'Inquiry Reference' %}",
|
|
observation: "{% trans 'Tracking Code' %}"
|
|
};
|
|
document.getElementById('resultRefLabel').textContent = refLabels[data.type] || "{% trans 'Reference' %}";
|
|
document.getElementById('resultReference').textContent = data.reference;
|
|
document.getElementById('resultStatusText').textContent = data.status_display;
|
|
|
|
var colorMap = {
|
|
amber: 'bg-amber-50 text-amber-700 border-amber-200',
|
|
blue: 'bg-blue-50 text-blue-700 border-blue-200',
|
|
emerald: 'bg-emerald-50 text-emerald-700 border-emerald-200',
|
|
rose: 'bg-rose-50 text-rose-700 border-rose-200',
|
|
slate: 'bg-slate-50 text-slate-700 border-slate-200',
|
|
sky: 'bg-sky-50 text-sky-700 border-sky-200',
|
|
teal: 'bg-teal-50 text-teal-700 border-teal-200'
|
|
};
|
|
var badge = document.getElementById('resultStatusBadge');
|
|
badge.className = 'px-6 py-3 rounded-2xl text-sm font-black uppercase tracking-wider shadow-sm border-b-4 ' + (colorMap[data.status_color] || colorMap.slate);
|
|
badge.textContent = data.status_display;
|
|
|
|
var esc = document.getElementById('resultEscalated');
|
|
if (data.escalated) { esc.classList.remove('hidden'); } else { esc.classList.add('hidden'); }
|
|
|
|
document.getElementById('resultProgressBar').style.width = data.progress + '%';
|
|
|
|
var cardsHtml = '';
|
|
(data.info_cards || []).forEach(function(card) {
|
|
var iconColor = card.alert ? 'text-rose-500' : (card.severity === 'critical' || card.severity === 'high' ? 'text-rose-500' : card.severity === 'medium' ? 'text-amber-500' : 'text-blue');
|
|
var alertDot = card.alert ? '<span class="absolute top-2 right-2 flex h-2 w-2"><span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-rose-400 opacity-75"></span><span class="relative inline-flex rounded-full h-2 w-2 bg-rose-500"></span></span>' : '';
|
|
cardsHtml += '<div class="bg-white p-6 rounded-2xl border border-slate-100 shadow-sm hover:shadow-md transition relative overflow-hidden">' +
|
|
'<i data-lucide="' + card.icon + '" class="w-5 h-5 ' + iconColor + ' mb-3"></i>' +
|
|
'<span class="block text-xs font-bold text-slate/50 uppercase">' + card.label + '</span>' +
|
|
'<p class="font-bold text-navy truncate">' + card.value + '</p>' +
|
|
alertDot +
|
|
'</div>';
|
|
});
|
|
document.getElementById('resultInfoCards').innerHTML = cardsHtml;
|
|
|
|
var timeline = data.timeline || [];
|
|
var timelineEl = document.getElementById('resultTimeline');
|
|
var noTimelineEl = document.getElementById('resultNoTimeline');
|
|
|
|
if (timeline.length > 0) {
|
|
noTimelineEl.classList.add('hidden');
|
|
var tlHtml = '';
|
|
timeline.forEach(function(item) {
|
|
var iconBg = item.type === 'status_change' ? 'bg-amber-100 text-amber-600' : (item.type === 'resolution' || item.type === 'response' ? 'bg-emerald-100 text-emerald-600' : 'bg-blue-50 text-blue-600');
|
|
tlHtml += '<div class="timeline-item flex gap-6 pb-10 relative">' +
|
|
'<div class="timeline-dot shrink-0 relative z-10">' +
|
|
'<div class="w-12 h-12 rounded-2xl flex items-center justify-center shadow-sm border-2 border-white ' + iconBg + '">' +
|
|
'<i data-lucide="' + item.icon + '" class="w-6 h-6"></i>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div class="flex-1 pt-1">' +
|
|
'<div class="flex flex-col md:flex-row md:items-center justify-between mb-2">' +
|
|
'<h4 class="font-black text-navy text-lg">' + item.title + '</h4>' +
|
|
'<time class="text-sm font-medium text-slate/40">' + item.created_at + '</time>' +
|
|
'</div>';
|
|
if (item.comment) {
|
|
tlHtml += '<div class="bg-slate-50/50 rounded-2xl p-5 border border-slate-100 text-slate-700 leading-relaxed shadow-inner">' + item.comment.replace(/\n/g, '<br>') + '</div>';
|
|
}
|
|
tlHtml += '</div></div>';
|
|
});
|
|
timelineEl.innerHTML = tlHtml;
|
|
timelineEl.classList.remove('hidden');
|
|
} else {
|
|
timelineEl.innerHTML = '';
|
|
timelineEl.classList.add('hidden');
|
|
noTimelineEl.classList.remove('hidden');
|
|
}
|
|
|
|
document.getElementById('resultsBox').classList.remove('hidden');
|
|
lucide.createIcons();
|
|
|
|
document.getElementById('resultsBox').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|