Initial commit

This commit is contained in:
2025-05-17 00:57:08 +02:00
commit fe69bdbc94
71 changed files with 6585 additions and 0 deletions

View File

@ -0,0 +1,253 @@
/**
* dashboard.js - JavaScript for the dashboard functionality
*
* This file handles the interactive features of the dashboard,
* including chart refreshing, dashboard filtering, and dashboard
* customization.
*/
document.addEventListener("DOMContentLoaded", function () {
// Chart responsiveness
function resizeCharts() {
const charts = document.querySelectorAll(".chart-container");
charts.forEach((chart) => {
if (chart.id && window.Plotly) {
Plotly.relayout(chart.id, {
"xaxis.automargin": true,
"yaxis.automargin": true,
});
}
});
}
// Handle window resize
window.addEventListener("resize", function () {
if (window.Plotly) {
resizeCharts();
}
});
// Time range filtering
const timeRangeDropdown = document.getElementById("timeRangeDropdown");
if (timeRangeDropdown) {
const timeRangeLinks = timeRangeDropdown.querySelectorAll(".dropdown-item");
timeRangeLinks.forEach((link) => {
link.addEventListener("click", function (e) {
const url = new URL(this.href);
const dashboardId = url.searchParams.get("dashboard_id");
const timeRange = url.searchParams.get("time_range");
// Fetch updated data via AJAX
if (dashboardId) {
fetchDashboardData(dashboardId, timeRange);
e.preventDefault();
}
});
});
}
// Function to fetch dashboard data
function fetchDashboardData(dashboardId, timeRange) {
const loadingOverlay = document.createElement("div");
loadingOverlay.className = "loading-overlay";
loadingOverlay.innerHTML =
'<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>';
document.querySelector("main").appendChild(loadingOverlay);
fetch(`/dashboard/api/dashboard/${dashboardId}/data/?time_range=${timeRange || "all"}`)
.then((response) => response.json())
.then((data) => {
updateDashboardStats(data);
updateDashboardCharts(data);
// Update URL without page reload
const url = new URL(window.location.href);
url.searchParams.set("dashboard_id", dashboardId);
if (timeRange) {
url.searchParams.set("time_range", timeRange);
}
window.history.pushState({}, "", url);
document.querySelector(".loading-overlay").remove();
})
.catch((error) => {
console.error("Error fetching dashboard data:", error);
document.querySelector(".loading-overlay").remove();
// Show error message
const alertElement = document.createElement("div");
alertElement.className = "alert alert-danger alert-dismissible fade show";
alertElement.setAttribute("role", "alert");
alertElement.innerHTML = `
Error loading dashboard data. Please try again.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
document.querySelector("main").prepend(alertElement);
});
}
// Function to update dashboard statistics
function updateDashboardStats(data) {
// Update total sessions
const totalSessionsElement = document.querySelector(".stats-card:nth-child(1) h3");
if (totalSessionsElement) {
totalSessionsElement.textContent = data.total_sessions;
}
// Update average response time
const avgResponseTimeElement = document.querySelector(".stats-card:nth-child(2) h3");
if (avgResponseTimeElement) {
avgResponseTimeElement.textContent = data.avg_response_time + "s";
}
// Update total tokens
const totalTokensElement = document.querySelector(".stats-card:nth-child(3) h3");
if (totalTokensElement) {
totalTokensElement.textContent = data.total_tokens;
}
// Update total cost
const totalCostElement = document.querySelector(".stats-card:nth-child(4) h3");
if (totalCostElement) {
totalCostElement.textContent = "€" + data.total_cost;
}
}
// Function to update dashboard charts
function updateDashboardCharts(data) {
// Update sessions over time chart
const timeSeriesData = data.time_series_data;
if (timeSeriesData && timeSeriesData.length > 0 && window.Plotly) {
const timeSeriesX = timeSeriesData.map((item) => item.date);
const timeSeriesY = timeSeriesData.map((item) => item.count);
Plotly.react(
"sessions-time-chart",
[
{
x: timeSeriesX,
y: timeSeriesY,
type: "scatter",
mode: "lines+markers",
line: {
color: "rgb(75, 192, 192)",
width: 2,
},
marker: {
color: "rgb(75, 192, 192)",
size: 6,
},
},
],
{
margin: { t: 10, r: 10, b: 40, l: 40 },
xaxis: {
title: "Date",
},
yaxis: {
title: "Number of Sessions",
},
}
);
}
// Update sentiment chart
const sentimentData = data.sentiment_data;
if (sentimentData && sentimentData.length > 0 && window.Plotly) {
const sentimentLabels = sentimentData.map((item) => item.sentiment);
const sentimentValues = sentimentData.map((item) => item.count);
const sentimentColors = sentimentLabels.map((sentiment) => {
if (sentiment.toLowerCase().includes("positive")) return "rgb(75, 192, 92)";
if (sentiment.toLowerCase().includes("negative")) return "rgb(255, 99, 132)";
if (sentiment.toLowerCase().includes("neutral")) return "rgb(255, 205, 86)";
return "rgb(201, 203, 207)";
});
Plotly.react(
"sentiment-chart",
[
{
values: sentimentValues,
labels: sentimentLabels,
type: "pie",
marker: {
colors: sentimentColors,
},
hole: 0.4,
textinfo: "label+percent",
insidetextorientation: "radial",
},
],
{
margin: { t: 10, r: 10, b: 10, l: 10 },
}
);
}
// Update country chart
const countryData = data.country_data;
if (countryData && countryData.length > 0 && window.Plotly) {
const countryLabels = countryData.map((item) => item.country);
const countryValues = countryData.map((item) => item.count);
Plotly.react(
"country-chart",
[
{
x: countryValues,
y: countryLabels,
type: "bar",
orientation: "h",
marker: {
color: "rgb(54, 162, 235)",
},
},
],
{
margin: { t: 10, r: 10, b: 40, l: 100 },
xaxis: {
title: "Number of Sessions",
},
}
);
}
// Update category chart
const categoryData = data.category_data;
if (categoryData && categoryData.length > 0 && window.Plotly) {
const categoryLabels = categoryData.map((item) => item.category);
const categoryValues = categoryData.map((item) => item.count);
Plotly.react(
"category-chart",
[
{
labels: categoryLabels,
values: categoryValues,
type: "pie",
textinfo: "label+percent",
insidetextorientation: "radial",
},
],
{
margin: { t: 10, r: 10, b: 10, l: 10 },
}
);
}
}
// Dashboard selector
const dashboardSelector = document.querySelectorAll('a[href^="?dashboard_id="]');
dashboardSelector.forEach((link) => {
link.addEventListener("click", function (e) {
const url = new URL(this.href);
const dashboardId = url.searchParams.get("dashboard_id");
// Fetch updated data via AJAX
if (dashboardId) {
fetchDashboardData(dashboardId);
e.preventDefault();
}
});
});
});

View File

@ -0,0 +1,152 @@
/**
* main.js - Global JavaScript functionality
*
* This file contains general JavaScript functionality used across
* the entire application, including navigation, forms, and UI interactions.
*/
document.addEventListener("DOMContentLoaded", function () {
// Initialize tooltips
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Initialize popovers
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl);
});
// Toggle sidebar on mobile
const sidebarToggle = document.querySelector("#sidebarToggle");
if (sidebarToggle) {
sidebarToggle.addEventListener("click", function () {
document.querySelector(".sidebar").classList.toggle("show");
});
}
// Auto-dismiss alerts after 5 seconds
setTimeout(function () {
var alerts = document.querySelectorAll(".alert:not(.alert-important)");
alerts.forEach(function (alert) {
if (alert && bootstrap.Alert.getInstance(alert)) {
bootstrap.Alert.getInstance(alert).close();
}
});
}, 5000);
// Form validation
const forms = document.querySelectorAll(".needs-validation");
forms.forEach(function (form) {
form.addEventListener(
"submit",
function (event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
}
form.classList.add("was-validated");
},
false
);
});
// Confirm dialogs
const confirmButtons = document.querySelectorAll("[data-confirm]");
confirmButtons.forEach(function (button) {
button.addEventListener("click", function (event) {
if (!confirm(this.dataset.confirm || "Are you sure?")) {
event.preventDefault();
}
});
});
// Back button
const backButtons = document.querySelectorAll(".btn-back");
backButtons.forEach(function (button) {
button.addEventListener("click", function (event) {
event.preventDefault();
window.history.back();
});
});
// File input customization
const fileInputs = document.querySelectorAll(".custom-file-input");
fileInputs.forEach(function (input) {
input.addEventListener("change", function (e) {
const fileName = this.files[0]?.name || "Choose file";
const nextSibling = this.nextElementSibling;
if (nextSibling) {
nextSibling.innerText = fileName;
}
});
});
// Search form submit on enter
const searchInputs = document.querySelectorAll(".search-input");
searchInputs.forEach(function (input) {
input.addEventListener("keypress", function (e) {
if (e.key === "Enter") {
e.preventDefault();
this.closest("form").submit();
}
});
});
// Toggle password visibility
const togglePasswordButtons = document.querySelectorAll(".toggle-password");
togglePasswordButtons.forEach(function (button) {
button.addEventListener("click", function () {
const target = document.querySelector(this.dataset.target);
if (target) {
const type = target.getAttribute("type") === "password" ? "text" : "password";
target.setAttribute("type", type);
this.querySelector("i").classList.toggle("fa-eye");
this.querySelector("i").classList.toggle("fa-eye-slash");
}
});
});
// Dropdown menu positioning
const dropdowns = document.querySelectorAll(".dropdown-menu");
dropdowns.forEach(function (dropdown) {
dropdown.addEventListener("click", function (e) {
e.stopPropagation();
});
});
// Responsive table handling
const tables = document.querySelectorAll(".table-responsive");
if (window.innerWidth < 768) {
tables.forEach(function (table) {
table.classList.add("table-responsive-force");
});
}
// Handle special links (printable views, exports)
const printLinks = document.querySelectorAll(".print-link");
printLinks.forEach(function (link) {
link.addEventListener("click", function (e) {
e.preventDefault();
window.print();
});
});
const exportLinks = document.querySelectorAll("[data-export]");
exportLinks.forEach(function (link) {
link.addEventListener("click", function (e) {
// Handle export functionality if needed
console.log("Export requested:", this.dataset.export);
});
});
// Handle sidebar collapse on small screens
function handleSidebarOnResize() {
if (window.innerWidth < 768) {
document.querySelector(".sidebar")?.classList.remove("show");
}
}
window.addEventListener("resize", handleSidebarOnResize);
});