Autosave: 20260227-151537
This commit is contained in:
parent
78b19857c0
commit
03ebc3a83e
Binary file not shown.
@ -25,6 +25,10 @@ urlpatterns = [
|
||||
path("", include("core.urls")),
|
||||
]
|
||||
|
||||
admin.site.site_header = "Blood Bank Management System"
|
||||
admin.site.site_title = "Admin Portal"
|
||||
admin.site.index_title = "Welcome to Blood Bank Management Portal"
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,24 +1,90 @@
|
||||
from django.contrib import admin
|
||||
from .models import Donor, BloodRequest, BloodBank, VaccineRecord
|
||||
import csv
|
||||
from django.http import HttpResponse
|
||||
from .models import (
|
||||
Donor, BloodRequest, BloodBank, VaccineRecord,
|
||||
DonationEvent, Hospital, UserProfile, Badge,
|
||||
Notification, Message, HealthReport
|
||||
)
|
||||
|
||||
def export_as_csv(self, request, queryset):
|
||||
meta = self.model._meta
|
||||
field_names = [field.name for field in meta.fields]
|
||||
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = f'attachment; filename={meta}.csv'
|
||||
writer = csv.writer(response)
|
||||
|
||||
writer.writerow(field_names)
|
||||
for obj in queryset:
|
||||
writer.writerow([getattr(obj, field) for field in field_names])
|
||||
|
||||
return response
|
||||
|
||||
export_as_csv.short_description = "Export Selected to CSV"
|
||||
|
||||
@admin.register(UserProfile)
|
||||
class UserProfileAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'blood_group', 'location', 'phone')
|
||||
list_filter = ('blood_group',)
|
||||
search_fields = ('user__username', 'phone', 'location')
|
||||
actions = [export_as_csv]
|
||||
|
||||
@admin.register(Badge)
|
||||
class BadgeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'description')
|
||||
|
||||
@admin.register(VaccineRecord)
|
||||
class VaccineRecordAdmin(admin.ModelAdmin):
|
||||
list_display = ('vaccine_name', 'user', 'dose_number', 'date_taken', 'location')
|
||||
list_filter = ('vaccine_name', 'date_taken')
|
||||
search_fields = ('vaccine_name', 'user__username', 'location')
|
||||
actions = [export_as_csv]
|
||||
|
||||
@admin.register(Donor)
|
||||
class DonorAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'blood_group', 'location', 'is_available')
|
||||
list_filter = ('blood_group', 'is_available')
|
||||
list_display = ('name', 'blood_group', 'location', 'is_available', 'is_verified')
|
||||
list_filter = ('blood_group', 'is_available', 'is_verified')
|
||||
search_fields = ('name', 'location', 'phone')
|
||||
actions = [export_as_csv]
|
||||
|
||||
@admin.register(Hospital)
|
||||
class HospitalAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'location', 'phone')
|
||||
search_fields = ('name', 'location')
|
||||
|
||||
@admin.register(BloodRequest)
|
||||
class BloodRequestAdmin(admin.ModelAdmin):
|
||||
list_display = ('patient_name', 'blood_group', 'urgency', 'status', 'created_at')
|
||||
list_display = ('patient_name', 'blood_group', 'urgency', 'status', 'hospital', 'created_at')
|
||||
list_filter = ('blood_group', 'urgency', 'status')
|
||||
search_fields = ('patient_name', 'hospital')
|
||||
actions = [export_as_csv]
|
||||
|
||||
@admin.register(BloodBank)
|
||||
class BloodBankAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'location')
|
||||
list_display = ('name', 'location', 'stock_a_plus', 'stock_b_plus', 'stock_o_plus', 'stock_ab_plus')
|
||||
search_fields = ('name', 'location')
|
||||
actions = [export_as_csv]
|
||||
|
||||
@admin.register(DonationEvent)
|
||||
class DonationEventAdmin(admin.ModelAdmin):
|
||||
list_display = ('donor', 'request', 'date', 'is_completed')
|
||||
list_filter = ('is_completed', 'date')
|
||||
search_fields = ('donor__name', 'request__patient_name')
|
||||
actions = [export_as_csv]
|
||||
|
||||
@admin.register(Notification)
|
||||
class NotificationAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'message', 'is_read', 'created_at')
|
||||
list_filter = ('is_read', 'created_at')
|
||||
|
||||
@admin.register(Message)
|
||||
class MessageAdmin(admin.ModelAdmin):
|
||||
list_display = ('sender', 'receiver', 'message_type', 'timestamp', 'is_read')
|
||||
list_filter = ('message_type', 'is_read', 'timestamp')
|
||||
|
||||
@admin.register(HealthReport)
|
||||
class HealthReportAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'user', 'hospital_name', 'report_date')
|
||||
list_filter = ('report_date',)
|
||||
search_fields = ('title', 'user__username', 'hospital_name')
|
||||
|
||||
@ -286,6 +286,8 @@
|
||||
<li class="{% if request.resolver_match.url_name == 'home' %}active{% endif %}"><a href="{% url 'home' %}"><i class="bi bi-grid-1x2-fill"></i> {% trans "Dashboard" %}</a></li>
|
||||
<li class="{% if request.resolver_match.url_name == 'donor_list' %}active{% endif %}"><a href="{% url 'donor_list' %}"><i class="bi bi-people-fill"></i> {% trans "Donors" %}</a></li>
|
||||
<li class="{% if request.resolver_match.url_name == 'blood_request_list' %}active{% endif %}"><a href="{% url 'blood_request_list' %}"><i class="bi bi-megaphone-fill"></i> {% trans "Blood Requests" %}</a></li>
|
||||
<li class="{% if request.resolver_match.url_name == 'donation_history' %}active{% endif %}"><a href="{% url 'donation_history' %}"><i class="bi bi-clock-history"></i> {% trans "Donation History" %}</a></li>
|
||||
<li class="{% if request.resolver_match.url_name == 'lives_saved' %}active{% endif %}"><a href="{% url 'lives_saved' %}"><i class="bi bi-heart-pulse"></i> {% trans "Lives Saved" %}</a></li>
|
||||
<li class="{% if request.resolver_match.url_name == 'blood_bank_list' %}active{% endif %}"><a href="{% url 'blood_bank_list' %}"><i class="bi bi-droplet-half"></i> {% trans "Blood Banks" %}</a></li>
|
||||
<li class="{% if request.resolver_match.url_name == 'hospital_list' %}active{% endif %}"><a href="{% url 'hospital_list' %}"><i class="bi bi-hospital-fill"></i> {% trans "Hospitals" %}</a></li>
|
||||
<li class="{% if request.resolver_match.url_name == 'live_map' %}active{% endif %}"><a href="{% url 'live_map' %}"><i class="bi bi-map text-danger"></i> {% trans "Live Alerts" %}</a></li>
|
||||
@ -354,9 +356,11 @@
|
||||
<!-- Notification Bell -->
|
||||
<a href="{% url 'notifications' %}" class="text-decoration-none me-2 position-relative">
|
||||
<i class="bi bi-bell-fill fs-5 text-secondary"></i>
|
||||
{% if unread_notifications_count > 0 %}
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="font-size: 0.6rem;">
|
||||
{{ user.notifications.count }}
|
||||
{{ unread_notifications_count }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
|
||||
<!-- Messages Inbox -->
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends 'core/base.html' %}
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
@ -6,13 +6,47 @@
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-8">
|
||||
<h2 class="display-5 fw-bold text-dark mb-2">{{ title }}</h2>
|
||||
<p class="text-muted">A transparent record of every life saved through the community's generosity.</p>
|
||||
<p class="text-muted">Explore the complete log of blood donations within the RaktaPulse community.</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
|
||||
<div class="stats-badge p-3 bg-white rounded shadow-sm border-start border-4 border-danger">
|
||||
<div class="stats-badge p-3 bg-white rounded shadow-sm border-start border-4 border-danger me-3">
|
||||
<span class="d-block text-muted small text-uppercase fw-bold">Total Impact</span>
|
||||
<span class="h4 fw-bold mb-0 text-danger">{{ donations.count }} Donations</span>
|
||||
</div>
|
||||
<a href="?export=csv{% if current_filters.blood_group %}&blood_group={{ current_filters.blood_group }}{% endif %}{% if current_filters.location %}&location={{ current_filters.location }}{% endif %}" class="btn btn-success rounded-pill shadow-sm">
|
||||
<i class="fas fa-file-csv me-2"></i> Export
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Section -->
|
||||
<div class="card border-0 shadow-sm rounded-4 mb-4">
|
||||
<div class="card-body p-4">
|
||||
<form method="GET" class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small fw-bold text-muted">Blood Group</label>
|
||||
<select name="blood_group" class="form-select rounded-pill border-light">
|
||||
<option value="">All Groups</option>
|
||||
{% for group in blood_groups %}
|
||||
<option value="{{ group }}" {% if current_filters.blood_group == group %}selected{% endif %}>{{ group }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label small fw-bold text-muted">Location</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-light border-end-0 rounded-start-pill">
|
||||
<i class="fas fa-map-marker-alt text-muted"></i>
|
||||
</span>
|
||||
<input type="text" name="location" class="form-control border-light border-start-0 rounded-end-pill" placeholder="Search by city or area..." value="{{ current_filters.location|default:'' }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-danger w-100 rounded-pill">
|
||||
<i class="fas fa-filter me-2"></i> Apply Filters
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -94,6 +94,67 @@
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.donor-list-container {
|
||||
max-height: 550px;
|
||||
overflow-y: auto;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.donor-list-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.donor-list-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.donor-list-container::-webkit-scrollbar-thumb {
|
||||
background: var(--pulse-red);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
background: linear-gradient(135deg, #1a1a1a, #2d1b1b);
|
||||
border: 1px solid rgba(255, 215, 0, 0.3);
|
||||
border-radius: 16px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
.hero-item {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 12px;
|
||||
padding: 8px 12px;
|
||||
margin-right: 10px;
|
||||
border: 1px solid rgba(255, 215, 0, 0.1);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 180px;
|
||||
}
|
||||
.live-pulse {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #FF4D4D;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
box-shadow: 0 0 0 rgba(255, 77, 77, 0.4);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(255, 77, 77, 0.7); }
|
||||
70% { box-shadow: 0 0 0 10px rgba(255, 77, 77, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(255, 77, 77, 0); }
|
||||
}
|
||||
.heroes-scroll {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
padding: 5px 0;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.heroes-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
background: #ffffff;
|
||||
border: 1px solid var(--border-color);
|
||||
@ -207,7 +268,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 col-6">
|
||||
<a href="{% url 'donation_history' %}" class="stat-card bg-danger bg-opacity-10 border-danger">
|
||||
<a href="{% url 'lives_saved' %}" 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>
|
||||
@ -216,13 +277,13 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 col-12">
|
||||
<div class="stat-card">
|
||||
<a href="#blood-bank-inventory" class="stat-card text-decoration-none">
|
||||
<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 class="stat-value text-dark">{{ stats.total_stock }} <small class="fs-6 fw-normal text-muted">/ {{ stats.total_capacity }}</small></div>
|
||||
<div class="stat-label text-secondary">{% trans "Stock Level" %}</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -231,7 +292,7 @@
|
||||
<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>
|
||||
<h4 class="mb-0 brand-font">Hero Community</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>
|
||||
@ -239,6 +300,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if recent_heroes %}
|
||||
<div class="hero-section">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="text-white small fw-bold">
|
||||
<span class="live-pulse"></span>
|
||||
Live: Today's Heroes (Last 24h)
|
||||
</span>
|
||||
<span class="badge bg-warning text-dark extra-small">{{ recent_heroes.count }} Active</span>
|
||||
</div>
|
||||
<div class="heroes-scroll">
|
||||
{% for hero in recent_heroes %}
|
||||
<div class="hero-item">
|
||||
{% if hero.donor.user and hero.donor.user.profile.profile_pic %}
|
||||
<img src="{{ hero.donor.user.profile.profile_pic.url }}" class="rounded-circle" style="width: 30px; height: 30px; object-fit: cover;">
|
||||
{% else %}
|
||||
<div class="rounded-circle bg-danger text-white d-flex align-items-center justify-content-center" style="width: 30px; height: 30px; font-size: 0.7rem;">
|
||||
{{ hero.donor.blood_group }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="overflow-hidden">
|
||||
<p class="text-white mb-0 small fw-bold text-truncate" style="max-width: 100px;">{{ hero.donor.name }}</p>
|
||||
<p class="text-warning extra-small mb-0" style="font-size: 0.65rem;">Saved Life {{ hero.date|timesince }} ago</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<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>
|
||||
@ -263,59 +353,61 @@
|
||||
<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 class="donor-list-container">
|
||||
<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>
|
||||
|
||||
<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
|
||||
<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 %}
|
||||
</h6>
|
||||
<p class="mb-0 text-secondary small"><i class="bi bi-geo-alt me-1"></i> {{ donor.location }}, {{ donor.district }}</p>
|
||||
<a href="tel:{{ donor.phone }}" class="btn btn-danger btn-sm px-3 rounded-pill">Call</a>
|
||||
</div>
|
||||
</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>
|
||||
{% 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>
|
||||
{% 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>
|
||||
|
||||
@ -370,9 +462,11 @@
|
||||
</h5>
|
||||
|
||||
<!-- Emergency SMS Concept Button -->
|
||||
{% if user.is_staff %}
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
<div class="request-feed">
|
||||
{% for req in blood_requests %}
|
||||
@ -413,7 +507,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Blood Bank Inventory -->
|
||||
<div class="glass-card">
|
||||
<div class="glass-card" id="blood-bank-inventory">
|
||||
<h5 class="brand-font mb-4">Blood Bank Inventory</h5>
|
||||
<div class="inventory-list">
|
||||
{% for bank in blood_banks %}
|
||||
@ -423,17 +517,23 @@
|
||||
<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>
|
||||
<span class="badge bg-dark border {% if bank.stock_a_plus < 5 %}border-danger text-danger{% else %}border-secondary text-secondary{% endif %} extra-small" style="font-size: 0.65rem;">A+: {{ bank.stock_a_plus }}</span>
|
||||
<span class="badge bg-dark border {% if bank.stock_a_minus < 5 %}border-danger text-danger{% else %}border-secondary text-secondary{% endif %} extra-small" style="font-size: 0.65rem;">A-: {{ bank.stock_a_minus }}</span>
|
||||
<span class="badge bg-dark border {% if bank.stock_b_plus < 5 %}border-danger text-danger{% else %}border-secondary text-secondary{% endif %} extra-small" style="font-size: 0.65rem;">B+: {{ bank.stock_b_plus }}</span>
|
||||
<span class="badge bg-dark border {% if bank.stock_b_minus < 5 %}border-danger text-danger{% else %}border-secondary text-secondary{% endif %} extra-small" style="font-size: 0.65rem;">B-: {{ bank.stock_b_minus }}</span>
|
||||
<span class="badge bg-dark border {% if bank.stock_o_plus < 5 %}border-danger text-danger{% else %}border-secondary text-secondary{% endif %} extra-small" style="font-size: 0.65rem;">O+: {{ bank.stock_o_plus }}</span>
|
||||
<span class="badge bg-dark border {% if bank.stock_o_minus < 5 %}border-danger text-danger{% else %}border-secondary text-secondary{% endif %} extra-small" style="font-size: 0.65rem;">O-: {{ bank.stock_o_minus }}</span>
|
||||
<span class="badge bg-dark border {% if bank.stock_ab_plus < 5 %}border-danger text-danger{% else %}border-secondary text-secondary{% endif %} extra-small" style="font-size: 0.65rem;">AB+: {{ bank.stock_ab_plus }}</span>
|
||||
<span class="badge bg-dark border {% if bank.stock_ab_minus < 5 %}border-danger text-danger{% else %}border-secondary text-secondary{% endif %} extra-small" style="font-size: 0.65rem;">AB-: {{ bank.stock_ab_minus }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-secondary text-center">No blood banks registered.</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if user.is_staff %}
|
||||
<a href="/admin/core/bloodbank/" class="btn btn-outline-secondary w-100 btn-sm mt-3">Manage Banks</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Extra Feature: Community Tip -->
|
||||
|
||||
91
core/templates/core/lives_saved.html
Normal file
91
core/templates/core/lives_saved.html
Normal file
@ -0,0 +1,91 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row mb-5 text-center">
|
||||
<div class="col-lg-8 mx-auto">
|
||||
<h1 class="display-4 fw-bold text-danger mb-3">{{ title }}</h1>
|
||||
<p class="lead text-muted">Every single donation has the potential to save up to three lives. Together, we are building a safer community.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm rounded-4 h-100 bg-danger text-white">
|
||||
<div class="card-body p-5 text-center">
|
||||
<div class="mb-3">
|
||||
<i class="fas fa-heartbeat fa-4x opacity-75"></i>
|
||||
</div>
|
||||
<h3 class="display-3 fw-bold mb-0">{{ total_impact }}</h3>
|
||||
<p class="text-uppercase fw-bold opacity-75">Total Lives Saved</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="card border-0 shadow-sm rounded-4 h-100">
|
||||
<div class="card-body p-5 text-center">
|
||||
<div class="mb-3">
|
||||
<i class="fas fa-tint fa-4x text-danger opacity-75"></i>
|
||||
</div>
|
||||
<h3 class="display-3 fw-bold mb-0 text-dark">{{ total_donations }}</h3>
|
||||
<p class="text-uppercase fw-bold text-muted">Successful Donations</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm rounded-4 mb-5">
|
||||
<div class="card-header bg-white py-4 border-bottom border-light text-center">
|
||||
<h4 class="mb-0 fw-bold">Recent Life-Saving Events</h4>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light text-muted small text-uppercase">
|
||||
<tr>
|
||||
<th class="ps-4 border-0 py-3">Hero</th>
|
||||
<th class="border-0 py-3">Impact</th>
|
||||
<th class="border-0 py-3">Location</th>
|
||||
<th class="pe-4 border-0 py-3 text-end">Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for donation in donations %}
|
||||
<tr>
|
||||
<td class="ps-4">
|
||||
<div class="fw-bold">{{ donation.donor_user.username }}</div>
|
||||
<div class="text-muted small">Verified Donor</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-success rounded-pill px-3">
|
||||
<i class="fas fa-plus me-1"></i> 3 Lives Saved
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="text-muted">{{ donation.request.location }}</div>
|
||||
</td>
|
||||
<td class="pe-4 text-end">
|
||||
<div class="fw-medium">{{ donation.date|date:"M d, Y" }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-5">
|
||||
<p class="text-muted mb-0">No recent events to display.</p>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<a href="{% url 'donation_history' %}" class="btn btn-danger rounded-pill px-5 py-3 shadow-sm">
|
||||
<i class="fas fa-history me-2"></i> View Full Donation History
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -40,4 +40,5 @@ urlpatterns = [
|
||||
path("update-location/", views.update_location, name="update_location"),
|
||||
path("emergency-sms/", views.emergency_sms, name="emergency_sms"),
|
||||
path("donation-history/", views.donation_history, name="donation_history"),
|
||||
path("lives-saved/", views.lives_saved, name="lives_saved"),
|
||||
]
|
||||
|
||||
@ -285,6 +285,13 @@ def home(request):
|
||||
actual_completed = DonationEvent.objects.filter(is_completed=True).count()
|
||||
completed_donations = actual_completed + demo_donations
|
||||
|
||||
# Find Recent Heroes (Last 24 Hours)
|
||||
last_24_hours = timezone.now() - timezone.timedelta(hours=24)
|
||||
recent_heroes = DonationEvent.objects.filter(
|
||||
is_completed=True,
|
||||
date__gte=last_24_hours
|
||||
).select_related('donor').order_by('-date')
|
||||
|
||||
stats = {
|
||||
"total_donors": Donor.objects.count() + demo_donors,
|
||||
"active_requests": BloodRequest.objects.filter(
|
||||
@ -315,11 +322,12 @@ def home(request):
|
||||
]
|
||||
|
||||
context = {
|
||||
"donors": donor_list_data[:8],
|
||||
"donors": donor_list_data[:15], # Increased count for scrollability
|
||||
"blood_requests": blood_requests[:6],
|
||||
"blood_banks": blood_banks,
|
||||
"blood_groups": [g[0] for g in BLOOD_GROUPS],
|
||||
"stats": stats,
|
||||
"recent_heroes": recent_heroes,
|
||||
"project_name": "RaktaPulse",
|
||||
"current_time": timezone.now(),
|
||||
"myths_vs_facts": myths_vs_facts,
|
||||
@ -333,6 +341,8 @@ def home(request):
|
||||
)
|
||||
context["involved_events"] = involved_events
|
||||
context["user_badges"] = request.user.profile.badges.all()
|
||||
context["unread_notifications_count"] = request.user.notifications.filter(is_read=False).count()
|
||||
context["unread_messages_count"] = Message.objects.filter(receiver=request.user, is_read=False).count()
|
||||
|
||||
return render(request, "core/index.html", context)
|
||||
|
||||
@ -478,17 +488,72 @@ def request_blood(request):
|
||||
}
|
||||
return render(request, 'core/request_blood.html', context)
|
||||
|
||||
import csv
|
||||
from django.http import HttpResponse
|
||||
|
||||
@login_required
|
||||
def donation_history(request):
|
||||
"""View to display the full history of completed donations."""
|
||||
"""View to display the full history of completed donations with filtering and export."""
|
||||
completed_donations = DonationEvent.objects.filter(is_completed=True).select_related('donor_user', 'request').order_by('-date')
|
||||
|
||||
# Filtering
|
||||
blood_group = request.GET.get('blood_group')
|
||||
location = request.GET.get('location')
|
||||
export = request.GET.get('export')
|
||||
|
||||
if blood_group:
|
||||
completed_donations = completed_donations.filter(request__blood_group=blood_group)
|
||||
if location:
|
||||
completed_donations = completed_donations.filter(request__location__icontains=location)
|
||||
|
||||
# Export to CSV
|
||||
if export == 'csv':
|
||||
response = HttpResponse(content_type='text/csv')
|
||||
response['Content-Disposition'] = 'attachment; filename="donation_history.csv"'
|
||||
|
||||
writer = csv.writer(response)
|
||||
writer.writerow(['Donor', 'Blood Group', 'Patient', 'Location', 'Hospital', 'Date'])
|
||||
|
||||
for donation in completed_donations:
|
||||
writer.writerow([
|
||||
donation.donor_user.username if donation.donor_user else donation.donor.name,
|
||||
donation.request.blood_group,
|
||||
donation.request.patient_name,
|
||||
donation.request.location,
|
||||
donation.request.hospital,
|
||||
donation.date.strftime('%Y-%m-%d %H:%M')
|
||||
])
|
||||
return response
|
||||
|
||||
context = {
|
||||
'donations': completed_donations,
|
||||
'title': 'Donation History & Lives Saved'
|
||||
'title': 'Donation History',
|
||||
'blood_groups': [g[0] for g in BLOOD_GROUPS],
|
||||
'current_filters': {
|
||||
'blood_group': blood_group,
|
||||
'location': location
|
||||
}
|
||||
}
|
||||
return render(request, 'core/donation_history.html', context)
|
||||
|
||||
@login_required
|
||||
def lives_saved(request):
|
||||
"""View to display the impact and lives saved through donations."""
|
||||
completed_donations = DonationEvent.objects.filter(is_completed=True).select_related('donor_user', 'request').order_by('-date')
|
||||
|
||||
total_donations = completed_donations.count()
|
||||
# Demo data from home view to keep consistency
|
||||
demo_donations = 157
|
||||
total_impact = (total_donations + demo_donations) * 3
|
||||
|
||||
context = {
|
||||
'donations': completed_donations[:10], # Show recent impact
|
||||
'total_donations': total_donations + demo_donations,
|
||||
'total_impact': total_impact,
|
||||
'title': 'Lives Saved & Community Impact',
|
||||
}
|
||||
return render(request, 'core/lives_saved.html', context)
|
||||
|
||||
@login_required
|
||||
@login_required
|
||||
def vaccination_dashboard(request):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user