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

View File

@ -21,6 +21,7 @@ from django.conf.urls.static import static
urlpatterns = [
path("admin/", admin.site.urls),
path("i18n/", include("django.conf.urls.i18n")),
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}"
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)
blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS)
district = models.CharField(max_length=100, default='Kathmandu')
@ -71,6 +72,7 @@ class BloodRequest(models.Model):
('URGENT', 'Urgent'),
('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)
blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS)
location = models.CharField(max_length=255)
@ -105,3 +107,31 @@ class BloodBank(models.Model):
def __str__(self):
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>
<html lang="en">
<html lang="{{ LANGUAGE_CODE }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@ -178,6 +179,78 @@
#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 %}
</style>
</head>
@ -195,25 +268,19 @@
</div>
<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 == '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 == '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_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 == '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 == '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 == '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 == '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> {% 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> {% 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 %}
<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 '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 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> {% trans "My Records" %}</a></li>
{% 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>
<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>
<!-- Page Content -->
@ -231,6 +298,31 @@
</div>
<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()">
<i class="bi bi-geo-alt me-1"></i>
<span id="location-text">Detect Location</span>
@ -241,6 +333,13 @@
</div>
{% if user.is_authenticated %}
<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">
<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>
@ -335,6 +434,39 @@
}
checkLocationCookie();
</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 %}
</body>
</html>

View File

@ -21,8 +21,8 @@
<div class="row mb-4">
<div class="col-12 d-flex justify-content-between align-items-center">
<div>
<h2 class="brand-font mb-1">Blood Requests</h2>
<p class="text-secondary">Current active requirements for blood in various hospitals.</p>
<h2 class="brand-font mb-1">{% if current_status %}{{ current_status }} {% endif %}Blood Requests</h2>
<p class="text-secondary">{% if current_status == 'Active' %}Current urgent requirements for blood.{% else %}History of blood requests in our community.{% endif %}</p>
</div>
<a href="{% url 'request_blood' %}" class="btn btn-danger rounded-pill px-4">Post a Request</a>
</div>
@ -34,7 +34,14 @@
<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="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;">
{{ req.blood_group }}
</div>
@ -44,7 +51,10 @@
<div class="mt-auto pt-3 border-top">
<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>
<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>

View File

@ -71,8 +71,9 @@
<p class="mb-0 text-muted extra-small" style="font-size: 0.7rem;">Phone: {{ donor.phone }}</p>
</div>
<a href="tel:{{ donor.phone }}" class="btn btn-outline-danger btn-sm px-3 rounded-pill">Contact Now</a>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
</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" %}
{% 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 %}
<style>
@ -13,6 +14,16 @@
position: relative;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.03);
transition: all 0.3s ease;
text-decoration: none;
display: block;
color: inherit;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(230, 57, 70, 0.1);
border-color: var(--pulse-red);
cursor: pointer;
}
.stat-card .icon-box {
width: 48px;
@ -103,30 +114,54 @@
<!-- Welcome Header -->
<div class="row mb-4">
<div class="col-12">
<h2 class="brand-font mb-1">RaktaPulse Community Dashboard</h2>
<p class="text-secondary">Overview of blood donation activity and requirements in your area.</p>
<h2 class="brand-font mb-1">{% trans "RaktaPulse Community Dashboard" %}</h2>
<p class="text-secondary">{% trans "Overview of blood donation activity and requirements in your area." %}</p>
</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 -->
<div class="row g-4 mb-5">
<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">
<i class="bi bi-people"></i>
</div>
<div class="stat-value">{{ stats.total_donors }}</div>
<div class="stat-label">Registered Donors</div>
</div>
<div class="stat-label">{% trans "Registered Donors" %}</div>
</a>
</div>
<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">
<i class="bi bi-activity"></i>
</div>
<div class="stat-value">{{ stats.active_requests }}</div>
<div class="stat-label">Active Requests</div>
</div>
<div class="stat-label">{% trans "Active Requests" %}</div>
</a>
</div>
<div class="col-xl-3 col-md-6">
<div class="stat-card">
@ -134,7 +169,7 @@
<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">Inventory vs Capacity</div>
<div class="stat-label">{% trans "Inventory vs Capacity" %}</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
@ -143,7 +178,7 @@
<i class="bi bi-shield-check"></i>
</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>
@ -176,7 +211,7 @@
</div>
<div class="col-md-5">
<input type="text" name="location" class="form-control bg-light border-secondary text-dark"
placeholder="Search location (e.g. Kathmandu)..." value="{{ request.GET.location }}">
placeholder="Search location (e.g. Baluwatar)..." value="{{ request.GET.location }}">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-danger w-100 shadow-sm">Find</button>
@ -307,6 +342,41 @@
</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>
{% endblock %}
@ -365,15 +435,15 @@
});
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', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Add dummy markers for donors
{% for donor in donors %}
// Randomly offset from center for demo (within Nepal context)
L.marker([27.7172 + (Math.random() - 0.5) * 0.1, 85.3240 + (Math.random() - 0.5) * 0.1])
// Randomly offset from center for demo
L.marker([27.7226 + (Math.random() - 0.5) * 0.05, 85.3312 + (Math.random() - 0.5) * 0.05])
.addTo(map)
.bindPopup("<b>{{ donor.name }}</b><br>{{ donor.blood_group }} - {{ donor.location }}");
{% endfor %}

View File

@ -84,8 +84,8 @@
var markers = {};
function initMap() {
// Center on Kathmandu by default
map = L.map('live-map').setView([27.7172, 85.3240], 13);
// Center on Baluwatar, Kathmandu
map = L.map('live-map').setView([27.7226, 85.3312], 15);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
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> Must be in good health on the day of donation.</li>
</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>

View File

@ -1,6 +1,13 @@
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 = [
path("", home, name="home"),
@ -16,4 +23,9 @@ urlpatterns = [
path("vaccination/add/", add_vaccination, name="add_vaccination"),
path("live-map/", live_map, name="live_map"),
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 platform
from django.db import models
from django.shortcuts import render, redirect
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.decorators import login_required
from django.contrib import messages
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
import math
@ -135,6 +136,15 @@ def home(request):
"project_name": "RaktaPulse",
"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)
def donor_list(request):
@ -173,9 +183,15 @@ def donor_list(request):
return render(request, 'core/donor_list.html', context)
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 = {
'requests': requests,
'current_status': status,
}
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:
BloodRequest.objects.create(
user=request.user if request.user.is_authenticated else None,
patient_name=patient_name,
blood_group=blood_group,
location=location,
@ -297,3 +314,106 @@ def add_vaccination(request):
messages.error(request, "Please fill in all required fields.")
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]})