v1
This commit is contained in:
437
templates/admin_submissions.html
Normal file
437
templates/admin_submissions.html
Normal file
@@ -0,0 +1,437 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Contact Submissions</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.nav {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.nav a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.nav a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.filters {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.filters form {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: end;
|
||||
}
|
||||
.filters label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.filters input,
|
||||
.filters select {
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.filters button {
|
||||
padding: 8px 15px;
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.filters button:hover {
|
||||
background: #0056b3;
|
||||
}
|
||||
.filters .clear-btn {
|
||||
background: #6c757d;
|
||||
}
|
||||
.filters .clear-btn:hover {
|
||||
background: #545b62;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
th,
|
||||
td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
th:hover {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
th.sort-asc::after {
|
||||
content: " ↑";
|
||||
}
|
||||
th.sort-desc::after {
|
||||
content: " ↓";
|
||||
}
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.message {
|
||||
padding: 8px;
|
||||
margin: 10px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.message.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.message.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
.pagination {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.pagination button {
|
||||
padding: 8px 12px;
|
||||
margin: 0 2px;
|
||||
border: 1px solid #ddd;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
.pagination button:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
.pagination button.active {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border-color: #007bff;
|
||||
}
|
||||
.pagination button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.delete-btn {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.delete-btn:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
}
|
||||
.submission-details {
|
||||
max-width: 300px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="nav">
|
||||
<a href="/admin/">Dashboard</a>
|
||||
<a href="/admin/settings">Settings</a>
|
||||
<a href="{{ url_for('auth.logout') }}">Logout</a>
|
||||
</div>
|
||||
|
||||
<h1>Contact Form Submissions</h1>
|
||||
|
||||
<div id="message"></div>
|
||||
|
||||
<div class="filters">
|
||||
<form id="filterForm">
|
||||
<div>
|
||||
<label for="email">Email Filter:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="Filter by email"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="date_from">Date From:</label>
|
||||
<input type="date" id="date_from" name="date_from" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="date_to">Date To:</label>
|
||||
<input type="date" id="date_to" name="date_to" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="per_page">Items per page:</label>
|
||||
<select id="per_page" name="per_page">
|
||||
<option value="25">25</option>
|
||||
<option value="50" selected>50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit">Apply Filters</button>
|
||||
<button type="button" class="clear-btn" onclick="clearFilters()">
|
||||
Clear
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="loading" class="loading" style="display: none">Loading...</div>
|
||||
|
||||
<table id="submissionsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-sort="id">ID</th>
|
||||
<th data-sort="name">Name</th>
|
||||
<th data-sort="email">Email</th>
|
||||
<th data-sort="company">Company</th>
|
||||
<th>Message</th>
|
||||
<th data-sort="created_at">Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="submissionsBody">
|
||||
<tr>
|
||||
<td colspan="7" class="no-data">Loading submissions...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination" id="pagination"></div>
|
||||
|
||||
<script>
|
||||
let currentPage = 1;
|
||||
let currentSortBy = "created_at";
|
||||
let currentSortOrder = "desc";
|
||||
|
||||
// Load submissions on page load
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
loadSubmissions();
|
||||
});
|
||||
|
||||
// Handle filter form submission
|
||||
document
|
||||
.getElementById("filterForm")
|
||||
.addEventListener("submit", function (e) {
|
||||
e.preventDefault();
|
||||
currentPage = 1;
|
||||
loadSubmissions();
|
||||
});
|
||||
|
||||
// Handle table header sorting
|
||||
document.querySelectorAll("th[data-sort]").forEach((header) => {
|
||||
header.addEventListener("click", function () {
|
||||
const sortBy = this.dataset.sort;
|
||||
if (currentSortBy === sortBy) {
|
||||
currentSortOrder = currentSortOrder === "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
currentSortBy = sortBy;
|
||||
currentSortOrder = "asc";
|
||||
}
|
||||
currentPage = 1;
|
||||
loadSubmissions();
|
||||
});
|
||||
});
|
||||
|
||||
function clearFilters() {
|
||||
document.getElementById("email").value = "";
|
||||
document.getElementById("date_from").value = "";
|
||||
document.getElementById("date_to").value = "";
|
||||
document.getElementById("per_page").value = "50";
|
||||
currentPage = 1;
|
||||
currentSortBy = "created_at";
|
||||
currentSortOrder = "desc";
|
||||
loadSubmissions();
|
||||
}
|
||||
|
||||
function loadSubmissions() {
|
||||
const loading = document.getElementById("loading");
|
||||
const table = document.getElementById("submissionsTable");
|
||||
|
||||
loading.style.display = "block";
|
||||
table.style.opacity = "0.5";
|
||||
|
||||
const params = new URLSearchParams({
|
||||
page: currentPage,
|
||||
per_page: document.getElementById("per_page").value,
|
||||
sort_by: currentSortBy,
|
||||
sort_order: currentSortOrder,
|
||||
email: document.getElementById("email").value,
|
||||
date_from: document.getElementById("date_from").value,
|
||||
date_to: document.getElementById("date_to").value,
|
||||
});
|
||||
|
||||
fetch(`/api/contact?${params}`)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.status === "ok") {
|
||||
displaySubmissions(data.submissions);
|
||||
displayPagination(data.pagination);
|
||||
} else {
|
||||
showMessage(
|
||||
"Error loading submissions: " +
|
||||
(data.message || "Unknown error"),
|
||||
"error"
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
showMessage("Error loading submissions", "error");
|
||||
})
|
||||
.finally(() => {
|
||||
loading.style.display = "none";
|
||||
table.style.opacity = "1";
|
||||
});
|
||||
}
|
||||
|
||||
function displaySubmissions(submissions) {
|
||||
const tbody = document.getElementById("submissionsBody");
|
||||
|
||||
if (submissions.length === 0) {
|
||||
tbody.innerHTML =
|
||||
'<tr><td colspan="7" class="no-data">No submissions found</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = submissions
|
||||
.map(
|
||||
(submission) => `
|
||||
<tr>
|
||||
<td>${submission.id}</td>
|
||||
<td>${escapeHtml(submission.name)}</td>
|
||||
<td>${escapeHtml(submission.email)}</td>
|
||||
<td>${escapeHtml(submission.company || "")}</td>
|
||||
<td class="submission-details">${escapeHtml(
|
||||
submission.message
|
||||
)}</td>
|
||||
<td>${new Date(submission.created_at).toLocaleString()}</td>
|
||||
<td><button class="delete-btn" onclick="deleteSubmission(${
|
||||
submission.id
|
||||
})">Delete</button></td>
|
||||
</tr>
|
||||
`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function displayPagination(pagination) {
|
||||
const paginationDiv = document.getElementById("pagination");
|
||||
|
||||
if (pagination.pages <= 1) {
|
||||
paginationDiv.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
|
||||
let buttons = [];
|
||||
|
||||
// Previous button
|
||||
buttons.push(
|
||||
`<button ${
|
||||
pagination.page <= 1 ? "disabled" : ""
|
||||
} onclick="changePage(${pagination.page - 1})">Previous</button>`
|
||||
);
|
||||
|
||||
// Page numbers
|
||||
const startPage = Math.max(1, pagination.page - 2);
|
||||
const endPage = Math.min(pagination.pages, pagination.page + 2);
|
||||
|
||||
for (let i = startPage; i <= endPage; i++) {
|
||||
buttons.push(
|
||||
`<button class="${
|
||||
i === pagination.page ? "active" : ""
|
||||
}" onclick="changePage(${i})">${i}</button>`
|
||||
);
|
||||
}
|
||||
|
||||
// Next button
|
||||
buttons.push(
|
||||
`<button ${
|
||||
pagination.page >= pagination.pages ? "disabled" : ""
|
||||
} onclick="changePage(${pagination.page + 1})">Next</button>`
|
||||
);
|
||||
|
||||
paginationDiv.innerHTML = buttons.join("");
|
||||
}
|
||||
|
||||
function changePage(page) {
|
||||
currentPage = page;
|
||||
loadSubmissions();
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
function deleteSubmission(id) {
|
||||
if (!confirm("Are you sure you want to delete this submission?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/api/contact/${id}`, {
|
||||
method: "DELETE",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.status === "ok") {
|
||||
showMessage("Submission deleted successfully", "success");
|
||||
loadSubmissions(); // Reload the current page
|
||||
} else {
|
||||
showMessage(
|
||||
"Error deleting submission: " +
|
||||
(data.message || "Unknown error"),
|
||||
"error"
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error:", error);
|
||||
showMessage("Error deleting submission", "error");
|
||||
});
|
||||
}
|
||||
|
||||
function showMessage(text, type) {
|
||||
const messageDiv = document.getElementById("message");
|
||||
messageDiv.className = `message ${type}`;
|
||||
messageDiv.textContent = text;
|
||||
messageDiv.style.display = "block";
|
||||
|
||||
// Auto-hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
messageDiv.style.display = "none";
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user