Autosave: 20260217-141859

This commit is contained in:
Flatlogic Bot 2026-02-17 14:18:59 +00:00
parent b61b163577
commit 67d8eb7e56
30 changed files with 1473 additions and 164 deletions

View File

@ -1,3 +1,18 @@
from django.contrib import admin
from .models import Donor, BloodRequest, BloodBank
# Register your models here.
@admin.register(Donor)
class DonorAdmin(admin.ModelAdmin):
list_display = ('name', 'blood_group', 'location', 'is_available')
list_filter = ('blood_group', 'is_available')
search_fields = ('name', 'location')
@admin.register(BloodRequest)
class BloodRequestAdmin(admin.ModelAdmin):
list_display = ('patient_name', 'blood_group', 'urgency', 'status', 'created_at')
list_filter = ('blood_group', 'urgency', 'status')
search_fields = ('patient_name', 'hospital')
@admin.register(BloodBank)
class BloodBankAdmin(admin.ModelAdmin):
list_display = ('name', 'location')

View File

@ -0,0 +1,61 @@
# Generated by Django 5.2.7 on 2026-02-17 13:38
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='BloodBank',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('location', models.CharField(max_length=255)),
('stock_a_plus', models.IntegerField(default=0)),
('stock_a_minus', models.IntegerField(default=0)),
('stock_b_plus', models.IntegerField(default=0)),
('stock_b_minus', models.IntegerField(default=0)),
('stock_o_plus', models.IntegerField(default=0)),
('stock_o_minus', models.IntegerField(default=0)),
('stock_ab_plus', models.IntegerField(default=0)),
('stock_ab_minus', models.IntegerField(default=0)),
],
),
migrations.CreateModel(
name='BloodRequest',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('patient_name', models.CharField(max_length=100)),
('blood_group', models.CharField(choices=[('A+', 'A+'), ('A-', 'A-'), ('B+', 'B+'), ('B-', 'B-'), ('O+', 'O+'), ('O-', 'O-'), ('AB+', 'AB+'), ('AB-', 'AB-')], max_length=5)),
('location', models.CharField(max_length=255)),
('urgency', models.CharField(choices=[('CRITICAL', 'Critical'), ('URGENT', 'Urgent'), ('NORMAL', 'Normal')], default='NORMAL', max_length=10)),
('hospital', models.CharField(max_length=255)),
('contact_number', models.CharField(max_length=20)),
('required_date', models.DateField(default=django.utils.timezone.now)),
('status', models.CharField(default='Active', max_length=20)),
('created_at', models.DateTimeField(auto_now_add=True)),
],
),
migrations.CreateModel(
name='Donor',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('blood_group', models.CharField(choices=[('A+', 'A+'), ('A-', 'A-'), ('B+', 'B+'), ('B-', 'B-'), ('O+', 'O+'), ('O-', 'O-'), ('AB+', 'AB+'), ('AB-', 'AB-')], max_length=5)),
('location', models.CharField(max_length=255)),
('latitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
('longitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)),
('phone', models.CharField(max_length=20)),
('is_available', models.BooleanField(default=True)),
('last_donation_date', models.DateField(blank=True, null=True)),
('vaccination_status', models.CharField(default='Unknown', max_length=100)),
],
),
]

View File

@ -0,0 +1,38 @@
# Generated by Django 5.2.7 on 2026-02-17 13:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='bloodbank',
name='contact_number',
field=models.CharField(blank=True, max_length=20, null=True),
),
migrations.AddField(
model_name='bloodbank',
name='is_24_7',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='donor',
name='avatar_url',
field=models.URLField(blank=True, null=True),
),
migrations.AddField(
model_name='donor',
name='email',
field=models.EmailField(blank=True, max_length=254, null=True),
),
migrations.AddField(
model_name='donor',
name='last_vaccination_date',
field=models.DateField(blank=True, null=True),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2026-02-17 13:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0002_bloodbank_contact_number_bloodbank_is_24_7_and_more'),
]
operations = [
migrations.AddField(
model_name='donor',
name='citizenship_no',
field=models.CharField(blank=True, max_length=50, null=True),
),
migrations.AddField(
model_name='donor',
name='district',
field=models.CharField(default='Kathmandu', max_length=100),
),
migrations.AddField(
model_name='donor',
name='is_verified',
field=models.BooleanField(default=False),
),
]

View File

@ -1,3 +1,64 @@
from django.db import models
from django.utils import timezone
# Create your models here.
class Donor(models.Model):
BLOOD_GROUPS = [
('A+', 'A+'), ('A-', 'A-'),
('B+', 'B+'), ('B-', 'B-'),
('O+', 'O+'), ('O-', 'O-'),
('AB+', 'AB+'), ('AB-', 'AB-'),
]
name = models.CharField(max_length=100)
blood_group = models.CharField(max_length=5, choices=BLOOD_GROUPS)
district = models.CharField(max_length=100, default='Kathmandu')
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)
phone = models.CharField(max_length=20)
email = models.EmailField(null=True, blank=True)
is_available = models.BooleanField(default=True)
is_verified = models.BooleanField(default=False)
citizenship_no = models.CharField(max_length=50, null=True, blank=True)
last_donation_date = models.DateField(null=True, blank=True)
vaccination_status = models.CharField(max_length=100, default='Unknown')
last_vaccination_date = models.DateField(null=True, blank=True)
avatar_url = models.URLField(null=True, blank=True)
def __str__(self):
return f"{self.name} ({self.blood_group}) - {'Verified' if self.is_verified else 'Unverified'}"
class BloodRequest(models.Model):
URGENCY_LEVELS = [
('CRITICAL', 'Critical'),
('URGENT', 'Urgent'),
('NORMAL', 'Normal'),
]
patient_name = models.CharField(max_length=100)
blood_group = models.CharField(max_length=5, choices=Donor.BLOOD_GROUPS)
location = models.CharField(max_length=255)
urgency = models.CharField(max_length=10, choices=URGENCY_LEVELS, default='NORMAL')
hospital = models.CharField(max_length=255)
contact_number = models.CharField(max_length=20)
required_date = models.DateField(default=timezone.now)
status = models.CharField(max_length=20, default='Active')
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f"{self.blood_group} for {self.patient_name}"
class BloodBank(models.Model):
name = models.CharField(max_length=100)
location = models.CharField(max_length=255)
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)
stock_a_minus = models.IntegerField(default=0)
stock_b_plus = models.IntegerField(default=0)
stock_b_minus = models.IntegerField(default=0)
stock_o_plus = models.IntegerField(default=0)
stock_o_minus = models.IntegerField(default=0)
stock_ab_plus = models.IntegerField(default=0)
stock_ab_minus = models.IntegerField(default=0)
def __str__(self):
return self.name

View File

@ -1,25 +1,272 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}Knowledge Base{% endblock %}</title>
{% if project_description %}
<meta name="description" content="{{ project_description }}">
<meta property="og:description" content="{{ project_description }}">
<meta property="twitter:description" content="{{ project_description }}">
{% endif %}
{% if project_image_url %}
<meta property="og:image" content="{{ project_image_url }}">
<meta property="twitter:image" content="{{ project_image_url }}">
{% endif %}
{% load static %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}?v={{ deployment_timestamp }}">
{% block head %}{% endblock %}
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}RaktaPulse Dashboard{% endblock %}</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
:root {
--dark-bg: #f8f9fa;
--sidebar-bg: #ffffff;
--card-bg: #ffffff;
--pulse-red: #E63946;
--pulse-red-light: #fff5f5;
--pulse-red-glow: rgba(230, 57, 70, 0.1);
--text-primary: #2b2d42;
--text-secondary: #6c757d;
--border-color: rgba(0, 0, 0, 0.08);
--sidebar-width: 260px;
}
body {
background-color: var(--dark-bg);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
margin: 0;
overflow-x: hidden;
}
@keyframes blink {
0% { opacity: 1; transform: scale(1); }
50% { opacity: 0.6; transform: scale(1.1); }
100% { opacity: 1; transform: scale(1); }
}
.blinking-logo {
animation: blink 1.5s infinite ease-in-out;
display: inline-block;
}
h1, h2, h3, .brand-font {
font-family: 'Outfit', sans-serif;
}
/* F-Shape Layout */
.wrapper {
display: flex;
width: 100%;
align-items: stretch;
}
/* Sidebar (Vertical stroke of F) */
#sidebar {
width: var(--sidebar-width);
background: var(--sidebar-bg);
min-height: 100vh;
position: fixed;
left: 0;
top: 0;
z-index: 1000;
border-right: 1px solid var(--border-color);
transition: all 0.3s;
}
#sidebar.collapsed {
margin-left: calc(-1 * var(--sidebar-width));
}
.sidebar-header {
padding: 30px 25px;
display: flex;
align-items: center;
}
.sidebar-menu {
padding: 0;
list-style: none;
}
.sidebar-menu li a {
padding: 15px 25px;
display: flex;
align-items: center;
color: var(--text-secondary);
text-decoration: none;
transition: all 0.3s;
font-weight: 500;
border-left: 4px solid transparent;
}
.sidebar-menu li a:hover, .sidebar-menu li.active a {
color: var(--pulse-red);
background: var(--pulse-red-light);
border-left-color: var(--pulse-red);
}
.sidebar-menu li a i {
margin-right: 15px;
font-size: 1.2rem;
}
/* Content Area */
#content {
width: calc(100% - var(--sidebar-width));
margin-left: var(--sidebar-width);
min-height: 100vh;
transition: all 0.3s;
}
#content.expanded {
width: 100%;
margin-left: 0;
}
/* Top Bar (Horizontal stroke 1 of F) */
.top-bar {
height: 70px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
display: flex;
align-items: center;
padding: 0 30px;
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
z-index: 999;
}
.search-box {
background: #f1f3f5;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 6px 15px;
display: flex;
align-items: center;
width: 300px;
}
.search-box input {
background: transparent;
border: none;
color: var(--text-primary);
padding-left: 10px;
width: 100%;
}
.search-box input:focus {
outline: none;
}
/* Glass Cards */
.glass-card {
background: var(--card-bg);
border-radius: 16px;
border: 1px solid var(--border-color);
padding: 24px;
transition: transform 0.3s ease;
}
.glass-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
/* Responsive */
@media (max-width: 992px) {
#sidebar { margin-left: calc(-1 * var(--sidebar-width)); }
#content { width: 100%; margin-left: 0; }
#sidebar.active { margin-left: 0; }
}
{% block head %}{% endblock %}
</style>
</head>
<body>
{% block content %}{% endblock %}
</body>
<div class="wrapper">
<!-- Sidebar -->
<nav id="sidebar">
<div class="sidebar-header">
<a href="/" class="text-decoration-none d-flex align-items-center">
<div class="bg-danger rounded-circle d-flex align-items-center justify-content-center me-2 blinking-logo" style="width: 35px; height: 35px;">
<i class="bi bi-droplet-fill text-white"></i>
</div>
<span class="fs-4 fw-bold text-danger brand-font">RaktaPulse</span>
</a>
</div>
<ul class="sidebar-menu mt-4">
<li class="{% if request.resolver_match.url_name == 'home' %}active{% endif %}"><a href="{% url 'home' %}"><i class="bi bi-grid-1x2-fill"></i> Dashboard</a></li>
<li class="{% if request.resolver_match.url_name == 'donor_list' %}active{% endif %}"><a href="{% url 'donor_list' %}"><i class="bi bi-people-fill"></i> Donors</a></li>
<li class="{% if request.resolver_match.url_name == 'blood_request_list' %}active{% endif %}"><a href="{% url 'blood_request_list' %}"><i class="bi bi-megaphone-fill"></i> Blood Requests</a></li>
<li class="{% if request.resolver_match.url_name == 'blood_bank_list' %}active{% endif %}"><a href="{% url 'blood_bank_list' %}"><i class="bi bi-hospital-fill"></i> Blood Banks</a></li>
<li class="{% if request.resolver_match.url_name == 'vaccination_info' %}active{% endif %}"><a href="{% url 'vaccination_info' %}"><i class="bi bi-shield-check"></i> Vaccination</a></li>
<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>
</div>
</div>
</nav>
<!-- Page Content -->
<div id="content">
<!-- Top Bar -->
<div class="top-bar justify-content-between">
<div class="d-flex align-items-center">
<button type="button" id="sidebarCollapse" class="btn btn-link text-danger me-3 p-0">
<i class="bi bi-list fs-3"></i>
</button>
<div class="search-box d-none d-sm-flex">
<i class="bi bi-search text-secondary"></i>
<input type="text" placeholder="Search anything...">
</div>
</div>
<div class="d-flex align-items-center gap-4">
<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" }}
</div>
{% if user.is_authenticated %}
<div class="d-flex align-items-center gap-2">
<span class="d-none d-sm-inline fw-bold text-dark">{{ user.username }}</span>
<a href="/logout" class="btn btn-outline-danger btn-sm">Logout</a>
</div>
{% else %}
<div class="d-flex align-items-center gap-2">
<a href="/login" class="btn btn-danger btn-sm px-3">Login</a>
<a href="/register" class="btn btn-outline-danger btn-sm px-3">Register</a>
</div>
{% endif %}
</div>
</div>
<div class="p-4 p-md-5">
{% block content %}{% endblock %}
</div>
</div>
</div>
<!-- Bootstrap 5 Bundle JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
document.getElementById('sidebarCollapse').addEventListener('click', function() {
document.getElementById('sidebar').classList.toggle('collapsed');
document.getElementById('content').classList.toggle('expanded');
// For mobile view, handle the 'active' class as well if needed
if (window.innerWidth <= 992) {
document.getElementById('sidebar').classList.toggle('active');
}
});
// Active class is handled server-side via Django templates.
</script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -0,0 +1,91 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Blood Banks - RaktaPulse{% endblock %}
{% block content %}
<div class="container-fluid p-0">
<div class="row mb-4">
<div class="col-12">
<h2 class="brand-font mb-1">Blood Banks</h2>
<p class="text-secondary">Official blood repositories and their current inventory levels.</p>
</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">
<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>
<h6 class="fw-bold text-dark mb-3">Inventory Levels (Units)</h6>
<div class="row g-2 mb-4">
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">A+</div>
<div class="fw-bold">{{ bank.stock_a_plus }}</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">A-</div>
<div class="fw-bold">{{ bank.stock_a_minus }}</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">B+</div>
<div class="fw-bold">{{ bank.stock_b_plus }}</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">B-</div>
<div class="fw-bold">{{ bank.stock_b_minus }}</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">O+</div>
<div class="fw-bold">{{ bank.stock_o_plus }}</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">O-</div>
<div class="fw-bold">{{ bank.stock_o_minus }}</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">AB+</div>
<div class="fw-bold">{{ bank.stock_ab_plus }}</div>
</div>
</div>
<div class="col-3">
<div class="p-2 border rounded text-center bg-light">
<div class="extra-small text-muted">AB-</div>
<div class="fw-bold">{{ bank.stock_ab_minus }}</div>
</div>
</div>
</div>
<button class="btn btn-danger w-100 rounded-pill">Contact Bank</button>
</div>
</div>
{% empty %}
<div class="col-12 text-center py-5">
<i class="bi bi-hospital fs-1 text-secondary opacity-25"></i>
<p class="text-secondary mt-3">No blood banks registered in the system.</p>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,61 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Blood Requests - RaktaPulse{% endblock %}
{% block content %}
<style>
.urgency-badge {
padding: 4px 10px;
border-radius: 6px;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
}
.bg-critical { background: #FF4D4D; color: #fff; }
.bg-urgent { background: #FFA500; color: #000; }
.bg-normal { background: #4CAF50; color: #fff; }
</style>
<div class="container-fluid p-0">
<div class="row mb-4">
<div class="col-12 d-flex justify-content-between align-items-center">
<div>
<h2 class="brand-font mb-1">Blood Requests</h2>
<p class="text-secondary">Current active requirements for blood in various hospitals.</p>
</div>
<button class="btn btn-danger rounded-pill px-4">Post a Request</button>
</div>
</div>
<div class="glass-card mb-4">
<div class="row">
{% for req in requests %}
<div class="col-md-6 col-xl-4 mb-4">
<div class="p-3 border rounded h-100 bg-light d-flex flex-column">
<div class="d-flex justify-content-between align-items-center mb-3">
<span class="urgency-badge bg-{{ req.urgency|lower }}">{{ req.urgency }}</span>
<div class="bg-danger bg-opacity-10 text-danger rounded-circle d-flex align-items-center justify-content-center fw-bold" style="width: 40px; height: 40px;">
{{ req.blood_group }}
</div>
</div>
<h5 class="mb-1 fw-bold text-dark">{{ req.patient_name }}</h5>
<p class="text-secondary small mb-3"><i class="bi bi-hospital me-1"></i> {{ req.hospital }}</p>
<div class="mt-auto pt-3 border-top">
<div class="d-flex justify-content-between align-items-center">
<span class="small text-muted"><i class="bi bi-clock me-1"></i> {{ req.created_at|timesince }} ago</span>
<a href="tel:{{ req.contact_number }}" class="btn btn-sm btn-danger px-3 rounded-pill">Help Now</a>
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12 text-center py-5">
<i class="bi bi-megaphone fs-1 text-secondary opacity-25"></i>
<p class="text-secondary mt-3">No active blood requests at the moment.</p>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,103 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Donors - RaktaPulse{% endblock %}
{% block content %}
<div class="container-fluid p-0">
<div class="row mb-4">
<div class="col-12">
<h2 class="brand-font mb-1">Blood Donors</h2>
<p class="text-secondary">Find and connect with blood donors in your community.</p>
</div>
</div>
<div class="glass-card mb-4">
<form method="GET" id="donorFilterForm" class="row g-3 mb-4">
<div class="col-md-3">
<select name="blood_group" class="form-select bg-light border-secondary text-dark">
<option value="">All Blood Groups</option>
{% for group in blood_groups %}
<option value="{{ group }}" {% if request.GET.blood_group == group %}selected{% endif %}>{{ group }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<input type="text" name="district" class="form-control bg-light border-secondary text-dark"
placeholder="Search district (e.g. Kathmandu)..." value="{{ request.GET.district }}">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-danger w-100 shadow-sm">Filter</button>
</div>
<div class="col-md-2">
<button type="button" id="findNearestBtn" class="btn btn-primary w-100 shadow-sm">
<i class="bi bi-geo-alt-fill me-1"></i> Nearest
</button>
</div>
<div class="col-md-2">
<a href="{% url 'donor_list' %}" class="btn btn-outline-secondary w-100">Reset</a>
</div>
<input type="hidden" name="lat" id="latInput" value="{{ request.GET.lat }}">
<input type="hidden" name="lng" id="lngInput" value="{{ request.GET.lng }}">
</form>
<div class="donor-list">
{% for donor in donors %}
<div class="donor-row d-flex align-items-center justify-content-between flex-wrap gap-3 p-3 mb-3 border rounded bg-light">
<div class="d-flex align-items-center gap-3">
<div class="bg-danger bg-opacity-10 text-danger rounded p-2 fw-bold" style="width: 45px; height: 45px; display: flex; align-items: center; justify-content: center;">
{{ donor.blood_group }}
</div>
<div>
<h6 class="mb-0 fw-bold text-dark">
{{ donor.name }}
{% if donor.is_verified %}
<span class="ms-1" title="Verified Donor"><i class="bi bi-patch-check-fill text-success"></i></span>
{% endif %}
{% if donor.distance and donor.distance < 1000 %}
<span class="ms-2 badge bg-info bg-opacity-10 text-info" style="font-size: 0.7rem;">
<i class="bi bi-distribute-vertical me-1"></i> {{ donor.distance|floatformat:1 }} km away
</span>
{% endif %}
</h6>
<p class="mb-0 text-secondary small"><i class="bi bi-geo-alt me-1"></i> {{ donor.location }}, {{ donor.district }}</p>
</div>
</div>
<div class="d-flex align-items-center gap-4">
<div class="text-end d-none d-sm-block">
<span class="badge {% if donor.is_available %}bg-success{% else %}bg-secondary{% endif %} bg-opacity-10 text-{% if donor.is_available %}success{% else %}secondary{% endif %} mb-1">
{% if donor.is_available %}Available{% else %}Unavailable{% endif %}
</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 %}
{% block scripts %}
<script>
document.getElementById('findNearestBtn').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('latInput').value = position.coords.latitude;
document.getElementById('lngInput').value = position.coords.longitude;
document.getElementById('donorFilterForm').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 %}

View File

@ -1,145 +1,382 @@
{% extends "base.html" %}
{% load static %}
{% block title %}{{ project_name }}{% endblock %}
{% block title %}RaktaPulse Dashboard - Lifeline of the Community{% endblock %}
{% block head %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-color-start: #6a11cb;
--bg-color-end: #2575fc;
--text-color: #ffffff;
--card-bg-color: rgba(255, 255, 255, 0.01);
--card-border-color: rgba(255, 255, 255, 0.1);
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: 'Inter', sans-serif;
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
color: var(--text-color);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
text-align: center;
overflow: hidden;
position: relative;
}
body::before {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' viewBox='0 0 100 100'><path d='M-10 10L110 10M10 -10L10 110' stroke-width='1' stroke='rgba(255,255,255,0.05)'/></svg>");
animation: bg-pan 20s linear infinite;
z-index: -1;
}
@keyframes bg-pan {
0% {
background-position: 0% 0%;
.stat-card {
background: #ffffff;
border-radius: 20px;
padding: 24px;
border: 1px solid var(--border-color);
position: relative;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.03);
}
.stat-card .icon-box {
width: 48px;
height: 48px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 15px;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
font-family: 'Outfit', sans-serif;
}
.stat-label {
color: var(--text-secondary);
font-size: 0.9rem;
font-weight: 500;
}
.blood-group-pill {
width: 40px;
height: 40px;
border-radius: 10px;
background: rgba(230, 57, 70, 0.1);
color: var(--pulse-red);
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
}
100% {
background-position: 100% 100%;
.donor-row {
background: #f8f9fa;
border-radius: 12px;
padding: 15px;
margin-bottom: 12px;
border: 1px solid #eee;
transition: all 0.3s;
}
}
main {
padding: 2rem;
}
.card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: 16px;
padding: 2.5rem 2rem;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.25);
}
h1 {
font-size: clamp(2.2rem, 3vw + 1.2rem, 3.2rem);
font-weight: 700;
margin: 0 0 1.2rem;
letter-spacing: -0.02em;
}
p {
margin: 0.5rem 0;
font-size: 1.1rem;
opacity: 0.92;
}
.loader {
margin: 1.5rem auto;
width: 56px;
height: 56px;
border: 4px solid rgba(255, 255, 255, 0.25);
border-top-color: #fff;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
.donor-row:hover {
border-color: var(--pulse-red);
background: var(--pulse-red-light);
}
}
.runtime code {
background: rgba(0, 0, 0, 0.25);
padding: 0.15rem 0.45rem;
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}
.urgency-badge {
padding: 4px 10px;
border-radius: 6px;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
}
.bg-critical { background: #FF4D4D; color: #fff; }
.bg-urgent { background: #FFA500; color: #000; }
.bg-normal { background: #4CAF50; color: #fff; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.progress {
background-color: #2A2A2A;
border-radius: 10px;
}
.vaccination-card {
background: linear-gradient(135deg, #E63946 0%, #d62828 100%);
border-radius: 20px;
padding: 25px;
color: white !important;
}
footer {
position: absolute;
bottom: 1rem;
width: 100%;
text-align: center;
font-size: 0.85rem;
opacity: 0.75;
}
.filter-btn {
background: #ffffff;
border: 1px solid var(--border-color);
color: var(--text-primary);
padding: 8px 16px;
border-radius: 10px;
font-size: 0.85rem;
}
.filter-btn.active {
background: var(--pulse-red);
border-color: var(--pulse-red);
}
</style>
{% endblock %}
{% block content %}
<main>
<div class="card">
<h1>Analyzing your requirements and generating your app…</h1>
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
<span class="sr-only">Loading…</span>
<div class="container-fluid p-0">
<!-- Welcome Header -->
<div class="row mb-4">
<div class="col-12">
<h2 class="brand-font mb-1">RaktaPulse Community Dashboard</h2>
<p class="text-secondary">Overview of blood donation activity and requirements in your area.</p>
</div>
</div>
<p class="hint">AppWizzy AI is collecting your requirements and applying the first changes.</p>
<p class="hint">This page will refresh automatically as the plan is implemented.</p>
<p class="runtime">
Runtime: Django <code>{{ django_version }}</code> · Python <code>{{ python_version }}</code>
— UTC <code>{{ current_time|date:"Y-m-d H:i:s" }}</code>
</p>
</div>
</main>
<footer>
Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC)
</footer>
{% endblock %}
<!-- Quick Stats -->
<div class="row g-4 mb-5">
<div class="col-xl-3 col-md-6">
<div class="stat-card">
<div class="icon-box bg-danger bg-opacity-10 text-danger">
<i class="bi bi-people"></i>
</div>
<div class="stat-value">{{ stats.total_donors }}</div>
<div class="stat-label">Registered Donors</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="stat-card">
<div class="icon-box bg-warning bg-opacity-10 text-warning">
<i class="bi bi-activity"></i>
</div>
<div class="stat-value">{{ stats.active_requests }}</div>
<div class="stat-label">Active Requests</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="stat-card">
<div class="icon-box bg-primary bg-opacity-10 text-primary">
<i class="bi bi-droplet"></i>
</div>
<div class="stat-value">{{ stats.total_stock }} <small class="fs-6 fw-normal">Units</small></div>
<div class="stat-label">Bank Inventory</div>
</div>
</div>
<div class="col-xl-3 col-md-6">
<div class="stat-card">
<div class="icon-box bg-success bg-opacity-10 text-success">
<i class="bi bi-shield-check"></i>
</div>
<div class="stat-value">{{ stats.vaccinated_percentage }}%</div>
<div class="stat-label">Vaccinated Donors</div>
</div>
</div>
</div>
<div class="row g-4">
<!-- Main Donor Search & Grid -->
<div class="col-lg-8">
<div class="glass-card mb-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0 brand-font">Available Donors</h4>
<div class="d-flex gap-2">
<button class="filter-btn active" id="btn-list">List View</button>
<button class="filter-btn" id="btn-map">Map View</button>
<button class="filter-btn" id="btn-nearest">Nearest First</button>
</div>
</div>
<div id="map-container" style="display: none; height: 400px; border-radius: 12px; margin-bottom: 20px; overflow: hidden; border: 1px solid var(--border-color);">
<div id="map" style="height: 100%;"></div>
</div>
<form method="GET" id="donorHomeFilterForm" class="row g-3 mb-4">
<div class="col-md-5">
<select name="blood_group" class="form-select bg-light border-secondary text-dark">
<option value="">All Blood Groups</option>
{% for group in blood_groups %}
<option value="{{ group }}" {% if request.GET.blood_group == group %}selected{% endif %}>{{ group }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-5">
<input type="text" name="location" class="form-control bg-light border-secondary text-dark"
placeholder="Search location (e.g. Kathmandu)..." value="{{ request.GET.location }}">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-danger w-100 shadow-sm">Find</button>
</div>
<input type="hidden" name="lat" id="latInputHome" value="{{ request.GET.lat }}">
<input type="hidden" name="lng" id="lngInputHome" value="{{ request.GET.lng }}">
</form>
<div class="donor-list">
{% for donor in donors %}
<div class="donor-row d-flex align-items-center justify-content-between flex-wrap gap-3">
<div class="d-flex align-items-center gap-3">
<div class="blood-group-pill">{{ donor.blood_group }}</div>
<div>
<h6 class="mb-0 fw-bold text-dark">
{{ donor.name }}
{% if donor.is_verified %}
<span class="ms-1" title="Verified Donor"><i class="bi bi-patch-check-fill text-success"></i></span>
{% endif %}
{% if donor.distance and donor.distance < 1000 %}
<span class="ms-2 badge bg-info bg-opacity-10 text-info" style="font-size: 0.65rem;">
{{ donor.distance|floatformat:1 }} km
</span>
{% endif %}
</h6>
<p class="mb-0 text-secondary small"><i class="bi bi-geo-alt me-1"></i> {{ donor.location }}, {{ donor.district }}</p>
</div>
</div>
<div class="d-flex align-items-center gap-4">
<div class="text-end d-none d-sm-block">
<span class="badge {% if donor.is_available %}bg-success{% else %}bg-secondary{% endif %} bg-opacity-10 text-{% if donor.is_available %}success{% else %}secondary{% endif %} mb-1">
{% if donor.is_available %}Available{% else %}Unavailable{% endif %}
</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>
</div>
</div>
{% empty %}
<div class="text-center py-5">
<i class="bi bi-search fs-1 text-secondary opacity-25"></i>
<p class="text-secondary mt-3">No donors match your search criteria.</p>
</div>
{% endfor %}
</div>
</div>
<!-- Vaccination Monitoring Section -->
<div class="vaccination-card">
<div class="row align-items-center">
<div class="col-md-7">
<h4 class="brand-font text-white mb-2">Vaccination & Eligibility</h4>
<p class="text-secondary small">We prioritize donors who are fully vaccinated to ensure maximum safety for recipients.</p>
<div class="mb-4">
<div class="d-flex justify-content-between small text-white mb-2">
<span>Community Immunity Level</span>
<span>{{ stats.vaccinated_percentage }}%</span>
</div>
<div class="progress" style="height: 10px;">
<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>
</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>
</div>
</div>
</div>
</div>
<!-- Sidebar Components -->
<div class="col-lg-4">
<!-- Urgent Requests -->
<div class="glass-card mb-4 border-start border-danger border-4">
<h5 class="brand-font mb-4 d-flex justify-content-between align-items-center">
Urgent Requests
<span class="badge bg-danger rounded-pill px-2" style="font-size: 0.6rem;">HOT</span>
</h5>
<div class="request-feed">
{% for req in blood_requests %}
<div class="mb-4 border-bottom border-light pb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="urgency-badge bg-{{ req.urgency|lower }}">{{ req.urgency }}</span>
<span class="fw-bold text-danger">{{ req.blood_group }}</span>
</div>
<h6 class="mb-1 text-dark">{{ req.patient_name }}</h6>
<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>
</div>
</div>
{% empty %}
<p class="text-secondary text-center">No active requests found.</p>
{% endfor %}
</div>
</div>
<!-- Blood Bank Inventory -->
<div class="glass-card">
<h5 class="brand-font mb-4">Blood Bank Inventory</h5>
<div class="inventory-list">
{% for bank in blood_banks %}
<div class="mb-3">
<div class="d-flex justify-content-between mb-1">
<span class="small fw-bold text-dark">{{ bank.name }}</span>
<span class="small text-secondary">24/7 Available</span>
</div>
<div class="d-flex gap-1 flex-wrap">
<span class="badge bg-dark border border-secondary text-secondary extra-small" style="font-size: 0.65rem;">A+ : {{ bank.stock_a_plus }}</span>
<span class="badge bg-dark border border-secondary text-secondary extra-small" style="font-size: 0.65rem;">B+ : {{ bank.stock_b_plus }}</span>
<span class="badge bg-dark border border-secondary text-secondary extra-small" style="font-size: 0.65rem;">O+ : {{ bank.stock_o_plus }}</span>
<span class="badge bg-dark border border-secondary text-secondary extra-small" style="font-size: 0.65rem;">AB+ : {{ bank.stock_ab_plus }}</span>
</div>
</div>
{% empty %}
<p class="text-secondary text-center">No blood banks registered.</p>
{% endfor %}
</div>
<a href="/admin/core/bloodbank/" class="btn btn-outline-secondary w-100 btn-sm mt-3">Manage Banks</a>
</div>
<!-- Extra Feature: Community Tip -->
<div class="glass-card mt-4 bg-gradient" style="background: linear-gradient(135deg, #1e1e1e 0%, #2d1b1b 100%);">
<h6 class="text-danger mb-2"><i class="bi bi-lightbulb me-2"></i>Lifesaver Tip</h6>
<p class="small text-secondary mb-0">Donating once can save up to three lives. Make sure to stay hydrated and rest before your appointment!</p>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
const btnList = document.getElementById('btn-list');
const btnMap = document.getElementById('btn-map');
const mapContainer = document.getElementById('map-container');
const donorList = document.querySelector('.donor-list');
let map;
btnMap.addEventListener('click', () => {
btnMap.classList.add('active');
btnList.classList.remove('active');
mapContainer.style.display = 'block';
donorList.style.display = 'none';
if (!map) {
initMap();
} else {
// Need to invalidate size if it was hidden when initialized
setTimeout(() => { map.invalidateSize(); }, 200);
}
});
btnList.addEventListener('click', () => {
btnList.classList.add('active');
btnMap.classList.remove('active');
mapContainer.style.display = 'none';
donorList.style.display = 'block';
});
const btnNearest = document.getElementById('btn-nearest');
btnNearest.addEventListener('click', () => {
const btn = btnNearest;
const originalContent = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition(function(position) {
document.getElementById('latInputHome').value = position.coords.latitude;
document.getElementById('lngInputHome').value = position.coords.longitude;
document.getElementById('donorHomeFilterForm').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;
}
});
function initMap() {
map = L.map('map').setView([27.7172, 85.3240], 12); // Centered on Kathmandu
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Add dummy markers for donors
{% for donor in donors %}
// Randomly offset from center for demo (within Nepal context)
L.marker([27.7172 + (Math.random() - 0.5) * 0.1, 85.3240 + (Math.random() - 0.5) * 0.1])
.addTo(map)
.bindPopup("<b>{{ donor.name }}</b><br>{{ donor.blood_group }} - {{ donor.location }}");
{% endfor %}
}
</script>
{% endblock %}

View File

@ -0,0 +1,60 @@
{% extends 'base.html' %}
{% block title %}Login - RaktaPulse{% endblock %}
{% block content %}
<div class="row justify-content-center py-5">
<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>
<h2 class="fw-bold text-danger brand-font">Welcome to RaktaPulse</h2>
<p class="text-secondary">Please login to your account</p>
</div>
<form method="post">
{% csrf_token %}
{% for field in form %}
<div class="mb-3">
<label class="form-label small fw-bold text-secondary text-uppercase">{{ field.label }}</label>
{{ field }}
{% if field.errors %}
<div class="text-danger small mt-1">{{ field.errors.0 }}</div>
{% endif %}
</div>
{% endfor %}
<button type="submit" class="btn btn-danger w-100 py-2 fw-bold mt-3">LOGIN</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>
</div>
</div>
</div>
<style>
input {
display: block;
width: 100%;
padding: 0.75rem 1rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #212529;
background-color: #f8f9fa;
background-clip: padding-box;
border: 1px solid #dee2e6;
border-radius: 8px;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
input:focus {
border-color: #E63946;
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(230, 57, 70, 0.1);
}
</style>
{% endblock %}

View File

@ -0,0 +1,57 @@
{% extends "base.html" %}
{% load static %}
{% block title %}Vaccination Status - RaktaPulse{% endblock %}
{% block content %}
<div class="container-fluid p-0">
<div class="row mb-4">
<div class="col-12">
<h2 class="brand-font mb-1">Vaccination Overview</h2>
<p class="text-secondary">Monitoring community immunity and donor eligibility.</p>
</div>
</div>
<div class="row g-4">
<div class="col-md-8">
<div class="glass-card mb-4">
<h4 class="brand-font mb-4 text-dark">Why Vaccination Matters?</h4>
<p>Ensuring our donors are vaccinated is crucial for the safety of both the donors and the recipients. Fully vaccinated individuals contribute to a safer blood supply chain.</p>
<div class="p-4 bg-light rounded border mb-4">
<h5 class="fw-bold mb-3">Community Progress</h5>
<div class="d-flex justify-content-between mb-2">
<span>Fully Vaccinated Donors</span>
<span class="fw-bold text-success">{{ stats.percentage }}%</span>
</div>
<div class="progress" style="height: 20px;">
<div class="progress-bar bg-success" style="width: {{ stats.percentage }}%">{{ stats.percentage }}%</div>
</div>
<p class="small text-muted mt-2">{{ stats.vaccinated_count }} out of {{ stats.total_donors }} registered donors are fully vaccinated.</p>
</div>
<h5 class="fw-bold mb-3">Eligibility Criteria</h5>
<ul class="list-group list-group-flush mb-4 bg-transparent">
<li class="list-group-item bg-transparent border-light"><i class="bi bi-check-circle-fill text-success me-2"></i> Must be fully vaccinated for priority donation.</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>
</ul>
</div>
</div>
<div class="col-md-4">
<div class="glass-card bg-danger bg-opacity-10 border-danger border-opacity-25">
<h5 class="text-danger fw-bold mb-3">Update Your Status</h5>
<p class="small text-secondary">Are you a donor? Keep your vaccination records up to date to maintain your verified status.</p>
<button class="btn btn-danger w-100 rounded-pill">Upload Certificate</button>
</div>
<div class="glass-card mt-4">
<h5 class="fw-bold mb-3">Contact Support</h5>
<p class="small text-secondary">Have questions about vaccination and donation? Our medical team is here to help.</p>
<a href="mailto:support@raktapulse.com" class="btn btn-outline-secondary w-100 rounded-pill">Email Support</a>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,7 +1,14 @@
from django.urls import path
from .views import home
from .views import home, login_view, logout_view, register_view, donor_list, blood_request_list, blood_bank_list, vaccination_info
urlpatterns = [
path("", home, name="home"),
path("login/", login_view, name="login"),
path("logout/", logout_view, name="logout"),
path("register/", register_view, name="register"),
path("donors/", donor_list, name="donor_list"),
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"),
]

View File

@ -1,25 +1,172 @@
import os
import platform
from django import get_version as django_version
from django.shortcuts import render
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 import messages
from django.utils import timezone
from .models import Donor, BloodRequest, BloodBank
import math
def haversine(lat1, lon1, lat2, lon2):
# Radius of the Earth in km
R = 6371.0
dlat = math.radians(lat2 - lat1)
dlon = math.radians(lon2 - lon1)
a = math.sin(dlat / 2)**2 + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(dlon / 2)**2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c
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")
else:
form = AuthenticationForm()
return render(request, "core/login.html", {"form": form})
def logout_view(request):
logout(request)
return redirect("home")
def register_view(request):
if request.method == "POST":
form = UserCreationForm(request.POST)
if form.is_valid():
user = form.save()
login(request, user)
return redirect("home")
else:
form = UserCreationForm()
return render(request, "core/register.html", {"form": form})
def home(request):
"""Render the landing screen with loader and environment details."""
host_name = request.get_host().lower()
agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic"
now = timezone.now()
"""Render the RaktaPulse Dashboard experience."""
query_blood = request.GET.get('blood_group', '')
query_location = request.GET.get('location', '')
user_lat = request.GET.get('lat')
user_lng = request.GET.get('lng')
donors = Donor.objects.all()
if query_blood:
donors = donors.filter(blood_group=query_blood)
if query_location:
donors = donors.filter(location__icontains=query_location)
donor_list_data = list(donors)
if user_lat and user_lng:
try:
u_lat = float(user_lat)
u_lng = float(user_lng)
for d in donor_list_data:
if d.latitude and d.longitude:
d.distance = haversine(u_lat, u_lng, float(d.latitude), float(d.longitude))
else:
d.distance = 999999 # Very far
donor_list_data.sort(key=lambda x: x.distance)
except ValueError:
donor_list_data.sort(key=lambda x: (-x.is_available, x.name))
else:
donor_list_data.sort(key=lambda x: (-x.is_available, x.name))
blood_requests = BloodRequest.objects.filter(status='Active').order_by('-urgency', '-created_at')
blood_banks = BloodBank.objects.all()
# Stats for Dashboard
stats = {
"total_donors": Donor.objects.count(),
"active_requests": BloodRequest.objects.filter(status='Active').count(),
"total_stock": sum([
bb.stock_a_plus + bb.stock_a_minus + bb.stock_b_plus + bb.stock_b_minus +
bb.stock_o_plus + bb.stock_o_minus + bb.stock_ab_plus + bb.stock_ab_minus
for bb in blood_banks
]),
"vaccinated_percentage": 0
}
total_d = stats["total_donors"]
if total_d > 0:
vaccinated_count = Donor.objects.filter(vaccination_status__icontains='Fully').count()
stats["vaccinated_percentage"] = int((vaccinated_count / total_d) * 100)
context = {
"project_name": "New Style",
"agent_brand": agent_brand,
"django_version": django_version(),
"python_version": platform.python_version(),
"current_time": now,
"host_name": host_name,
"project_description": os.getenv("PROJECT_DESCRIPTION", ""),
"project_image_url": os.getenv("PROJECT_IMAGE_URL", ""),
"donors": donor_list_data[:8],
"blood_requests": blood_requests[:6],
"blood_banks": blood_banks,
"blood_groups": [g[0] for g in Donor.BLOOD_GROUPS],
"stats": stats,
"project_name": "RaktaPulse",
"current_time": timezone.now(),
}
return render(request, "core/index.html", context)
def donor_list(request):
blood_group = request.GET.get('blood_group', '')
district = request.GET.get('district', '')
user_lat = request.GET.get('lat')
user_lng = request.GET.get('lng')
donors = Donor.objects.all()
if blood_group:
donors = donors.filter(blood_group=blood_group)
if district:
donors = donors.filter(district__icontains=district)
donor_list_data = list(donors)
if user_lat and user_lng:
try:
u_lat = float(user_lat)
u_lng = float(user_lng)
for d in donor_list_data:
if d.latitude and d.longitude:
d.distance = haversine(u_lat, u_lng, float(d.latitude), float(d.longitude))
else:
d.distance = 999999
donor_list_data.sort(key=lambda x: x.distance)
except ValueError:
donor_list_data.sort(key=lambda x: (-x.is_verified, x.name))
else:
donor_list_data.sort(key=lambda x: (-x.is_verified, x.name))
context = {
'donors': donor_list_data,
'blood_groups': [g[0] for g in Donor.BLOOD_GROUPS],
}
return render(request, 'core/donor_list.html', context)
def blood_request_list(request):
requests = BloodRequest.objects.all().order_by('-created_at')
context = {
'requests': requests,
}
return render(request, 'core/blood_request_list.html', context)
def blood_bank_list(request):
banks = BloodBank.objects.all()
context = {
'banks': banks,
}
return render(request, 'core/blood_bank_list.html', context)
def vaccination_info(request):
stats = {
"total_donors": Donor.objects.count(),
"vaccinated_count": Donor.objects.filter(vaccination_status__icontains='Fully').count(),
}
if stats["total_donors"] > 0:
stats["percentage"] = int((stats["vaccinated_count"] / stats["total_donors"]) * 100)
else:
stats["percentage"] = 0
return render(request, 'core/vaccination_info.html', {'stats': stats})

96
populate_data.py Normal file
View File

@ -0,0 +1,96 @@
import os
import django
import datetime
import random
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
django.setup()
from core.models import Donor, BloodRequest, BloodBank
from django.utils import timezone
def run():
# Clear existing data
Donor.objects.all().delete()
BloodRequest.objects.all().delete()
BloodBank.objects.all().delete()
# Create Donors (Nepali Context)
donors_data = [
('Arjun Thapa', 'O+', 'Kathmandu', 'New Baneshwor', '9841000000', 'Fully Vaccinated', True),
('Sita Shrestha', 'A-', 'Lalitpur', 'Patan Durbar Square', '9841111111', 'Fully Vaccinated', True),
('Rajesh Hamal', 'B+', 'Bhaktapur', 'Suryabinayak', '9841222222', 'Partially Vaccinated', True),
('Bipana Thapa', 'AB+', 'Kaski', 'Lakeside, Pokhara', '9841333333', 'Fully Vaccinated', False),
('Nischal Basnet', 'O-', 'Rupandehi', 'Butwal', '9841444444', 'Not Vaccinated', False),
('Swastima Khadka', 'A+', 'Chitwan', 'Bharatpur', '9841555555', 'Fully Vaccinated', True),
]
for name, bg, dist, loc, ph, vac, ver in donors_data:
Donor.objects.create(
name=name,
blood_group=bg,
district=dist,
location=loc,
phone=ph,
is_available=True,
is_verified=ver,
citizenship_no=f"123-{random.randint(1000, 9999)}" if ver else None,
vaccination_status=vac,
last_vaccination_date=timezone.now().date() - datetime.timedelta(days=random.randint(30, 180))
)
# Create Blood Requests (Nepali Context)
BloodRequest.objects.create(
patient_name='Ram Bahadur',
blood_group='O+',
location='Teaching Hospital, Maharajgunj',
urgency='CRITICAL',
hospital='Teaching Hospital',
contact_number='9800000001'
)
BloodRequest.objects.create(
patient_name='Maya Devi',
blood_group='B+',
location='Bir Hospital, Kathmandu',
urgency='URGENT',
hospital='Bir Hospital',
contact_number='9800000002'
)
BloodRequest.objects.create(
patient_name='Hari Prasad',
blood_group='AB-',
location='Patan Hospital',
urgency='NORMAL',
hospital='Patan Hospital',
contact_number='9800000003'
)
# Create Blood Bank (Nepali Context)
BloodBank.objects.create(
name='Nepal Red Cross Society',
location='Soalteemode, Kathmandu',
contact_number='01-4270650',
is_24_7=True,
stock_o_plus=150,
stock_o_minus=45,
stock_a_plus=120,
stock_a_minus=30,
stock_b_plus=90,
stock_b_minus=20,
stock_ab_plus=40,
stock_ab_minus=15
)
BloodBank.objects.create(
name='Bhaktapur Blood Bank',
location='Dudhpati, Bhaktapur',
contact_number='01-6612266',
is_24_7=True,
stock_o_plus=60,
stock_a_plus=45,
stock_b_plus=30
)
print("Sample data populated successfully.")
if __name__ == "__main__":
run()