Refactor HTML templates for improved readability and consistency

- Updated search_results_table.html to enhance formatting and maintain consistent indentation.
- Refined search_results.html layout for better structure and clarity.
- Improved upload.html for better organization and readability of the upload form and data source table.
- Removed unnecessary lines in package.json and streamlined devDependencies section.
This commit is contained in:
2025-05-17 21:45:50 +02:00
parent 6b19cbcb51
commit e8f2d2adc2
35 changed files with 3406 additions and 3588 deletions

View File

@ -18,8 +18,8 @@ indent_size = 4
# HTML and Django/Jinja2 template files
[*.{html,htm}]
indent_style = tab
indent_size = 4
indent_size = 2
# Allow prettier to format Django/Jinja templates properly
# The following comment options can be used in individual files if needed:
# <!-- prettier-ignore -->

1
.gitignore vendored
View File

@ -407,6 +407,7 @@ pyrightconfig.json
*Zone.Identifier
examples/
**/migrations/[0-9]**.py
package-lock.json
# UV specific
.uv/

View File

@ -3,6 +3,7 @@ default_install_hook_types:
- post-checkout
- post-merge
- post-rewrite
repos:
# uv hooks for dependency management
- repo: https://github.com/astral-sh/uv-pre-commit
@ -33,18 +34,18 @@ repos:
# rev: 3.0.7
# hooks:
# - id: djhtml
# entry: djhtml --tabwidth 4 --
# - id: djcss
# - id: djjs
# entry: djhtml --tabwidth 4
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
rev: v3.1.0
hooks:
- id: prettier
types_or: [javascript, jsx, ts, tsx, css, scss, html, json, yaml, markdown]
additional_dependencies:
- prettier
- prettier-plugin-jinja-template
# types_or: [javascript, jsx, ts, tsx, css, scss, json, yaml, markdown]
# exclude: '.*\.html$'
# Ruff for linting and formatting
- repo: https://github.com/astral-sh/ruff-pre-commit

View File

@ -47,3 +47,6 @@ docker-compose.override.yml
.vscode/
*.swp
*.swo
# Ignore all SQLite3 files:
**/*.sqlite3

View File

@ -11,7 +11,7 @@
"requirePragma": false,
"semi": true,
"singleQuote": false,
"useTabs": true,
"useTabs": false,
"overrides": [
{
"files": ["*.html"],

View File

@ -1,4 +1,4 @@
.PHONY: venv install install-dev lint test format clean run migrate makemigrations superuser setup-node format-js
.PHONY: venv install install-dev lint test format clean run migrate makemigrations superuser setup-node
# Create a virtual environment
venv:
@ -25,13 +25,9 @@ format:
uv run -m ruff format dashboard_project
uv run -m black dashboard_project
# Format JavaScript/CSS/HTML files with Prettier
format-js:
npx run format
# Setup Node.js dependencies
setup-node:
npm install
npm install --include=dev
# Clean Python cache files
clean:
@ -45,6 +41,9 @@ clean:
find . -type d -name ".coverage" -exec rm -rf {} +
find . -type d -name "htmlcov" -exec rm -rf {} +
find . -type d -name ".ruff_cache" -exec rm -rf {} +
find . -type d -name ".mypy_cache" -exec rm -rf {} +
find . -type d -name ".tox" -exec rm -rf {} +
find . -type d -name "node_modules" -exec rm -rf {} +
rm -rf build/
rm -rf dist/

86
TODO.md
View File

@ -1,31 +1,57 @@
# TODO List
# LiveGraphs Project TODO
- When I zoom into the dasboard page, the graphs don't scale/adjust to fit the window until I completely refresh the page, can we solve that?
- Add export functionality to the dashboard:
- File formats:
- CSV
- Excel
- JSON
- XML
- HTML
- PDF
- Make the export button a dropdown with the following options:
- Export as CSV
- Export as Excel
- Export as JSON
- Export as XML
- Export as HTML
- Export as PDF
- Make the export data section folded by default and only show the export button.
- Adjust the downloaded file name to include the company name, date and time of the export.
- Add a button to download the CSV file for the selected company.
- Make it possible to modify the column names in the CSV file through the admin interface.
- Add possibility to add a company logo in the admin interface.
- Add periodic download from <https://proto.notso.ai/XY/chats> possibility for the XY company.
- Authentication: Basic Auth
- URL: <https://proto.notso.ai/XY/chats>
- Username: xxxx
- Password: xxxx
- Reduce amount of rows in the table to fit the screen.
- Add dark mode/theming to the dashboard.
- Add Notso AI branding to the dashboard.
## Dashboard UI Improvements
### Responsiveness
- [ ] Fix dashboard graphs scaling/adjustment when zooming (currently requires page refresh)
### Theming
- [ ] Add dark mode/light mode toggle
- [ ] Add Notso AI branding elements
- [ ] Implement responsive table design (reduce rows to fit screen)
### Data Export
- [ ] Implement multi-format export functionality
- [ ] CSV format
- [ ] Excel format
- [ ] JSON format
- [ ] XML format
- [ ] HTML format
- [ ] PDF format
- [ ] Create dropdown menu for export options
- [ ] Make export data section collapsible (folded by default)
- [ ] Add company name, date and timestamp to exported filenames
## Admin Interface Enhancements
### Company Management
- [ ] Add company logo upload functionality
- [ ] Add direct CSV download button for each company (superusers only)
- [ ] Include company name, date and timestamp in filename
- [ ] Add UI for customizing CSV column names
## Data Integration
### External Data Sources
- [ ] Implement periodic data download from external API
- [ ] Source: <https://proto.notso.ai/XY/chats>
- [ ] Authentication: Basic Auth
- [ ] Credentials: [stored securely]
- [ ] Add scheduling options for data refresh
## Technical Debt
### Performance Optimization
- [ ] Profile and optimize dashboard rendering
- [ ] Implement lazy loading for dashboard elements
### Testing
- [ ] Add unit tests for export functionality
- [ ] Add integration tests for data import process

View File

@ -91,8 +91,8 @@ AUTH_PASSWORD_VALIDATORS = [
]
# Internationalization
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
LANGUAGE_CODE = "nl"
TIME_ZONE = "Europe/Amsterdam"
USE_I18N = True
USE_TZ = True

View File

@ -1,9 +1,8 @@
<!-- templates/accounts/login.html -->
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Login | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% load crispy_forms_tags %}
{% block title %}
Login | Chat Analytics
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
@ -13,17 +12,14 @@
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
{% csrf_token %} {{ form|crispy }}
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</form>
</div>
<div class="card-footer text-center">
<p class="mb-0">
Don't have an account? <a href="{% url 'register' %}">Register</a>
</p>
<p class="mb-0">Don't have an account? <a href="{% url 'register' %}">Register</a></p>
</div>
</div>
</div>

View File

@ -1,9 +1,7 @@
<!-- templates/accounts/password_change.html -->
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Change Password | Chat Analytics{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -24,8 +22,7 @@
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
{% csrf_token %} {{ form|crispy }}
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Change Password</button>
</div>

View File

@ -1,8 +1,5 @@
<!-- templates/accounts/password_change_done.html -->
{% extends 'base.html' %}
{% block title %}Password Changed | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% block title %}Password Changed | Chat Analytics{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -25,10 +22,7 @@
<div class="mb-4">
<i class="fas fa-check-circle fa-4x text-success mb-3"></i>
<h4>Your password has been changed successfully!</h4>
<p>
Your new password is now active. You can use it the next time you log
in.
</p>
<p>Your new password is now active. You can use it the next time you log in.</p>
</div>
<div class="mt-4">

View File

@ -1,8 +1,6 @@
<!-- templates/accounts/profile.html -->
{% extends 'base.html' %}
{% block title %}My Profile | Chat Analytics{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -62,9 +60,7 @@
</div>
</div>
<div class="card-footer">
<a href="{% url 'password_change' %}" class="btn btn-primary"
>Change Password</a
>
<a href="{% url 'password_change' %}" class="btn btn-primary">Change Password</a>
</div>
</div>
</div>
@ -118,9 +114,7 @@
<div class="card h-100">
<div class="card-body text-center">
<h5 class="card-title">Manage Users</h5>
<p class="card-text">
Manage users and assign them to companies.
</p>
<p class="card-text">Manage users and assign them to companies.</p>
<a
href="{% url 'admin:accounts_customuser_changelist' %}"
class="btn btn-primary"
@ -133,9 +127,7 @@
<div class="card h-100">
<div class="card-body text-center">
<h5 class="card-title">Manage Companies</h5>
<p class="card-text">
Create and edit companies in the system.
</p>
<p class="card-text">Create and edit companies in the system.</p>
<a
href="{% url 'admin:accounts_company_changelist' %}"
class="btn btn-primary"
@ -149,11 +141,7 @@
<div class="card-body text-center">
<h5 class="card-title">Admin Dashboard</h5>
<p class="card-text">Go to the full admin dashboard.</p>
<a
href="{% url 'admin:index' %}"
class="btn btn-primary"
>Admin Dashboard</a
>
<a href="{% url 'admin:index' %}" class="btn btn-primary">Admin Dashboard</a>
</div>
</div>
</div>
@ -162,12 +150,8 @@
<div class="card h-100">
<div class="card-body text-center">
<h5 class="card-title">Manage Dashboards</h5>
<p class="card-text">
Create and edit dashboards for your company.
</p>
<a
href="{% url 'create_dashboard' %}"
class="btn btn-primary"
<p class="card-text">Create and edit dashboards for your company.</p>
<a href="{% url 'create_dashboard' %}" class="btn btn-primary"
>Manage Dashboards</a
>
</div>
@ -177,14 +161,8 @@
<div class="card h-100">
<div class="card-body text-center">
<h5 class="card-title">Upload Data</h5>
<p class="card-text">
Upload and manage data sources for analysis.
</p>
<a
href="{% url 'upload_data' %}"
class="btn btn-primary"
>Upload Data</a
>
<p class="card-text">Upload and manage data sources for analysis.</p>
<a href="{% url 'upload_data' %}" class="btn btn-primary">Upload Data</a>
</div>
</div>
</div>
@ -192,12 +170,8 @@
<div class="card h-100">
<div class="card-body text-center">
<h5 class="card-title">Search Sessions</h5>
<p class="card-text">
Search and analyze chat sessions.
</p>
<a
href="{% url 'search_chat_sessions' %}"
class="btn btn-primary"
<p class="card-text">Search and analyze chat sessions.</p>
<a href="{% url 'search_chat_sessions' %}" class="btn btn-primary"
>Search Sessions</a
>
</div>

View File

@ -1,9 +1,8 @@
<!-- templates/accounts/register.html -->
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Register | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% load crispy_forms_tags %}
{% block title %}
Register | Chat Analytics
{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-md-6">
@ -13,17 +12,14 @@
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
{% csrf_token %} {{ form|crispy }}
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">Register</button>
</div>
</form>
</div>
<div class="card-footer text-center">
<p class="mb-0">
Already have an account? <a href="{% url 'login' %}">Login</a>
</p>
<p class="mb-0">Already have an account? <a href="{% url 'login' %}">Login</a></p>
</div>
</div>
</div>

View File

@ -81,37 +81,24 @@
aria-expanded="false"
>
{% if user.company %}
<span class="badge bg-info me-1"
>{{ user.company.name }}</span
>
<span class="badge bg-info me-1">{{ user.company.name }}</span>
{% endif %}
{{ user.username }}
</button>
<ul
class="dropdown-menu dropdown-menu-end"
aria-labelledby="userDropdown"
>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<li>
<a
class="dropdown-item ajax-nav-link"
href="{% url 'profile' %}"
>Profile</a
>
<a class="dropdown-item ajax-nav-link" href="{% url 'profile' %}">Profile</a>
</li>
{% if user.is_staff %}
<li>
<a class="dropdown-item" href="{% url 'admin:index' %}"
>Admin</a
>
<a class="dropdown-item" href="{% url 'admin:index' %}">Admin</a>
</li>
{% endif %}
<li>
<hr class="dropdown-divider" />
</li>
<li>
<a class="dropdown-item" href="{% url 'logout' %}"
>Logout</a
>
<a class="dropdown-item" href="{% url 'logout' %}">Logout</a>
</li>
</ul>
</div>
@ -195,10 +182,7 @@
<li class="nav-header"><strong>Data Sources</strong></li>
{% for data_source in data_sources %}
<li class="nav-item">
<a
class="nav-link"
href="{% url 'data_source_detail' data_source.id %}"
>
<a class="nav-link" href="{% url 'data_source_detail' data_source.id %}">
<i class="fas fa-database me-2"></i>
{{ data_source.name }}
</a>
@ -224,10 +208,7 @@
{# </div> #}
{# {% endif %} #}
<div id="main-content">
{% block content %}
{% endblock %}
</div>
<div id="main-content">{% block content %}{% endblock %}</div>
</main>
</div>
</div>
@ -276,7 +257,7 @@
{% block extra_js %}
{{ block.super }}
{% if messages %}
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1100;">
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1100">
<!-- Toasts will be appended here -->
</div>
@ -296,9 +277,7 @@
if (!toastContainer) return;
// Find all message data elements
const messageDataElements = document.querySelectorAll(
'script[id^="message-data-"]',
);
const messageDataElements = document.querySelectorAll('script[id^="message-data-"]');
messageDataElements.forEach(function (dataElement) {
try {
const messageData = JSON.parse(dataElement.textContent);
@ -330,10 +309,7 @@
}
const toastId =
"toast-" +
Date.now() +
"-" +
Math.random().toString(36).substring(2, 11);
"toast-" + Date.now() + "-" + Math.random().toString(36).substring(2, 11);
const toastHtml = `
<div id="${toastId}" class="toast ${toastClass}" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">

View File

@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% block title %}Chat Session {{ session.session_id }} | Chat Analytics{% endblock %}
{% block title %}
Chat Session {{ session.session_id }} | Chat Analytics
{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -31,12 +31,8 @@
<strong>Start Time:</strong>
{{ session.start_time|date:"F d, Y H:i" }}
</p>
<p>
<strong>End Time:</strong> {{ session.end_time|date:"F d, Y H:i" }}
</p>
<p>
<strong>IP Address:</strong> {{ session.ip_address|default:"N/A" }}
</p>
<p><strong>End Time:</strong> {{ session.end_time|date:"F d, Y H:i" }}</p>
<p><strong>IP Address:</strong> {{ session.ip_address|default:"N/A" }}</p>
<p><strong>Country:</strong> {{ session.country|default:"N/A" }}</p>
<p><strong>Language:</strong> {{ session.language|default:"N/A" }}</p>
</div>
@ -47,27 +43,19 @@
{{ session.avg_response_time|floatformat:2 }}s
</p>
<p><strong>Tokens:</strong> {{ session.tokens }}</p>
<p>
<strong>Token Cost:</strong> €{{ session.tokens_eur|floatformat:2 }}
</p>
<p><strong>Token Cost:</strong> €{{ session.tokens_eur|floatformat:2 }}</p>
<p><strong>Category:</strong> {{ session.category|default:"N/A" }}</p>
<p>
<strong>Sentiment:</strong>
{% if session.sentiment %}
{% if 'positive' in session.sentiment|lower %}
<span class="badge bg-success"
>{{ session.sentiment }}</span
>
<span class="badge bg-success">{{ session.sentiment }}</span>
{% elif 'negative' in session.sentiment|lower %}
<span class="badge bg-danger">{{ session.sentiment }}</span>
{% elif 'neutral' in session.sentiment|lower %}
<span class="badge bg-warning"
>{{ session.sentiment }}</span
>
<span class="badge bg-warning">{{ session.sentiment }}</span>
{% else %}
<span class="badge bg-secondary"
>{{ session.sentiment }}</span
>
<span class="badge bg-secondary">{{ session.sentiment }}</span>
{% endif %}
{% else %}
<span class="text-muted">N/A</span>
@ -130,8 +118,8 @@
</div>
<div class="card-body">
{% if session.full_transcript %}
<div class="chat-transcript" style="max-height: 500px; overflow-y: auto;">
<pre style="white-space: pre-wrap; font-family: inherit;">
<div class="chat-transcript" style="max-height: 500px; overflow-y: auto">
<pre style="white-space: pre-wrap; font-family: inherit">
{{ session.full_transcript }}</pre
>
</div>

View File

@ -1,9 +1,8 @@
<!-- templates/dashboard/dashboard.html -->
{% extends 'base.html' %}
{% load static %}
{% block title %}Dashboard | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% load static %}
{% block title %}
Dashboard | Chat Analytics
{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -42,30 +41,22 @@
</button>
<ul class="dropdown-menu" aria-labelledby="timeRangeDropdown">
<li>
<a
class="dropdown-item"
href="?dashboard_id={{ selected_dashboard.id }}&time_range=7"
<a class="dropdown-item" href="?dashboard_id={{ selected_dashboard.id }}&time_range=7"
>Last 7 days</a
>
</li>
<li>
<a
class="dropdown-item"
href="?dashboard_id={{ selected_dashboard.id }}&time_range=30"
<a class="dropdown-item" href="?dashboard_id={{ selected_dashboard.id }}&time_range=30"
>Last 30 days</a
>
</li>
<li>
<a
class="dropdown-item"
href="?dashboard_id={{ selected_dashboard.id }}&time_range=90"
<a class="dropdown-item" href="?dashboard_id={{ selected_dashboard.id }}&time_range=90"
>Last 90 days</a
>
</li>
<li>
<a
class="dropdown-item"
href="?dashboard_id={{ selected_dashboard.id }}&time_range=all"
<a class="dropdown-item" href="?dashboard_id={{ selected_dashboard.id }}&time_range=all"
>All time</a
>
</li>
@ -159,30 +150,31 @@
</div>
</div>
{% endblock %}
{% block extra_js %}
<!-- prettier-ignore-start -->
<!-- Store the JSON data in script tags to avoid parsing issues -->
<script type="application/json" id="time-series-data">{{ time_series_data_json|safe }}</script>
<script type="application/json" id="sentiment-data">{{ sentiment_data_json|safe }}</script>
<script type="application/json" id="country-data">{{ country_data_json|safe }}</script>
<script type="application/json" id="category-data">{{ category_data_json|safe }}</script>
<!-- prettier-ignore-end -->
<!-- Store the JSON data in script tags to avoid parsing issues -->
<script type="application/json" id="time-series-data">
{{ time_series_data_json|safe }}
</script>
<script type="application/json" id="sentiment-data">
{{ sentiment_data_json|safe }}
</script>
<script type="application/json" id="country-data">
{{ country_data_json|safe }}
</script>
<script type="application/json" id="category-data">
{{ category_data_json|safe }}
</script>
<!-- prettier-ignore-end -->
<script>
document.addEventListener("DOMContentLoaded", function () {
try {
// Parse the dashboard data components from script tags
const timeSeriesData = JSON.parse(
document.getElementById("time-series-data").textContent,
);
const sentimentData = JSON.parse(
document.getElementById("sentiment-data").textContent,
);
const timeSeriesData = JSON.parse(document.getElementById("time-series-data").textContent);
const sentimentData = JSON.parse(document.getElementById("sentiment-data").textContent);
const countryData = JSON.parse(document.getElementById("country-data").textContent);
const categoryData = JSON.parse(
document.getElementById("category-data").textContent,
);
const categoryData = JSON.parse(document.getElementById("category-data").textContent);
console.log("Time series data loaded:", timeSeriesData);
console.log("Sentiment data loaded:", sentimentData);
@ -233,8 +225,7 @@
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("negative")) return "rgb(255, 99, 132)";
if (sentiment.toLowerCase().includes("neutral")) return "rgb(255, 205, 86)";
return "rgb(201, 203, 207)";
});

View File

@ -1,7 +1,4 @@
{% extends 'base.html' %}
{% block title %}Delete Dashboard | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% block title %}Delete Dashboard | Chat Analytics{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -22,12 +19,11 @@
</div>
<div class="card-body">
<p class="lead">
Are you sure you want to delete the dashboard
"<strong>{{ dashboard.name }}</strong>"?
Are you sure you want to delete the dashboard "<strong>{{ dashboard.name }}</strong>"?
</p>
<p>
This action cannot be undone. The dashboard will be permanently deleted, but
the underlying data sources will remain intact.
This action cannot be undone. The dashboard will be permanently deleted, but the
underlying data sources will remain intact.
</p>
<form method="post">

View File

@ -1,11 +1,12 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% extends 'base.html' %} {% load crispy_forms_tags %}
{% block title %}
{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}
{% if is_create %}
Create Dashboard
{% else %}
Edit Dashboard
{% endif %}
| Chat Analytics
{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -28,8 +29,7 @@
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
{% csrf_token %} {{ form|crispy }}
<div class="d-grid gap-2">
<button type="submit" class="btn btn-primary">
{% if is_create %}Create Dashboard{% else %}Update Dashboard{% endif %}

View File

@ -1,8 +1,5 @@
<!-- templates/dashboard/data_source_confirm_delete.html -->
{% extends 'base.html' %}
{% block title %}Delete Data Source | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% block title %}Delete Data Source | Chat Analytics{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -30,17 +27,14 @@
"<strong>{{ data_source.name }}</strong>"?
</p>
<p>
This action cannot be undone. The data source and all associated chat
sessions ({{ data_source.chat_sessions.count }} sessions) will be
permanently deleted.
This action cannot be undone. The data source and all associated chat sessions
({{ data_source.chat_sessions.count }} sessions) will be permanently deleted.
</p>
<form method="post">
{% csrf_token %}
<div class="d-flex justify-content-between mt-4">
<a
href="{% url 'data_source_detail' data_source.id %}"
class="btn btn-secondary"
<a href="{% url 'data_source_detail' data_source.id %}" class="btn btn-secondary"
>Cancel</a
>
<button type="submit" class="btn btn-danger">Delete Data Source</button>

View File

@ -1,9 +1,9 @@
<!-- templates/dashboard/data_source_detail.html -->
{% extends 'base.html' %}
{% load dashboard_extras %}
{% block title %}{{ data_source.name }} | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% load dashboard_extras %}
{% block title %}
{{ data_source.name }}
| Chat Analytics
{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -19,10 +19,7 @@
>
<i class="fas fa-file-csv"></i> Export CSV
</a>
<a
href="{% url 'delete_data_source' data_source.id %}"
class="btn btn-sm btn-outline-danger"
>
<a href="{% url 'delete_data_source' data_source.id %}" class="btn btn-sm btn-outline-danger">
<i class="fas fa-trash"></i> Delete
</a>
</div>
@ -68,11 +65,7 @@
placeholder="Search sessions..."
aria-label="Search sessions"
/>
<input
type="hidden"
name="data_source_id"
value="{{ data_source.id }}"
/>
<input type="hidden" name="data_source_id" value="{{ data_source.id }}" />
<button class="btn btn-outline-primary" type="submit">
<i class="fas fa-search"></i>
</button>
@ -115,21 +108,13 @@
<td>
{% if session.sentiment %}
{% if 'positive' in session.sentiment|lower %}
<span class="badge bg-success"
>{{ session.sentiment }}</span
>
<span class="badge bg-success">{{ session.sentiment }}</span>
{% elif 'negative' in session.sentiment|lower %}
<span class="badge bg-danger"
>{{ session.sentiment }}</span
>
<span class="badge bg-danger">{{ session.sentiment }}</span>
{% elif 'neutral' in session.sentiment|lower %}
<span class="badge bg-warning"
>{{ session.sentiment }}</span
>
<span class="badge bg-warning">{{ session.sentiment }}</span>
{% else %}
<span class="badge bg-secondary"
>{{ session.sentiment }}</span
>
<span class="badge bg-secondary">{{ session.sentiment }}</span>
{% endif %}
{% else %}
<span class="text-muted">N/A</span>
@ -147,10 +132,7 @@
<i class="fas fa-eye"></i>
</a>
{% else %}
<button
class="btn btn-sm btn-outline-secondary"
disabled
>
<button class="btn btn-sm btn-outline-secondary" disabled>
<i class="fas fa-eye-slash"></i>
</button>
{% endif %}
@ -158,9 +140,7 @@
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center">
No chat sessions found.
</td>
<td colspan="9" class="text-center">No chat sessions found.</td>
</tr>
{% endfor %}
</tbody>
@ -197,23 +177,17 @@
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">
<a class="page-link" href="?page={{ num }}"
>{{ num }}</a
>
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ num }}"
>{{ num }}</a
>
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a

View File

@ -1,9 +1,8 @@
<!-- templates/dashboard/data_view.html -->
{% extends 'base.html' %}
{% load dashboard_extras %}
{% block title %}Data View | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% load dashboard_extras %}
{% block title %}
Data View | Chat Analytics
{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -11,10 +10,7 @@
<h1 class="h2">Data View</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<a
href="{% url 'dashboard' %}"
class="btn btn-sm btn-outline-secondary ajax-nav-link"
>
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary ajax-nav-link">
<i class="fas fa-arrow-left"></i> Back to Dashboard
</a>
{% if selected_data_source %}
@ -41,24 +37,16 @@
<a class="dropdown-item ajax-nav-link" href="?view=all">All Sessions</a>
</li>
<li>
<a class="dropdown-item ajax-nav-link" href="?view=recent"
>Recent Sessions</a
>
<a class="dropdown-item ajax-nav-link" href="?view=recent">Recent Sessions</a>
</li>
<li>
<a class="dropdown-item ajax-nav-link" href="?view=positive"
>Positive Sentiment</a
>
<a class="dropdown-item ajax-nav-link" href="?view=positive">Positive Sentiment</a>
</li>
<li>
<a class="dropdown-item ajax-nav-link" href="?view=negative"
>Negative Sentiment</a
>
<a class="dropdown-item ajax-nav-link" href="?view=negative">Negative Sentiment</a>
</li>
<li>
<a class="dropdown-item ajax-nav-link" href="?view=escalated"
>Escalated Sessions</a
>
<a class="dropdown-item ajax-nav-link" href="?view=escalated">Escalated Sessions</a>
</li>
</ul>
</div>
@ -75,16 +63,14 @@
<div class="card-body">
<form method="get" class="row g-3 align-items-center filter-form">
<div class="col-md-6">
<select
name="data_source_id"
class="form-select"
aria-label="Select Data Source"
>
<select name="data_source_id" class="form-select" aria-label="Select Data Source">
<option value="">All Data Sources</option>
{% for ds in data_sources %}
<option
value="{{ ds.id }}"
{% if selected_data_source.id == ds.id %}selected{% endif %}
{% if selected_data_source.id == ds.id %}
selected
{% endif %}
>
{{ ds.name }}
</option>
@ -93,28 +79,17 @@
</div>
<div class="col-md-4">
<select name="view" class="form-select" aria-label="Select View">
<option value="all" {% if view == 'all' %}selected{% endif %}>
All Sessions
</option>
<option value="all" {% if view == 'all' %}selected{% endif %}>All Sessions</option>
<option value="recent" {% if view == 'recent' %}selected{% endif %}>
Recent Sessions
</option>
<option
value="positive"
{% if view == 'positive' %}selected{% endif %}
>
<option value="positive" {% if view == 'positive' %}selected{% endif %}>
Positive Sentiment
</option>
<option
value="negative"
{% if view == 'negative' %}selected{% endif %}
>
<option value="negative" {% if view == 'negative' %}selected{% endif %}>
Negative Sentiment
</option>
<option
value="escalated"
{% if view == 'escalated' %}selected{% endif %}
>
<option value="escalated" {% if view == 'escalated' %}selected{% endif %}>
Escalated Sessions
</option>
</select>
@ -136,28 +111,14 @@
<h5 class="card-title mb-0">Export Data</h5>
</div>
<div class="card-body">
<form
id="export-form"
method="get"
action="{% url 'export_chats_csv' %}"
class="row g-3"
>
<form id="export-form" method="get" action="{% url 'export_chats_csv' %}" class="row g-3">
<!-- Pass current filters to export -->
<input
type="hidden"
name="data_source_id"
value="{{ selected_data_source.id }}"
/>
<input type="hidden" name="data_source_id" value="{{ selected_data_source.id }}" />
<input type="hidden" name="view" value="{{ view }}" />
<div class="col-md-3">
<label for="start_date" class="form-label">Start Date</label>
<input
type="date"
name="start_date"
id="start_date"
class="form-control"
/>
<input type="date" name="start_date" id="start_date" class="form-control" />
</div>
<div class="col-md-3">
<label for="end_date" class="form-label">End Date</label>
@ -211,9 +172,7 @@
{% if selected_data_source %}
for {{ selected_data_source.name }}
{% endif %}
{% if view != 'all' %}
({{ view|title }})
{% endif %}
{% if view != 'all' %}({{ view|title }}){% endif %}
</h5>
<span class="badge bg-primary">{{ page_obj.paginator.count }} sessions</span>
</div>
@ -227,9 +186,7 @@
</div>
<!-- Data table container that will be updated via AJAX -->
<div id="ajax-content-container">
{% include "dashboard/partials/data_table.html" %}
</div>
<div id="ajax-content-container">{% include "dashboard/partials/data_table.html" %}</div>
</div>
</div>
</div>
@ -288,7 +245,6 @@
</div>
{% endif %}
{% endblock %}
{% block extra_js %}
<script>
// Function to update the summary section with new data

View File

@ -1,8 +1,5 @@
<!-- templates/dashboard/no_company.html -->
{% extends 'base.html' %}
{% block title %}No Company | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% block title %}No Company | Chat Analytics{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -20,22 +17,18 @@
<div class="mb-4">
<i class="fas fa-building fa-4x text-warning mb-3"></i>
<h4>You are not currently associated with any company</h4>
<p class="lead">
You need to be associated with a company to access the dashboard.
</p>
<p class="lead">You need to be associated with a company to access the dashboard.</p>
</div>
<p>
Please contact an administrator to have your account assigned to a company.
Once your account is associated with a company, you'll be able to access the
dashboard and its features.
Please contact an administrator to have your account assigned to a company. Once your
account is associated with a company, you'll be able to access the dashboard and its
features.
</p>
<div class="mt-4">
<a href="{% url 'profile' %}" class="btn btn-primary">View Your Profile</a>
<a href="{% url 'logout' %}" class="btn btn-outline-secondary ms-2"
>Logout</a
>
<a href="{% url 'logout' %}" class="btn btn-outline-secondary ms-2">Logout</a>
</div>
</div>
</div>

View File

@ -99,9 +99,7 @@
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
{% for num in page_obj.paginator.page_range %}{% if page_obj.number == num %}
<li class="page-item active">
<a
class="page-link pagination-link"
@ -119,9 +117,7 @@
>{{ num }}</a
>
</li>
{% endif %}
{% endfor %}
{% endif %}{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a

View File

@ -20,9 +20,7 @@
<td>{{ session.session_id|truncatechars:10 }}</td>
<td>{{ session.start_time|date:"M d, Y H:i" }}</td>
<td>
<a
href="{% url 'data_source_detail' session.data_source.id %}"
class="ajax-nav-link"
<a href="{% url 'data_source_detail' session.data_source.id %}" class="ajax-nav-link"
>{{ session.data_source.name|truncatechars:15 }}</a
>
</td>
@ -62,9 +60,7 @@
</tr>
{% empty %}
<tr>
<td colspan="9" class="text-center">
No chat sessions found matching your criteria.
</td>
<td colspan="9" class="text-center">No chat sessions found matching your criteria.</td>
</tr>
{% endfor %}
</tbody>
@ -107,9 +103,7 @@
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
{% for num in page_obj.paginator.page_range %}{% if page_obj.number == num %}
<li class="page-item active">
<a
class="page-link pagination-link"
@ -127,9 +121,7 @@
>{{ num }}</a
>
</li>
{% endif %}
{% endfor %}
{% endif %}{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a

View File

@ -1,8 +1,5 @@
<!-- templates/dashboard/search_results.html -->
{% extends 'base.html' %}
{% block title %}Search Results | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% block title %}Search Results | Chat Analytics{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
@ -22,11 +19,7 @@
<h5 class="card-title mb-0">Search Chat Sessions</h5>
</div>
<div class="card-body">
<form
method="get"
action="{% url 'search_chat_sessions' %}"
class="search-form"
>
<form method="get" action="{% url 'search_chat_sessions' %}" class="search-form">
<div class="input-group">
<input
type="text"
@ -37,11 +30,7 @@
aria-label="Search sessions"
/>
{% if data_source %}
<input
type="hidden"
name="data_source_id"
value="{{ data_source.id }}"
/>
<input type="hidden" name="data_source_id" value="{{ data_source.id }}" />
{% endif %}
<button class="btn btn-outline-primary" type="submit">
<i class="fas fa-search"></i> Search
@ -49,8 +38,8 @@
</div>
<div class="mt-2 text-muted">
<small
>Search by session ID, country, language, sentiment, category, or
message content.</small
>Search by session ID, country, language, sentiment, category, or message
content.</small
>
</div>
</form>
@ -87,7 +76,6 @@
</div>
</div>
{% endblock %}
{% block extra_js %}
<!-- No need for extra JavaScript here, using common ajax-pagination.js -->
{% endblock %}

View File

@ -1,10 +1,8 @@
<!-- templates/dashboard/upload.html -->
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% load dashboard_extras %}
{% block title %}Upload Data | Chat Analytics{% endblock %}
{% extends 'base.html' %} {% load crispy_forms_tags %} {% load dashboard_extras %}
{% block title %}
Upload Data | Chat Analytics
{% endblock %}
{% block content %}
<div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"

View File

@ -1,15 +1,4 @@
{
"name": "livegraphsdjango",
"version": "0.1.0",
"description": "Live Graphs Django Dashboard",
"private": true,
"scripts": {
"lint": "eslint dashboard_project/static/js/",
"format": "prettier --write \"dashboard_project/static/**/*.{js,css,html}\"",
"format:check": "prettier --check \"dashboard_project/static/**/*.{js,css,html}\"",
"stylelint": "stylelint \"dashboard_project/static/css/**/*.css\" --fix",
"stylelint:check": "stylelint \"dashboard_project/static/css/**/*.css\""
},
"devDependencies": {
"prettier": "^3.5.3",
"prettier-plugin-jinja-template": "^2.1.0"