557 lines
19 KiB
HTML
557 lines
19 KiB
HTML
{% extends "layouts/base.html" %}
|
||
{% load i18n %}
|
||
{% load static %}
|
||
|
||
{% block title %}{{ physician.get_full_name }} - {% trans "Physicians" %} - PX360{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
:root {
|
||
--hh-navy: #005696;
|
||
--hh-blue: #007bbd;
|
||
--hh-light: #eef6fb;
|
||
--hh-slate: #64748b;
|
||
--hh-success: #10b981;
|
||
--hh-warning: #f59e0b;
|
||
--hh-danger: #ef4444;
|
||
--hh-shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||
--hh-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||
--hh-shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
/* Page Header */
|
||
.page-header {
|
||
background: linear-gradient(135deg, var(--hh-navy) 0%, #0069a8 50%, var(--hh-blue) 100%);
|
||
color: white;
|
||
padding: 2rem 2.5rem;
|
||
border-radius: 1rem;
|
||
margin-bottom: 2rem;
|
||
box-shadow: var(--hh-shadow-lg);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.page-header::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 200px;
|
||
height: 200px;
|
||
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.breadcrumb {
|
||
background: transparent;
|
||
padding: 0;
|
||
margin: 0 0 1rem 0;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.breadcrumb-item a {
|
||
color: rgba(255,255,255,0.85);
|
||
text-decoration: none;
|
||
transition: color 0.2s ease;
|
||
}
|
||
|
||
.breadcrumb-item a:hover {
|
||
color: white;
|
||
}
|
||
|
||
.breadcrumb-item.active {
|
||
color: rgba(255,255,255,0.7);
|
||
}
|
||
|
||
.breadcrumb-item + .breadcrumb-item::before {
|
||
color: rgba(255,255,255,0.5);
|
||
content: "›";
|
||
padding: 0 0.5rem;
|
||
}
|
||
|
||
/* Stat Cards */
|
||
.stat-card {
|
||
background: white;
|
||
border-radius: 1rem;
|
||
padding: 1.5rem;
|
||
box-shadow: var(--hh-shadow-md);
|
||
border: 1px solid #e2e8f0;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
height: 100%;
|
||
}
|
||
|
||
.stat-card:hover {
|
||
box-shadow: var(--hh-shadow-lg);
|
||
transform: translateY(-4px);
|
||
}
|
||
|
||
.stat-card.primary {
|
||
border-left: 4px solid var(--hh-navy);
|
||
}
|
||
|
||
.stat-card.success {
|
||
border-left: 4px solid var(--hh-success);
|
||
}
|
||
|
||
.stat-card.warning {
|
||
border-left: 4px solid var(--hh-warning);
|
||
}
|
||
|
||
.stat-card.info {
|
||
border-left: 4px solid var(--hh-blue);
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 2rem;
|
||
font-weight: 800;
|
||
color: #1e293b;
|
||
margin-bottom: 0.25rem;
|
||
}
|
||
|
||
.stat-label {
|
||
color: #64748b;
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.025em;
|
||
}
|
||
|
||
/* Info Card */
|
||
.info-card {
|
||
background: white;
|
||
border-radius: 1rem;
|
||
border: 1px solid #e2e8f0;
|
||
overflow: hidden;
|
||
box-shadow: var(--hh-shadow-md);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.info-card:hover {
|
||
box-shadow: var(--hh-shadow-lg);
|
||
}
|
||
|
||
.info-card .card-header {
|
||
background: linear-gradient(135deg, var(--hh-light), #e0f2fe);
|
||
padding: 1.25rem 1.75rem;
|
||
border-bottom: 1px solid #bae6fd;
|
||
}
|
||
|
||
.info-card .card-header h5 {
|
||
color: var(--hh-navy);
|
||
font-weight: 700;
|
||
margin: 0;
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.info-card .card-body {
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.info-item {
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
|
||
.info-item:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.info-label {
|
||
color: #64748b;
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
margin-bottom: 0.375rem;
|
||
}
|
||
|
||
.info-value {
|
||
color: #1e293b;
|
||
font-size: 1rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Status Badge */
|
||
.status-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0.5rem 1rem;
|
||
border-radius: 9999px;
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.status-badge.active {
|
||
background: linear-gradient(135deg, #dcfce7, #bbf7d0);
|
||
color: #166534;
|
||
}
|
||
|
||
.status-badge.inactive {
|
||
background: linear-gradient(135deg, #f1f5f9, #e2e8f0);
|
||
color: #475569;
|
||
}
|
||
|
||
/* Table */
|
||
.ratings-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
}
|
||
|
||
.ratings-table th {
|
||
background: linear-gradient(135deg, var(--hh-light), #e0f2fe);
|
||
padding: 0.875rem 1rem;
|
||
text-align: left;
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.05em;
|
||
color: var(--hh-navy);
|
||
border-bottom: 2px solid #bae6fd;
|
||
}
|
||
|
||
.ratings-table td {
|
||
padding: 1rem;
|
||
border-bottom: 1px solid #f1f5f9;
|
||
color: #475569;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.ratings-table tbody tr {
|
||
transition: background-color 0.2s ease;
|
||
}
|
||
|
||
.ratings-table tbody tr:hover {
|
||
background-color: var(--hh-light);
|
||
}
|
||
|
||
.ratings-table tbody tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
|
||
/* Rating Badge */
|
||
.rating-badge {
|
||
display: inline-block;
|
||
padding: 0.25rem 0.75rem;
|
||
border-radius: 9999px;
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.rating-badge.positive {
|
||
background: linear-gradient(135deg, #dcfce7, #bbf7d0);
|
||
color: #166534;
|
||
}
|
||
|
||
.rating-badge.neutral {
|
||
background: linear-gradient(135deg, #fef3c7, #fde68a);
|
||
color: #92400e;
|
||
}
|
||
|
||
.rating-badge.negative {
|
||
background: linear-gradient(135deg, #fee2e2, #fecaca);
|
||
color: #991b1b;
|
||
}
|
||
|
||
/* Trend Badge */
|
||
.trend-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.375rem;
|
||
padding: 0.375rem 0.75rem;
|
||
border-radius: 0.5rem;
|
||
font-size: 0.875rem;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.trend-badge.improving {
|
||
background: linear-gradient(135deg, #dcfce7, #bbf7d0);
|
||
color: #166534;
|
||
}
|
||
|
||
.trend-badge.declining {
|
||
background: linear-gradient(135deg, #fee2e2, #fecaca);
|
||
color: #991b1b;
|
||
}
|
||
|
||
/* Empty State */
|
||
.empty-state {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 3rem 1.5rem;
|
||
text-align: center;
|
||
}
|
||
|
||
.empty-state-icon {
|
||
width: 80px;
|
||
height: 80px;
|
||
background: linear-gradient(135deg, var(--hh-light), #e0f2fe);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 1.5rem;
|
||
color: var(--hh-blue);
|
||
}
|
||
|
||
/* Animations */
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.animate-in {
|
||
animation: fadeIn 0.5s ease-out forwards;
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="px-4 py-6">
|
||
<!-- Page Header -->
|
||
<div class="page-header animate-in">
|
||
<nav aria-label="breadcrumb">
|
||
<ol class="breadcrumb">
|
||
<li class="breadcrumb-item"><a href="{% url 'physicians:physician_list' %}">{% trans "Physicians" %}</a></li>
|
||
<li class="breadcrumb-item active">{{ physician.get_full_name }}</li>
|
||
</ol>
|
||
</nav>
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<h1 class="text-2xl font-bold mb-2">
|
||
<i data-lucide="user" class="w-7 h-7 inline-block me-2"></i>
|
||
{{ physician.get_full_name }}
|
||
</h1>
|
||
<p class="text-white/90 text-base">
|
||
{% if physician.specialization %}{{ physician.specialization }}{% endif %}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
{% if physician.status == 'active' %}
|
||
<span class="status-badge active">
|
||
<i data-lucide="check-circle" class="w-4 h-4"></i>
|
||
{% trans "Active" %}
|
||
</span>
|
||
{% else %}
|
||
<span class="status-badge inactive">
|
||
<i data-lucide="x-circle" class="w-4 h-4"></i>
|
||
{% trans "Inactive" %}
|
||
</span>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||
<!-- Left Column: Physician Info -->
|
||
<div class="lg:col-span-1 space-y-6">
|
||
<!-- Basic Information -->
|
||
<div class="info-card animate-in">
|
||
<div class="card-header">
|
||
<h5>
|
||
<i data-lucide="user" class="w-5 h-5 inline-block me-2"></i>
|
||
{% trans "Basic Information" %}
|
||
</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="info-item">
|
||
<p class="info-label">{% trans "License Number" %}</p>
|
||
<p class="info-value">{{ physician.license_number }}</p>
|
||
</div>
|
||
<div class="info-item">
|
||
<p class="info-label">{% trans "Specialization" %}</p>
|
||
<p class="info-value">{{ physician.specialization|default:"-" }}</p>
|
||
</div>
|
||
<div class="info-item">
|
||
<p class="info-label">{% trans "Hospital" %}</p>
|
||
<p class="info-value">{{ physician.hospital.name }}</p>
|
||
</div>
|
||
{% if physician.department %}
|
||
<div class="info-item">
|
||
<p class="info-label">{% trans "Department" %}</p>
|
||
<p class="info-value">{{ physician.department.name }}</p>
|
||
</div>
|
||
{% endif %}
|
||
{% if physician.email %}
|
||
<div class="info-item">
|
||
<p class="info-label">{% trans "Email" %}</p>
|
||
<p class="info-value">{{ physician.email }}</p>
|
||
</div>
|
||
{% endif %}
|
||
{% if physician.phone %}
|
||
<div class="info-item">
|
||
<p class="info-label">{% trans "Phone" %}</p>
|
||
<p class="info-value">{{ physician.phone }}</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Current Month Performance -->
|
||
{% if current_month_rating %}
|
||
<div class="info-card animate-in">
|
||
<div class="card-header">
|
||
<h5>
|
||
<i data-lucide="star" class="w-5 h-5 inline-block me-2"></i>
|
||
{% trans "Current Month" %}
|
||
</h5>
|
||
</div>
|
||
<div class="card-body text-center">
|
||
<div class="mb-4">
|
||
<p class="text-6xl font-bold text-navy mb-2">{{ current_month_rating.average_rating|floatformat:2 }}</p>
|
||
<p class="text-slate text-sm">{% trans "Average Rating" %}</p>
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-4 mb-4">
|
||
<div class="bg-light rounded-xl p-4">
|
||
<p class="text-2xl font-bold text-navy">{{ current_month_rating.total_surveys }}</p>
|
||
<p class="text-slate text-xs">{% trans "Surveys" %}</p>
|
||
</div>
|
||
<div class="bg-light rounded-xl p-4">
|
||
{% if current_month_rating.hospital_rank %}
|
||
<p class="text-2xl font-bold text-navy">#{{ current_month_rating.hospital_rank }}</p>
|
||
<p class="text-slate text-xs">{% trans "Rank" %}</p>
|
||
{% else %}
|
||
<p class="text-2xl font-bold text-slate-400">-</p>
|
||
<p class="text-slate text-xs">{% trans "No Rank" %}</p>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Trend -->
|
||
{% if trend != 'stable' %}
|
||
<div>
|
||
{% if trend == 'improving' %}
|
||
<span class="trend-badge improving">
|
||
<i data-lucide="trending-up" class="w-4 h-4"></i>
|
||
{% trans "Improving" %} {{ trend_percentage|floatformat:1 }}%
|
||
</span>
|
||
{% else %}
|
||
<span class="trend-badge declining">
|
||
<i data-lucide="trending-down" class="w-4 h-4"></i>
|
||
{% trans "Declining" %} {{ trend_percentage|floatformat:1 }}%
|
||
</span>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Right Column: Performance Metrics -->
|
||
<div class="lg:col-span-2 space-y-6">
|
||
<!-- Year-to-Date Performance -->
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 animate-in">
|
||
<div class="stat-card primary">
|
||
<p class="stat-label">{% trans "YTD Average Rating" %}</p>
|
||
{% if ytd_average %}
|
||
<p class="stat-value text-green-600">{{ ytd_average|floatformat:2 }}</p>
|
||
{% else %}
|
||
<p class="stat-value text-slate-400">-</p>
|
||
{% endif %}
|
||
</div>
|
||
<div class="stat-card info">
|
||
<p class="stat-label">{% trans "YTD Total Surveys" %}</p>
|
||
<p class="stat-value">{{ ytd_surveys|default:0 }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Best & Worst Months -->
|
||
{% if best_month or worst_month %}
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 animate-in">
|
||
{% if best_month %}
|
||
<div class="stat-card success">
|
||
<p class="stat-label">{% trans "Best Month" %}</p>
|
||
<p class="stat-value">{{ best_month.average_rating|floatformat:2 }}</p>
|
||
<p class="text-slate text-sm">{{ best_month.year }}-{{ best_month.month|stringformat:"02d" }}</p>
|
||
</div>
|
||
{% endif %}
|
||
{% if worst_month %}
|
||
<div class="stat-card warning">
|
||
<p class="stat-label">{% trans "Lowest Month" %}</p>
|
||
<p class="stat-value">{{ worst_month.average_rating|floatformat:2 }}</p>
|
||
<p class="text-slate text-sm">{{ worst_month.year }}-{{ worst_month.month|stringformat:"02d" }}</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Ratings History -->
|
||
<div class="info-card animate-in">
|
||
<div class="card-header">
|
||
<h5>
|
||
<i data-lucide="circle-help" class="w-5 h-5 inline-block me-2"></i>
|
||
{% trans "Ratings History" %} ({% trans "Last 12 Months" %})
|
||
</h5>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
{% if ratings_history %}
|
||
<div class="overflow-x-auto">
|
||
<table class="ratings-table">
|
||
<thead>
|
||
<tr>
|
||
<th>{% trans "Month" %}</th>
|
||
<th>{% trans "Rating" %}</th>
|
||
<th>{% trans "Surveys" %}</th>
|
||
<th>{% trans "Positive" %}</th>
|
||
<th>{% trans "Neutral" %}</th>
|
||
<th>{% trans "Negative" %}</th>
|
||
<th>{% trans "Rank" %}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for rating in ratings_history %}
|
||
<tr>
|
||
<td class="font-semibold">{{ rating.year }}-{{ rating.month|stringformat:"02d" }}</td>
|
||
<td>
|
||
<strong class="text-navy">{{ rating.average_rating|floatformat:2 }}</strong>
|
||
</td>
|
||
<td>{{ rating.total_surveys }}</td>
|
||
<td>
|
||
<span class="rating-badge positive">{{ rating.positive_count }}</span>
|
||
</td>
|
||
<td>
|
||
<span class="rating-badge neutral">{{ rating.neutral_count }}</span>
|
||
</td>
|
||
<td>
|
||
<span class="rating-badge negative">{{ rating.negative_count }}</span>
|
||
</td>
|
||
<td>
|
||
{% if rating.hospital_rank %}
|
||
<span class="font-semibold text-navy">#{{ rating.hospital_rank }}</span>
|
||
{% else %}
|
||
<span class="text-slate-400">-</span>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="empty-state">
|
||
<div class="empty-state-icon">
|
||
<i data-lucide="table" class="w-10 h-10"></i>
|
||
</div>
|
||
<p class="text-slate font-medium mb-2">{% trans "No rating history available" %}</p>
|
||
<p class="text-slate text-sm">{% trans "Ratings will appear here once the physician has received survey responses." %}</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
lucide.createIcons();
|
||
});
|
||
</script>
|
||
{% endblock %}
|