2026-02-19 08:08:18 +00:00

543 lines
25 KiB
HTML

{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% block title %}{% trans "RaktaPulse Dashboard" %} - {% trans "Lifeline of the Community" %}{% endblock %}
{% block head %}
<style>
.stat-card {
background: #ffffff;
border-radius: 20px;
padding: 24px;
border: 1px solid var(--border-color);
position: relative;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.03);
transition: all 0.3s ease;
text-decoration: none;
display: block;
color: inherit;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(230, 57, 70, 0.1);
border-color: var(--pulse-red);
cursor: pointer;
}
.stat-card .icon-box {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 15px;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
font-family: 'Outfit', sans-serif;
}
.stat-label {
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: 500;
}
.blood-group-pill {
width: 40px;
height: 40px;
border-radius: 10px;
background: rgba(230, 57, 70, 0.1);
color: var(--pulse-red);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
}
.donor-row {
background: #f8f9fa;
border-radius: 12px;
padding: 15px;
margin-bottom: 12px;
border: 1px solid #eee;
transition: all 0.3s;
}
.donor-row:hover {
border-color: var(--pulse-red);
background: var(--pulse-red-light);
}
.urgency-badge {
padding: 4px 10px;
border-radius: 6px;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
}
.bg-critical { background: #FF4D4D; color: #fff; }
.bg-urgent { background: #FFA500; color: #000; }
.bg-normal { background: #4CAF50; color: #fff; }
.progress {
background-color: #2A2A2A;
border-radius: 10px;
}
.vaccination-card {
background: linear-gradient(135deg, #E63946 0%, #d62828 100%);
border-radius: 20px;
padding: 25px;
color: white !important;
}
.filter-btn {
background: #ffffff;
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 8px 16px;
border-radius: 10px;
font-size: 0.85rem;
}
.filter-btn.active {
background: var(--pulse-red);
border-color: var(--pulse-red);
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid p-0">
<!-- Welcome Header -->
<div class="row mb-4 align-items-center">
<div class="col-md-7">
<h2 class="brand-font mb-1">{% trans "RaktaPulse Community Dashboard" %}</h2>
<p class="text-secondary mb-0">{% trans "Overview of blood donation activity and requirements in your area." %}</p>
</div>
{% if user.is_authenticated and user_badges %}
<div class="col-md-5 text-md-end mt-3 mt-md-0">
<div class="d-flex flex-wrap gap-2 justify-content-md-end">
{% for badge in user_badges %}
<div class="badge-item d-flex align-items-center gap-2 px-3 py-2 bg-white rounded-pill border shadow-sm {% if badge.name == 'First-Time Donor' %}d-md-none{% endif %}" title="{{ badge.description }}">
<i class="{{ badge.icon_class }} text-warning"></i>
<span class="small fw-bold text-dark">{{ badge.name }}</span>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% if involved_events %}
<!-- Active involvements / Message section -->
<div class="row mb-4">
<div class="col-12">
<div class="glass-card border-start border-primary border-4 bg-primary bg-opacity-10">
<h5 class="brand-font mb-3"><i class="bi bi-chat-dots-fill me-2"></i>{% trans "Action Required: Donations in Progress" %}</h5>
<div class="row g-3">
{% for event in involved_events %}
<div class="col-md-6 col-lg-4">
<div class="p-3 border rounded bg-white shadow-sm">
<p class="mb-2"><strong>{{ event.donor.name }}</strong> is helping <strong>{{ event.request.patient_name }}</strong></p>
<div class="d-flex justify-content-between">
<span class="small text-muted">{{ event.date|date:"M d, Y" }}</span>
<a href="{% url 'complete_donation' event.id %}" class="btn btn-sm btn-success px-3 rounded-pill">{% trans "Mark Completed" %}</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endif %}
<!-- Quick Stats & Impact Counter -->
<div class="row g-4 mb-5">
<div class="col-xl-2 col-md-4 col-6">
<a href="{% url 'donor_list' %}" class="stat-card">
<div class="icon-box bg-danger bg-opacity-10 text-danger">
<i class="bi bi-people"></i>
</div>
<div class="stat-value">{{ stats.total_donors }}</div>
<div class="stat-label">{% trans "Donors" %}</div>
</a>
</div>
<div class="col-xl-2 col-md-4 col-6">
<a href="{% url 'blood_request_list' %}?status=Active" class="stat-card">
<div class="icon-box bg-warning bg-opacity-10 text-warning">
<i class="bi bi-activity"></i>
</div>
<div class="stat-value">{{ stats.active_requests }}</div>
<div class="stat-label">{% trans "Requests" %}</div>
</a>
</div>
<div class="col-xl-2 col-md-4 col-6">
<div class="stat-card">
<div class="icon-box bg-success bg-opacity-10 text-success">
<i class="bi bi-check2-circle"></i>
</div>
<div class="stat-value">{{ stats.completed_donations }}</div>
<div class="stat-label">{% trans "Donations" %}</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-6">
<div class="stat-card bg-danger bg-opacity-10 border-danger">
<div class="icon-box bg-danger text-white">
<i class="bi bi-heart-fill"></i>
</div>
<div class="stat-value text-danger">{{ stats.lives_saved }}</div>
<div class="stat-label text-danger">{% trans "Lives Saved" %}</div>
</div>
</div>
<div class="col-xl-3 col-md-6 col-12">
<div class="stat-card">
<div class="icon-box bg-primary bg-opacity-10 text-primary">
<i class="bi bi-droplet"></i>
</div>
<div class="stat-value">{{ stats.total_stock }} <small class="fs-6 fw-normal">/ {{ stats.total_capacity }}</small></div>
<div class="stat-label">{% trans "Stock Level" %}</div>
</div>
</div>
</div>
<div class="row g-4">
<!-- Main Donor Search & Grid -->
<div class="col-lg-8">
<div class="glass-card mb-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0 brand-font">Available Donors</h4>
<div class="d-flex gap-2">
<button class="filter-btn active" id="btn-list">List View</button>
<button class="filter-btn" id="btn-map">Map View</button>
<button class="filter-btn" id="btn-nearest">Nearest First</button>
</div>
</div>
<div id="map-container" style="display: none; height: 400px; border-radius: 12px; margin-bottom: 20px; overflow: hidden; border: 1px solid var(--border-color);">
<div id="map" style="height: 100%;"></div>
</div>
<form method="GET" id="donorHomeFilterForm" class="row g-3 mb-4">
<div class="col-md-5">
<select name="blood_group" class="form-select bg-light border-secondary text-dark">
<option value="">All Blood Groups</option>
{% for group in blood_groups %}
<option value="{{ group }}" {% if request.GET.blood_group == group %}selected{% endif %}>{{ group }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-5">
<input type="text" name="location" class="form-control bg-light border-secondary text-dark"
placeholder="Search location (e.g. Baluwatar)..." value="{{ request.GET.location }}">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-danger w-100 shadow-sm">Find</button>
</div>
<input type="hidden" name="lat" id="latInputHome" value="{{ request.GET.lat }}">
<input type="hidden" name="lng" id="lngInputHome" value="{{ request.GET.lng }}">
</form>
<div class="donor-list">
{% for donor in donors %}
<div class="donor-row d-flex align-items-center justify-content-between flex-wrap gap-3">
<div class="d-flex align-items-center gap-3">
<div class="position-relative">
{% if donor.user and donor.user.profile.profile_pic %}
<img src="{{ donor.user.profile.profile_pic.url }}" alt="{{ donor.name }}" class="rounded-circle" style="width: 45px; height: 45px; object-fit: cover;">
<span class="position-absolute bottom-0 end-0 badge rounded-pill bg-danger border border-white" style="font-size: 0.6rem; padding: 0.2rem 0.4rem;">
{{ donor.blood_group }}
</span>
{% else %}
<div class="blood-group-pill">{{ donor.blood_group }}</div>
{% endif %}
</div>
<div>
<h6 class="mb-0 fw-bold text-dark">
{{ donor.name }}
{% if donor.is_verified %}
<span class="ms-1" title="Verified Donor"><i class="bi bi-patch-check-fill text-success"></i></span>
{% endif %}
{% if donor.distance and donor.distance < 1000 %}
<span class="ms-2 badge bg-info bg-opacity-10 text-info" style="font-size: 0.65rem;">
{{ donor.distance|floatformat:1 }} km
</span>
{% endif %}
</h6>
<p class="mb-0 text-secondary small"><i class="bi bi-geo-alt me-1"></i> {{ donor.location }}, {{ donor.district }}</p>
</div>
</div>
<div class="d-flex align-items-center gap-4">
<div class="text-end d-none d-sm-block">
<span class="badge {% if donor.is_available %}bg-success{% else %}bg-secondary{% endif %} bg-opacity-10 text-{% if donor.is_available %}success{% else %}secondary{% endif %} mb-1">
{% if donor.is_available %}Available{% else %}Unavailable{% endif %}
</span>
<p class="mb-0 text-muted extra-small" style="font-size: 0.7rem;">Last Donated: {{ donor.last_donation_date|default:"Never" }}</p>
</div>
<div class="d-flex gap-2">
{% if donor.user %}
<a href="{% url 'chat' donor.user.username %}" class="btn btn-outline-danger btn-sm rounded-pill" title="Message">
<i class="bi bi-chat-dots-fill"></i>
</a>
{% endif %}
<a href="tel:{{ donor.phone }}" class="btn btn-danger btn-sm px-3 rounded-pill">Call</a>
</div>
</div>
</div>
{% empty %}
<div class="text-center py-5">
<i class="bi bi-search fs-1 text-secondary opacity-25"></i>
<p class="text-secondary mt-3">No donors match your search criteria.</p>
</div>
{% endfor %}
</div>
</div>
<!-- Vaccination Monitoring Section -->
<div class="vaccination-card">
<div class="row align-items-center">
<div class="col-md-7">
<h4 class="brand-font text-white mb-2">Vaccination & Eligibility</h4>
<p class="text-secondary small">We prioritize donors who are fully vaccinated to ensure maximum safety for recipients.</p>
<div class="mb-4">
<div class="d-flex justify-content-between small text-white mb-2">
<span>Community Immunity Level</span>
<span>{{ stats.vaccinated_percentage }}%</span>
</div>
<div class="progress" style="height: 10px;">
<div class="progress-bar bg-success" style="width: {{ stats.vaccinated_percentage }}%"></div>
</div>
</div>
<a href="{% url 'vaccination_dashboard' %}" class="btn btn-success btn-sm px-4">Update Status</a>
</div>
<div class="col-md-5 d-none d-md-block text-center">
<i class="bi bi-shield-plus text-success" style="font-size: 5rem; opacity: 0.2;"></i>
</div>
</div>
</div>
</div>
<!-- Sidebar Components -->
<div class="col-lg-4">
<!-- Urgent Requests -->
<div class="glass-card mb-4 border-start border-danger border-4">
<h5 class="brand-font mb-4 d-flex justify-content-between align-items-center">
Urgent Requests
<span class="badge bg-danger rounded-pill px-2" style="font-size: 0.6rem;">HOT</span>
</h5>
<!-- Emergency SMS Concept Button -->
<button onclick="sendEmergencyAlert()" class="btn btn-danger w-100 mb-4 py-2 rounded-pill shadow-sm">
<i class="bi bi-broadcast me-2"></i>{% trans "Send Emergency SMS Alert" %}
</button>
<div class="request-feed">
{% for req in blood_requests %}
<div class="mb-4 border-bottom border-light pb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="urgency-badge bg-{{ req.urgency|lower }}">{{ req.urgency }}</span>
<span class="fw-bold text-danger">{{ req.blood_group }}</span>
</div>
<h6 class="mb-1 text-dark">{{ req.patient_name }}</h6>
<p class="text-secondary extra-small mb-2"><i class="bi bi-hospital me-1"></i> {{ req.hospital }}</p>
{% with acceptance=req.donations.first %}
{% if acceptance %}
<div class="mb-2 p-1 px-2 rounded bg-success bg-opacity-10">
<p class="mb-0 extra-small text-success">
<i class="bi bi-person-check-fill me-1"></i>
Accepted by: <strong>{{ acceptance.donor.name }}</strong>
</p>
</div>
{% endif %}
{% endwith %}
<div class="d-flex justify-content-between align-items-center">
<small class="text-muted" style="font-size: 0.7rem;">{{ req.created_at|timesince }} ago</small>
<div class="d-flex gap-2 align-items-center">
{% if req.user %}
<a href="{% url 'chat' req.user.username %}" class="text-secondary" title="Message">
<i class="bi bi-chat-dots-fill"></i>
</a>
{% endif %}
<a href="tel:{{ req.contact_number }}" class="btn btn-link text-danger p-0 text-decoration-none small">Help Now →</a>
</div>
</div>
</div>
{% empty %}
<p class="text-secondary text-center">No active requests found.</p>
{% endfor %}
</div>
</div>
<!-- Blood Bank Inventory -->
<div class="glass-card">
<h5 class="brand-font mb-4">Blood Bank Inventory</h5>
<div class="inventory-list">
{% for bank in blood_banks %}
<div class="mb-3">
<div class="d-flex justify-content-between mb-1">
<span class="small fw-bold text-dark">{{ bank.name }}</span>
<span class="small text-secondary">24/7 Available</span>
</div>
<div class="d-flex gap-1 flex-wrap">
<span class="badge bg-dark border border-secondary text-secondary extra-small" style="font-size: 0.65rem;">A+ : {{ bank.stock_a_plus }}</span>
<span class="badge bg-dark border border-secondary text-secondary extra-small" style="font-size: 0.65rem;">B+ : {{ bank.stock_b_plus }}</span>
<span class="badge bg-dark border border-secondary text-secondary extra-small" style="font-size: 0.65rem;">O+ : {{ bank.stock_o_plus }}</span>
<span class="badge bg-dark border border-secondary text-secondary extra-small" style="font-size: 0.65rem;">AB+ : {{ bank.stock_ab_plus }}</span>
</div>
</div>
{% empty %}
<p class="text-secondary text-center">No blood banks registered.</p>
{% endfor %}
</div>
<a href="/admin/core/bloodbank/" class="btn btn-outline-secondary w-100 btn-sm mt-3">Manage Banks</a>
</div>
<!-- Extra Feature: Community Tip -->
<div class="glass-card mt-4 bg-gradient" style="background: linear-gradient(135deg, #1e1e1e 0%, #2d1b1b 100%);">
<h6 class="text-danger mb-2"><i class="bi bi-lightbulb me-2"></i>Lifesaver Tip</h6>
<p class="small text-secondary mb-0">Donating once can save up to three lives. Make sure to stay hydrated and rest before your appointment!</p>
</div>
</div>
</div>
<!-- Myths vs Facts Section -->
<div class="row g-4 mt-2 mb-5">
<div class="col-12">
<div class="glass-card bg-light bg-opacity-10 border-danger">
<h4 class="brand-font mb-4 text-center"><i class="bi bi-question-diamond text-danger me-2"></i>{% trans "Top 5 Myths vs. Facts about Blood Donation" %}</h4>
<div class="row g-4">
{% for item in myths_vs_facts %}
<div class="col-md-4 col-lg-2 {% if forloop.first %}offset-lg-1{% endif %}">
<div class="myth-fact-card p-3 h-100 bg-white rounded-4 shadow-sm border-bottom border-danger border-3">
<div class="text-danger small fw-bold mb-2">{% trans "MYTH" %}</div>
<p class="small text-muted mb-3 italic">"{{ item.myth }}"</p>
<div class="text-success small fw-bold mb-1">{% trans "FACT" %}</div>
<p class="extra-small text-dark mb-0">{{ item.fact }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const btnList = document.getElementById('btn-list');
const btnMap = document.getElementById('btn-map');
const mapContainer = document.getElementById('map-container');
const donorList = document.querySelector('.donor-list');
let map;
btnMap.addEventListener('click', () => {
btnMap.classList.add('active');
btnList.classList.remove('active');
mapContainer.style.display = 'block';
donorList.style.display = 'none';
if (!map) {
initMap();
} else {
// Need to invalidate size if it was hidden when initialized
setTimeout(() => { map.invalidateSize(); }, 200);
}
});
btnList.addEventListener('click', () => {
btnList.classList.add('active');
btnMap.classList.remove('active');
mapContainer.style.display = 'none';
donorList.style.display = 'block';
});
const btnNearest = document.getElementById('btn-nearest');
btnNearest.addEventListener('click', () => {
const btn = btnNearest;
const originalContent = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(function(position) {
document.getElementById('latInputHome').value = position.coords.latitude;
document.getElementById('lngInputHome').value = position.coords.longitude;
document.getElementById('donorHomeFilterForm').submit();
}, function(error) {
alert("Error getting location: " + error.message);
btn.disabled = false;
btn.innerHTML = originalContent;
});
} else {
alert("Geolocation is not supported by this browser.");
btn.disabled = false;
btn.innerHTML = originalContent;
}
});
function initMap() {
map = L.map('map').setView([27.7226, 85.3312], 14); // Centered on Baluwatar, Kathmandu
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Add dummy markers for donors
{% for donor in donors %}
// Randomly offset from center for demo
L.marker([27.7226 + (Math.random() - 0.5) * 0.05, 85.3312 + (Math.random() - 0.5) * 0.05])
.addTo(map)
.bindPopup("<b>{{ donor.name }}</b><br>{{ donor.blood_group }} - {{ donor.location }}");
{% endfor %}
}
function sendEmergencyAlert() {
if (!confirm("This will simulate sending an emergency SMS to all nearby donors of a specific blood group. Continue?")) return;
const bloodGroup = prompt("Enter the required blood group (e.g., A+, O-):", "O+");
if (!bloodGroup) return;
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(function(position) {
const data = {
blood_group: bloodGroup,
latitude: position.coords.latitude,
longitude: position.coords.longitude
};
fetch('/emergency-sms/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token }}'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.status === 'success') {
alert(result.message);
} else {
alert("Error: " + result.message);
}
})
.catch(error => {
console.error('Error:', error);
alert("Concept Demo: SMS API call simulated for " + bloodGroup + " donors nearby.");
});
}, function(error) {
alert("Error getting location: " + error.message);
});
} else {
alert("Geolocation is not supported.");
}
}
</script>
{% endblock %}