diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 3c20f11..d1a5bd9 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/urls.py b/config/urls.py index 701370b..4321ed9 100644 --- a/config/urls.py +++ b/config/urls.py @@ -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) diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 7b9a1bb..42ebf60 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index e2cd584..384084c 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 7b85a88..50e031b 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 660771f..9e3ca05 100644 --- a/core/admin.py +++ b/core/admin.py @@ -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') diff --git a/core/templates/base.html b/core/templates/base.html index 1e0e226..0a323df 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -286,6 +286,8 @@
  • {% trans "Dashboard" %}
  • {% trans "Donors" %}
  • {% trans "Blood Requests" %}
  • +
  • {% trans "Donation History" %}
  • +
  • {% trans "Lives Saved" %}
  • {% trans "Blood Banks" %}
  • {% trans "Hospitals" %}
  • {% trans "Live Alerts" %}
  • @@ -354,9 +356,11 @@ + {% if unread_notifications_count > 0 %} - {{ user.notifications.count }} + {{ unread_notifications_count }} + {% endif %} diff --git a/core/templates/core/donation_history.html b/core/templates/core/donation_history.html index a1030ca..775b4af 100644 --- a/core/templates/core/donation_history.html +++ b/core/templates/core/donation_history.html @@ -1,4 +1,4 @@ -{% extends 'core/base.html' %} +{% extends 'base.html' %} {% load static %} {% block content %} @@ -6,13 +6,47 @@

    {{ title }}

    -

    A transparent record of every life saved through the community's generosity.

    +

    Explore the complete log of blood donations within the RaktaPulse community.

    -
    +
    Total Impact {{ donations.count }} Donations
    + + Export + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    + + + + +
    +
    +
    + +
    +
    diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 91940a3..d16a1a1 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -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 @@
    - +
    @@ -216,13 +277,13 @@
    -
    +
    -
    {{ stats.total_stock }} / {{ stats.total_capacity }}
    -
    {% trans "Stock Level" %}
    -
    +
    {{ stats.total_stock }} / {{ stats.total_capacity }}
    +
    {% trans "Stock Level" %}
    +
    @@ -231,7 +292,7 @@
    -

    Available Donors

    +

    Hero Community

    @@ -239,6 +300,35 @@
    + {% if recent_heroes %} +
    +
    + + + Live: Today's Heroes (Last 24h) + + {{ recent_heroes.count }} Active +
    +
    + {% for hero in recent_heroes %} +
    + {% if hero.donor.user and hero.donor.user.profile.profile_pic %} + + {% else %} +
    + {{ hero.donor.blood_group }} +
    + {% endif %} +
    +

    {{ hero.donor.name }}

    +

    Saved Life {{ hero.date|timesince }} ago

    +
    +
    + {% endfor %} +
    +
    + {% endif %} + @@ -263,59 +353,61 @@ -
    - {% for donor in donors %} -
    -
    -
    - {% if donor.user and donor.user.profile.profile_pic %} - {{ donor.name }} - - {{ donor.blood_group }} - - {% else %} -
    {{ donor.blood_group }}
    - {% endif %} +
    +
    + {% for donor in donors %} +
    +
    +
    + {% if donor.user and donor.user.profile.profile_pic %} + {{ donor.name }} + + {{ donor.blood_group }} + + {% else %} +
    {{ donor.blood_group }}
    + {% endif %} +
    +
    + +
    + {{ donor.name }} + {% if donor.is_verified %} + + {% endif %} + {% if donor.distance and donor.distance < 1000 %} + + {{ donor.distance|floatformat:1 }} km + + {% endif %} +
    +

    {{ donor.location }}, {{ donor.district }}

    +
    -
    - -
    - {{ donor.name }} - {% if donor.is_verified %} - - {% endif %} - {% if donor.distance and donor.distance < 1000 %} - - {{ donor.distance|floatformat:1 }} km +
    +
    + + {% if donor.is_available %}Available{% else %}Unavailable{% endif %} +

    Last Donated: {{ donor.last_donation_date|default:"Never" }}

    +
    +
    + {% if donor.user %} + + + {% endif %} -
    -

    {{ donor.location }}, {{ donor.district }}

    + Call +
    -
    -
    - - {% if donor.is_available %}Available{% else %}Unavailable{% endif %} - -

    Last Donated: {{ donor.last_donation_date|default:"Never" }}

    -
    -
    - {% if donor.user %} - - - - {% endif %} - Call -
    + {% empty %} +
    + +

    No donors match your search criteria.

    + {% endfor %}
    - {% empty %} -
    - -

    No donors match your search criteria.

    -
    - {% endfor %}
    @@ -370,9 +462,11 @@ + {% if user.is_staff %} + {% endif %}
    {% for req in blood_requests %} @@ -413,7 +507,7 @@
    -
    +
    Blood Bank Inventory
    {% for bank in blood_banks %} @@ -423,17 +517,23 @@ 24/7 Available
    - A+ : {{ bank.stock_a_plus }} - B+ : {{ bank.stock_b_plus }} - O+ : {{ bank.stock_o_plus }} - AB+ : {{ bank.stock_ab_plus }} + A+: {{ bank.stock_a_plus }} + A-: {{ bank.stock_a_minus }} + B+: {{ bank.stock_b_plus }} + B-: {{ bank.stock_b_minus }} + O+: {{ bank.stock_o_plus }} + O-: {{ bank.stock_o_minus }} + AB+: {{ bank.stock_ab_plus }} + AB-: {{ bank.stock_ab_minus }}
    {% empty %}

    No blood banks registered.

    {% endfor %}
    + {% if user.is_staff %} Manage Banks + {% endif %}
    diff --git a/core/templates/core/lives_saved.html b/core/templates/core/lives_saved.html new file mode 100644 index 0000000..becbc6a --- /dev/null +++ b/core/templates/core/lives_saved.html @@ -0,0 +1,91 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +
    +
    +
    +

    {{ title }}

    +

    Every single donation has the potential to save up to three lives. Together, we are building a safer community.

    +
    +
    + +
    +
    +
    +
    +
    + +
    +

    {{ total_impact }}

    +

    Total Lives Saved

    +
    +
    +
    +
    +
    +
    +
    + +
    +

    {{ total_donations }}

    +

    Successful Donations

    +
    +
    +
    +
    + +
    +
    +

    Recent Life-Saving Events

    +
    +
    +
    + + + + + + + + + + + {% for donation in donations %} + + + + + + + {% empty %} + + + + {% endfor %} + +
    HeroImpactLocationDate
    +
    {{ donation.donor_user.username }}
    +
    Verified Donor
    +
    + + 3 Lives Saved + + +
    {{ donation.request.location }}
    +
    +
    {{ donation.date|date:"M d, Y" }}
    +
    +

    No recent events to display.

    +
    +
    +
    +
    + + +
    +{% endblock %} diff --git a/core/urls.py b/core/urls.py index 3cb4ff1..18c4bf7 100644 --- a/core/urls.py +++ b/core/urls.py @@ -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"), ] diff --git a/core/views.py b/core/views.py index 736ba8a..9795189 100644 --- a/core/views.py +++ b/core/views.py @@ -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):