Autosave: 20260227-151537

This commit is contained in:
Flatlogic Bot 2026-02-27 15:15:37 +00:00
parent 78b19857c0
commit 03ebc3a83e
12 changed files with 434 additions and 69 deletions

View File

@ -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)

View File

@ -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')

View File

@ -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 -->

View File

@ -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>

View File

@ -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 -->

View 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 %}

View File

@ -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"),
]

View File

@ -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):