Autosave: 20260217-141859
This commit is contained in:
parent
b61b163577
commit
67d8eb7e56
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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')
|
||||
|
||||
61
core/migrations/0001_initial.py
Normal file
61
core/migrations/0001_initial.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
91
core/templates/core/blood_bank_list.html
Normal file
91
core/templates/core/blood_bank_list.html
Normal 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 %}
|
||||
61
core/templates/core/blood_request_list.html
Normal file
61
core/templates/core/blood_request_list.html
Normal 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 %}
|
||||
103
core/templates/core/donor_list.html
Normal file
103
core/templates/core/donor_list.html
Normal 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 %}
|
||||
@ -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 %}
|
||||
|
||||
60
core/templates/core/login.html
Normal file
60
core/templates/core/login.html
Normal 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 %}
|
||||
57
core/templates/core/vaccination_info.html
Normal file
57
core/templates/core/vaccination_info.html
Normal 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 %}
|
||||
@ -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"),
|
||||
]
|
||||
|
||||
177
core/views.py
177
core/views.py
@ -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
96
populate_data.py
Normal 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()
|
||||
Loading…
x
Reference in New Issue
Block a user