minor update

This commit is contained in:
2025-08-22 06:11:32 +05:30
parent 34e3cd9951
commit 5cf728275c
4 changed files with 159 additions and 95 deletions
+1
View File
@@ -23,6 +23,7 @@ INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.humanize',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+3 -3
View File
@@ -128,7 +128,7 @@
{% if user.is_authenticated %}
<a href="{% url 'view_all_issues' %}" class="btn btn-primary">View All Issues</a>
{% else %}
<a href="{% url 'register' %}" class="btn btn-outline-primary">View All Issues</a>
<a href="{% url 'login' %}" class="btn btn-outline-primary">View All Issues</a>
{% endif %}
</div>
<div class="row g-4">
@@ -179,7 +179,7 @@
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
<h4>No issues reported yet</h4>
<p class="text-muted">Be the first to report an issue in your community!</p>
<a href="{% url 'register' %}" class="btn btn-primary">Get Started</a>
<a href="{% url 'login' %}" class="btn btn-primary">Get Started</a>
</div>
{% endfor %}
</div>
@@ -193,7 +193,7 @@
{% if user.is_authenticated %}
<a href="{% url 'citizen_dashboard' %}" class="btn btn-light btn-lg px-4">Report an Issue</a>
{% else %}
<a href="{% url 'register' %}" class="btn btn-light btn-lg px-4">Get Started Now</a>
<a href="{% url 'login' %}" class="btn btn-light btn-lg px-4">Get Started Now</a>
{% endif %}
</div>
</section>
+155 -92
View File
@@ -1,6 +1,6 @@
{% extends "core/base.html" %}
{% block content %}
{% load humanize %}
{% block content %}
<div class="container my-5">
<!-- Header + Filter -->
<div class="d-flex flex-wrap justify-content-between align-items-center mb-4 gap-3">
@@ -8,19 +8,24 @@
<form method="get" class="d-flex flex-wrap gap-2">
<select name="status" class="form-select">
<option value="">All Status</option>
<option value="reported" {% if request.GET.status == "reported" %}selected{% endif %}>Reported</option>
<option value="acknowledged" {% if request.GET.status == "acknowledged" %}selected{% endif %}>Acknowledged</option>
<option value="in_progress" {% if request.GET.status == "in_progress" %}selected{% endif %}>In Progress</option>
<option value="resolved" {% if request.GET.status == "resolved" %}selected{% endif %}>Resolved</option>
<option value="reported" {% if request.GET.status|default:'' == "reported" %}selected{% endif %}>Reported</option>
<option value="acknowledged" {% if request.GET.status|default:'' == "acknowledged" %}selected{% endif %}>Acknowledged
</option>
<option value="in_progress" {% if request.GET.status|default:'' == "in_progress" %}selected{% endif %}>In Progress
</option>
<option value="resolved" {% if request.GET.status|default:'' == "resolved" %}selected{% endif %}>Resolved</option>
</select>
<select name="category" class="form-select">
<option value="">All Categories</option>
{% for cat in categories %}
<option value="{{ cat.id }}" {% if request.GET.category == cat.id|stringformat:"s" %}selected{% endif %}>
<option value="{{ cat.id }}" {% if request.GET.category|default:'' == cat.id|stringformat:"s" %}selected{% endif %}>
{{ cat.name }}
</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary">
<i class="fas fa-filter me-1"></i> Filter
</button>
@@ -33,27 +38,37 @@
<div class="col-md-4">
<div class="card issue-card h-100 shadow-sm border-0">
{% if issue.photo %}
<img src="{{ issue.photo.url }}" class="card-img-top" alt="{{ issue.title }}" style="height: 200px; object-fit: cover;">
<img src="{{ issue.photo.url }}" class="card-img-top issue-image" alt="{{ issue.title }}"
style="height: 200px; object-fit: cover; cursor: pointer" data-bs-toggle="modal"
data-bs-target="#imageModal" data-image="{{ issue.photo.url }}" />
{% else %}
<img src="https://via.placeholder.com/300x200/6c757d/ffffff?text=No+Image" class="card-img-top" alt="No image">
<img src="https://via.placeholder.com/300x200/6c757d/ffffff?text=No+Image" class="card-img-top"
alt="No image" />
{% endif %}
<div class="card-body d-flex flex-column">
<!-- Category Badge -->
<span class="badge bg-{% if issue.category.name == 'Roads' %}warning text-dark{% elif issue.category.name == 'Sanitation' %}success{% else %}info{% endif %} mb-2">
<span
class="badge bg-{% if issue.category.name == 'Roads' %}warning text-dark{% elif issue.category.name == 'Sanitation' %}success{% else %}info{% endif %} mb-2">
{{ issue.category.name|default:"General" }}
</span>
<!-- Title + Description -->
<h5 class="card-title">{{ issue.title|truncatewords:6 }}</h5>
<p class="card-text text-muted flex-grow-1">{{ issue.description|truncatewords:18 }}</p>
<p class="card-text text-muted flex-grow-1">
{{ issue.description|truncatewords:18 }}
</p>
<!-- Location + Vote -->
<div class="d-flex justify-content-between align-items-center mt-3">
<small class="text-muted"><i class="fas fa-map-marker-alt me-1"></i> {{ issue.location|truncatewords:2 }}</small>
<small class="text-muted">
<i class="fas fa-map-marker-alt me-1"></i> {{ issue.location|truncatewords:2 }}
</small>
<div class="vote-section">
{% if user.is_authenticated %}
<button class="btn btn-sm btn-outline-primary vote-btn {% if issue.user_has_voted %}active{% endif %}"
data-issue-id="{{ issue.id }}">
<button
class="btn btn-sm btn-outline-primary vote-btn {% if issue.user_has_voted %}active{% endif %}"
data-issue-id="{{ issue.id }}">
<i class="fas fa-thumbs-up"></i>
<span class="vote-count">{{ issue.vote_count }}</span>
</button>
@@ -67,8 +82,10 @@
</div>
</div>
<div class="card-footer bg-transparent">
<small class="text-{% if issue.status == 'resolved' %}success{% elif issue.status == 'in_progress' %}warning{% else %}info{% endif %}">
<i class="fas fa-{% if issue.status == 'resolved' %}check-circle{% elif issue.status == 'in_progress' %}tasks{% else %}clock{% endif %} me-1"></i>
<small
class="text-{% if issue.status == 'resolved' %}success{% elif issue.status == 'in_progress' %}warning{% else %}info{% endif %}">
<i
class="fas fa-{% if issue.status == 'resolved' %}check-circle{% elif issue.status == 'in_progress' %}tasks{% else %}clock{% endif %} me-1"></i>
{{ issue.get_status_display }}
</small>
</div>
@@ -78,89 +95,135 @@
<div class="col-12 text-center py-5">
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
<h4>No issues reported yet</h4>
<p class="text-muted">Be the first to report an issue in your community!</p>
<p class="text-muted">
Be the first to report an issue in your community!
</p>
<a href="{% url 'citizen_dashboard' %}" class="btn btn-primary">Report an Issue</a>
</div>
{% endfor %}
</div>
</div>
<div class="modal fade" id="imageModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content bg-light">
<div class="modal-body text-center p-0">
<img id="modalImage" src="" class="img-fluid zoomable" alt="Zoomed image" />
</div>
</div>
</div>
</div>
<!-- Styling -->
<style>
.issue-card {
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
.issue-card:hover {
transform: translateY(-6px);
box-shadow: 0 8px 20px rgba(0,0,0,0.15);
}
.vote-btn.active {
background-color: #0d6efd;
color: white;
border-color: #0d6efd;
}
.vote-btn:hover:not(:disabled) {
transform: scale(1.05);
transition: transform 0.2s;
}
.vote-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>
{% endblock %}
{% block extra_js %}
<script>
// CSRF Helper
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
document.cookie.split(';').forEach(cookie => {
cookie = cookie.trim();
if (cookie.startsWith(name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
}
});
}
return cookieValue;
}
const csrftoken = getCookie('csrftoken');
// Handle vote clicks
document.addEventListener('click', function (e) {
const btn = e.target.closest('.vote-btn');
if (!btn) return;
e.preventDefault();
const issueId = btn.dataset.issueId;
if (!issueId) return;
const originalHTML = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
fetch("{% url 'vote_issue' 0 %}".replace('0', issueId), {
method: 'POST',
headers: {
'X-CSRFToken': csrftoken,
'X-Requested-With': 'XMLHttpRequest'
.issue-card {
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
})
.then(r => r.json())
.then(data => {
if (!data.success) throw new Error(data.error || 'Vote failed');
btn.classList.toggle('active', data.voted);
btn.innerHTML = '<i class="fas fa-thumbs-up"></i> <span class="vote-count">' + data.vote_count + '</span>';
})
.catch(err => {
console.error(err);
alert('Error: ' + err.message);
btn.innerHTML = originalHTML;
})
.finally(() => {
btn.disabled = false;
});
});
.issue-card:hover {
transform: translateY(-6px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}
.vote-btn.active {
background-color: #0d6efd;
color: white;
border-color: #0d6efd;
}
.vote-btn:hover:not(:disabled) {
transform: scale(1.05);
transition: transform 0.2s;
}
.vote-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
#modalImage {
max-height: 85vh;
width: 100%;
object-fit: contain;
transition: transform 0.3s ease-in-out;
cursor: zoom-in;
}
#modalImage.zoomed {
transform: scale(1.5);
cursor: zoom-out;
}
</style>
{% endblock %} {% block extra_js %}
<script>
// CSRF Helper
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
document.cookie.split(";").forEach((cookie) => {
cookie = cookie.trim();
if (cookie.startsWith(name + "=")) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
}
});
}
return cookieValue;
}
const csrftoken = getCookie("csrftoken");
// Handle vote clicks
document.addEventListener("click", function (e) {
const btn = e.target.closest(".vote-btn");
if (!btn) return;
e.preventDefault();
const issueId = btn.dataset.issueId;
if (!issueId) return;
const originalHTML = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
fetch("{% url 'vote_issue' 0 %}".replace("0", issueId), {
method: "POST",
headers: {
"X-CSRFToken": csrftoken,
"X-Requested-With": "XMLHttpRequest",
},
})
.then((r) => r.json())
.then((data) => {
if (!data.success) throw new Error(data.error || "Vote failed");
btn.classList.toggle("active", data.voted);
btn.innerHTML =
'<i class="fas fa-thumbs-up"></i> <span class="vote-count">' +
data.vote_count +
"</span>";
})
.catch((err) => {
console.error(err);
alert("Error: " + err.message);
btn.innerHTML = originalHTML;
})
.finally(() => {
btn.disabled = false;
});
});
// 🔥 Handle image modal
const imageModal = document.getElementById("imageModal");
const modalImage = document.getElementById("modalImage");
document.querySelectorAll(".issue-image").forEach((img) => {
img.addEventListener("click", function () {
const src = this.getAttribute("data-image");
modalImage.src = src;
modalImage.classList.remove("zoomed");
});
});
// Toggle zoom on click inside modal
modalImage.addEventListener("click", function () {
this.classList.toggle("zoomed");
});
</script>
{% endblock %}
{% endblock %}