Autosave: 20260218-053623
This commit is contained in:
parent
0f9878cadc
commit
800e9db512
Binary file not shown.
Binary file not shown.
@ -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/
|
||||||
|
|||||||
@ -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")),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -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"
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
34
core/templates/core/feedback.html
Normal file
34
core/templates/core/feedback.html
Normal 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 %}
|
||||||
@ -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 %}
|
||||||
|
|||||||
@ -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'
|
||||||
|
|||||||
35
core/templates/core/notifications.html
Normal file
35
core/templates/core/notifications.html
Normal 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 %}
|
||||||
37
core/templates/core/register_donor.html
Normal file
37
core/templates/core/register_donor.html
Normal 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 %}
|
||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
14
core/urls.py
14
core/urls.py
@ -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"),
|
||||||
]
|
]
|
||||||
|
|||||||
124
core/views.py
124
core/views.py
@ -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]})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user