base setup

This commit is contained in:
2026-01-07 12:09:20 +05:30
commit 0c275efea1
278 changed files with 11228 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<h2 class="text-2xl font-bold mb-6 text-gray-800">Admin Dashboard</h2>
<div class="flex gap-5">
<a href="{% url 'contact_list' %}"
class="px-6 py-3 border-2 border-blue-600 rounded-xl font-semibold text-blue-600 hover:text-blue-800 transition-colors">
View Contacts
</a>
<a href="{% url 'subscriber_list' %}"
class="px-6 py-3 border-2 border-green-600 rounded-xl font-semibold text-green-600 hover:text-green-800 transition-colors">
View Subscribers
</a>
<a href="{% url 'user_activity' %}"
class="px-6 py-3 border-2 border-green-600 rounded-xl font-semibold text-green-600 hover:text-green-800 transition-colors">
View Activiy
</a>
</div>
{% endblock %}

View File

@@ -0,0 +1,188 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Appointment Leads - Admin{% endblock %}
{% block content %}
<div class="container mx-auto mt-10 px-4 max-w-7xl">
<div class="bg-white shadow-lg rounded-2xl overflow-hidden">
<!-- Header -->
<div class="px-8 py-6 border-b border-gray-200 bg-gradient-to-r from-indigo-50 to-purple-50">
<h2 class="text-3xl font-bold text-gray-800">Appointment Requests</h2>
<p class="text-gray-600 mt-2">Manage appointments: update status, reschedule, and track progress</p>
</div>
{% if appointments %}
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<!-- Table Head -->
<thead class="bg-gray-100">
<tr>
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">#</th>
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ticket ID</th>
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Contact</th>
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Expert</th>
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Scheduled For</th>
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
{% for apt in appointments %}
<tr class="hover:bg-gray-50 transition">
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">{{ forloop.counter }}</td>
<td class="px-6 py-4 whitespace-nowrap font-medium text-indigo-600">
APP-{{ apt.pk|stringformat:"06d" }}
</td>
<td class="px-6 py-4 whitespace-nowrap font-medium text-gray-900">
{{ apt.full_name }}
</td>
<td class="px-6 py-4 text-sm text-gray-600">
{{ apt.email }}<br>
<small class="text-gray-500">{{ apt.phone }}</small>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
{{ apt.get_meeting_with_display }}
</td>
<td class="px-6 py-4 text-sm">
{{ apt.appointment_datetime|date:"d M Y" }} at {{ apt.appointment_datetime|time:"h:i A" }}
</td>
<!-- Status Badge -->
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-3 py-1 inline-flex text-xs font-semibold rounded-full
{% if apt.status == 'pending' %}bg-yellow-100 text-yellow-800
{% elif apt.status == 'contacted' %}bg-blue-100 text-blue-800
{% elif apt.status == 'in_process' %}bg-purple-100 text-purple-800
{% elif apt.status == 'rescheduled' %}bg-indigo-100 text-indigo-800
{% elif apt.status == 'completed' %}bg-green-100 text-green-800
{% elif apt.status == 'cancelled' %}bg-red-100 text-red-800
{% else %}bg-gray-100 text-gray-800{% endif %}">
{{ apt.get_status_display }}
</span>
</td>
<!-- Actions -->
<td class="px-6 py-4 text-sm">
<button onclick="openModal('modal-{{ apt.id }}')"
class="bg-gradient-to-r from-indigo-600 to-purple-600 text-white px-4 py-2 rounded-lg text-xs font-medium hover:shadow-lg transition">
Manage
</button>
<!-- MODAL -->
<div id="modal-{{ apt.id }}" class="fixed inset-0 bg-black bg-opacity-60 hidden flex items-center justify-center z-50">
<div class="bg-white w-full max-w-lg rounded-2xl shadow-2xl p-8 relative animate-fadeIn">
<button onclick="closeModal('modal-{{ apt.id }}')"
class="absolute top-4 right-6 text-gray-400 hover:text-gray-600 text-3xl">&times;</button>
<h3 class="text-2xl font-bold mb-6 text-gray-800">
Manage Appointment {{ apt.full_name }}
</h3>
<p class="text-sm text-gray-500 mb-6">Ticket: APP-{{ apt.pk|stringformat:"06d" }}</p>
<!-- Update Status -->
<form method="post" class="mb-8">
{% csrf_token %}
<input type="hidden" name="appointment_id" value="{{ apt.id }}">
<input type="hidden" name="action" value="update_status">
<label class="block text-sm font-semibold text-gray-700 mb-2">Update Status</label>
<select name="status" class="w-full border border-gray-300 rounded-lg px-4 py-3 focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
{% for value, label in status_choices %}
<option value="{{ value }}" {% if apt.status == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
<button type="submit" class="mt-4 w-full bg-indigo-600 text-white py-3 rounded-lg font-medium hover:bg-indigo-700 transition">
Update Status
</button>
</form>
<!-- Reschedule -->
<form method="post">
{% csrf_token %}
<input type="hidden" name="appointment_id" value="{{ apt.id }}">
<input type="hidden" name="action" value="reschedule">
<label class="block text-sm font-semibold text-gray-700 mb-2">Reschedule Appointment</label>
<div class="grid grid-cols-2 gap-4 mb-4">
<input type="date" name="new_date"
value="{{ apt.appointment_datetime|date:'Y-m-d' }}"
class="border border-gray-300 rounded-lg px-4 py-3" required>
<input type="time" name="new_time"
value="{{ apt.appointment_datetime|time:'H:i' }}"
class="border border-gray-300 rounded-lg px-4 py-3" required>
</div>
<button type="submit" class="w-full bg-green-600 text-white py-3 rounded-lg font-medium hover:bg-green-700 transition">
Reschedule Appointment
</button>
</form>
<!-- Notes (Read-only) -->
{% if apt.notes %}
<div class="mt-8 p-4 bg-gray-50 rounded-lg">
<p class="text-sm font-medium text-gray-700">User Notes:</p>
<p class="text-sm text-gray-600 mt-1">{{ apt.notes }}</p>
</div>
{% endif %}
</div>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="p-16 text-center">
<p class="text-xl text-gray-500">No appointment requests yet.</p>
</div>
{% endif %}
</div>
</div>
<!-- Modal Scripts -->
<script>
function openModal(id) {
document.getElementById(id).classList.remove('hidden');
document.getElementById(id).classList.add('flex');
}
function closeModal(id) {
document.getElementById(id).classList.add('hidden');
document.getElementById(id).classList.remove('flex');
}
// Close modal when clicking outside
window.addEventListener('click', function(e) {
document.querySelectorAll('[id^="modal-"]').forEach(modal => {
if (e.target === modal) {
modal.classList.add('hidden');
modal.classList.remove('flex');
}
});
});
</script>
<style>
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fadeIn { animation: fadeIn 0.3s ease-out; }
</style>
{% endblock %}

View File

@@ -0,0 +1,239 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<!-- Back -->
<div class="mb-6">
<a href="{% url 'admin_dashboard' %}"
class="inline-block px-4 py-2 border-2 border-gray-800 rounded-xl font-bold text-gray-800 hover:text-gray-900 hover:border-gray-900">
&larr; Admin Dashboard
</a>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-8">
<h1 class="text-3xl font-bold mb-6 text-gray-800 text-center">All Contacts</h1>
<!-- ================= FILTER ================= -->
<!-- ================= FILTER ================= -->
<form method="get" class="mb-6 flex flex-col sm:flex-row gap-3">
<!-- Search -->
<input
type="text"
name="search"
value="{{ request.GET.search }}"
placeholder="Search by name, email, or message..."
class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring"
>
<!-- Junk Filter -->
<select
name="status"
class="px-4 py-2 border rounded-lg focus:outline-none focus:ring">
<option value="">All Messages</option>
<option value="inbox"
{% if request.GET.status == "inbox" %}selected{% endif %}>
Inbox
</option>
<option value="junk"
{% if request.GET.status == "junk" %}selected{% endif %}>
Junk
</option>
</select>
<button class="px-6 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">
Apply
</button>
</form>
<!-- ================= TABLE ================= -->
<div class="overflow-x-auto shadow-lg rounded-lg">
<table class="min-w-[700px] w-full table-auto bg-white divide-y divide-gray-200">
<thead class="bg-indigo-600">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase">Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase">Message</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase">Date</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for c in contacts %}
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 font-medium">
{{ c.name }}
</td>
<td class="px-6 py-4 text-gray-600">
{{ c.email }}
</td>
<td class="px-6 py-4 max-w-xs truncate text-gray-700">
{{ c.message }}
</td>
<td class="px-6 py-4 text-gray-500 whitespace-nowrap">
{{ c.created_at|date:"M d, Y H:i" }}
</td>
<!-- ================= ACTIONS ================= -->
<td class="px-6 py-4 flex gap-3">
<!-- View -->
<button
onclick="openModal(
'{{ c.name }}',
'{{ c.email }}',
'{{ c.message|escapejs }}',
'{{ c.created_at|date:"M d, Y H:i" }}'
)"
class="text-indigo-600 hover:text-indigo-800"
title="View">
<i class="fas fa-eye"></i>
</button>
<!-- Junk -->
{% if not c.is_junk %}
<form method="post"
action="{% url 'set_message_to_junk' c.id %}"
onsubmit="return confirmJunk('{{ c.email }}', '{{ c.phone|default:'' }}')">
{% csrf_token %}
<button
type="submit"
class="text-red-600 hover:text-red-800"
title="Mark as junk">
<i class="fas fa-trash"></i>
</button>
</form>
{% else %}
<span class="text-xs text-gray-400">Junk</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr>
<td colspan="5" class="text-center py-10 text-gray-400">
No contacts found.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- ================= PAGINATION ================= -->
{% if is_paginated %}
<div class="mt-6 flex justify-center items-center gap-2">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}&search={{ request.GET.search }}&status={{ request.GET.status }}"
class="px-3 py-1 border rounded hover:bg-gray-100">
Prev
</a>
{% endif %}
<span class="px-3 py-1 bg-indigo-600 text-white rounded">
{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}&search={{ request.GET.search }}&status={{ request.GET.status }}"
class="px-3 py-1 border rounded hover:bg-gray-100">
Next
</a>
{% endif %}
</div>
{% endif %}
</div>
<!-- ================= MODAL ================= -->
<div id="contactModal"
class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center z-50">
<div class="bg-white rounded-xl shadow-2xl max-w-lg w-full p-6 relative">
<button onclick="closeModal()"
class="absolute top-3 right-3 text-gray-400 hover:text-gray-700 text-2xl">
&times;
</button>
<h2 class="text-2xl font-bold mb-4 text-gray-800">Contact Details</h2>
<div class="space-y-4">
<p><strong>Name:</strong> <span id="modalName"></span></p>
<p><strong>Email:</strong> <span id="modalEmail"></span></p>
<p><strong>Date:</strong> <span id="modalDate"></span></p>
<div>
<p class="font-semibold mb-1">Message</p>
<div id="modalMessage"
class="bg-gray-100 p-3 rounded-lg text-sm whitespace-pre-wrap">
</div>
</div>
</div>
<div class="mt-6 text-right">
<button onclick="closeModal()"
class="px-4 py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700">
Close
</button>
</div>
</div>
</div>
<!-- ================= JS ================= -->
<script>
function confirmJunk(email, phone) {
let message = "Set ALL messages";
if (email) {
message += " from email: " + email;
}
if (phone) {
message += email ? " and phone: " + phone : " from phone: " + phone;
}
message += " to junk?\n\nThis will also add the user to the spam list.";
return confirm(message);
}
</script>
<script>
function openModal(name, email, message, date) {
document.getElementById("modalName").innerText = name;
document.getElementById("modalEmail").innerText = email;
document.getElementById("modalMessage").innerText = message;
document.getElementById("modalDate").innerText = date;
document.getElementById("contactModal").classList.remove("hidden");
}
function closeModal() {
document.getElementById("contactModal").classList.add("hidden");
}
</script>
{% endblock %}

View File

@@ -0,0 +1,22 @@
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
{% load static %}
<h2 class="mb-4">SEO Configuration</h2>
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }}">{{ message }}</div>
{% endfor %}
{% endif %}
<form method="post" class="card p-4 shadow-sm">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Save</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,60 @@
{% extends 'base.html' %}
{% block content %}
{% load crispy_forms_tags %}
<div class="mb-6">
<a href="{% url 'admin_dashboard' %}"
class="inline-block px-4 py-2 border-2 border-gray-800 rounded-xl font-bold text-gray-800 hover:text-gray-900 hover:border-gray-900 transition-colors">
&larr; Admin Dashboard
</a>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mt-8">
<h2 class="text-3xl font-bold mb-6 text-gray-800 text-center">Subscriber List</h2>
<div class="overflow-x-auto shadow-lg rounded-lg">
<table class="min-w-full bg-white divide-y divide-gray-200">
<thead class="bg-indigo-600">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">Email</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">Subscribed At</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-white uppercase tracking-wider">Action</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
{% for subscriber in subscribers %}
<tr class="hover:bg-gray-50 transition-colors">
<td class="px-6 py-4 whitespace-nowrap text-gray-800 font-medium">{{ subscriber.id }}</td>
<td class="px-6 py-4 whitespace-nowrap text-gray-700">{{ subscriber.email }}</td>
<td class="px-6 py-4 whitespace-nowrap text-gray-600">{{ subscriber.subscribed_at|date:"M d, Y H:i" }}</td>
<td class="px-6 py-4 whitespace-nowrap">
{% if subscriber.is_active %}
<span class="text-green-600 font-semibold">✅ Active</span>
{% else %}
<span class="text-red-600 font-semibold">❌ Inactive</span>
{% endif %}
</td>
<td class="px-6 py-4 whitespace-nowrap">
<a href="{% url 'toggle_subscriber' subscriber.id %}"
class="text-indigo-600 hover:text-indigo-800 font-medium">
{% if subscriber.is_active %}Deactivate{% else %}Activate{% endif %}
</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="6" class="text-center py-8 text-gray-400">No subscribers yet.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}