Update dashboard
This commit is contained in:
+526
-392
File diff suppressed because it is too large
Load Diff
+2
-402
@@ -1463,406 +1463,6 @@ Management{% endblock %} {% block extra_head %}
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %} {% block extra_scripts %}
|
||||||
<script>
|
<script src="{{ url_for('static', filename='js/dashboard.js') }}"></script>
|
||||||
class ProjectDashboardManager {
|
|
||||||
constructor() {
|
|
||||||
this.expandedProjects = new Set();
|
|
||||||
this.currentModalQR = null;
|
|
||||||
this.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
const saved = localStorage.getItem("expandedProjects");
|
|
||||||
if (saved) {
|
|
||||||
this.expandedProjects = new Set(JSON.parse(saved));
|
|
||||||
this.restoreProjectStates();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
restoreProjectStates() {
|
|
||||||
this.expandedProjects.forEach((projectId) => {
|
|
||||||
this.expandProject(projectId, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleProject(projectId) {
|
|
||||||
const isExpanded = this.expandedProjects.has(projectId);
|
|
||||||
|
|
||||||
if (isExpanded) {
|
|
||||||
this.collapseProject(projectId);
|
|
||||||
} else {
|
|
||||||
this.expandProject(projectId);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveExpandedState();
|
|
||||||
}
|
|
||||||
|
|
||||||
expandProject(projectId, animate = true) {
|
|
||||||
const projectQR = document.getElementById(`project-qr-${projectId}`);
|
|
||||||
const toggle = document.getElementById(`toggle-${projectId}`);
|
|
||||||
const header = toggle?.closest(".project-header");
|
|
||||||
|
|
||||||
if (projectQR && toggle) {
|
|
||||||
projectQR.classList.add("expanded");
|
|
||||||
toggle.classList.add("expanded");
|
|
||||||
header?.classList.add("expanded");
|
|
||||||
this.expandedProjects.add(projectId);
|
|
||||||
|
|
||||||
if (animate) {
|
|
||||||
setTimeout(() => {
|
|
||||||
projectQR.scrollIntoView({
|
|
||||||
behavior: "smooth",
|
|
||||||
block: "nearest",
|
|
||||||
});
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collapseProject(projectId) {
|
|
||||||
const projectQR = document.getElementById(`project-qr-${projectId}`);
|
|
||||||
const toggle = document.getElementById(`toggle-${projectId}`);
|
|
||||||
const header = toggle?.closest(".project-header");
|
|
||||||
|
|
||||||
if (projectQR && toggle) {
|
|
||||||
projectQR.classList.remove("expanded");
|
|
||||||
toggle.classList.remove("expanded");
|
|
||||||
header?.classList.remove("expanded");
|
|
||||||
this.expandedProjects.delete(projectId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveExpandedState() {
|
|
||||||
localStorage.setItem(
|
|
||||||
"expandedProjects",
|
|
||||||
JSON.stringify([...this.expandedProjects])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async toggleQRCodeStatus(qrId) {
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/qr-codes/${qrId}/toggle-status`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const result = await response.json();
|
|
||||||
if (result.success) {
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast(result.message, "success");
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
throw new Error(
|
|
||||||
result.message || "Failed to toggle QR code status"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error("Failed to toggle QR code status");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Toggle status failed:", error);
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast("Failed to update QR code status", "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openQRModalFromData(element) {
|
|
||||||
const qrData = {
|
|
||||||
name: element.dataset.qrName,
|
|
||||||
image: element.querySelector("img").src,
|
|
||||||
location: element.dataset.qrLocation,
|
|
||||||
address: element.dataset.qrAddress,
|
|
||||||
event: element.dataset.qrEvent,
|
|
||||||
qr_url: element.dataset.qrUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.openQRModal(qrData);
|
|
||||||
}
|
|
||||||
|
|
||||||
openQRModal(qrData) {
|
|
||||||
const modal = document.getElementById("qrModal");
|
|
||||||
const modalImage = document.getElementById("modalQRImage");
|
|
||||||
const modalTitle = document.getElementById("modalTitle");
|
|
||||||
|
|
||||||
const modalQRName = document.getElementById("modalQRName");
|
|
||||||
const modalQRLocation = document.getElementById("modalQRLocation");
|
|
||||||
const modalQRAddress = document.getElementById("modalQRAddress");
|
|
||||||
const modalQREvent = document.getElementById("modalQREvent");
|
|
||||||
const modalQRDestination = document.getElementById("modalQRDestination");
|
|
||||||
|
|
||||||
if (modal && modalImage && modalTitle) {
|
|
||||||
modalTitle.textContent = `QR Code: ${qrData.name}`;
|
|
||||||
modalImage.src = qrData.image;
|
|
||||||
modalImage.alt = `QR Code for ${qrData.name}`;
|
|
||||||
|
|
||||||
if (modalQRName) modalQRName.textContent = qrData.name || "-";
|
|
||||||
if (modalQRLocation)
|
|
||||||
modalQRLocation.textContent = qrData.location || "-";
|
|
||||||
if (modalQRAddress) modalQRAddress.textContent = qrData.address || "-";
|
|
||||||
if (modalQREvent)
|
|
||||||
modalQREvent.textContent = qrData.event || "No event specified";
|
|
||||||
|
|
||||||
if (modalQRDestination && qrData.qr_url) {
|
|
||||||
const destinationUrl = `${window.location.origin}/qr/${qrData.qr_url}`;
|
|
||||||
const linkElement = modalQRDestination.querySelector("a");
|
|
||||||
if (linkElement) {
|
|
||||||
linkElement.href = destinationUrl;
|
|
||||||
linkElement.innerHTML = `
|
|
||||||
<i class="fas fa-external-link-alt"></i>
|
|
||||||
${destinationUrl}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
} else if (modalQRDestination) {
|
|
||||||
modalQRDestination.innerHTML =
|
|
||||||
'<span style="color: var(--gray-500); font-style: italic;">No destination URL available</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
this.currentModalQR = {
|
|
||||||
name: qrData.name,
|
|
||||||
image: qrData.image,
|
|
||||||
location: qrData.location,
|
|
||||||
address: qrData.address,
|
|
||||||
event: qrData.event,
|
|
||||||
qr_url: qrData.qr_url,
|
|
||||||
destination_url: qrData.qr_url
|
|
||||||
? `${window.location.origin}/qr/${qrData.qr_url}`
|
|
||||||
: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
modal.style.display = "flex";
|
|
||||||
|
|
||||||
document.addEventListener(
|
|
||||||
"keydown",
|
|
||||||
this.handleModalKeydown.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeQRModal() {
|
|
||||||
const modal = document.getElementById("qrModal");
|
|
||||||
if (modal) {
|
|
||||||
modal.style.display = "none";
|
|
||||||
this.currentModalQR = null;
|
|
||||||
|
|
||||||
document.removeEventListener(
|
|
||||||
"keydown",
|
|
||||||
this.handleModalKeydown.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleModalKeydown(event) {
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
this.closeQRModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadQRFromCard(button) {
|
|
||||||
const qrCard = button.closest(".qr-card");
|
|
||||||
const img = qrCard.querySelector("img");
|
|
||||||
const qrName = qrCard.dataset.qrName;
|
|
||||||
|
|
||||||
if (img && img.src) {
|
|
||||||
const base64Data = img.src.split("base64,")[1];
|
|
||||||
this.downloadQR(base64Data, qrName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadQR(base64Image, filename) {
|
|
||||||
try {
|
|
||||||
const base64Data = base64Image.includes("base64,")
|
|
||||||
? base64Image.split("base64,")[1]
|
|
||||||
: base64Image;
|
|
||||||
|
|
||||||
const link = document.createElement("a");
|
|
||||||
link.href = `data:image/png;base64,${base64Data}`;
|
|
||||||
link.download = `${filename
|
|
||||||
.replace(/[^a-z0-9]/gi, "_")
|
|
||||||
.toLowerCase()}_qr_code.png`;
|
|
||||||
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast("QR code downloaded successfully!", "success");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Download error:", error);
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast("Failed to download QR code", "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadModalQR() {
|
|
||||||
if (this.currentModalQR) {
|
|
||||||
const base64Data = this.currentModalQR.image.split("base64,")[1];
|
|
||||||
this.downloadQR(base64Data, this.currentModalQR.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copyModalQRData() {
|
|
||||||
if (this.currentModalQR) {
|
|
||||||
const data = `QR Code: ${this.currentModalQR.name}\nLocation: ${
|
|
||||||
this.currentModalQR.location
|
|
||||||
}\nAddress: ${this.currentModalQR.address}\nEvent: ${
|
|
||||||
this.currentModalQR.event
|
|
||||||
}${
|
|
||||||
this.currentModalQR.destination_url
|
|
||||||
? `\nQR Link: ${this.currentModalQR.destination_url}`
|
|
||||||
: ""
|
|
||||||
}`;
|
|
||||||
|
|
||||||
navigator.clipboard
|
|
||||||
.writeText(data)
|
|
||||||
.then(() => {
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast(
|
|
||||||
"QR code information copied to clipboard!",
|
|
||||||
"success"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
const textArea = document.createElement("textarea");
|
|
||||||
textArea.value = data;
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.select();
|
|
||||||
try {
|
|
||||||
document.execCommand("copy");
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast(
|
|
||||||
"QR code information copied to clipboard!",
|
|
||||||
"success"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast("Failed to copy to clipboard", "error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copyQRDestination() {
|
|
||||||
if (this.currentModalQR && this.currentModalQR.destination_url) {
|
|
||||||
navigator.clipboard
|
|
||||||
.writeText(this.currentModalQR.destination_url)
|
|
||||||
.then(() => {
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast(
|
|
||||||
"QR destination link copied to clipboard!",
|
|
||||||
"success"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast("Failed to copy QR link", "error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (window.showToast) {
|
|
||||||
window.showToast("No QR destination link available", "warning");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image Lightbox Functions
|
|
||||||
openImageLightbox(previewElement, qrName) {
|
|
||||||
console.log("Opening lightbox for:", qrName); // Debug log
|
|
||||||
|
|
||||||
const img = previewElement.querySelector("img");
|
|
||||||
|
|
||||||
if (img && img.src) {
|
|
||||||
const lightbox = document.getElementById("imageLightbox");
|
|
||||||
const lightboxImage = document.getElementById("lightboxImage");
|
|
||||||
const lightboxInfo = document.getElementById("lightboxInfo");
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"Lightbox elements found:",
|
|
||||||
!!lightbox,
|
|
||||||
!!lightboxImage,
|
|
||||||
!!lightboxInfo
|
|
||||||
); // Debug log
|
|
||||||
|
|
||||||
if (lightbox && lightboxImage && lightboxInfo) {
|
|
||||||
lightboxImage.src = img.src;
|
|
||||||
lightboxImage.alt = img.alt;
|
|
||||||
lightboxInfo.textContent = `QR Code: ${qrName}`;
|
|
||||||
|
|
||||||
lightbox.style.display = "flex";
|
|
||||||
console.log("Lightbox should be visible now"); // Debug log
|
|
||||||
|
|
||||||
// Add keyboard listener for ESC key
|
|
||||||
document.addEventListener(
|
|
||||||
"keydown",
|
|
||||||
this.handleLightboxKeydown.bind(this)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.error("Lightbox elements not found");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error("Image element not found or no src");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeImageLightbox() {
|
|
||||||
console.log("Closing lightbox"); // Debug log
|
|
||||||
const lightbox = document.getElementById("imageLightbox");
|
|
||||||
if (lightbox) {
|
|
||||||
lightbox.style.display = "none";
|
|
||||||
|
|
||||||
// Remove keyboard listener
|
|
||||||
document.removeEventListener(
|
|
||||||
"keydown",
|
|
||||||
this.handleLightboxKeydown.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleLightboxKeydown(event) {
|
|
||||||
if (event.key === "Escape") {
|
|
||||||
this.closeImageLightbox();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let dashboardManager;
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
dashboardManager = new ProjectDashboardManager();
|
|
||||||
|
|
||||||
window.toggleProject = (projectId) =>
|
|
||||||
dashboardManager.toggleProject(projectId);
|
|
||||||
window.toggleQRCodeStatus = (qrId) =>
|
|
||||||
dashboardManager.toggleQRCodeStatus(qrId);
|
|
||||||
window.openQRModalFromData = (element) =>
|
|
||||||
dashboardManager.openQRModalFromData(element);
|
|
||||||
window.closeQRModal = () => dashboardManager.closeQRModal();
|
|
||||||
window.downloadModalQR = () => dashboardManager.downloadModalQR();
|
|
||||||
window.copyModalQRData = () => dashboardManager.copyModalQRData();
|
|
||||||
window.copyQRDestination = () => dashboardManager.copyQRDestination();
|
|
||||||
window.downloadQRFromCard = (button) =>
|
|
||||||
dashboardManager.downloadQRFromCard(button);
|
|
||||||
window.openImageLightbox = (element, qrName) =>
|
|
||||||
dashboardManager.openImageLightbox(element, qrName);
|
|
||||||
window.closeImageLightbox = () => dashboardManager.closeImageLightbox();
|
|
||||||
|
|
||||||
console.log("Project Dashboard initialized successfully");
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user