RaktaPluse
This commit is contained in:
parent
67d8eb7e56
commit
d86257fa5a
Binary file not shown.
@ -180,3 +180,6 @@ if EMAIL_USE_SSL:
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
LOGIN_URL = '/login/'
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,5 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from .models import Donor, BloodRequest, BloodBank
|
||||
from .models import Donor, BloodRequest, BloodBank, VaccineRecord
|
||||
|
||||
@admin.register(VaccineRecord)
|
||||
class VaccineRecordAdmin(admin.ModelAdmin):
|
||||
list_display = ('vaccine_name', 'user', 'dose_number', 'date_taken', 'location')
|
||||
list_filter = ('vaccine_name', 'date_taken')
|
||||
search_fields = ('vaccine_name', 'user__username', 'location')
|
||||
|
||||
@admin.register(Donor)
|
||||
class DonorAdmin(admin.ModelAdmin):
|
||||
|
||||
30
core/migrations/0004_vaccinerecord.py
Normal file
30
core/migrations/0004_vaccinerecord.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-17 14:37
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0003_donor_citizenship_no_donor_district_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='VaccineRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('vaccine_name', models.CharField(max_length=100)),
|
||||
('dose_number', models.PositiveIntegerField(default=1)),
|
||||
('date_taken', models.DateField()),
|
||||
('location', models.CharField(max_length=255)),
|
||||
('center_name', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('notes', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='vaccine_records', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-17 15:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0004_vaccinerecord'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='bloodbank',
|
||||
name='latitude',
|
||||
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='bloodbank',
|
||||
name='longitude',
|
||||
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.7 on 2026-02-17 15:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0005_bloodbank_latitude_bloodbank_longitude'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='bloodrequest',
|
||||
name='latitude',
|
||||
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='bloodrequest',
|
||||
name='longitude',
|
||||
field=models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True),
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0004_vaccinerecord.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0004_vaccinerecord.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,5 +1,19 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class VaccineRecord(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='vaccine_records')
|
||||
vaccine_name = models.CharField(max_length=100)
|
||||
dose_number = models.PositiveIntegerField(default=1)
|
||||
date_taken = models.DateField()
|
||||
location = models.CharField(max_length=255)
|
||||
center_name = models.CharField(max_length=255, null=True, blank=True)
|
||||
notes = models.TextField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.vaccine_name} - Dose {self.dose_number} for {self.user.username}"
|
||||
|
||||
class Donor(models.Model):
|
||||
BLOOD_GROUPS = [
|
||||
@ -38,6 +52,8 @@ class BloodRequest(models.Model):
|
||||
location = models.CharField(max_length=255)
|
||||
urgency = models.CharField(max_length=10, choices=URGENCY_LEVELS, default='NORMAL')
|
||||
hospital = models.CharField(max_length=255)
|
||||
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
||||
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
||||
contact_number = models.CharField(max_length=20)
|
||||
required_date = models.DateField(default=timezone.now)
|
||||
status = models.CharField(max_length=20, default='Active')
|
||||
@ -49,6 +65,8 @@ class BloodRequest(models.Model):
|
||||
class BloodBank(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
location = models.CharField(max_length=255)
|
||||
latitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
||||
longitude = models.DecimalField(max_digits=9, decimal_places=6, null=True, blank=True)
|
||||
contact_number = models.CharField(max_length=20, null=True, blank=True)
|
||||
is_24_7 = models.BooleanField(default=True)
|
||||
stock_a_plus = models.IntegerField(default=0)
|
||||
|
||||
@ -199,14 +199,18 @@
|
||||
<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>
|
||||
{% if user.is_authenticated %}
|
||||
<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>
|
||||
{% endif %}
|
||||
<li><a href="/admin/"><i class="bi bi-gear-fill"></i> 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:911" class="btn btn-sm btn-danger w-100 fw-bold">Emergency: 911</a>
|
||||
<a href="tel:1115" class="btn btn-sm btn-danger w-100 fw-bold">Emergency: 1115</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@ -226,6 +230,10 @@
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-4">
|
||||
<div id="location-status" class="d-none d-md-flex align-items-center text-secondary small cursor-pointer" style="cursor: pointer;" onclick="detectLocation()">
|
||||
<i class="bi bi-geo-alt me-2"></i>
|
||||
<span id="location-text">Detect Location</span>
|
||||
</div>
|
||||
<div class="d-none d-md-flex align-items-center text-secondary small">
|
||||
<i class="bi bi-calendar3 me-2"></i>
|
||||
{{ current_time|date:"D, M d, Y" }}
|
||||
@ -245,6 +253,16 @@
|
||||
</div>
|
||||
|
||||
<div class="p-4 p-md-5">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show shadow-sm border-0 mb-4" role="alert">
|
||||
{% if message.tags == 'success' %}<i class="bi bi-check-circle-fill me-2"></i>{% endif %}
|
||||
{% if message.tags == 'error' %}<i class="bi bi-exclamation-triangle-fill me-2"></i>{% endif %}
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
@ -265,7 +283,51 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Active class is handled server-side via Django templates.
|
||||
function detectLocation() {
|
||||
const text = document.getElementById('location-text');
|
||||
const originalText = text.innerText;
|
||||
text.innerText = "Locating...";
|
||||
|
||||
if ("geolocation" in navigator) {
|
||||
navigator.geolocation.getCurrentPosition(function(position) {
|
||||
const lat = position.coords.latitude;
|
||||
const lng = position.coords.longitude;
|
||||
text.innerText = lat.toFixed(4) + ", " + lng.toFixed(4);
|
||||
|
||||
// Save to cookies for 1 day
|
||||
document.cookie = `user_lat=${lat}; path=/; max-age=86400`;
|
||||
document.cookie = `user_lng=${lng}; path=/; max-age=86400`;
|
||||
|
||||
// If we are on a list page without lat/lng, reload with them
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (!urlParams.has('lat')) {
|
||||
urlParams.set('lat', lat);
|
||||
urlParams.set('lng', lng);
|
||||
window.location.search = urlParams.toString();
|
||||
}
|
||||
}, function(error) {
|
||||
text.innerText = "Error";
|
||||
console.error(error);
|
||||
});
|
||||
} else {
|
||||
text.innerText = "Not supported";
|
||||
}
|
||||
}
|
||||
|
||||
// Check if location is already in cookies
|
||||
function checkLocationCookie() {
|
||||
const cookies = document.cookie.split(';');
|
||||
let lat = null, lng = null;
|
||||
for (let cookie of cookies) {
|
||||
const [name, value] = cookie.trim().split('=');
|
||||
if (name === 'user_lat') lat = value;
|
||||
if (name === 'user_lng') lng = value;
|
||||
}
|
||||
if (lat && lng) {
|
||||
document.getElementById('location-text').innerText = parseFloat(lat).toFixed(4) + ", " + parseFloat(lng).toFixed(4);
|
||||
}
|
||||
}
|
||||
checkLocationCookie();
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
|
||||
74
core/templates/core/add_vaccination.html
Normal file
74
core/templates/core/add_vaccination.html
Normal file
@ -0,0 +1,74 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Add Vaccination Record - {{ project_name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="glass-card shadow-lg p-4 p-md-5">
|
||||
<div class="text-center mb-4">
|
||||
<div class="icon-box bg-danger bg-opacity-10 text-danger rounded-circle d-inline-flex p-3 mb-3">
|
||||
<i class="bi bi-shield-plus fs-1"></i>
|
||||
</div>
|
||||
<h2 class="brand-font text-dark">Add Vaccine Record</h2>
|
||||
<p class="text-secondary">Keep your immunization data accurate and up to date.</p>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-dark fw-bold">Vaccine Name</label>
|
||||
<select name="vaccine_name" class="form-select bg-light border-secondary text-dark" required>
|
||||
<option value="">Select Vaccine...</option>
|
||||
<option value="COVID-19 (Covishield/AstraZeneca)">COVID-19 (Covishield/AstraZeneca)</option>
|
||||
<option value="COVID-19 (Vero Cell)">COVID-19 (Vero Cell)</option>
|
||||
<option value="COVID-19 (Pfizer/BioNTech)">COVID-19 (Pfizer/BioNTech)</option>
|
||||
<option value="COVID-19 (Moderna)">COVID-19 (Moderna)</option>
|
||||
<option value="COVID-19 (Johnson & Johnson)">COVID-19 (Johnson & Johnson)</option>
|
||||
<option value="Hepatitis B">Hepatitis B</option>
|
||||
<option value="Influenza (Flu)">Influenza (Flu)</option>
|
||||
<option value="HPV">HPV</option>
|
||||
<option value="Tetanus">Tetanus</option>
|
||||
<option value="Other">Other</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-dark fw-bold">Dose Number</label>
|
||||
<input type="number" name="dose_number" class="form-control bg-light border-secondary text-dark" min="1" value="1" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-dark fw-bold">Date Taken</label>
|
||||
<input type="date" name="date_taken" class="form-control bg-light border-secondary text-dark" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-dark fw-bold">City/District</label>
|
||||
<input type="text" name="location" class="form-control bg-light border-secondary text-dark" placeholder="e.g. Kathmandu" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-dark fw-bold">Center Name</label>
|
||||
<input type="text" name="center_name" class="form-control bg-light border-secondary text-dark" placeholder="e.g. Teaching Hospital">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label text-dark fw-bold">Notes (Optional)</label>
|
||||
<textarea name="notes" class="form-control bg-light border-secondary text-dark" rows="3" placeholder="Any side effects or remarks..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<button type="submit" class="btn btn-danger py-3 rounded-pill fw-bold shadow-sm">Save Record</button>
|
||||
<a href="{% url 'vaccination_dashboard' %}" class="btn btn-light py-3 rounded-pill fw-bold">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -6,24 +6,47 @@
|
||||
{% block content %}
|
||||
<div class="container-fluid p-0">
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="col-md-8">
|
||||
<h2 class="brand-font mb-1">Blood Banks</h2>
|
||||
<p class="text-secondary">Official blood repositories and their current inventory levels.</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end">
|
||||
<button type="button" id="findNearestBankBtn" class="btn btn-primary shadow-sm">
|
||||
<i class="bi bi-geo-alt-fill me-1"></i> Nearest First
|
||||
</button>
|
||||
<a href="{% url 'blood_bank_list' %}" class="btn btn-outline-secondary ms-2">Reset</a>
|
||||
<form id="bankFilterForm" method="GET" style="display:none;">
|
||||
<input type="hidden" name="lat" id="latInputBank">
|
||||
<input type="hidden" name="lng" id="lngInputBank">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{% for bank in banks %}
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="glass-card h-100">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h4 class="brand-font mb-0 text-dark">{{ bank.name }}</h4>
|
||||
{% if bank.is_24_7 %}
|
||||
<span class="badge bg-success bg-opacity-10 text-success">24/7 Available</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<p class="text-secondary mb-4"><i class="bi bi-geo-alt me-2"></i> {{ bank.location }}</p>
|
||||
<p class="text-secondary mb-4"><i class="bi bi-telephone me-2"></i> {{ bank.contact_number }}</p>
|
||||
|
||||
{% if bank.distance and bank.distance < 1000 %}
|
||||
<div class="mb-3">
|
||||
<span class="badge bg-info bg-opacity-10 text-info" style="font-size: 0.75rem;">
|
||||
<i class="bi bi-distribute-vertical me-1"></i> {{ bank.distance|floatformat:1 }} km away from you
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<p class="text-secondary mb-3"><i class="bi bi-geo-alt me-2"></i> {{ bank.location }}</p>
|
||||
<p class="text-secondary mb-4">
|
||||
<a href="tel:{{ bank.contact_number }}" class="text-decoration-none text-secondary">
|
||||
<i class="bi bi-telephone me-2"></i> {{ bank.contact_number }}
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h6 class="fw-bold text-dark mb-3">Inventory Levels (Units)</h6>
|
||||
<div class="row g-2 mb-4">
|
||||
@ -77,7 +100,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-danger w-100 rounded-pill">Contact Bank</button>
|
||||
<a href="tel:{{ bank.contact_number }}" class="btn btn-danger w-100 rounded-pill">Contact Bank</a>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
@ -89,3 +112,30 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.getElementById('findNearestBankBtn').addEventListener('click', function() {
|
||||
const btn = this;
|
||||
const originalContent = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Locating...';
|
||||
|
||||
if ("geolocation" in navigator) {
|
||||
navigator.geolocation.getCurrentPosition(function(position) {
|
||||
document.getElementById('latInputBank').value = position.coords.latitude;
|
||||
document.getElementById('lngInputBank').value = position.coords.longitude;
|
||||
document.getElementById('bankFilterForm').submit();
|
||||
}, function(error) {
|
||||
alert("Error getting location: " + error.message);
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalContent;
|
||||
});
|
||||
} else {
|
||||
alert("Geolocation is not supported by this browser.");
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = originalContent;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<h2 class="brand-font mb-1">Blood Requests</h2>
|
||||
<p class="text-secondary">Current active requirements for blood in various hospitals.</p>
|
||||
</div>
|
||||
<button class="btn btn-danger rounded-pill px-4">Post a Request</button>
|
||||
<a href="{% url 'request_blood' %}" class="btn btn-danger rounded-pill px-4">Post a Request</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@ -70,10 +70,13 @@
|
||||
</span>
|
||||
<p class="mb-0 text-muted extra-small" style="font-size: 0.7rem;">Phone: {{ donor.phone }}</p>
|
||||
</div>
|
||||
<button class="btn btn-outline-danger btn-sm px-3 rounded-pill" onclick="alert('Contacting {{ donor.name }} at {{ donor.phone }}...')">Contact Now</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<a href="tel:{{ donor.phone }}" class="btn btn-outline-danger btn-sm px-3 rounded-pill">Contact Now</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
|
||||
@ -212,7 +212,7 @@
|
||||
</span>
|
||||
<p class="mb-0 text-muted extra-small" style="font-size: 0.7rem;">Last Donated: {{ donor.last_donation_date|default:"Never" }}</p>
|
||||
</div>
|
||||
<button class="btn btn-outline-danger btn-sm px-3 rounded-pill" onclick="alert('Initiating contact with {{ donor.name }}...')">Contact</button>
|
||||
<a href="tel:{{ donor.phone }}" class="btn btn-outline-danger btn-sm px-3 rounded-pill">Contact</a>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
@ -239,7 +239,7 @@
|
||||
<div class="progress-bar bg-success" style="width: {{ stats.vaccinated_percentage }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm px-4">Update Status</button>
|
||||
<a href="{% url 'vaccination_dashboard' %}" class="btn btn-success btn-sm px-4">Update Status</a>
|
||||
</div>
|
||||
<div class="col-md-5 d-none d-md-block text-center">
|
||||
<i class="bi bi-shield-plus text-success" style="font-size: 5rem; opacity: 0.2;"></i>
|
||||
@ -267,7 +267,7 @@
|
||||
<p class="text-secondary extra-small mb-2"><i class="bi bi-hospital me-1"></i> {{ req.hospital }}</p>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted" style="font-size: 0.7rem;">{{ req.created_at|timesince }} ago</small>
|
||||
<a href="#" class="btn btn-link text-danger p-0 text-decoration-none small">Help Now →</a>
|
||||
<a href="tel:{{ req.contact_number }}" class="btn btn-link text-danger p-0 text-decoration-none small">Help Now →</a>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
|
||||
134
core/templates/core/live_map.html
Normal file
134
core/templates/core/live_map.html
Normal file
@ -0,0 +1,134 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="bi bi-map text-danger"></i> Live Alert Map</h2>
|
||||
<span class="badge bg-danger pulse">LIVE UPDATES</span>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body p-0">
|
||||
<div id="live-map" style="height: 600px; width: 100%; border-radius: 8px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h5 class="mb-0">Recent Alerts</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Patient</th>
|
||||
<th>Blood Group</th>
|
||||
<th>Hospital</th>
|
||||
<th>Urgency</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for req in requests %}
|
||||
<tr>
|
||||
<td>{{ req.patient_name }}</td>
|
||||
<td><span class="badge bg-danger">{{ req.blood_group }}</span></td>
|
||||
<td>{{ req.hospital }}</td>
|
||||
<td>
|
||||
{% if req.urgency == 'CRITICAL' %}
|
||||
<span class="badge bg-dark">CRITICAL</span>
|
||||
{% elif req.urgency == 'URGENT' %}
|
||||
<span class="badge bg-warning text-dark">URGENT</span>
|
||||
{% else %}
|
||||
<span class="badge bg-info">NORMAL</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="focusOnMarker({{ req.latitude }}, {{ req.longitude }})">
|
||||
<i class="bi bi-eye"></i> View on Map
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">No active alerts found.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.pulse {
|
||||
animation: pulse-red 2s infinite;
|
||||
}
|
||||
@keyframes pulse-red {
|
||||
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7); }
|
||||
70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(220, 53, 69, 0); }
|
||||
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var map;
|
||||
var markers = {};
|
||||
|
||||
function initMap() {
|
||||
// Center on Kathmandu by default
|
||||
map = L.map('live-map').setView([27.7172, 85.3240], 13);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
{% for req in requests %}
|
||||
{% if req.latitude and req.longitude %}
|
||||
var markerColor = 'blue';
|
||||
{% if req.urgency == 'CRITICAL' %}
|
||||
markerColor = 'red';
|
||||
{% elif req.urgency == 'URGENT' %}
|
||||
markerColor = 'orange';
|
||||
{% endif %}
|
||||
|
||||
var marker = L.circleMarker([{{ req.latitude }}, {{ req.longitude }}], {
|
||||
radius: 10,
|
||||
fillColor: markerColor,
|
||||
color: "#fff",
|
||||
weight: 1,
|
||||
opacity: 1,
|
||||
fillOpacity: 0.8
|
||||
}).addTo(map);
|
||||
|
||||
marker.bindPopup(`
|
||||
<strong>{{ req.patient_name }}</strong><br>
|
||||
Blood Group: <span class="badge bg-danger">{{ req.blood_group }}</span><br>
|
||||
Hospital: {{ req.hospital }}<br>
|
||||
Urgency: {{ req.urgency }}<br>
|
||||
<a href="tel:{{ req.contact_number }}" class="btn btn-sm btn-primary mt-2">Contact</a>
|
||||
`);
|
||||
|
||||
markers["{{ req.id }}"] = marker;
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
function focusOnMarker(lat, lng) {
|
||||
map.setView([lat, lng], 16);
|
||||
// Find marker at this location and open its popup if possible
|
||||
// (Simplified for now)
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initMap);
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -3,58 +3,97 @@
|
||||
{% block title %}Login - RaktaPulse{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center py-5">
|
||||
<div class="row justify-content-center align-items-center py-5" style="min-height: 80vh;">
|
||||
<div class="col-md-5">
|
||||
<div class="glass-card shadow-sm border-danger border-opacity-10">
|
||||
<div class="text-center mb-4">
|
||||
<div class="bg-danger rounded-circle d-inline-flex align-items-center justify-content-center mb-3 blinking-logo" style="width: 60px; height: 60px;">
|
||||
<i class="bi bi-droplet-fill text-white fs-2"></i>
|
||||
<div class="glass-card shadow-lg border-danger border-opacity-10 p-4 p-md-5">
|
||||
<div class="text-center mb-5">
|
||||
<div class="bg-danger rounded-circle d-inline-flex align-items-center justify-content-center mb-4 blinking-logo shadow-danger" style="width: 70px; height: 70px; box-shadow: 0 0 20px rgba(230, 57, 70, 0.3);">
|
||||
<i class="bi bi-droplet-fill text-white fs-1"></i>
|
||||
</div>
|
||||
<h2 class="fw-bold text-danger brand-font">Welcome to RaktaPulse</h2>
|
||||
<p class="text-secondary">Please login to your account</p>
|
||||
<h2 class="fw-bold text-danger brand-font mb-2">Welcome Back</h2>
|
||||
<p class="text-secondary">Enter your credentials to access RaktaPulse</p>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger border-0 rounded-3 mb-4 small">
|
||||
{% for error in form.non_field_errors %}
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" class="needs-validation">
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-secondary text-uppercase">{{ field.label }}</label>
|
||||
<div class="mb-4">
|
||||
<label class="form-label small fw-bold text-secondary text-uppercase mb-2" for="{{ field.id_for_label }}">
|
||||
<i class="bi bi-{% if 'username' in field.name %}person{% else %}key{% endif %} me-1"></i> {{ field.label }}
|
||||
</label>
|
||||
{{ field }}
|
||||
{% if field.errors %}
|
||||
<div class="text-danger small mt-1">{{ field.errors.0 }}</div>
|
||||
<div class="text-danger small mt-1">
|
||||
{% for error in field.errors %}{{ error }}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<button type="submit" class="btn btn-danger w-100 py-2 fw-bold mt-3">LOGIN</button>
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="rememberMe">
|
||||
<label class="form-check-label small text-secondary" for="rememberMe">Remember me</label>
|
||||
</div>
|
||||
<a href="#" class="text-danger small text-decoration-none fw-500">Forgot password?</a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-danger w-100 py-3 fw-bold mt-2 shadow-sm">
|
||||
LOG IN <i class="bi bi-arrow-right-short ms-1"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<p class="text-secondary small">Don't have an account? <a href="{% url 'register' %}" class="text-danger fw-bold text-decoration-none">Register here</a></p>
|
||||
<div class="text-center mt-5">
|
||||
<p class="text-secondary small mb-4">Don't have an account? <a href="{% url 'register' %}" class="text-danger fw-bold text-decoration-none">Create one now</a></p>
|
||||
<hr class="opacity-10 mb-4">
|
||||
<a href="{% url 'home' %}" class="btn btn-link btn-sm text-secondary text-decoration-none">
|
||||
<i class="bi bi-house-door me-1"></i> Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
input {
|
||||
input:not([type="checkbox"]) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
padding: 0.85rem 1.25rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
background-color: #f8f9fa;
|
||||
color: #2b2d42;
|
||||
background-color: #ffffff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
input:focus {
|
||||
input:not([type="checkbox"]):focus {
|
||||
border-color: #E63946;
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 0.25rem rgba(230, 57, 70, 0.1);
|
||||
box-shadow: 0 0 0 4px rgba(230, 57, 70, 0.08);
|
||||
background-color: #fff;
|
||||
}
|
||||
.glass-card {
|
||||
background: #ffffff;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
}
|
||||
.fw-500 { font-weight: 500; }
|
||||
.shadow-danger {
|
||||
box-shadow: 0 10px 20px rgba(230, 57, 70, 0.2);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
89
core/templates/core/register.html
Normal file
89
core/templates/core/register.html
Normal file
@ -0,0 +1,89 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Create Account - RaktaPulse{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center align-items-center py-5" style="min-height: 80vh;">
|
||||
<div class="col-md-6 col-lg-5">
|
||||
<div class="glass-card shadow-lg border-danger border-opacity-10 p-4 p-md-5">
|
||||
<div class="text-center mb-5">
|
||||
<div class="bg-danger rounded-circle d-inline-flex align-items-center justify-content-center mb-4 blinking-logo shadow-danger" style="width: 70px; height: 70px; box-shadow: 0 0 20px rgba(230, 57, 70, 0.3);">
|
||||
<i class="bi bi-person-plus-fill text-white fs-1"></i>
|
||||
</div>
|
||||
<h2 class="fw-bold text-danger brand-font mb-2">Join RaktaPulse</h2>
|
||||
<p class="text-secondary">Be a hero. Start saving lives today.</p>
|
||||
</div>
|
||||
|
||||
<form method="post" class="needs-validation">
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label small fw-bold text-secondary text-uppercase mb-2" for="{{ field.id_for_label }}">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
{{ field }}
|
||||
{% if field.help_text %}
|
||||
<div class="form-text small mb-1">{{ field.help_text|safe }}</div>
|
||||
{% endif %}
|
||||
{% if field.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{% for error in field.errors %}{{ error }}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<button type="submit" class="btn btn-danger w-100 py-3 fw-bold mt-4 shadow-sm">
|
||||
CREATE ACCOUNT <i class="bi bi-check-circle ms-1"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="text-center mt-5">
|
||||
<p class="text-secondary small mb-4">Already have an account? <a href="{% url 'login' %}" class="text-danger fw-bold text-decoration-none">Log in here</a></p>
|
||||
<hr class="opacity-10 mb-4">
|
||||
<a href="{% url 'home' %}" class="btn btn-link btn-sm text-secondary text-decoration-none">
|
||||
<i class="bi bi-house-door me-1"></i> Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
input:not([type="checkbox"]) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0.85rem 1.25rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #2b2d42;
|
||||
background-color: #ffffff;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
input:not([type="checkbox"]):focus {
|
||||
border-color: #E63946;
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 4px rgba(230, 57, 70, 0.08);
|
||||
background-color: #fff;
|
||||
}
|
||||
.glass-card {
|
||||
background: #ffffff;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(0,0,0,0.05);
|
||||
}
|
||||
.shadow-danger {
|
||||
box-shadow: 0 10px 20px rgba(230, 57, 70, 0.2);
|
||||
}
|
||||
.helptext ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin-top: 5px;
|
||||
color: #6c757d;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
99
core/templates/core/request_blood.html
Normal file
99
core/templates/core/request_blood.html
Normal file
@ -0,0 +1,99 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Post Blood Request - RaktaPulse{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card shadow-sm border-0 rounded-4">
|
||||
<div class="card-body p-4 p-md-5">
|
||||
<div class="text-center mb-4">
|
||||
<div class="bg-danger bg-opacity-10 text-danger rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 60px; height: 60px;">
|
||||
<i class="bi bi-megaphone-fill fs-3"></i>
|
||||
</div>
|
||||
<h2 class="fw-bold">Post a Blood Request</h2>
|
||||
<p class="text-secondary">Fill in the details to alert nearby donors.</p>
|
||||
</div>
|
||||
|
||||
<form method="POST" id="requestForm">
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-secondary">Patient Name *</label>
|
||||
<input type="text" name="patient_name" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-secondary">Blood Group *</label>
|
||||
<select name="blood_group" class="form-select rounded-3" required>
|
||||
<option value="" selected disabled>Select Group</option>
|
||||
{% for group in blood_groups %}
|
||||
<option value="{{ group }}">{{ group }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-secondary">Hospital Name *</label>
|
||||
<input type="text" name="hospital" class="form-control rounded-3" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold text-secondary">Urgency *</label>
|
||||
<select name="urgency" class="form-select rounded-3">
|
||||
{% for val, label in urgency_levels %}
|
||||
<option value="{{ val }}">{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold text-secondary">Location (City/Area) *</label>
|
||||
<input type="text" name="location" class="form-control rounded-3" placeholder="e.g. Balaju, Kathmandu" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label small fw-bold text-secondary">Contact Number *</label>
|
||||
<input type="text" name="contact_number" class="form-control rounded-3" required>
|
||||
</div>
|
||||
|
||||
<!-- Hidden coordinates -->
|
||||
<input type="hidden" name="latitude" id="id_latitude">
|
||||
<input type="hidden" name="longitude" id="id_longitude">
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info mt-4 d-flex align-items-center small" id="geo-status">
|
||||
<i class="bi bi-geo-alt-fill me-2"></i>
|
||||
<span>Fetching your precise location for the live map...</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-danger w-100 py-3 rounded-3 fw-bold shadow-sm">
|
||||
<i class="bi bi-send-fill me-2"></i> Broadcast Alert
|
||||
</button>
|
||||
<a href="{% url 'blood_request_list' %}" class="btn btn-link w-100 mt-2 text-secondary text-decoration-none small">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if ("geolocation" in navigator) {
|
||||
navigator.geolocation.getCurrentPosition(function(position) {
|
||||
document.getElementById('id_latitude').value = position.coords.latitude;
|
||||
document.getElementById('id_longitude').value = position.coords.longitude;
|
||||
const status = document.getElementById('geo-status');
|
||||
status.classList.replace('alert-info', 'alert-success');
|
||||
status.innerHTML = '<i class="bi bi-check-circle-fill me-2"></i> Precise location captured! This will appear on the live map.';
|
||||
}, function(error) {
|
||||
const status = document.getElementById('geo-status');
|
||||
status.classList.replace('alert-info', 'alert-warning');
|
||||
status.innerHTML = '<i class="bi bi-exclamation-triangle-fill me-2"></i> Could not get precise location. You can still post, but it won\'t show on the live map.';
|
||||
console.warn("Geolocation error:", error);
|
||||
});
|
||||
} else {
|
||||
document.getElementById('geo-status').style.display = 'none';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
103
core/templates/core/vaccination_dashboard.html
Normal file
103
core/templates/core/vaccination_dashboard.html
Normal file
@ -0,0 +1,103 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}My Vaccination Records - {{ project_name }}{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
.vaccine-card {
|
||||
background: #ffffff;
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
border: 1px solid var(--border-color);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.vaccine-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.05);
|
||||
}
|
||||
.dose-badge {
|
||||
background: var(--pulse-red-light);
|
||||
color: var(--pulse-red);
|
||||
font-weight: 700;
|
||||
padding: 5px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.date-badge {
|
||||
background: #f8f9fa;
|
||||
color: #6c757d;
|
||||
padding: 5px 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 0.8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h2 class="brand-font mb-1 text-dark">Vaccination Journey</h2>
|
||||
<p class="text-secondary">Track and manage your immunization history safely.</p>
|
||||
</div>
|
||||
<a href="{% url 'add_vaccination' %}" class="btn btn-danger rounded-pill px-4 shadow-sm">
|
||||
<i class="bi bi-plus-lg me-2"></i>Add New Record
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if messages %}
|
||||
<div class="mb-4">
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show rounded-4 shadow-sm border-0" role="alert">
|
||||
<i class="bi {% if message.tags == 'success' %}bi-check-circle-fill{% else %}bi-exclamation-triangle-fill{% endif %} me-2"></i>
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
{% for record in records %}
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="vaccine-card shadow-sm">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<h5 class="fw-bold mb-0 text-dark">{{ record.vaccine_name }}</h5>
|
||||
<span class="dose-badge">Dose {{ record.dose_number }}</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||
<span class="date-badge"><i class="bi bi-calendar-event"></i> {{ record.date_taken|date:"M d, Y" }}</span>
|
||||
<span class="date-badge"><i class="bi bi-geo-alt"></i> {{ record.center_name|default:record.location }}</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 border-top pt-2">
|
||||
<p class="text-secondary small mb-0">
|
||||
<strong>Location:</strong> {{ record.location }}
|
||||
</p>
|
||||
{% if record.notes %}
|
||||
<p class="text-secondary small mt-2 mb-0">
|
||||
<i class="bi bi-sticky me-1"></i> {{ record.notes|truncatechars:100 }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center py-5">
|
||||
<div class="glass-card py-5">
|
||||
<i class="bi bi-shield-slash fs-1 text-secondary opacity-25"></i>
|
||||
<h4 class="mt-3 text-dark">No records found</h4>
|
||||
<p class="text-secondary">Start tracking your vaccinations today to keep your community safe.</p>
|
||||
<a href="{% url 'add_vaccination' %}" class="btn btn-outline-danger mt-2 rounded-pill px-4">Add Your First Record</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -1,6 +1,6 @@
|
||||
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
|
||||
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
|
||||
|
||||
urlpatterns = [
|
||||
path("", home, name="home"),
|
||||
@ -11,4 +11,8 @@ urlpatterns = [
|
||||
path("requests/", blood_request_list, name="blood_request_list"),
|
||||
path("banks/", blood_bank_list, name="blood_bank_list"),
|
||||
path("vaccination/", vaccination_info, name="vaccination_info"),
|
||||
path("vaccination/dashboard/", vaccination_dashboard, name="vaccination_dashboard"),
|
||||
path("vaccination/add/", add_vaccination, name="add_vaccination"),
|
||||
path("live-map/", live_map, name="live_map"),
|
||||
path("request-blood/", request_blood, name="request_blood"),
|
||||
]
|
||||
|
||||
117
core/views.py
117
core/views.py
@ -3,9 +3,10 @@ import platform
|
||||
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
|
||||
from .models import Donor, BloodRequest, BloodBank, VaccineRecord
|
||||
import math
|
||||
|
||||
def haversine(lat1, lon1, lat2, lon2):
|
||||
@ -24,12 +25,12 @@ def login_view(request):
|
||||
if request.method == "POST":
|
||||
form = AuthenticationForm(request, data=request.POST)
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data.get('username')
|
||||
password = form.cleaned_data.get('password')
|
||||
user = authenticate(username=username, password=password)
|
||||
if user is not None:
|
||||
login(request, user)
|
||||
return redirect("home")
|
||||
user = form.get_user()
|
||||
login(request, user)
|
||||
messages.success(request, f"Welcome back, {user.username}!")
|
||||
return redirect("home")
|
||||
else:
|
||||
messages.error(request, "Invalid username or password. Please try again.")
|
||||
else:
|
||||
form = AuthenticationForm()
|
||||
return render(request, "core/login.html", {"form": form})
|
||||
@ -153,9 +154,27 @@ def blood_request_list(request):
|
||||
return render(request, 'core/blood_request_list.html', context)
|
||||
|
||||
def blood_bank_list(request):
|
||||
user_lat = request.GET.get('lat')
|
||||
user_lng = request.GET.get('lng')
|
||||
|
||||
banks = BloodBank.objects.all()
|
||||
bank_list_data = list(banks)
|
||||
|
||||
if user_lat and user_lng:
|
||||
try:
|
||||
u_lat = float(user_lat)
|
||||
u_lng = float(user_lng)
|
||||
for b in bank_list_data:
|
||||
if b.latitude and b.longitude:
|
||||
b.distance = haversine(u_lat, u_lng, float(b.latitude), float(b.longitude))
|
||||
else:
|
||||
b.distance = 999999
|
||||
bank_list_data.sort(key=lambda x: x.distance)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
context = {
|
||||
'banks': banks,
|
||||
'banks': bank_list_data,
|
||||
}
|
||||
return render(request, 'core/blood_bank_list.html', context)
|
||||
|
||||
@ -170,3 +189,85 @@ def vaccination_info(request):
|
||||
stats["percentage"] = 0
|
||||
|
||||
return render(request, 'core/vaccination_info.html', {'stats': stats})
|
||||
|
||||
def live_map(request):
|
||||
"""View to display live alerts/requests on a map."""
|
||||
active_requests = BloodRequest.objects.filter(status='Active').order_by('-created_at')
|
||||
|
||||
# Also include blood banks and donors optionally if we want a full map
|
||||
# But focusing on alerts as requested.
|
||||
context = {
|
||||
'requests': active_requests,
|
||||
'title': 'Live Alert Map',
|
||||
}
|
||||
return render(request, 'core/live_map.html', context)
|
||||
|
||||
def request_blood(request):
|
||||
"""View to create a new blood request with geolocation."""
|
||||
if request.method == "POST":
|
||||
patient_name = request.POST.get('patient_name')
|
||||
blood_group = request.POST.get('blood_group')
|
||||
location = request.POST.get('location')
|
||||
urgency = request.POST.get('urgency')
|
||||
hospital = request.POST.get('hospital')
|
||||
contact_number = request.POST.get('contact_number')
|
||||
latitude = request.POST.get('latitude')
|
||||
longitude = request.POST.get('longitude')
|
||||
|
||||
if patient_name and blood_group and hospital and contact_number:
|
||||
BloodRequest.objects.create(
|
||||
patient_name=patient_name,
|
||||
blood_group=blood_group,
|
||||
location=location,
|
||||
urgency=urgency,
|
||||
hospital=hospital,
|
||||
contact_number=contact_number,
|
||||
latitude=latitude if latitude else None,
|
||||
longitude=longitude if longitude else None
|
||||
)
|
||||
messages.success(request, "Blood request posted successfully! Help is on the way.")
|
||||
return redirect('blood_request_list')
|
||||
else:
|
||||
messages.error(request, "Please fill in all required fields.")
|
||||
|
||||
context = {
|
||||
'blood_groups': [g[0] for g in Donor.BLOOD_GROUPS],
|
||||
'urgency_levels': BloodRequest.URGENCY_LEVELS,
|
||||
}
|
||||
return render(request, 'core/request_blood.html', context)
|
||||
|
||||
@login_required
|
||||
def vaccination_dashboard(request):
|
||||
records = VaccineRecord.objects.filter(user=request.user).order_by('-date_taken')
|
||||
context = {
|
||||
'records': records,
|
||||
'project_name': "RaktaPulse",
|
||||
}
|
||||
return render(request, 'core/vaccination_dashboard.html', context)
|
||||
|
||||
@login_required
|
||||
def add_vaccination(request):
|
||||
if request.method == "POST":
|
||||
vaccine_name = request.POST.get('vaccine_name')
|
||||
dose_number = request.POST.get('dose_number')
|
||||
date_taken = request.POST.get('date_taken')
|
||||
location = request.POST.get('location')
|
||||
center_name = request.POST.get('center_name')
|
||||
notes = request.POST.get('notes')
|
||||
|
||||
if vaccine_name and dose_number and date_taken:
|
||||
VaccineRecord.objects.create(
|
||||
user=request.user,
|
||||
vaccine_name=vaccine_name,
|
||||
dose_number=dose_number,
|
||||
date_taken=date_taken,
|
||||
location=location,
|
||||
center_name=center_name,
|
||||
notes=notes
|
||||
)
|
||||
messages.success(request, "Vaccination record added successfully!")
|
||||
return redirect('vaccination_dashboard')
|
||||
else:
|
||||
messages.error(request, "Please fill in all required fields.")
|
||||
|
||||
return render(request, 'core/add_vaccination.html')
|
||||
|
||||
25
populate_coords.py
Normal file
25
populate_coords.py
Normal file
@ -0,0 +1,25 @@
|
||||
import os
|
||||
import django
|
||||
import random
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||
django.setup()
|
||||
|
||||
from core.models import Donor, BloodBank
|
||||
|
||||
# Kathmandu center approx: 27.7172, 85.3240
|
||||
def update_coords():
|
||||
for donor in Donor.objects.all():
|
||||
donor.latitude = 27.7172 + (random.random() - 0.5) * 0.1
|
||||
donor.longitude = 85.3240 + (random.random() - 0.5) * 0.1
|
||||
donor.save()
|
||||
|
||||
for bank in BloodBank.objects.all():
|
||||
bank.latitude = 27.7172 + (random.random() - 0.5) * 0.1
|
||||
bank.longitude = 85.3240 + (random.random() - 0.5) * 0.1
|
||||
bank.save()
|
||||
|
||||
print("Updated coordinates for donors and banks.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
update_coords()
|
||||
Loading…
x
Reference in New Issue
Block a user