Autosave: 20260218-053623

This commit is contained in:
Flatlogic Bot 2026-02-18 05:36:23 +00:00
parent 0f9878cadc
commit 800e9db512
21 changed files with 609 additions and 43 deletions

View File

@ -61,6 +61,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
@ -141,6 +142,15 @@ USE_I18N = True
USE_TZ = True USE_TZ = True
LANGUAGES = [
('en', 'English'),
('ne', 'Nepali'),
]
LOCALE_PATHS = [
BASE_DIR / 'locale',
]
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/ # https://docs.djangoproject.com/en/5.2/howto/static-files/

View File

@ -21,6 +21,7 @@ from django.conf.urls.static import static
urlpatterns = [ urlpatterns = [
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("i18n/", include("django.conf.urls.i18n")),
path("", include("core.urls")), path("", include("core.urls")),
] ]

View File

@ -0,0 +1,58 @@
# Generated by Django 5.2.7 on 2026-02-18 05:21
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_userprofile'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='bloodrequest',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='blood_requests', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='donor',
name='user',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='donor_profile', to=settings.AUTH_USER_MODEL),
),
migrations.CreateModel(
name='DonationEvent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(default=django.utils.timezone.now)),
('is_completed', models.BooleanField(default=False)),
('donor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='donations', to='core.donor')),
('donor_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='my_donations', to=settings.AUTH_USER_MODEL)),
('request', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='donations', to='core.bloodrequest')),
],
),
migrations.CreateModel(
name='Feedback',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField()),
('rating', models.PositiveIntegerField(default=5)),
('created_at', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedbacks', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Notification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('message', models.TextField()),
('is_read', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -46,6 +46,7 @@ class VaccineRecord(models.Model):
return f"{self.vaccine_name} - Dose {self.dose_number} for {self.user.username}" return f"{self.vaccine_name} - Dose {self.dose_number} for {self.user.username}"
class Donor(models.Model): class Donor(models.Model):
user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='donor_profile')
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS) blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS)
district = models.CharField(max_length=100, default='Kathmandu') district = models.CharField(max_length=100, default='Kathmandu')
@ -71,6 +72,7 @@ class BloodRequest(models.Model):
('URGENT', 'Urgent'), ('URGENT', 'Urgent'),
('NORMAL', 'Normal'), ('NORMAL', 'Normal'),
] ]
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blood_requests', null=True, blank=True)
patient_name = models.CharField(max_length=100) patient_name = models.CharField(max_length=100)
blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS) blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS)
location = models.CharField(max_length=255) location = models.CharField(max_length=255)
@ -105,3 +107,31 @@ class BloodBank(models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
class DonationEvent(models.Model):
donor = models.ForeignKey(Donor, on_delete=models.CASCADE, related_name='donations')
request = models.ForeignKey(BloodRequest, on_delete=models.CASCADE, related_name='donations')
donor_user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='my_donations')
date = models.DateTimeField(default=timezone.now)
is_completed = models.BooleanField(default=False)
def __str__(self):
return f"Donation by {self.donor.name} for {self.request.patient_name}"
class Notification(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='notifications')
message = models.TextField()
is_read = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Notification for {self.user.username}: {self.message[:20]}..."
class Feedback(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='feedbacks')
content = models.TextField()
rating = models.PositiveIntegerField(default=5)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"Feedback from {self.user.username} - {self.rating} stars"

View File

@ -1,5 +1,6 @@
{% load i18n %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="{{ LANGUAGE_CODE }}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -178,6 +179,78 @@
#sidebar.active { margin-left: 0; } #sidebar.active { margin-left: 0; }
} }
/* SOS Floating Button */
.sos-btn {
position: fixed;
bottom: 30px;
right: 30px;
width: 65px;
height: 65px;
background-color: var(--pulse-red);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
box-shadow: 0 4px 15px rgba(230, 57, 70, 0.4);
z-index: 9999;
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
font-weight: 800;
font-size: 1.1rem;
border: 3px solid white;
}
.sos-btn:hover {
transform: scale(1.1);
color: white;
box-shadow: 0 6px 20px rgba(230, 57, 70, 0.6);
}
.sos-btn .pulse-ring {
position: absolute;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: var(--pulse-red);
opacity: 0.6;
animation: sos-pulse 2s infinite;
z-index: -1;
}
.sos-content {
display: flex;
flex-direction: column;
align-items: center;
line-height: 1;
}
.sos-text {
font-size: 1.1rem;
font-weight: 800;
}
.sos-number {
font-size: 0.7rem;
font-weight: 600;
margin-top: 2px;
}
@keyframes sos-pulse {
0% { transform: scale(1); opacity: 0.6; }
100% { transform: scale(1.8); opacity: 0; }
}
@media (max-width: 576px) {
.sos-btn {
bottom: 20px;
right: 20px;
width: 55px;
height: 55px;
font-size: 0.9rem;
}
}
{% block head %}{% endblock %} {% block head %}{% endblock %}
</style> </style>
</head> </head>
@ -195,25 +268,19 @@
</div> </div>
<ul class="sidebar-menu mt-4"> <ul class="sidebar-menu mt-4">
<li class="{% if request.resolver_match.url_name == 'home' %}active{% endif %}"><a href="{% url 'home' %}"><i class="bi bi-grid-1x2-fill"></i> Dashboard</a></li> <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> Donors</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> Blood Requests</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 == 'blood_bank_list' %}active{% endif %}"><a href="{% url 'blood_bank_list' %}"><i class="bi bi-hospital-fill"></i> Blood Banks</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-hospital-fill"></i> {% trans "Blood Banks" %}</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> Live Alerts</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>
<li class="{% if request.resolver_match.url_name == 'vaccination_info' %}active{% endif %}"><a href="{% url 'vaccination_info' %}"><i class="bi bi-shield-check"></i> Vaccination</a></li> <li class="{% if request.resolver_match.url_name == 'vaccination_info' %}active{% endif %}"><a href="{% url 'vaccination_info' %}"><i class="bi bi-shield-check"></i> {% trans "Vaccination" %}</a></li>
<li class="{% if request.resolver_match.url_name == 'submit_feedback' %}active{% endif %}"><a href="{% url 'submit_feedback' %}"><i class="bi bi-chat-left-heart-fill"></i> {% trans "Feedback" %}</a></li>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li class="{% if request.resolver_match.url_name == 'profile' %}active{% endif %}"><a href="{% url 'profile' %}"><i class="bi bi-person-bounding-box"></i> My Profile</a></li> <li class="{% if request.resolver_match.url_name == 'profile' %}active{% endif %}"><a href="{% url 'profile' %}"><i class="bi bi-person-bounding-box"></i> {% trans "My Profile" %}</a></li>
<li class="{% if 'vaccination_dashboard' in request.resolver_match.url_name or 'add_vaccination' in request.resolver_match.url_name %}active{% endif %}"><a href="{% url 'vaccination_dashboard' %}"><i class="bi bi-journal-check"></i> My Records</a></li> <li class="{% if 'vaccination_dashboard' in request.resolver_match.url_name or 'add_vaccination' in request.resolver_match.url_name %}active{% endif %}"><a href="{% url 'vaccination_dashboard' %}"><i class="bi bi-journal-check"></i> {% trans "My Records" %}</a></li>
{% endif %} {% endif %}
<li><a href="/admin/"><i class="bi bi-gear-fill"></i> Settings</a></li> <li><a href="/admin/"><i class="bi bi-gear-fill"></i> {% trans "Settings" %}</a></li>
</ul> </ul>
<div class="position-absolute bottom-0 w-100 p-4">
<div class="glass-card p-3 bg-opacity-10 border-danger border-opacity-25" style="background: rgba(230, 57, 70, 0.05);">
<p class="small text-secondary mb-2">Need Help?</p>
<a href="tel:1115" class="btn btn-sm btn-danger w-100 fw-bold">Emergency: 1115</a>
</div>
</div>
</nav> </nav>
<!-- Page Content --> <!-- Page Content -->
@ -231,6 +298,31 @@
</div> </div>
<div class="d-flex align-items-center gap-2 gap-md-4"> <div class="d-flex align-items-center gap-2 gap-md-4">
<!-- Language Switcher -->
<div class="dropdown">
<button class="btn btn-link text-secondary dropdown-toggle d-flex align-items-center gap-1 p-0 text-decoration-none small" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-translate"></i>
<span class="d-none d-sm-inline">{{ LANGUAGE_CODE|upper }}</span>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow-sm border-0">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<li>
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}">
<input name="language" type="hidden" value="{{ language.code }}">
<button type="submit" class="dropdown-item {% if language.code == LANGUAGE_CODE %}active{% endif %}">
{{ language.name_local }} ({{ language.code|upper }})
</button>
</form>
</li>
{% endfor %}
</ul>
</div>
<div id="location-status" class="d-none d-xl-flex align-items-center text-secondary small cursor-pointer" style="cursor: pointer;" onclick="detectLocation()"> <div id="location-status" class="d-none d-xl-flex align-items-center text-secondary small cursor-pointer" style="cursor: pointer;" onclick="detectLocation()">
<i class="bi bi-geo-alt me-1"></i> <i class="bi bi-geo-alt me-1"></i>
<span id="location-text">Detect Location</span> <span id="location-text">Detect Location</span>
@ -241,6 +333,13 @@
</div> </div>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<!-- 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>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger" style="font-size: 0.6rem;">
{{ user.notifications.count }}
</span>
</a>
<a href="{% url 'profile' %}" class="text-decoration-none d-flex align-items-center gap-2"> <a href="{% url 'profile' %}" class="text-decoration-none d-flex align-items-center gap-2">
<div class="bg-danger bg-opacity-10 rounded-circle p-1 d-flex align-items-center justify-content-center" style="width: 32px; height: 32px;"> <div class="bg-danger bg-opacity-10 rounded-circle p-1 d-flex align-items-center justify-content-center" style="width: 32px; height: 32px;">
<i class="bi bi-person-fill text-danger"></i> <i class="bi bi-person-fill text-danger"></i>
@ -335,6 +434,39 @@
} }
checkLocationCookie(); checkLocationCookie();
</script> </script>
<a href="javascript:void(0);" class="sos-btn" id="sosButton" title="Single click: Request Form | Double click: Call 1115">
<div class="pulse-ring"></div>
<div class="sos-content">
<span class="sos-text">SOS</span>
<span class="sos-number">1115</span>
</div>
</a>
<script>
(function() {
const sosBtn = document.getElementById('sosButton');
let clickTimer = null;
sosBtn.addEventListener('click', function(e) {
e.preventDefault();
if (clickTimer === null) {
// First click: start a timer
clickTimer = setTimeout(function() {
clickTimer = null;
// Single click action: Go to request form
window.location.href = "{% url 'request_blood' %}";
}, 300); // 300ms window for double click
} else {
// Second click within 300ms: clear timer and trigger call
clearTimeout(clickTimer);
clickTimer = null;
window.location.href = "tel:1115";
}
});
})();
</script>
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}
</body> </body>
</html> </html>

View File

@ -21,8 +21,8 @@
<div class="row mb-4"> <div class="row mb-4">
<div class="col-12 d-flex justify-content-between align-items-center"> <div class="col-12 d-flex justify-content-between align-items-center">
<div> <div>
<h2 class="brand-font mb-1">Blood Requests</h2> <h2 class="brand-font mb-1">{% if current_status %}{{ current_status }} {% endif %}Blood Requests</h2>
<p class="text-secondary">Current active requirements for blood in various hospitals.</p> <p class="text-secondary">{% if current_status == 'Active' %}Current urgent requirements for blood.{% else %}History of blood requests in our community.{% endif %}</p>
</div> </div>
<a href="{% url 'request_blood' %}" class="btn btn-danger rounded-pill px-4">Post a Request</a> <a href="{% url 'request_blood' %}" class="btn btn-danger rounded-pill px-4">Post a Request</a>
</div> </div>
@ -34,7 +34,14 @@
<div class="col-md-6 col-xl-4 mb-4"> <div class="col-md-6 col-xl-4 mb-4">
<div class="p-3 border rounded h-100 bg-light d-flex flex-column"> <div class="p-3 border rounded h-100 bg-light d-flex flex-column">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-3">
<span class="urgency-badge bg-{{ req.urgency|lower }}">{{ req.urgency }}</span> <div class="d-flex gap-2">
<span class="urgency-badge bg-{{ req.urgency|lower }}">{{ req.urgency }}</span>
{% if req.status == 'Active' %}
<span class="badge bg-success bg-opacity-10 text-success small" style="font-size: 0.7rem;">Active</span>
{% else %}
<span class="badge bg-secondary bg-opacity-10 text-secondary small" style="font-size: 0.7rem;">{{ req.status }}</span>
{% endif %}
</div>
<div class="bg-danger bg-opacity-10 text-danger rounded-circle d-flex align-items-center justify-content-center fw-bold" style="width: 40px; height: 40px;"> <div class="bg-danger bg-opacity-10 text-danger rounded-circle d-flex align-items-center justify-content-center fw-bold" style="width: 40px; height: 40px;">
{{ req.blood_group }} {{ req.blood_group }}
</div> </div>
@ -44,7 +51,10 @@
<div class="mt-auto pt-3 border-top"> <div class="mt-auto pt-3 border-top">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<span class="small text-muted"><i class="bi bi-clock me-1"></i> {{ req.created_at|timesince }} ago</span> <span class="small text-muted"><i class="bi bi-clock me-1"></i> {{ req.created_at|timesince }} ago</span>
<a href="tel:{{ req.contact_number }}" class="btn btn-sm btn-danger px-3 rounded-pill">Help Now</a> <div class="d-flex gap-2">
<a href="{% url 'volunteer_for_request' req.id %}" class="btn btn-sm btn-outline-danger px-3 rounded-pill">Volunteer</a>
<a href="tel:{{ req.contact_number }}" class="btn btn-sm btn-danger px-3 rounded-pill">Call</a>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -71,8 +71,9 @@
<p class="mb-0 text-muted extra-small" style="font-size: 0.7rem;">Phone: {{ donor.phone }}</p> <p class="mb-0 text-muted extra-small" style="font-size: 0.7rem;">Phone: {{ donor.phone }}</p>
</div> </div>
<a href="tel:{{ donor.phone }}" class="btn btn-outline-danger btn-sm px-3 rounded-pill">Contact Now</a> <a href="tel:{{ donor.phone }}" class="btn btn-outline-danger btn-sm px-3 rounded-pill">Contact Now</a>
</div> </div>
{% endfor %} </div>
{% endfor %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,34 @@
{% extends 'core/base.html' %}
{% load i18n %}
{% block content %}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="glass-card p-4">
<h2 class="mb-4">{% trans "Share Your Feedback" %}</h2>
<p class="text-muted">{% trans "We value your experience. Please let us know how we can improve." %}</p>
<form method="POST">
{% csrf_token %}
<div class="mb-3">
<label for="rating" class="form-label">{% trans "Rating" %}</label>
<select name="rating" id="rating" class="form-select">
<option value="5">⭐⭐⭐⭐⭐ (Excellent)</option>
<option value="4">⭐⭐⭐⭐ (Good)</option>
<option value="3">⭐⭐⭐ (Average)</option>
<option value="2">⭐⭐ (Poor)</option>
<option value="1">⭐ (Very Poor)</option>
</select>
</div>
<div class="mb-3">
<label for="content" class="form-label">{% trans "Your Feedback" %}</label>
<textarea name="content" id="content" rows="5" class="form-control" placeholder="{% trans 'Tell us about your experience...' %}" required></textarea>
</div>
<button type="submit" class="btn btn-primary w-100">{% trans "Submit Feedback" %}</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,7 +1,8 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% load i18n %}
{% block title %}RaktaPulse Dashboard - Lifeline of the Community{% endblock %} {% block title %}{% trans "RaktaPulse Dashboard" %} - {% trans "Lifeline of the Community" %}{% endblock %}
{% block head %} {% block head %}
<style> <style>
@ -13,6 +14,16 @@
position: relative; position: relative;
overflow: hidden; overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.03); 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 { .stat-card .icon-box {
width: 48px; width: 48px;
@ -103,30 +114,54 @@
<!-- Welcome Header --> <!-- Welcome Header -->
<div class="row mb-4"> <div class="row mb-4">
<div class="col-12"> <div class="col-12">
<h2 class="brand-font mb-1">RaktaPulse Community Dashboard</h2> <h2 class="brand-font mb-1">{% trans "RaktaPulse Community Dashboard" %}</h2>
<p class="text-secondary">Overview of blood donation activity and requirements in your area.</p> <p class="text-secondary">{% trans "Overview of blood donation activity and requirements in your area." %}</p>
</div> </div>
</div> </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 --> <!-- Quick Stats -->
<div class="row g-4 mb-5"> <div class="row g-4 mb-5">
<div class="col-xl-3 col-md-6"> <div class="col-xl-3 col-md-6">
<div class="stat-card"> <a href="{% url 'donor_list' %}" class="stat-card">
<div class="icon-box bg-danger bg-opacity-10 text-danger"> <div class="icon-box bg-danger bg-opacity-10 text-danger">
<i class="bi bi-people"></i> <i class="bi bi-people"></i>
</div> </div>
<div class="stat-value">{{ stats.total_donors }}</div> <div class="stat-value">{{ stats.total_donors }}</div>
<div class="stat-label">Registered Donors</div> <div class="stat-label">{% trans "Registered Donors" %}</div>
</div> </a>
</div> </div>
<div class="col-xl-3 col-md-6"> <div class="col-xl-3 col-md-6">
<div class="stat-card"> <a href="{% url 'blood_request_list' %}?status=Active" class="stat-card">
<div class="icon-box bg-warning bg-opacity-10 text-warning"> <div class="icon-box bg-warning bg-opacity-10 text-warning">
<i class="bi bi-activity"></i> <i class="bi bi-activity"></i>
</div> </div>
<div class="stat-value">{{ stats.active_requests }}</div> <div class="stat-value">{{ stats.active_requests }}</div>
<div class="stat-label">Active Requests</div> <div class="stat-label">{% trans "Active Requests" %}</div>
</div> </a>
</div> </div>
<div class="col-xl-3 col-md-6"> <div class="col-xl-3 col-md-6">
<div class="stat-card"> <div class="stat-card">
@ -134,7 +169,7 @@
<i class="bi bi-droplet"></i> <i class="bi bi-droplet"></i>
</div> </div>
<div class="stat-value">{{ stats.total_stock }} <small class="fs-6 fw-normal">/ {{ stats.total_capacity }}</small></div> <div class="stat-value">{{ stats.total_stock }} <small class="fs-6 fw-normal">/ {{ stats.total_capacity }}</small></div>
<div class="stat-label">Inventory vs Capacity</div> <div class="stat-label">{% trans "Inventory vs Capacity" %}</div>
</div> </div>
</div> </div>
<div class="col-xl-3 col-md-6"> <div class="col-xl-3 col-md-6">
@ -143,7 +178,7 @@
<i class="bi bi-shield-check"></i> <i class="bi bi-shield-check"></i>
</div> </div>
<div class="stat-value">{{ stats.vaccinated_percentage }}%</div> <div class="stat-value">{{ stats.vaccinated_percentage }}%</div>
<div class="stat-label">Vaccinated Donors</div> <div class="stat-label">{% trans "Vaccinated Donors" %}</div>
</div> </div>
</div> </div>
</div> </div>
@ -176,7 +211,7 @@
</div> </div>
<div class="col-md-5"> <div class="col-md-5">
<input type="text" name="location" class="form-control bg-light border-secondary text-dark" <input type="text" name="location" class="form-control bg-light border-secondary text-dark"
placeholder="Search location (e.g. Kathmandu)..." value="{{ request.GET.location }}"> placeholder="Search location (e.g. Baluwatar)..." value="{{ request.GET.location }}">
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<button type="submit" class="btn btn-danger w-100 shadow-sm">Find</button> <button type="submit" class="btn btn-danger w-100 shadow-sm">Find</button>
@ -307,6 +342,41 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Blood & Vaccine Facts Section -->
<div class="row g-4 mt-2 mb-5">
<div class="col-12">
<div class="glass-card">
<h4 class="brand-font mb-4"><i class="bi bi-info-circle text-danger me-2"></i>Blood & Vaccine Facts</h4>
<div class="row g-4">
<div class="col-md-3">
<div class="p-3 border border-light rounded-4 h-100 bg-light bg-opacity-10">
<h6 class="fw-bold text-danger"><i class="bi bi-droplet-fill me-2"></i>Lifesaver</h6>
<p class="small text-secondary mb-0">A single blood donation can save up to three lives. Your contribution has a massive impact on the community.</p>
</div>
</div>
<div class="col-md-3">
<div class="p-3 border border-light rounded-4 h-100 bg-light bg-opacity-10">
<h6 class="fw-bold text-danger"><i class="bi bi-shield-check me-2"></i>Vaccines & Donation</h6>
<p class="small text-secondary mb-0">Most vaccines (like COVID-19 or Flu) don't stop you from donating if you feel well. Stay protected and keep saving lives!</p>
</div>
</div>
<div class="col-md-3">
<div class="p-3 border border-light rounded-4 h-100 bg-light bg-opacity-10">
<h6 class="fw-bold text-danger"><i class="bi bi-award me-2"></i>Universal Type</h6>
<p class="small text-secondary mb-0">O-Negative is the universal donor type. It's vital for emergency rooms and trauma centers during critical hours.</p>
</div>
</div>
<div class="col-md-3">
<div class="p-3 border border-light rounded-4 h-100 bg-light bg-opacity-10">
<h6 class="fw-bold text-danger"><i class="bi bi-lightning-charge me-2"></i>Quick Recovery</h6>
<p class="small text-secondary mb-0">Your body replaces blood plasma within 24-48 hours. Just stay hydrated and have a light snack after your donation!</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
{% endblock %} {% endblock %}
@ -365,15 +435,15 @@
}); });
function initMap() { function initMap() {
map = L.map('map').setView([27.7172, 85.3240], 12); // Centered on Kathmandu 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', { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors' attribution: '© OpenStreetMap contributors'
}).addTo(map); }).addTo(map);
// Add dummy markers for donors // Add dummy markers for donors
{% for donor in donors %} {% for donor in donors %}
// Randomly offset from center for demo (within Nepal context) // Randomly offset from center for demo
L.marker([27.7172 + (Math.random() - 0.5) * 0.1, 85.3240 + (Math.random() - 0.5) * 0.1]) L.marker([27.7226 + (Math.random() - 0.5) * 0.05, 85.3312 + (Math.random() - 0.5) * 0.05])
.addTo(map) .addTo(map)
.bindPopup("<b>{{ donor.name }}</b><br>{{ donor.blood_group }} - {{ donor.location }}"); .bindPopup("<b>{{ donor.name }}</b><br>{{ donor.blood_group }} - {{ donor.location }}");
{% endfor %} {% endfor %}

View File

@ -84,8 +84,8 @@
var markers = {}; var markers = {};
function initMap() { function initMap() {
// Center on Kathmandu by default // Center on Baluwatar, Kathmandu
map = L.map('live-map').setView([27.7172, 85.3240], 13); map = L.map('live-map').setView([27.7226, 85.3312], 15);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors' attribution: '© OpenStreetMap contributors'

View File

@ -0,0 +1,35 @@
{% extends 'core/base.html' %}
{% load i18n %}
{% block content %}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="glass-card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="mb-0">{% trans "Notifications" %}</h2>
<span class="badge bg-primary">{{ notifications.count }}</span>
</div>
{% if notifications %}
<div class="list-group list-group-flush">
{% for note in notifications %}
<div class="list-group-item bg-transparent border-0 mb-3 p-3 glass-card {% if not note.is_read %}border-start border-primary border-4{% endif %}">
<div class="d-flex justify-content-between">
<p class="mb-1">{{ note.message }}</p>
<small class="text-muted">{{ note.created_at|timesince }} {% trans "ago" %}</small>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-bell-slash fa-3x text-muted mb-3"></i>
<p>{% trans "No notifications yet." %}</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,37 @@
{% extends 'core/base.html' %}
{% load i18n %}
{% block content %}
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-md-6">
<div class="glass-card p-4">
<h2 class="mb-4">{% trans "Become a Blood Donor" %}</h2>
<p class="text-muted">{% trans "Join our community of lifesavers. Your donation can save lives." %}</p>
<form method="POST">
{% csrf_token %}
<div class="mb-3">
<label for="blood_group" class="form-label">{% trans "Blood Group" %}</label>
<select name="blood_group" id="blood_group" class="form-select" required>
<option value="">{% trans "Select Blood Group" %}</option>
{% for group in blood_groups %}
<option value="{{ group }}">{{ group }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="phone" class="form-label">{% trans "Phone Number" %}</label>
<input type="text" name="phone" id="phone" class="form-control" required>
</div>
<div class="mb-3">
<label for="location" class="form-label">{% trans "Location" %}</label>
<input type="text" name="location" id="location" class="form-control" placeholder="e.g. Baluwatar, Kathmandu">
</div>
<button type="submit" class="btn btn-danger w-100">{% trans "Register as Donor" %}</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -36,6 +36,22 @@
<li class="list-group-item bg-transparent border-light"><i class="bi bi-check-circle-fill text-success me-2"></i> At least 14 days must have passed since the last dose.</li> <li class="list-group-item bg-transparent border-light"><i class="bi bi-check-circle-fill text-success me-2"></i> At least 14 days must have passed since the last dose.</li>
<li class="list-group-item bg-transparent border-light"><i class="bi bi-check-circle-fill text-success me-2"></i> Must be in good health on the day of donation.</li> <li class="list-group-item bg-transparent border-light"><i class="bi bi-check-circle-fill text-success me-2"></i> Must be in good health on the day of donation.</li>
</ul> </ul>
<h5 class="fw-bold mb-3 mt-4">Interesting Vaccination Facts</h5>
<div class="row g-3">
<div class="col-sm-6">
<div class="p-3 bg-light rounded border h-100">
<h6 class="fw-bold text-primary small">Ancient Roots</h6>
<p class="small mb-0 text-secondary">The word "vaccine" comes from the Latin <em>vacca</em> (cow), honoring the first smallpox vaccine derived from cowpox.</p>
</div>
</div>
<div class="col-sm-6">
<div class="p-3 bg-light rounded border h-100">
<h6 class="fw-bold text-primary small">Immune Memory</h6>
<p class="small mb-0 text-secondary">Vaccines create "memory cells" that allow your body to recognize and fight pathogens years after the initial shot.</p>
</div>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,13 @@
from django.urls import path from django.urls import path
from .views import home, login_view, logout_view, register_view, donor_list, blood_request_list, blood_bank_list, vaccination_info, vaccination_dashboard, add_vaccination, live_map, request_blood, profile from .views import (
home, login_view, logout_view, register_view, donor_list,
blood_request_list, blood_bank_list, vaccination_info,
vaccination_dashboard, add_vaccination, live_map,
request_blood, profile, volunteer_for_request,
complete_donation, submit_feedback, notifications_view,
register_donor
)
urlpatterns = [ urlpatterns = [
path("", home, name="home"), path("", home, name="home"),
@ -16,4 +23,9 @@ urlpatterns = [
path("vaccination/add/", add_vaccination, name="add_vaccination"), path("vaccination/add/", add_vaccination, name="add_vaccination"),
path("live-map/", live_map, name="live_map"), path("live-map/", live_map, name="live_map"),
path("request-blood/", request_blood, name="request_blood"), path("request-blood/", request_blood, name="request_blood"),
path("volunteer/<int:request_id>/", volunteer_for_request, name="volunteer_for_request"),
path("complete-donation/<int:event_id>/", complete_donation, name="complete_donation"),
path("feedback/", submit_feedback, name="submit_feedback"),
path("notifications/", notifications_view, name="notifications"),
path("register-donor/", register_donor, name="register_donor"),
] ]

View File

@ -1,12 +1,13 @@
import os import os
import platform import platform
from django.db import models
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth import login, logout, authenticate from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from django.utils import timezone from django.utils import timezone
from .models import Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile, BLOOD_GROUPS from .models import Donor, BloodRequest, BloodBank, VaccineRecord, UserProfile, BLOOD_GROUPS, DonationEvent, Notification, Feedback
from .forms import UserUpdateForm, ProfileUpdateForm from .forms import UserUpdateForm, ProfileUpdateForm
import math import math
@ -135,6 +136,15 @@ def home(request):
"project_name": "RaktaPulse", "project_name": "RaktaPulse",
"current_time": timezone.now(), "current_time": timezone.now(),
} }
if request.user.is_authenticated:
# Get active involvements (where user is donor or requester)
involved_events = DonationEvent.objects.filter(
(models.Q(donor_user=request.user) | models.Q(request__user=request.user)),
is_completed=False
)
context["involved_events"] = involved_events
return render(request, "core/index.html", context) return render(request, "core/index.html", context)
def donor_list(request): def donor_list(request):
@ -173,9 +183,15 @@ def donor_list(request):
return render(request, 'core/donor_list.html', context) return render(request, 'core/donor_list.html', context)
def blood_request_list(request): def blood_request_list(request):
requests = BloodRequest.objects.all().order_by('-created_at') status = request.GET.get('status', '')
requests = BloodRequest.objects.all()
if status:
requests = requests.filter(status=status)
requests = requests.order_by('-created_at')
context = { context = {
'requests': requests, 'requests': requests,
'current_status': status,
} }
return render(request, 'core/blood_request_list.html', context) return render(request, 'core/blood_request_list.html', context)
@ -242,6 +258,7 @@ def request_blood(request):
if patient_name and blood_group and hospital and contact_number: if patient_name and blood_group and hospital and contact_number:
BloodRequest.objects.create( BloodRequest.objects.create(
user=request.user if request.user.is_authenticated else None,
patient_name=patient_name, patient_name=patient_name,
blood_group=blood_group, blood_group=blood_group,
location=location, location=location,
@ -297,3 +314,106 @@ def add_vaccination(request):
messages.error(request, "Please fill in all required fields.") messages.error(request, "Please fill in all required fields.")
return render(request, 'core/add_vaccination.html') return render(request, 'core/add_vaccination.html')
@login_required
def volunteer_for_request(request, request_id):
blood_request = BloodRequest.objects.get(id=request_id)
donor_profile = getattr(request.user, 'donor_profile', None)
if not donor_profile:
messages.error(request, "You need to be registered as a donor to volunteer.")
return redirect('donor_list')
# Check if already volunteered
if DonationEvent.objects.filter(donor=donor_profile, request=blood_request).exists():
messages.warning(request, "You have already volunteered for this request.")
else:
DonationEvent.objects.create(
donor=donor_profile,
request=blood_request,
donor_user=request.user
)
messages.success(request, "Thank you for volunteering! The requester has been notified.")
# Notify the requester
if blood_request.user:
Notification.objects.create(
user=blood_request.user,
message=f"Donor {request.user.username} has volunteered to help {blood_request.patient_name}!"
)
return redirect('blood_request_list')
@login_required
def complete_donation(request, event_id):
event = DonationEvent.objects.get(id=event_id)
# Only the requester or the donor can mark as complete (for simplicity, letting both)
if request.user == event.donor_user or (event.request.user and request.user == event.request.user):
event.is_completed = True
event.save()
# Notify both
Notification.objects.create(
user=event.donor_user,
message=f"Thank you for your donation to {event.request.patient_name}! Your feedback is valuable to us."
)
if event.request.user:
Notification.objects.create(
user=event.request.user,
message=f"We hope the donation for {event.request.patient_name} went well. Please share your feedback!"
)
messages.success(request, "Donation marked as completed. Thank you!")
else:
messages.error(request, "You are not authorized to complete this event.")
return redirect('home')
@login_required
def submit_feedback(request):
if request.method == "POST":
content = request.POST.get('content')
rating = request.POST.get('rating')
if content:
Feedback.objects.create(
user=request.user,
content=content,
rating=rating if rating else 5
)
messages.success(request, "Thank you for your feedback!")
return redirect('home')
return render(request, 'core/feedback.html')
@login_required
def notifications_view(request):
notifications = Notification.objects.filter(user=request.user).order_by('-created_at')
# Mark as read when viewed
notifications.filter(is_read=False).update(is_read=True)
return render(request, 'core/notifications.html', {'notifications': notifications})
@login_required
def register_donor(request):
if hasattr(request.user, 'donor_profile'):
messages.info(request, "You are already registered as a donor.")
return redirect('profile')
if request.method == "POST":
blood_group = request.POST.get('blood_group')
location = request.POST.get('location')
phone = request.POST.get('phone')
if blood_group and phone:
Donor.objects.create(
user=request.user,
name=request.user.username,
blood_group=blood_group,
location=location,
phone=phone,
is_available=True
)
messages.success(request, "Congratulations! You are now a registered donor.")
return redirect('donor_list')
else:
messages.error(request, "Please fill in all required fields.")
return render(request, 'core/register_donor.html', {'blood_groups': [g[0] for g in BLOOD_GROUPS]})