From e8f2d2adc2f235557c3202209ef77dff86dfbbed Mon Sep 17 00:00:00 2001 From: Kaj Kowalski Date: Sat, 17 May 2025 21:45:50 +0200 Subject: [PATCH] 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. --- .editorconfig | 4 +- .gitignore | 1 + .pre-commit-config.yaml | 9 +- .prettierignore | 3 + .prettierrc | 2 +- .vscode/settings.json | 90 +-- Makefile | 11 +- PRETTIER_SETUP.md | 12 +- TODO.md | 86 ++- .../dashboard_project/settings.py | 4 +- dashboard_project/static/css/dashboard.css | 324 ++++----- dashboard_project/static/css/style.css | 374 +++++------ .../static/js/ajax-navigation.js | 476 ++++++------- .../static/js/ajax-pagination.js | 166 ++--- dashboard_project/static/js/dashboard.js | 484 ++++++------- dashboard_project/static/js/main.js | 256 +++---- .../templates/accounts/login.html | 54 +- .../templates/accounts/password_change.html | 57 +- .../accounts/password_change_done.html | 72 +- .../templates/accounts/profile.html | 384 +++++------ .../templates/accounts/register.html | 54 +- dashboard_project/templates/base.html | 634 +++++++++--------- .../dashboard/chat_session_detail.html | 274 ++++---- .../templates/dashboard/dashboard.html | 613 +++++++++-------- .../dashboard/dashboard_confirm_delete.html | 76 +-- .../templates/dashboard/dashboard_form.html | 76 +-- .../dashboard/data_source_confirm_delete.html | 90 ++- .../dashboard/data_source_detail.html | 480 +++++++------ .../templates/dashboard/data_view.html | 574 ++++++++-------- .../templates/dashboard/no_company.html | 69 +- .../dashboard/partials/data_table.html | 304 +++++---- .../partials/search_results_table.html | 316 +++++---- .../templates/dashboard/search_results.html | 158 ++--- .../templates/dashboard/upload.html | 388 ++++++----- package.json | 19 +- 35 files changed, 3406 insertions(+), 3588 deletions(-) diff --git a/.editorconfig b/.editorconfig index 5291b78..506c4a2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -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: # diff --git a/.gitignore b/.gitignore index 6da3b4e..329712d 100644 --- a/.gitignore +++ b/.gitignore @@ -407,6 +407,7 @@ pyrightconfig.json *Zone.Identifier examples/ **/migrations/[0-9]**.py +package-lock.json # UV specific .uv/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5aa97b6..754af15 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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 diff --git a/.prettierignore b/.prettierignore index 8b63252..e2e7e83 100644 --- a/.prettierignore +++ b/.prettierignore @@ -47,3 +47,6 @@ docker-compose.override.yml .vscode/ *.swp *.swo + +# Ignore all SQLite3 files: +**/*.sqlite3 diff --git a/.prettierrc b/.prettierrc index d1fa5e7..6aec78f 100644 --- a/.prettierrc +++ b/.prettierrc @@ -11,7 +11,7 @@ "requirePragma": false, "semi": true, "singleQuote": false, - "useTabs": true, + "useTabs": false, "overrides": [ { "files": ["*.html"], diff --git a/.vscode/settings.json b/.vscode/settings.json index 304cca9..8c4fed7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,47 +1,47 @@ { - "[css]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "[html]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "[javascript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "[python]": { - "editor.codeActionsOnSave": { - "source.fixAll": "explicit", - "source.organizeImports": "explicit" - }, - "editor.defaultFormatter": "charliermarsh.ruff", - "editor.formatOnSave": true - }, - "[toml]": { - "editor.defaultFormatter": "tamasfe.even-better-toml" - }, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true, - "emmet.includeLanguages": { - "django-html": "html", - "jinja-html": "html" - }, - "emmet.syntaxProfiles": { - "html": { - "inline_break": 2 - } - }, - "files.associations": { - "*.html": "html" - }, - "html.format.wrapAttributes": "auto", - "html.format.wrapLineLength": 100, - "notebook.codeActionsOnSave": { - "notebook.source.fixAll": "explicit", - "notebook.source.organizeImports": "explicit" - }, - "notebook.formatOnSave.enabled": true, - "prettier.requireConfig": true + "[css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "[python]": { + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true + }, + "[toml]": { + "editor.defaultFormatter": "tamasfe.even-better-toml" + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "emmet.includeLanguages": { + "django-html": "html", + "jinja-html": "html" + }, + "emmet.syntaxProfiles": { + "html": { + "inline_break": 2 + } + }, + "files.associations": { + "*.html": "html" + }, + "html.format.wrapAttributes": "auto", + "html.format.wrapLineLength": 100, + "notebook.codeActionsOnSave": { + "notebook.source.fixAll": "explicit", + "notebook.source.organizeImports": "explicit" + }, + "notebook.formatOnSave.enabled": true, + "prettier.requireConfig": true } diff --git a/Makefile b/Makefile index a53f2ef..b0939d6 100644 --- a/Makefile +++ b/Makefile @@ -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/ diff --git a/PRETTIER_SETUP.md b/PRETTIER_SETUP.md index 8bec34b..60fc5ff 100644 --- a/PRETTIER_SETUP.md +++ b/PRETTIER_SETUP.md @@ -64,12 +64,12 @@ For VSCode users, install the Prettier extension and add these settings to your ```json { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "[html]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "prettier.requireConfig": true + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "prettier.requireConfig": true } ``` diff --git a/TODO.md b/TODO.md index 5f50f0c..949ce1a 100644 --- a/TODO.md +++ b/TODO.md @@ -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 possibility for the XY company. - - Authentication: Basic Auth - - URL: - - 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: + - [ ] 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 diff --git a/dashboard_project/dashboard_project/settings.py b/dashboard_project/dashboard_project/settings.py index 901db5c..2ce84ea 100644 --- a/dashboard_project/dashboard_project/settings.py +++ b/dashboard_project/dashboard_project/settings.py @@ -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 diff --git a/dashboard_project/static/css/dashboard.css b/dashboard_project/static/css/dashboard.css index 3401b43..9911b73 100644 --- a/dashboard_project/static/css/dashboard.css +++ b/dashboard_project/static/css/dashboard.css @@ -4,311 +4,311 @@ /* Dashboard grid layout */ .dashboard-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - /* Slightly larger minmax for widgets */ - gap: 1.5rem; + /* Slightly larger minmax for widgets */ + gap: 1.5rem; - /* Increased gap */ + /* Increased gap */ } /* Dashboard widget cards */ .dashboard-widget { - display: flex; + display: flex; - /* Allow flex for content alignment */ - flex-direction: column; + /* Allow flex for content alignment */ + flex-direction: column; - /* Stack header, body, footer vertically */ - height: 100%; + /* Stack header, body, footer vertically */ + height: 100%; - /* Ensure widgets fill grid cell height */ + /* Ensure widgets fill grid cell height */ } .dashboard-widget .card-header { - display: flex; - justify-content: space-between; - align-items: center; + display: flex; + justify-content: space-between; + align-items: center; } .dashboard-widget .card-header .widget-title { - font-size: 1.1rem; + font-size: 1.1rem; - /* Slightly larger widget titles */ - font-weight: 600; + /* Slightly larger widget titles */ + font-weight: 600; } .dashboard-widget .card-header .widget-actions { - display: flex; - gap: 0.5rem; + display: flex; + gap: 0.5rem; } .dashboard-widget .card-header .widget-actions .btn { - width: 32px; + width: 32px; - /* Slightly larger action buttons */ - height: 32px; - padding: 0; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.85rem; - background-color: transparent; - border: 1px solid transparent; - color: #6c757d; + /* Slightly larger action buttons */ + height: 32px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.85rem; + background-color: transparent; + border: 1px solid transparent; + color: #6c757d; } .dashboard-widget .card-header .widget-actions .btn:hover { - background-color: #f0f0f0; - border-color: #e0e0e0; - color: #333; + background-color: #f0f0f0; + border-color: #e0e0e0; + color: #333; } .dashboard-widget .card-body { - flex-grow: 1; + flex-grow: 1; - /* Allow card body to take available space */ - padding: 1.25rem; + /* Allow card body to take available space */ + padding: 1.25rem; - /* Consistent padding */ + /* Consistent padding */ } /* Chart widgets */ .chart-widget .card-body { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .chart-widget .chart-container { - flex: 1; - min-height: 250px; + flex: 1; + min-height: 250px; - /* Adjusted min-height */ - width: 100%; + /* Adjusted min-height */ + width: 100%; - /* Ensure it takes full width of card body */ + /* Ensure it takes full width of card body */ } /* Stat widgets / Stat Cards */ .stat-card { - text-align: center; - padding: 1.5rem; + text-align: center; + padding: 1.5rem; - /* Generous padding */ + /* Generous padding */ } .stat-card .stat-icon { - font-size: 2.25rem; + font-size: 2.25rem; - /* Larger icon */ - margin-bottom: 1rem; - display: inline-block; - width: 4.5rem; - height: 4.5rem; - line-height: 4.5rem; - text-align: center; - border-radius: 50%; - background-color: #e9f2ff; + /* Larger icon */ + margin-bottom: 1rem; + display: inline-block; + width: 4.5rem; + height: 4.5rem; + line-height: 4.5rem; + text-align: center; + border-radius: 50%; + background-color: #e9f2ff; - /* Light blue background for icon */ - color: #007bff; + /* Light blue background for icon */ + color: #007bff; - /* Primary color for icon */ + /* Primary color for icon */ } .stat-card .stat-value { - font-size: 2.25rem; + font-size: 2.25rem; - /* Larger stat value */ - font-weight: 700; - margin-bottom: 0.25rem; + /* Larger stat value */ + font-weight: 700; + margin-bottom: 0.25rem; - /* Reduced margin */ - line-height: 1.1; - color: #212529; + /* Reduced margin */ + line-height: 1.1; + color: #212529; - /* Darker color for value */ + /* Darker color for value */ } .stat-card .stat-label { - font-size: 0.9rem; + font-size: 0.9rem; - /* Slightly larger label */ - color: #6c757d; - margin-bottom: 0; + /* Slightly larger label */ + color: #6c757d; + margin-bottom: 0; } /* Dashboard theme variations */ .dashboard-theme-light .card { - background-color: #fff; + background-color: #fff; } .dashboard-theme-dark { - background-color: #212529; - color: #f8f9fa; + background-color: #212529; + color: #f8f9fa; } .dashboard-theme-dark .card { - background-color: #343a40; - color: #f8f9fa; - border-color: #495057; + background-color: #343a40; + color: #f8f9fa; + border-color: #495057; } .dashboard-theme-dark .card-header { - background-color: #495057; - border-bottom-color: #6c757d; + background-color: #495057; + border-bottom-color: #6c757d; } .dashboard-theme-dark .stat-card .stat-label { - color: #adb5bd; + color: #adb5bd; } /* Time period selector */ .time-period-selector { - display: flex; - align-items: center; - gap: 0.75rem; + display: flex; + align-items: center; + gap: 0.75rem; - /* Increased gap */ - margin-bottom: 1.5rem; + /* Increased gap */ + margin-bottom: 1.5rem; - /* Increased margin */ + /* Increased margin */ } .time-period-selector .btn-group { - flex-wrap: wrap; + flex-wrap: wrap; } .time-period-selector .btn { - padding: 0.375rem 0.75rem; + padding: 0.375rem 0.75rem; - /* Bootstrap-like padding */ - font-size: 0.875rem; + /* Bootstrap-like padding */ + font-size: 0.875rem; } /* Custom metric selector */ .metric-selector { - max-width: 100%; - overflow-x: auto; - white-space: nowrap; - padding-bottom: 0.5rem; - margin-bottom: 1rem; + max-width: 100%; + overflow-x: auto; + white-space: nowrap; + padding-bottom: 0.5rem; + margin-bottom: 1rem; } .metric-selector .nav-link { - white-space: nowrap; - padding: 0.5rem 1rem; - font-weight: 500; + white-space: nowrap; + padding: 0.5rem 1rem; + font-weight: 500; } .metric-selector .nav-link.active { - background-color: #007bff; - color: white; - border-radius: 0.25rem; + background-color: #007bff; + color: white; + border-radius: 0.25rem; } /* Dashboard loading states */ .widget-placeholder { - min-height: 300px; - background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%); + min-height: 300px; + background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%); - /* Lighter gradient */ - background-size: 200% 100%; - animation: loading 1.8s infinite ease-in-out; + /* Lighter gradient */ + background-size: 200% 100%; + animation: loading 1.8s infinite ease-in-out; - /* Smoother animation */ - border-radius: 0.5rem; + /* Smoother animation */ + border-radius: 0.5rem; - /* Consistent with cards */ + /* Consistent with cards */ } @keyframes loading { - 0% { - background-position: 200% 0; - } + 0% { + background-position: 200% 0; + } - 100% { - background-position: -200% 0; - } + 100% { + background-position: -200% 0; + } } /* Dashboard empty states */ .empty-state { - padding: 2.5rem; + padding: 2.5rem; - /* Increased padding */ - text-align: center; - color: #6c757d; - background-color: #f8f9fa; + /* Increased padding */ + text-align: center; + color: #6c757d; + background-color: #f8f9fa; - /* Light background for empty state */ - border-radius: 0.5rem; - border: 1px dashed #ced4da; + /* Light background for empty state */ + border-radius: 0.5rem; + border: 1px dashed #ced4da; - /* Dashed border */ + /* Dashed border */ } .empty-state .empty-state-icon { - font-size: 3.5rem; + font-size: 3.5rem; - /* Larger icon */ - margin-bottom: 1.5rem; - opacity: 0.4; + /* Larger icon */ + margin-bottom: 1.5rem; + opacity: 0.4; } .empty-state .empty-state-message { - font-size: 1.2rem; + font-size: 1.2rem; - /* Slightly larger message */ - margin-bottom: 1.5rem; - font-weight: 500; + /* Slightly larger message */ + margin-bottom: 1.5rem; + font-weight: 500; } .empty-state .btn { - margin-top: 1rem; + margin-top: 1rem; } /* Responsive adjustments */ @media (width <=767.98px) { - .dashboard-grid { - grid-template-columns: 1fr; - } + .dashboard-grid { + grid-template-columns: 1fr; + } - .stat-card { - padding: 1rem; - } + .stat-card { + padding: 1rem; + } - .stat-card .stat-icon { - font-size: 1.5rem; - width: 3rem; - height: 3rem; - line-height: 3rem; - } + .stat-card .stat-icon { + font-size: 1.5rem; + width: 3rem; + height: 3rem; + line-height: 3rem; + } - .stat-card .stat-value { - font-size: 1.5rem; - } + .stat-card .stat-value { + font-size: 1.5rem; + } } /* --- Stat Boxes Alignment Fix (Bottom Align, No Overlap) --- */ .stats-row { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; - align-items: stretch; + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + align-items: stretch; } .stats-card { - flex: 1 1 0; - min-width: 200px; - display: flex; - flex-direction: column; - justify-content: flex-end; + flex: 1 1 0; + min-width: 200px; + display: flex; + flex-direction: column; + justify-content: flex-end; - /* Push content to bottom */ - align-items: flex-start; - box-sizing: border-box; + /* Push content to bottom */ + align-items: flex-start; + box-sizing: border-box; - /* Remove min-height/height for natural stretch */ + /* Remove min-height/height for natural stretch */ } diff --git a/dashboard_project/static/css/style.css b/dashboard_project/static/css/style.css index e30d0a3..6b0763a 100644 --- a/dashboard_project/static/css/style.css +++ b/dashboard_project/static/css/style.css @@ -4,361 +4,361 @@ /* General Styles */ body { - font-family: - -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - background-color: #f4f7f9; + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + background-color: #f4f7f9; - /* Lighter, cleaner background */ - color: #333; + /* Lighter, cleaner background */ + color: #333; - /* Darker text for better contrast */ - line-height: 1.6; - display: flex; + /* Darker text for better contrast */ + line-height: 1.6; + display: flex; - /* Added for sticky footer */ - flex-direction: column; + /* Added for sticky footer */ + flex-direction: column; - /* Added for sticky footer */ - min-height: 100vh; + /* Added for sticky footer */ + min-height: 100vh; - /* Ensures body takes at least full viewport height */ + /* Ensures body takes at least full viewport height */ } /* Navbar adjustments (if needed, Bootstrap usually handles this well) */ .navbar { - box-shadow: 0 2px 4px rgb(0 0 0 / 5%); + box-shadow: 0 2px 4px rgb(0 0 0 / 5%); - /* Subtle shadow for depth */ + /* Subtle shadow for depth */ } /* Helper Classes */ .text-truncate-2 { - display: -webkit-box; - -webkit-line-clamp: 2; - line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; } .cursor-pointer { - cursor: pointer; + cursor: pointer; } .min-w-150 { - min-width: 150px; + min-width: 150px; } /* Card styles */ .card { - border: 1px solid #e0e5e9; + border: 1px solid #e0e5e9; - /* Lighter border */ - border-radius: 0.5rem; + /* Lighter border */ + border-radius: 0.5rem; - /* Slightly more rounded corners */ - box-shadow: 0 4px 12px rgb(0 0 0 / 8%); + /* Slightly more rounded corners */ + box-shadow: 0 4px 12px rgb(0 0 0 / 8%); - /* Softer, more modern shadow */ - transition: - transform 0.2s ease-in-out, - box-shadow 0.2s ease-in-out; - margin-bottom: 1.5rem; + /* Softer, more modern shadow */ + transition: + transform 0.2s ease-in-out, + box-shadow 0.2s ease-in-out; + margin-bottom: 1.5rem; - /* Consistent margin */ + /* Consistent margin */ } .card-hover:hover { - transform: translateY(-3px); - box-shadow: 0 6px 16px rgb(0 0 0 / 10%); + transform: translateY(-3px); + box-shadow: 0 6px 16px rgb(0 0 0 / 10%); } .card-header { - background-color: #fff; + background-color: #fff; - /* Clean white header */ - border-bottom: 1px solid #e0e5e9; - font-weight: 500; + /* Clean white header */ + border-bottom: 1px solid #e0e5e9; + font-weight: 500; - /* Slightly bolder header text */ - padding: 0.75rem 1.25rem; + /* Slightly bolder header text */ + padding: 0.75rem 1.25rem; } .card-title { - font-size: 1.15rem; + font-size: 1.15rem; - /* Adjusted card title size */ - font-weight: 600; + /* Adjusted card title size */ + font-weight: 600; } /* Sidebar enhancements */ .sidebar { - background-color: #fff; + background-color: #fff; - /* White sidebar for a cleaner look */ - border-right: 1px solid #e0e5e9; - box-shadow: 2px 0 5px rgb(0 0 0 / 3%); - transition: all 0.3s; + /* White sidebar for a cleaner look */ + border-right: 1px solid #e0e5e9; + box-shadow: 2px 0 5px rgb(0 0 0 / 3%); + transition: all 0.3s; } .sidebar-sticky { - padding-top: 1rem; + padding-top: 1rem; } .sidebar .nav-link { - color: #4a5568; + color: #4a5568; - /* Softer link color */ - padding: 0.65rem 1.25rem; + /* Softer link color */ + padding: 0.65rem 1.25rem; - /* Adjusted padding */ - border-radius: 0.375rem; + /* Adjusted padding */ + border-radius: 0.375rem; - /* Bootstrap-like rounded corners for links */ - margin: 0.1rem 0.5rem; + /* Bootstrap-like rounded corners for links */ + margin: 0.1rem 0.5rem; - /* Margin around links */ - font-weight: 500; + /* Margin around links */ + font-weight: 500; } .sidebar .nav-link:hover { - color: #007bff; + color: #007bff; - /* Primary color on hover */ - background-color: #e9f2ff; + /* Primary color on hover */ + background-color: #e9f2ff; - /* Light blue background on hover */ + /* Light blue background on hover */ } .sidebar .nav-link.active { - color: #007bff; - background-color: #d6e4ff; + color: #007bff; + background-color: #d6e4ff; - /* Slightly darker blue for active */ - font-weight: 600; + /* Slightly darker blue for active */ + font-weight: 600; } .sidebar .nav-link i.me-2 { - width: 20px; + width: 20px; - /* Ensure icons align well */ - text-align: center; - margin-right: 0.75rem !important; + /* Ensure icons align well */ + text-align: center; + margin-right: 0.75rem !important; - /* Consistent icon spacing */ + /* Consistent icon spacing */ } .sidebar .nav-header { - font-size: 0.8rem; - text-transform: uppercase; - letter-spacing: 0.08em; - color: #718096; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: #718096; - /* Softer header color */ - padding: 0.5rem 1.25rem; - margin-top: 1rem; + /* Softer header color */ + padding: 0.5rem 1.25rem; + margin-top: 1rem; } /* Dashboard stats cards */ .stats-card { - border-radius: 0.5rem; - overflow: hidden; + border-radius: 0.5rem; + overflow: hidden; } .stats-card h3 { - font-size: 1.75rem; - font-weight: 600; + font-size: 1.75rem; + font-weight: 600; } .stats-card p { - font-size: 0.875rem; - margin-bottom: 0; - opacity: 0.8; + font-size: 0.875rem; + margin-bottom: 0; + opacity: 0.8; } /* Chart containers */ .chart-container { - width: 100%; - height: 300px; - position: relative; + width: 100%; + height: 300px; + position: relative; } /* Loading overlay */ .loading-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgb(255 255 255 / 70%); - display: flex; - justify-content: center; - align-items: center; - z-index: 9999; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(255 255 255 / 70%); + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; } /* Table enhancements */ .table { - border-color: #e0e5e9; + border-color: #e0e5e9; } .table th { - font-weight: 600; + font-weight: 600; - /* Bolder table headers */ - color: #4a5568; - background-color: #f8f9fc; + /* Bolder table headers */ + color: #4a5568; + background-color: #f8f9fc; - /* Light background for headers */ + /* Light background for headers */ } .table-striped tbody tr:nth-of-type(odd) { - background-color: rgb(0 0 0 / 2%); + background-color: rgb(0 0 0 / 2%); - /* Very subtle striping */ + /* Very subtle striping */ } .table-hover tbody tr:hover { - background-color: #e9f2ff; + background-color: #e9f2ff; - /* Consistent hover with sidebar */ + /* Consistent hover with sidebar */ } /* Form improvements */ .form-control, .form-select { - border-color: #ced4da; - border-radius: 0.375rem; + border-color: #ced4da; + border-radius: 0.375rem; - /* Consistent border radius */ - padding: 0.5rem 0.75rem; + /* Consistent border radius */ + padding: 0.5rem 0.75rem; - /* Adjusted padding */ + /* Adjusted padding */ } .form-control:focus, .form-select:focus { - border-color: #86b7fe; + border-color: #86b7fe; - /* Bootstrap focus color */ - box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%); + /* Bootstrap focus color */ + box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%); - /* Bootstrap focus shadow */ + /* Bootstrap focus shadow */ } /* Button styling */ .btn { - border-radius: 0.375rem; + border-radius: 0.375rem; - /* Consistent border radius */ - padding: 0.5rem 1rem; + /* Consistent border radius */ + padding: 0.5rem 1rem; - /* Standard button padding */ - font-weight: 500; - transition: - background-color 0.15s ease-in-out, - border-color 0.15s ease-in-out, - box-shadow 0.15s ease-in-out; + /* Standard button padding */ + font-weight: 500; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; } .btn-primary { - background-color: #007bff; - border-color: #007bff; + background-color: #007bff; + border-color: #007bff; } .btn-primary:hover { - background-color: #0069d9; - border-color: #0062cc; + background-color: #0069d9; + border-color: #0062cc; } .btn-secondary { - background-color: #6c757d; - border-color: #6c757d; + background-color: #6c757d; + border-color: #6c757d; } .btn-secondary:hover { - background-color: #5a6268; - border-color: #545b62; + background-color: #5a6268; + border-color: #545b62; } /* Alert styling */ .alert { - border-radius: 0.375rem; - padding: 0.9rem 1.25rem; + border-radius: 0.375rem; + padding: 0.9rem 1.25rem; } /* Chat transcript styling */ .chat-transcript { - background-color: #f8f9fa; - border: 1px solid #e9ecef; - border-radius: 0.25rem; - padding: 1rem; - max-height: 500px; - overflow-y: auto; - font-size: 0.875rem; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 0.25rem; + padding: 1rem; + max-height: 500px; + overflow-y: auto; + font-size: 0.875rem; } .chat-transcript pre { - white-space: pre-wrap; - font-family: inherit; - margin-bottom: 0; + white-space: pre-wrap; + font-family: inherit; + margin-bottom: 0; } /* Footer styling */ footer { - background-color: #fff; + background-color: #fff; - /* White footer */ - border-top: 1px solid #e0e5e9; - padding: 1.5rem 0; - color: #6c757d; - font-size: 0.9rem; - margin-top: auto; + /* White footer */ + border-top: 1px solid #e0e5e9; + padding: 1.5rem 0; + color: #6c757d; + font-size: 0.9rem; + margin-top: auto; - /* Added for sticky footer */ + /* Added for sticky footer */ } /* Responsive adjustments */ @media (width <=767.98px) { - .main-content { - margin-left: 0; - } + .main-content { + margin-left: 0; + } - .stats-card h3 { - font-size: 1.5rem; - } + .stats-card h3 { + font-size: 1.5rem; + } - .chart-container { - height: 250px; - } + .chart-container { + height: 250px; + } - .card-title { - font-size: 1.25rem; - } + .card-title { + font-size: 1.25rem; + } } /* Print styles */ @media print { - .sidebar, - .navbar, - .btn, - footer { - display: none !important; - } + .sidebar, + .navbar, + .btn, + footer { + display: none !important; + } - .main-content { - margin-left: 0 !important; - padding: 0 !important; - } + .main-content { + margin-left: 0 !important; + padding: 0 !important; + } - .card { - break-inside: avoid; - border: none !important; - box-shadow: none !important; - } + .card { + break-inside: avoid; + border: none !important; + box-shadow: none !important; + } - .chart-container { - break-inside: avoid; - height: auto !important; - } + .chart-container { + break-inside: avoid; + height: auto !important; + } } diff --git a/dashboard_project/static/js/ajax-navigation.js b/dashboard_project/static/js/ajax-navigation.js index 4b46307..7e069bc 100644 --- a/dashboard_project/static/js/ajax-navigation.js +++ b/dashboard_project/static/js/ajax-navigation.js @@ -6,269 +6,269 @@ */ document.addEventListener("DOMContentLoaded", function () { - // Only initialize if AJAX navigation is enabled - if (typeof ENABLE_AJAX_NAVIGATION !== "undefined" && ENABLE_AJAX_NAVIGATION) { - setupAjaxNavigation(); - } + // Only initialize if AJAX navigation is enabled + if (typeof ENABLE_AJAX_NAVIGATION !== "undefined" && ENABLE_AJAX_NAVIGATION) { + setupAjaxNavigation(); + } - // Function to set up AJAX navigation for the application - function setupAjaxNavigation() { - // Configuration - const config = { - mainContentSelector: "#main-content", // Selector for the main content area - navLinkSelector: ".ajax-nav-link", // Selector for links to handle with AJAX - loadingIndicatorId: "nav-loading-indicator", // ID of the loading indicator - excludePatterns: [ - // URL patterns to exclude from AJAX navigation - /\.(pdf|xlsx?|docx?|csv|zip|png|jpe?g|gif|svg)$/i, // File downloads - /\/admin\//, // Admin pages - /\/accounts\/logout\//, // Logout page - /\/api\//, // API endpoints - ], - }; + // Function to set up AJAX navigation for the application + function setupAjaxNavigation() { + // Configuration + const config = { + mainContentSelector: "#main-content", // Selector for the main content area + navLinkSelector: ".ajax-nav-link", // Selector for links to handle with AJAX + loadingIndicatorId: "nav-loading-indicator", // ID of the loading indicator + excludePatterns: [ + // URL patterns to exclude from AJAX navigation + /\.(pdf|xlsx?|docx?|csv|zip|png|jpe?g|gif|svg)$/i, // File downloads + /\/admin\//, // Admin pages + /\/accounts\/logout\//, // Logout page + /\/api\//, // API endpoints + ], + }; - // Create and insert the loading indicator - if (!document.getElementById(config.loadingIndicatorId)) { - const loadingIndicator = document.createElement("div"); - loadingIndicator.id = config.loadingIndicatorId; - loadingIndicator.className = "position-fixed top-0 start-0 end-0"; - loadingIndicator.innerHTML = - '
'; - loadingIndicator.style.display = "none"; - loadingIndicator.style.zIndex = "9999"; - document.body.appendChild(loadingIndicator); - } + // Create and insert the loading indicator + if (!document.getElementById(config.loadingIndicatorId)) { + const loadingIndicator = document.createElement("div"); + loadingIndicator.id = config.loadingIndicatorId; + loadingIndicator.className = "position-fixed top-0 start-0 end-0"; + loadingIndicator.innerHTML = + '
'; + loadingIndicator.style.display = "none"; + loadingIndicator.style.zIndex = "9999"; + document.body.appendChild(loadingIndicator); + } - // Get the loading indicator element - const loadingIndicator = document.getElementById(config.loadingIndicatorId); + // Get the loading indicator element + const loadingIndicator = document.getElementById(config.loadingIndicatorId); - // Get the main content container - const mainContent = document.querySelector(config.mainContentSelector); - if (!mainContent) { - console.warn("Main content container not found. AJAX navigation disabled."); - return; - } + // Get the main content container + const mainContent = document.querySelector(config.mainContentSelector); + if (!mainContent) { + console.warn("Main content container not found. AJAX navigation disabled."); + return; + } - // Function to check if a URL should be excluded from AJAX navigation - function shouldExcludeUrl(url) { - for (const pattern of config.excludePatterns) { - if (pattern.test(url)) { - return true; - } - } - return false; - } + // Function to check if a URL should be excluded from AJAX navigation + function shouldExcludeUrl(url) { + for (const pattern of config.excludePatterns) { + if (pattern.test(url)) { + return true; + } + } + return false; + } - // Function to show the loading indicator - function showLoading() { - loadingIndicator.style.display = "block"; - } + // Function to show the loading indicator + function showLoading() { + loadingIndicator.style.display = "block"; + } - // Function to hide the loading indicator - function hideLoading() { - loadingIndicator.style.display = "none"; - } + // Function to hide the loading indicator + function hideLoading() { + loadingIndicator.style.display = "none"; + } - // Function to handle AJAX page navigation - function handlePageNavigation(url, pushState = true) { - if (shouldExcludeUrl(url)) { - window.location.href = url; - return; - } - showLoading(); - const currentScrollPos = window.scrollY; - fetch(url, { - headers: { - "X-Requested-With": "XMLHttpRequest", - "X-AJAX-Navigation": "true", - Accept: "text/html", - }, - }) - .then((response) => { - if (!response.ok) - throw new Error(`Network response was not ok: ${response.status}`); - return response.text(); - }) - .then((html) => { - // Parse the HTML and extract #main-content - const tempDiv = document.createElement("div"); - tempDiv.innerHTML = html; - const newContent = tempDiv.querySelector(config.mainContentSelector); - if (!newContent) throw new Error("Could not find main content in the response"); - mainContent.innerHTML = newContent.innerHTML; - // Update the page title - const titleMatch = html.match(/(.*?)<\/title>/i); - if (titleMatch) document.title = titleMatch[1]; - // Re-initialize dynamic content - reloadScripts(mainContent); - attachEventListeners(); - initializePageScripts(); - if (pushState) { - history.pushState( - { url: url, title: document.title, scrollPos: currentScrollPos }, - document.title, - url, - ); - window.scrollTo({ top: 0, behavior: "smooth" }); - } else if (window.history.state && window.history.state.scrollPos) { - window.scrollTo({ top: window.history.state.scrollPos }); - } - hideLoading(); - }) - .catch((error) => { - console.error("Error during AJAX navigation:", error); - hideLoading(); - window.location.href = url; - }); - } + // Function to handle AJAX page navigation + function handlePageNavigation(url, pushState = true) { + if (shouldExcludeUrl(url)) { + window.location.href = url; + return; + } + showLoading(); + const currentScrollPos = window.scrollY; + fetch(url, { + headers: { + "X-Requested-With": "XMLHttpRequest", + "X-AJAX-Navigation": "true", + Accept: "text/html", + }, + }) + .then((response) => { + if (!response.ok) + throw new Error(`Network response was not ok: ${response.status}`); + return response.text(); + }) + .then((html) => { + // Parse the HTML and extract #main-content + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = html; + const newContent = tempDiv.querySelector(config.mainContentSelector); + if (!newContent) throw new Error("Could not find main content in the response"); + mainContent.innerHTML = newContent.innerHTML; + // Update the page title + const titleMatch = html.match(/<title>(.*?)<\/title>/i); + if (titleMatch) document.title = titleMatch[1]; + // Re-initialize dynamic content + reloadScripts(mainContent); + attachEventListeners(); + initializePageScripts(); + if (pushState) { + history.pushState( + { url: url, title: document.title, scrollPos: currentScrollPos }, + document.title, + url, + ); + window.scrollTo({ top: 0, behavior: "smooth" }); + } else if (window.history.state && window.history.state.scrollPos) { + window.scrollTo({ top: window.history.state.scrollPos }); + } + hideLoading(); + }) + .catch((error) => { + console.error("Error during AJAX navigation:", error); + hideLoading(); + window.location.href = url; + }); + } - // Function to reload and execute scripts in new content - function reloadScripts(container) { - const scripts = container.getElementsByTagName("script"); - for (let script of scripts) { - const newScript = document.createElement("script"); + // Function to reload and execute scripts in new content + function reloadScripts(container) { + const scripts = container.getElementsByTagName("script"); + for (let script of scripts) { + const newScript = document.createElement("script"); - // Copy all attributes - Array.from(script.attributes).forEach((attr) => { - newScript.setAttribute(attr.name, attr.value); - }); + // Copy all attributes + Array.from(script.attributes).forEach((attr) => { + newScript.setAttribute(attr.name, attr.value); + }); - // Copy inline script content - newScript.textContent = script.textContent; + // Copy inline script content + newScript.textContent = script.textContent; - // Replace old script with new one - script.parentNode.replaceChild(newScript, script); - } - } + // Replace old script with new one + script.parentNode.replaceChild(newScript, script); + } + } - // Function to handle form submissions - function handleFormSubmission(form, e) { - e.preventDefault(); + // Function to handle form submissions + function handleFormSubmission(form, e) { + e.preventDefault(); - // Show loading indicator - showLoading(); + // Show loading indicator + showLoading(); - // Get form data - const formData = new FormData(form); - const method = form.method.toLowerCase(); - const url = form.action || window.location.href; + // Get form data + const formData = new FormData(form); + const method = form.method.toLowerCase(); + const url = form.action || window.location.href; - // Configure fetch options - const fetchOptions = { - method: method, - headers: { - "X-AJAX-Navigation": "true", - }, - }; + // Configure fetch options + const fetchOptions = { + method: method, + headers: { + "X-AJAX-Navigation": "true", + }, + }; - // Handle different HTTP methods - if (method === "get") { - const queryParams = new URLSearchParams(formData).toString(); - handlePageNavigation(url + (queryParams ? "?" + queryParams : "")); - } else { - fetchOptions.body = formData; + // Handle different HTTP methods + if (method === "get") { + const queryParams = new URLSearchParams(formData).toString(); + handlePageNavigation(url + (queryParams ? "?" + queryParams : "")); + } else { + fetchOptions.body = formData; - fetch(url, fetchOptions) - .then((response) => { - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - return response.json(); - }) - .then((data) => { - if (data.redirect) { - // Handle server-side redirects - handlePageNavigation(data.redirect, true); - } else { - // Update page content - mainContent.innerHTML = data.html; - document.title = data.title || document.title; + fetch(url, fetchOptions) + .then((response) => { + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + return response.json(); + }) + .then((data) => { + if (data.redirect) { + // Handle server-side redirects + handlePageNavigation(data.redirect, true); + } else { + // Update page content + mainContent.innerHTML = data.html; + document.title = data.title || document.title; - // Re-initialize dynamic content - reloadScripts(mainContent); - attachEventListeners(); - initializePageScripts(); + // Re-initialize dynamic content + reloadScripts(mainContent); + attachEventListeners(); + initializePageScripts(); - // Update URL if needed - if (data.url) { - history.pushState({ url: data.url }, document.title, data.url); - } - } - }) - .catch((error) => { - console.error("Form submission error:", error); - // Fallback to traditional form submission - form.submit(); - }) - .finally(() => { - hideLoading(); - }); - } - } + // Update URL if needed + if (data.url) { + history.pushState({ url: data.url }, document.title, data.url); + } + } + }) + .catch((error) => { + console.error("Form submission error:", error); + // Fallback to traditional form submission + form.submit(); + }) + .finally(() => { + hideLoading(); + }); + } + } - // Function to initialize scripts needed for the new page content - function initializePageScripts() { - // Re-initialize any custom scripts that might be needed - if (typeof setupAjaxPagination === "function") { - setupAjaxPagination(); - } + // Function to initialize scripts needed for the new page content + function initializePageScripts() { + // Re-initialize any custom scripts that might be needed + if (typeof setupAjaxPagination === "function") { + setupAjaxPagination(); + } - // Initialize Bootstrap tooltips, popovers, etc. - if (typeof bootstrap !== "undefined") { - // Initialize tooltips - const tooltipTriggerList = [].slice.call( - document.querySelectorAll('[data-bs-toggle="tooltip"]'), - ); - tooltipTriggerList.map(function (tooltipTriggerEl) { - return new bootstrap.Tooltip(tooltipTriggerEl); - }); + // Initialize Bootstrap tooltips, popovers, etc. + if (typeof bootstrap !== "undefined") { + // Initialize tooltips + const tooltipTriggerList = [].slice.call( + document.querySelectorAll('[data-bs-toggle="tooltip"]'), + ); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); - // Initialize popovers - const popoverTriggerList = [].slice.call( - document.querySelectorAll('[data-bs-toggle="popover"]'), - ); - popoverTriggerList.map(function (popoverTriggerEl) { - return new bootstrap.Popover(popoverTriggerEl); - }); - } - } + // Initialize popovers + const popoverTriggerList = [].slice.call( + document.querySelectorAll('[data-bs-toggle="popover"]'), + ); + popoverTriggerList.map(function (popoverTriggerEl) { + return new bootstrap.Popover(popoverTriggerEl); + }); + } + } - // Function to attach event listeners to forms and links - function attachEventListeners() { - // Handle AJAX navigation links - document.querySelectorAll(config.navLinkSelector).forEach((link) => { - if (!link.dataset.ajaxNavInitialized) { - link.addEventListener("click", function (e) { - if (e.ctrlKey || e.metaKey || e.shiftKey || shouldExcludeUrl(this.href)) { - return; // Let the browser handle these cases - } - e.preventDefault(); - handlePageNavigation(this.href); - }); - link.dataset.ajaxNavInitialized = "true"; - } - }); + // Function to attach event listeners to forms and links + function attachEventListeners() { + // Handle AJAX navigation links + document.querySelectorAll(config.navLinkSelector).forEach((link) => { + if (!link.dataset.ajaxNavInitialized) { + link.addEventListener("click", function (e) { + if (e.ctrlKey || e.metaKey || e.shiftKey || shouldExcludeUrl(this.href)) { + return; // Let the browser handle these cases + } + e.preventDefault(); + handlePageNavigation(this.href); + }); + link.dataset.ajaxNavInitialized = "true"; + } + }); - // Handle forms with AJAX - document - .querySelectorAll("form.ajax-form, form.search-form, form.filter-form") - .forEach((form) => { - if (!form.dataset.ajaxFormInitialized) { - form.addEventListener("submit", (e) => handleFormSubmission(form, e)); - form.dataset.ajaxFormInitialized = "true"; - } - }); - } + // Handle forms with AJAX + document + .querySelectorAll("form.ajax-form, form.search-form, form.filter-form") + .forEach((form) => { + if (!form.dataset.ajaxFormInitialized) { + form.addEventListener("submit", (e) => handleFormSubmission(form, e)); + form.dataset.ajaxFormInitialized = "true"; + } + }); + } - // Initial attachment of event listeners - attachEventListeners(); + // Initial attachment of event listeners + attachEventListeners(); - // Handle browser back/forward buttons - window.addEventListener("popstate", function (event) { - if (event.state && event.state.url) { - handlePageNavigation(event.state.url, false); - } else { - // Fallback to current URL if no state - handlePageNavigation(window.location.href, false); - } - }); - } + // Handle browser back/forward buttons + window.addEventListener("popstate", function (event) { + if (event.state && event.state.url) { + handlePageNavigation(event.state.url, false); + } else { + // Fallback to current URL if no state + handlePageNavigation(window.location.href, false); + } + }); + } }); diff --git a/dashboard_project/static/js/ajax-pagination.js b/dashboard_project/static/js/ajax-pagination.js index 16f1889..3dcff3a 100644 --- a/dashboard_project/static/js/ajax-pagination.js +++ b/dashboard_project/static/js/ajax-pagination.js @@ -6,101 +6,101 @@ */ document.addEventListener("DOMContentLoaded", function () { - // Initialize AJAX pagination - setupAjaxPagination(); + // Initialize AJAX pagination + setupAjaxPagination(); - // Function to set up AJAX pagination for the entire application - function setupAjaxPagination() { - // Configuration - can be customized per page if needed - const config = { - contentContainerId: "ajax-content-container", // ID of the container to update - loadingSpinnerId: "ajax-loading-spinner", // ID of the loading spinner - paginationLinkClass: "pagination-link", // Class for pagination links - retryMessage: "An error occurred while loading data. Please try again.", - }; + // Function to set up AJAX pagination for the entire application + function setupAjaxPagination() { + // Configuration - can be customized per page if needed + const config = { + contentContainerId: "ajax-content-container", // ID of the container to update + loadingSpinnerId: "ajax-loading-spinner", // ID of the loading spinner + paginationLinkClass: "pagination-link", // Class for pagination links + retryMessage: "An error occurred while loading data. Please try again.", + }; - // Get container elements - const contentContainer = document.getElementById(config.contentContainerId); - const loadingSpinner = document.getElementById(config.loadingSpinnerId); + // Get container elements + const contentContainer = document.getElementById(config.contentContainerId); + const loadingSpinner = document.getElementById(config.loadingSpinnerId); - // Exit if the page doesn't have the required elements - if (!contentContainer || !loadingSpinner) return; + // Exit if the page doesn't have the required elements + if (!contentContainer || !loadingSpinner) return; - // Function to handle pagination clicks - function setupPaginationListeners() { - document.querySelectorAll("." + config.paginationLinkClass).forEach((link) => { - link.addEventListener("click", function (e) { - e.preventDefault(); - handleAjaxNavigation(this.href); + // Function to handle pagination clicks + function setupPaginationListeners() { + document.querySelectorAll("." + config.paginationLinkClass).forEach((link) => { + link.addEventListener("click", function (e) { + e.preventDefault(); + handleAjaxNavigation(this.href); - // Get the page number if available - const page = this.getAttribute("data-page"); + // Get the page number if available + const page = this.getAttribute("data-page"); - // Update browser URL without refreshing - const newUrl = this.href; - history.pushState({ url: newUrl, page: page }, "", newUrl); - }); - }); - } + // Update browser URL without refreshing + const newUrl = this.href; + history.pushState({ url: newUrl, page: page }, "", newUrl); + }); + }); + } - // Function to handle AJAX navigation - function handleAjaxNavigation(url) { - // Show loading spinner - contentContainer.classList.add("d-none"); - loadingSpinner.classList.remove("d-none"); + // Function to handle AJAX navigation + function handleAjaxNavigation(url) { + // Show loading spinner + contentContainer.classList.add("d-none"); + loadingSpinner.classList.remove("d-none"); - // Fetch data via AJAX - fetch(url, { - headers: { - "X-Requested-With": "XMLHttpRequest", - }, - }) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok: ${response.status}`); - } - return response.json(); - }) - .then((data) => { - if (data.status === "success") { - // Update the content - contentContainer.innerHTML = data.html_data; + // Fetch data via AJAX + fetch(url, { + headers: { + "X-Requested-With": "XMLHttpRequest", + }, + }) + .then((response) => { + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.status}`); + } + return response.json(); + }) + .then((data) => { + if (data.status === "success") { + // Update the content + contentContainer.innerHTML = data.html_data; - // Re-attach event listeners to new pagination links - setupPaginationListeners(); + // Re-attach event listeners to new pagination links + setupPaginationListeners(); - // Update any summary data if present and the page provides it - if (typeof updateSummary === "function" && data.summary) { - updateSummary(data); - } + // Update any summary data if present and the page provides it + if (typeof updateSummary === "function" && data.summary) { + updateSummary(data); + } - // Hide loading spinner, show content - loadingSpinner.classList.add("d-none"); - contentContainer.classList.remove("d-none"); + // Hide loading spinner, show content + loadingSpinner.classList.add("d-none"); + contentContainer.classList.remove("d-none"); - // Scroll to top of the content container - contentContainer.scrollIntoView({ behavior: "smooth", block: "start" }); - } - }) - .catch((error) => { - console.error("Error fetching data:", error); - loadingSpinner.classList.add("d-none"); - contentContainer.classList.remove("d-none"); - alert(config.retryMessage); - }); - } + // Scroll to top of the content container + contentContainer.scrollIntoView({ behavior: "smooth", block: "start" }); + } + }) + .catch((error) => { + console.error("Error fetching data:", error); + loadingSpinner.classList.add("d-none"); + contentContainer.classList.remove("d-none"); + alert(config.retryMessage); + }); + } - // Initial setup of event listeners - setupPaginationListeners(); + // Initial setup of event listeners + setupPaginationListeners(); - // Handle browser back/forward buttons - window.addEventListener("popstate", function (event) { - if (event.state && event.state.url) { - handleAjaxNavigation(event.state.url); - } else { - // If no state, fetch current URL - handleAjaxNavigation(window.location.href); - } - }); - } + // Handle browser back/forward buttons + window.addEventListener("popstate", function (event) { + if (event.state && event.state.url) { + handleAjaxNavigation(event.state.url); + } else { + // If no state, fetch current URL + handleAjaxNavigation(window.location.href); + } + }); + } }); diff --git a/dashboard_project/static/js/dashboard.js b/dashboard_project/static/js/dashboard.js index 2fc82d3..6cf7388 100644 --- a/dashboard_project/static/js/dashboard.js +++ b/dashboard_project/static/js/dashboard.js @@ -7,272 +7,272 @@ */ 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, - }); - } - }); - } + // 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(); - } - }); + // 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"); + // 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(); - } - }); - }); - } + // 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); + // 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) => { - if (!response.ok) { - throw new Error(`Network response was not ok: ${response.status}`); - } - return response.json(); - }) - .then((data) => { - console.log("Dashboard API response:", data); - updateDashboardStats(data); - updateDashboardCharts(data); + fetch(`/dashboard/api/dashboard/${dashboardId}/data/?time_range=${timeRange || "all"}`) + .then((response) => { + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.status}`); + } + return response.json(); + }) + .then((data) => { + console.log("Dashboard API response:", 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); + // 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(); + 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 = ` + // 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); - }); - } + 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; - } + // 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 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 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; - } - } + // 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) { - // Check if Plotly is available - if (!window.Plotly) { - console.error("Plotly library not loaded!"); - document.querySelectorAll(".chart-container").forEach((container) => { - container.innerHTML = - '<div class="text-center py-5"><p class="text-danger">Chart library not available. Please refresh the page.</p></div>'; - }); - return; - } + // Function to update dashboard charts + function updateDashboardCharts(data) { + // Check if Plotly is available + if (!window.Plotly) { + console.error("Plotly library not loaded!"); + document.querySelectorAll(".chart-container").forEach((container) => { + container.innerHTML = + '<div class="text-center py-5"><p class="text-danger">Chart library not available. Please refresh the page.</p></div>'; + }); + return; + } - // Update sessions over time chart - const timeSeriesData = data.time_series_data; - if (timeSeriesData && timeSeriesData.length > 0) { - try { - const timeSeriesX = timeSeriesData.map((item) => item.date); - const timeSeriesY = timeSeriesData.map((item) => item.count); + // Update sessions over time chart + const timeSeriesData = data.time_series_data; + if (timeSeriesData && timeSeriesData.length > 0) { + try { + 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", - }, - }, - ); - } catch (error) { - console.error("Error rendering time series chart:", error); - document.getElementById("sessions-time-chart").innerHTML = - '<div class="text-center py-5"><p class="text-danger">Error rendering chart.</p></div>'; - } - } else { - document.getElementById("sessions-time-chart").innerHTML = - '<div class="text-center py-5"><p class="text-muted">No time series data available</p></div>'; - } + 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", + }, + }, + ); + } catch (error) { + console.error("Error rendering time series chart:", error); + document.getElementById("sessions-time-chart").innerHTML = + '<div class="text-center py-5"><p class="text-danger">Error rendering chart.</p></div>'; + } + } else { + document.getElementById("sessions-time-chart").innerHTML = + '<div class="text-center py-5"><p class="text-muted">No time series data available</p></div>'; + } - // 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)"; - }); + // 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 }, - }, - ); - } + 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); + // 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", - }, - }, - ); - } + 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); + // 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 }, - }, - ); - } - } + 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"); + // 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(); - } - }); - }); + // Fetch updated data via AJAX + if (dashboardId) { + fetchDashboardData(dashboardId); + e.preventDefault(); + } + }); + }); }); diff --git a/dashboard_project/static/js/main.js b/dashboard_project/static/js/main.js index e8a0d5d..0983627 100644 --- a/dashboard_project/static/js/main.js +++ b/dashboard_project/static/js/main.js @@ -6,147 +6,147 @@ */ 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 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); - }); + // 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"); - }); - } + // 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); + // 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, - ); - }); + // 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(); - } - }); - }); + // 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(); - }); - }); + // 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; - } - }); - }); + // 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(); - } - }); - }); + // 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"); - } - }); - }); + // 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(); - }); - }); + // 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"); - }); - } + // 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(); - }); - }); + // 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); - }); - }); + 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"); - } - } + // Handle sidebar collapse on small screens + function handleSidebarOnResize() { + if (window.innerWidth < 768) { + document.querySelector(".sidebar")?.classList.remove("show"); + } + } - window.addEventListener("resize", handleSidebarOnResize); + window.addEventListener("resize", handleSidebarOnResize); }); diff --git a/dashboard_project/templates/accounts/login.html b/dashboard_project/templates/accounts/login.html index 450e4a8..232a4a7 100644 --- a/dashboard_project/templates/accounts/login.html +++ b/dashboard_project/templates/accounts/login.html @@ -1,31 +1,27 @@ <!-- templates/accounts/login.html --> -{% 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"> - <div class="card mt-4"> - <div class="card-header"> - <h4 class="card-title mb-0">Login</h4> - </div> - <div class="card-body"> - <form method="post"> - {% 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> - </div> - </div> - </div> - </div> +{% 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"> + <div class="card mt-4"> + <div class="card-header"> + <h4 class="card-title mb-0">Login</h4> + </div> + <div class="card-body"> + <form method="post"> + {% 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> + </div> + </div> + </div> + </div> {% endblock %} diff --git a/dashboard_project/templates/accounts/password_change.html b/dashboard_project/templates/accounts/password_change.html index 9228cc3..09f5957 100644 --- a/dashboard_project/templates/accounts/password_change.html +++ b/dashboard_project/templates/accounts/password_change.html @@ -1,37 +1,34 @@ <!-- 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" - > - <h1 class="h2">Change Password</h1> - <div class="btn-toolbar mb-2 mb-md-0"> - <a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary"> - <i class="fas fa-arrow-left"></i> Back to Profile - </a> - </div> - </div> + <div + class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom" + > + <h1 class="h2">Change Password</h1> + <div class="btn-toolbar mb-2 mb-md-0"> + <a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary"> + <i class="fas fa-arrow-left"></i> Back to Profile + </a> + </div> + </div> - <div class="row justify-content-center"> - <div class="col-md-6"> - <div class="card"> - <div class="card-header"> - <h5 class="card-title mb-0">Change Your Password</h5> - </div> - <div class="card-body"> - <form method="post"> - {% csrf_token %} - {{ form|crispy }} - <div class="d-grid gap-2"> - <button type="submit" class="btn btn-primary">Change Password</button> - </div> - </form> - </div> - </div> - </div> - </div> + <div class="row justify-content-center"> + <div class="col-md-6"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title mb-0">Change Your Password</h5> + </div> + <div class="card-body"> + <form method="post"> + {% csrf_token %} {{ form|crispy }} + <div class="d-grid gap-2"> + <button type="submit" class="btn btn-primary">Change Password</button> + </div> + </form> + </div> + </div> + </div> + </div> {% endblock %} diff --git a/dashboard_project/templates/accounts/password_change_done.html b/dashboard_project/templates/accounts/password_change_done.html index 1a8c441..76f47d5 100644 --- a/dashboard_project/templates/accounts/password_change_done.html +++ b/dashboard_project/templates/accounts/password_change_done.html @@ -1,44 +1,38 @@ <!-- 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" - > - <h1 class="h2">Password Changed</h1> - <div class="btn-toolbar mb-2 mb-md-0"> - <a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary"> - <i class="fas fa-arrow-left"></i> Back to Profile - </a> - </div> - </div> + <div + class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom" + > + <h1 class="h2">Password Changed</h1> + <div class="btn-toolbar mb-2 mb-md-0"> + <a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary"> + <i class="fas fa-arrow-left"></i> Back to Profile + </a> + </div> + </div> - <div class="row justify-content-center"> - <div class="col-md-6"> - <div class="card"> - <div class="card-header bg-success text-white"> - <h5 class="card-title mb-0">Password Changed Successfully</h5> - </div> - <div class="card-body text-center"> - <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> - </div> + <div class="row justify-content-center"> + <div class="col-md-6"> + <div class="card"> + <div class="card-header bg-success text-white"> + <h5 class="card-title mb-0">Password Changed Successfully</h5> + </div> + <div class="card-body text-center"> + <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> + </div> - <div class="mt-4"> - <a href="{% url 'profile' %}" class="btn btn-primary">Return to Profile</a> - <a href="{% url 'dashboard' %}" class="btn btn-outline-secondary ms-2" - >Go to Dashboard</a - > - </div> - </div> - </div> - </div> - </div> + <div class="mt-4"> + <a href="{% url 'profile' %}" class="btn btn-primary">Return to Profile</a> + <a href="{% url 'dashboard' %}" class="btn btn-outline-secondary ms-2" + >Go to Dashboard</a + > + </div> + </div> + </div> + </div> + </div> {% endblock %} diff --git a/dashboard_project/templates/accounts/profile.html b/dashboard_project/templates/accounts/profile.html index 69422b1..b768dbb 100644 --- a/dashboard_project/templates/accounts/profile.html +++ b/dashboard_project/templates/accounts/profile.html @@ -1,213 +1,187 @@ <!-- 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" - > - <h1 class="h2">My Profile</h1> - <div class="btn-toolbar mb-2 mb-md-0"> - <a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary"> - <i class="fas fa-arrow-left"></i> Back to Dashboard - </a> - </div> - </div> + <div + class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom" + > + <h1 class="h2">My Profile</h1> + <div class="btn-toolbar mb-2 mb-md-0"> + <a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary"> + <i class="fas fa-arrow-left"></i> Back to Dashboard + </a> + </div> + </div> - <div class="row"> - <div class="col-md-6"> - <div class="card"> - <div class="card-header"> - <h5 class="card-title mb-0">Account Information</h5> - </div> - <div class="card-body"> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Username:</div> - <div class="col-md-8">{{ user.username }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Email:</div> - <div class="col-md-8">{{ user.email }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Company:</div> - <div class="col-md-8"> - {% if user.company %} - {{ user.company.name }} - {% else %} - <span class="text-muted">Not assigned to a company</span> - {% endif %} - </div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Role:</div> - <div class="col-md-8"> - {% if user.is_staff %} - <span class="badge bg-danger">Admin</span> - {% elif user.is_company_admin %} - <span class="badge bg-primary">Company Admin</span> - {% else %} - <span class="badge bg-secondary">User</span> - {% endif %} - </div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Last Login:</div> - <div class="col-md-8">{{ user.last_login|date:"F d, Y H:i" }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Date Joined:</div> - <div class="col-md-8">{{ user.date_joined|date:"F d, Y H:i" }}</div> - </div> - </div> - <div class="card-footer"> - <a href="{% url 'password_change' %}" class="btn btn-primary" - >Change Password</a - > - </div> - </div> - </div> + <div class="row"> + <div class="col-md-6"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title mb-0">Account Information</h5> + </div> + <div class="card-body"> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Username:</div> + <div class="col-md-8">{{ user.username }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Email:</div> + <div class="col-md-8">{{ user.email }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Company:</div> + <div class="col-md-8"> + {% if user.company %} + {{ user.company.name }} + {% else %} + <span class="text-muted">Not assigned to a company</span> + {% endif %} + </div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Role:</div> + <div class="col-md-8"> + {% if user.is_staff %} + <span class="badge bg-danger">Admin</span> + {% elif user.is_company_admin %} + <span class="badge bg-primary">Company Admin</span> + {% else %} + <span class="badge bg-secondary">User</span> + {% endif %} + </div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Last Login:</div> + <div class="col-md-8">{{ user.last_login|date:"F d, Y H:i" }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Date Joined:</div> + <div class="col-md-8">{{ user.date_joined|date:"F d, Y H:i" }}</div> + </div> + </div> + <div class="card-footer"> + <a href="{% url 'password_change' %}" class="btn btn-primary">Change Password</a> + </div> + </div> + </div> - {% if user.company %} - <div class="col-md-6"> - <div class="card"> - <div class="card-header"> - <h5 class="card-title mb-0">Company Information</h5> - </div> - <div class="card-body"> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Company Name:</div> - <div class="col-md-8">{{ user.company.name }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Description:</div> - <div class="col-md-8"> - {{ user.company.description|default:"No description available." }} - </div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Created:</div> - <div class="col-md-8">{{ user.company.created_at|date:"F d, Y" }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Total Employees:</div> - <div class="col-md-8">{{ user.company.employees.count }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Data Sources:</div> - <div class="col-md-8">{{ user.company.data_sources.count }}</div> - </div> - </div> - </div> - </div> - {% endif %} - </div> + {% if user.company %} + <div class="col-md-6"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title mb-0">Company Information</h5> + </div> + <div class="card-body"> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Company Name:</div> + <div class="col-md-8">{{ user.company.name }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Description:</div> + <div class="col-md-8"> + {{ user.company.description|default:"No description available." }} + </div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Created:</div> + <div class="col-md-8">{{ user.company.created_at|date:"F d, Y" }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Total Employees:</div> + <div class="col-md-8">{{ user.company.employees.count }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Data Sources:</div> + <div class="col-md-8">{{ user.company.data_sources.count }}</div> + </div> + </div> + </div> + </div> + {% endif %} + </div> - {% if user.is_company_admin or user.is_staff %} - <div class="row mt-4"> - <div class="col-12"> - <div class="card"> - <div class="card-header"> - <h5 class="card-title mb-0">Admin Actions</h5> - </div> - <div class="card-body"> - <div class="row"> - {% if user.is_staff %} - <div class="col-md-4 mb-3"> - <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> - <a - href="{% url 'admin:accounts_customuser_changelist' %}" - class="btn btn-primary" - >Manage Users</a - > - </div> - </div> - </div> - <div class="col-md-4 mb-3"> - <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> - <a - href="{% url 'admin:accounts_company_changelist' %}" - class="btn btn-primary" - >Manage Companies</a - > - </div> - </div> - </div> - <div class="col-md-4 mb-3"> - <div class="card h-100"> - <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 - > - </div> - </div> - </div> - {% elif user.is_company_admin %} - <div class="col-md-4 mb-3"> - <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" - >Manage Dashboards</a - > - </div> - </div> - </div> - <div class="col-md-4 mb-3"> - <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 - > - </div> - </div> - </div> - <div class="col-md-4 mb-3"> - <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" - >Search Sessions</a - > - </div> - </div> - </div> - {% endif %} - </div> - </div> - </div> - </div> - </div> - {% endif %} + {% if user.is_company_admin or user.is_staff %} + <div class="row mt-4"> + <div class="col-12"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title mb-0">Admin Actions</h5> + </div> + <div class="card-body"> + <div class="row"> + {% if user.is_staff %} + <div class="col-md-4 mb-3"> + <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> + <a + href="{% url 'admin:accounts_customuser_changelist' %}" + class="btn btn-primary" + >Manage Users</a + > + </div> + </div> + </div> + <div class="col-md-4 mb-3"> + <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> + <a + href="{% url 'admin:accounts_company_changelist' %}" + class="btn btn-primary" + >Manage Companies</a + > + </div> + </div> + </div> + <div class="col-md-4 mb-3"> + <div class="card h-100"> + <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> + </div> + </div> + </div> + {% elif user.is_company_admin %} + <div class="col-md-4 mb-3"> + <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" + >Manage Dashboards</a + > + </div> + </div> + </div> + <div class="col-md-4 mb-3"> + <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> + </div> + </div> + </div> + <div class="col-md-4 mb-3"> + <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" + >Search Sessions</a + > + </div> + </div> + </div> + {% endif %} + </div> + </div> + </div> + </div> + </div> + {% endif %} {% endblock %} diff --git a/dashboard_project/templates/accounts/register.html b/dashboard_project/templates/accounts/register.html index cc85a85..376b977 100644 --- a/dashboard_project/templates/accounts/register.html +++ b/dashboard_project/templates/accounts/register.html @@ -1,31 +1,27 @@ <!-- templates/accounts/register.html --> -{% 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"> - <div class="card mt-4"> - <div class="card-header"> - <h4 class="card-title mb-0">Register</h4> - </div> - <div class="card-body"> - <form method="post"> - {% 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> - </div> - </div> - </div> - </div> +{% 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"> + <div class="card mt-4"> + <div class="card-header"> + <h4 class="card-title mb-0">Register</h4> + </div> + <div class="card-body"> + <form method="post"> + {% 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> + </div> + </div> + </div> + </div> {% endblock %} diff --git a/dashboard_project/templates/base.html b/dashboard_project/templates/base.html index 1545d34..ef7a361 100644 --- a/dashboard_project/templates/base.html +++ b/dashboard_project/templates/base.html @@ -2,339 +2,315 @@ {% load static %} <!doctype html> <html lang="en"> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>{% block title %}Chat Analytics Dashboard{% endblock %} + + + + {% block title %}Chat Analytics Dashboard{% endblock %} - - + + - - + + - - + + - - - + + + - {% block extra_css %}{% endblock %} - + {% block extra_css %}{% endblock %} + - - - -
-
- - +
+
+ + - -
- {# {% if messages %} #} - {#
#} - {# {% for message in messages %} #} - {# #} - {# {% endfor %} #} - {#
#} - {# {% endif %} #} + +
+ {# {% if messages %} #} + {#
#} + {# {% for message in messages %} #} + {# #} + {# {% endfor %} #} + {#
#} + {# {% endif %} #} -
- {% block content %} - {% endblock %} -
-
-
-
+
{% block content %}{% endblock %}
+ +
+
-
-
-

© {% now "Y" %} KJANAT All rights reserved. | Chat Analytics Dashboard.

-
-
+
+
+

© {% now "Y" %} KJANAT All rights reserved. | Chat Analytics Dashboard.

+
+
- - - - + + + + - - - - + + + + - - + + - - + + - {% block extra_js %} - {{ block.super }} - {% if messages %} -
- -
+ {% block extra_js %} + {{ block.super }} + {% if messages %} +
+ +
- {% for message in messages %} - - - {% endfor %} + {% for message in messages %} + + + {% endfor %} - - {% endif %} - {% endblock %} - + toast.show(); + } + } + }); + + {% endif %} + {% endblock %} + diff --git a/dashboard_project/templates/dashboard/chat_session_detail.html b/dashboard_project/templates/dashboard/chat_session_detail.html index 2661ad5..fe6dfaf 100644 --- a/dashboard_project/templates/dashboard/chat_session_detail.html +++ b/dashboard_project/templates/dashboard/chat_session_detail.html @@ -1,145 +1,133 @@ {% extends 'base.html' %} - -{% block title %}Chat Session {{ session.session_id }} | Chat Analytics{% endblock %} - -{% block content %} -
-

Chat Session: {{ session.session_id }}

- -
- -
-
-
-
-
Session Information
-
-
-
-
-

Session ID: {{ session.session_id }}

-

- Start Time: - {{ session.start_time|date:"F d, Y H:i" }} -

-

- End Time: {{ session.end_time|date:"F d, Y H:i" }} -

-

- IP Address: {{ session.ip_address|default:"N/A" }} -

-

Country: {{ session.country|default:"N/A" }}

-

Language: {{ session.language|default:"N/A" }}

-
-
-

Messages Sent: {{ session.messages_sent }}

-

- Average Response Time: - {{ session.avg_response_time|floatformat:2 }}s -

-

Tokens: {{ session.tokens }}

-

- Token Cost: €{{ session.tokens_eur|floatformat:2 }} -

-

Category: {{ session.category|default:"N/A" }}

-

- Sentiment: - {% if session.sentiment %} - {% if 'positive' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'negative' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'neutral' in session.sentiment|lower %} - {{ session.sentiment }} - {% else %} - {{ session.sentiment }} - {% endif %} - {% else %} - N/A - {% endif %} -

-
-
-
-
-

Initial Message:

-
-
-

{{ session.initial_msg|default:"N/A" }}

-
-
-
-
-
-
-
-
-
-
-
Additional Info
-
-
-

- Escalated: {% if session.escalated %} - Yes - {% else %} - No - {% endif %} -

-

- Forwarded to HR: {% if session.forwarded_hr %} - Yes - {% else %} - No - {% endif %} -

-

User Rating: {{ session.user_rating|default:"N/A" }}

-
-

- Data Source: - {{ session.data_source.name }} -

-

Company: {{ session.data_source.company.name }}

-
-
-
-
- -
-
-
-
-
Full Transcript
-
-
- {% if session.full_transcript %} -
-
-{{ session.full_transcript }}
-
- {% else %} -

No transcript available.

- {% endif %} -
-
-
-
+{% block title %} + Chat Session {{ session.session_id }} | Chat Analytics +{% endblock %} +{% block content %} +
+

Chat Session: {{ session.session_id }}

+ +
+ +
+
+
+
+
Session Information
+
+
+
+
+

Session ID: {{ session.session_id }}

+

+ Start Time: + {{ session.start_time|date:"F d, Y H:i" }} +

+

End Time: {{ session.end_time|date:"F d, Y H:i" }}

+

IP Address: {{ session.ip_address|default:"N/A" }}

+

Country: {{ session.country|default:"N/A" }}

+

Language: {{ session.language|default:"N/A" }}

+
+
+

Messages Sent: {{ session.messages_sent }}

+

+ Average Response Time: + {{ session.avg_response_time|floatformat:2 }}s +

+

Tokens: {{ session.tokens }}

+

Token Cost: €{{ session.tokens_eur|floatformat:2 }}

+

Category: {{ session.category|default:"N/A" }}

+

+ Sentiment: + {% if session.sentiment %} + {% if 'positive' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'negative' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'neutral' in session.sentiment|lower %} + {{ session.sentiment }} + {% else %} + {{ session.sentiment }} + {% endif %} + {% else %} + N/A + {% endif %} +

+
+
+
+
+

Initial Message:

+
+
+

{{ session.initial_msg|default:"N/A" }}

+
+
+
+
+
+
+
+
+
+
+
Additional Info
+
+
+

+ Escalated: {% if session.escalated %} + Yes + {% else %} + No + {% endif %} +

+

+ Forwarded to HR: {% if session.forwarded_hr %} + Yes + {% else %} + No + {% endif %} +

+

User Rating: {{ session.user_rating|default:"N/A" }}

+
+

+ Data Source: + {{ session.data_source.name }} +

+

Company: {{ session.data_source.company.name }}

+
+
+
+
+ +
+
+
+
+
Full Transcript
+
+
+ {% if session.full_transcript %} +
+
+{{ session.full_transcript }}
+
+ {% else %} +

No transcript available.

+ {% endif %} +
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/dashboard.html b/dashboard_project/templates/dashboard/dashboard.html index 575ed6e..c4c2674 100644 --- a/dashboard_project/templates/dashboard/dashboard.html +++ b/dashboard_project/templates/dashboard/dashboard.html @@ -1,329 +1,320 @@ -{% extends 'base.html' %} -{% load static %} - -{% block title %}Dashboard | Chat Analytics{% endblock %} - +{% extends 'base.html' %} {% load static %} +{% block title %} + Dashboard | Chat Analytics +{% endblock %} {% block content %} -
-

{{ selected_dashboard.name }}

-
- - -
-
+
+

{{ selected_dashboard.name }}

+
+ + +
+
-
-
-
-
-
Total Sessions
-

{{ dashboard_data.total_sessions }}

-

Chat conversations

-
-
-
-
-
-
-
Avg Response Time
-

{{ dashboard_data.avg_response_time }}s

-

Average response

-
-
-
-
-
-
-
Total Tokens
-

{{ dashboard_data.total_tokens }}

-

Total usage

-
-
-
-
-
-
-
Total Cost
-

€{{ dashboard_data.total_cost }}

-

Token cost

-
-
-
-
+
+
+
+
+
Total Sessions
+

{{ dashboard_data.total_sessions }}

+

Chat conversations

+
+
+
+
+
+
+
Avg Response Time
+

{{ dashboard_data.avg_response_time }}s

+

Average response

+
+
+
+
+
+
+
Total Tokens
+

{{ dashboard_data.total_tokens }}

+

Total usage

+
+
+
+
+
+
+
Total Cost
+

€{{ dashboard_data.total_cost }}

+

Token cost

+
+
+
+
-
-
-
-
-
Sessions Over Time
-
-
-
-
-
-
-
-
-
-
Sentiment Analysis
-
-
-
-
-
-
-
+
+
+
+
+
Sessions Over Time
+
+
+
+
+
+
+
+
+
+
Sentiment Analysis
+
+
+
+
+
+
+
-
-
-
-
-
Top Countries
-
-
-
-
-
-
-
-
-
-
Categories
-
-
-
-
-
-
-
+
+
+
+
+
Top Countries
+
+
+
+
+
+
+
+
+
+
Categories
+
+
+
+
+
+
+
{% endblock %} - {% block extra_js %} - - - - - - - + + + + + + + - + Plotly.newPlot( + "category-chart", + [ + { + labels: categoryLabels, + values: categoryValues, + type: "pie", + textinfo: "label+percent", + insidetextorientation: "radial", + }, + ], + { + margin: { t: 10, r: 10, b: 10, l: 10 }, + }, + ); + } else { + document.getElementById("category-chart").innerHTML = + '

No category data available

'; + } + } catch (error) { + console.error("Error rendering charts:", error); + document.querySelectorAll(".chart-container").forEach((container) => { + container.innerHTML = + '

Error loading chart data. Please refresh the page.

'; + }); + } + }); + {% endblock %} diff --git a/dashboard_project/templates/dashboard/dashboard_confirm_delete.html b/dashboard_project/templates/dashboard/dashboard_confirm_delete.html index c407162..6c8e913 100644 --- a/dashboard_project/templates/dashboard/dashboard_confirm_delete.html +++ b/dashboard_project/templates/dashboard/dashboard_confirm_delete.html @@ -1,44 +1,40 @@ -{% extends 'base.html' %} - -{% block title %}Delete Dashboard | Chat Analytics{% endblock %} - +{% extends 'base.html' %} {% block title %}Delete Dashboard | Chat Analytics{% endblock %} {% block content %} -
-

Delete Dashboard

- -
+
+

Delete Dashboard

+ +
-
-
-
-
-
Confirm Deletion
-
-
-

- Are you sure you want to delete the dashboard - "{{ dashboard.name }}"? -

-

- This action cannot be undone. The dashboard will be permanently deleted, but - the underlying data sources will remain intact. -

+
+
+
+
+
Confirm Deletion
+
+
+

+ Are you sure you want to delete the dashboard "{{ dashboard.name }}"? +

+

+ This action cannot be undone. The dashboard will be permanently deleted, but the + underlying data sources will remain intact. +

-
- {% csrf_token %} -
- Cancel - -
-
-
-
-
-
+
+ {% csrf_token %} +
+ Cancel + +
+
+
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/dashboard_form.html b/dashboard_project/templates/dashboard/dashboard_form.html index 46e3860..6dad95b 100644 --- a/dashboard_project/templates/dashboard/dashboard_form.html +++ b/dashboard_project/templates/dashboard/dashboard_form.html @@ -1,43 +1,43 @@ -{% 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 %} - | Chat Analytics + {% if is_create %} + Create Dashboard + {% else %} + Edit Dashboard + {% endif %} + | Chat Analytics {% endblock %} - {% block content %} -
-

{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}

- -
+
+

{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}

+ +
-
-
-
-
-
- {% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %} -
-
-
-
- {% csrf_token %} - {{ form|crispy }} -
- -
-
-
-
-
-
+
+
+
+
+
+ {% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %} +
+
+
+
+ {% csrf_token %} {{ form|crispy }} +
+ +
+
+
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/data_source_confirm_delete.html b/dashboard_project/templates/dashboard/data_source_confirm_delete.html index 21a69f0..1d5d897 100644 --- a/dashboard_project/templates/dashboard/data_source_confirm_delete.html +++ b/dashboard_project/templates/dashboard/data_source_confirm_delete.html @@ -1,53 +1,47 @@ -{% extends 'base.html' %} - -{% block title %}Delete Data Source | Chat Analytics{% endblock %} - +{% extends 'base.html' %} {% block title %}Delete Data Source | Chat Analytics{% endblock %} {% block content %} -
-

Delete Data Source

- -
+
+

Delete Data Source

+ +
-
-
-
-
-
Confirm Deletion
-
-
-

- Are you sure you want to delete the data source - "{{ data_source.name }}"? -

-

- This action cannot be undone. The data source and all associated chat - sessions ({{ data_source.chat_sessions.count }} sessions) will be - permanently deleted. -

+
+
+
+
+
Confirm Deletion
+
+
+

+ Are you sure you want to delete the data source + "{{ data_source.name }}"? +

+

+ This action cannot be undone. The data source and all associated chat sessions + ({{ data_source.chat_sessions.count }} sessions) will be permanently deleted. +

-
- {% csrf_token %} -
- Cancel - -
-
-
-
-
-
+
+ {% csrf_token %} +
+ Cancel + +
+
+
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/data_source_detail.html b/dashboard_project/templates/dashboard/data_source_detail.html index 3fa0061..09ad3a2 100644 --- a/dashboard_project/templates/dashboard/data_source_detail.html +++ b/dashboard_project/templates/dashboard/data_source_detail.html @@ -1,255 +1,229 @@ -{% extends 'base.html' %} -{% load dashboard_extras %} - -{% block title %}{{ data_source.name }} | Chat Analytics{% endblock %} - -{% block content %} - - -
-
-
-
-
Data Source Details
-
-
-
-
-

Name: {{ data_source.name }}

-

- Uploaded At: - {{ data_source.uploaded_at|date:"F d, Y H:i" }} -

-

File: {{ data_source.file.name|split:"/"|last }}

-
-
-

Company: {{ data_source.company.name }}

-

Total Sessions: {{ page_obj.paginator.count }}

-

Description: {{ data_source.description }}

-
-
-
-
-
-
-
-
-
Filter Sessions
-
-
-
-
- - - -
-
-
-
-
-
- -
-
-
-
-
Chat Sessions ({{ page_obj.paginator.count }})
-
-
-
- - - - - - - - - - - - - - - - {% for session in page_obj %} - - - - - - - - - - - - {% empty %} - - - - {% endfor %} - -
Session IDStart TimeCountryLanguageSentimentMessagesTokensCategoryActions
{{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }}{{ session.country }}{{ session.language }} - {% if session.sentiment %} - {% if 'positive' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'negative' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'neutral' in session.sentiment|lower %} - {{ session.sentiment }} - {% else %} - {{ session.sentiment }} - {% endif %} - {% else %} - N/A - {% endif %} - {{ session.messages_sent }}{{ session.tokens }}{{ session.category|default:"N/A" }} - {% if session.session_id %} - - - - {% else %} - - {% endif %} -
- No chat sessions found. -
-
- - {% if page_obj.paginator.num_pages > 1 %} - - {% endif %} -
-
-
-
+{% extends 'base.html' %} {% load dashboard_extras %} +{% block title %} + {{ data_source.name }} + | Chat Analytics +{% endblock %} +{% block content %} + + +
+
+
+
+
Data Source Details
+
+
+
+
+

Name: {{ data_source.name }}

+

+ Uploaded At: + {{ data_source.uploaded_at|date:"F d, Y H:i" }} +

+

File: {{ data_source.file.name|split:"/"|last }}

+
+
+

Company: {{ data_source.company.name }}

+

Total Sessions: {{ page_obj.paginator.count }}

+

Description: {{ data_source.description }}

+
+
+
+
+
+
+
+
+
Filter Sessions
+
+
+
+
+ + + +
+
+
+
+
+
+ +
+
+
+
+
Chat Sessions ({{ page_obj.paginator.count }})
+
+
+
+ + + + + + + + + + + + + + + + {% for session in page_obj %} + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
Session IDStart TimeCountryLanguageSentimentMessagesTokensCategoryActions
{{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }}{{ session.country }}{{ session.language }} + {% if session.sentiment %} + {% if 'positive' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'negative' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'neutral' in session.sentiment|lower %} + {{ session.sentiment }} + {% else %} + {{ session.sentiment }} + {% endif %} + {% else %} + N/A + {% endif %} + {{ session.messages_sent }}{{ session.tokens }}{{ session.category|default:"N/A" }} + {% if session.session_id %} + + + + {% else %} + + {% endif %} +
No chat sessions found.
+
+ + {% if page_obj.paginator.num_pages > 1 %} + + {% endif %} +
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/data_view.html b/dashboard_project/templates/dashboard/data_view.html index 1587cfd..022baf6 100644 --- a/dashboard_project/templates/dashboard/data_view.html +++ b/dashboard_project/templates/dashboard/data_view.html @@ -1,320 +1,276 @@ -{% 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 %} -
-

Data View

-
-
- - Back to Dashboard - - {% if selected_data_source %} - - View Source - - {% endif %} -
- -
-
+
+

Data View

+
+
+ + Back to Dashboard + + {% if selected_data_source %} + + View Source + + {% endif %} +
+ +
+
- -
-
-
-
-
Data Source Selection
-
-
-
-
- -
-
- -
-
- -
-
-
-
-
-
+ +
+
+
+
+
Data Source Selection
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
- -
-
-
-
-
Export Data
-
-
-
- - - + +
+
+
+
+
Export Data
+
+
+ + + + -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
- -
-
-
-
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+
- -
-
-
-
-
- Chat Sessions - {% if selected_data_source %} - for {{ selected_data_source.name }} - {% endif %} - {% if view != 'all' %} - ({{ view|title }}) - {% endif %} -
- {{ page_obj.paginator.count }} sessions -
-
- -
-
- Loading... -
-

Loading data...

-
+ +
+
+
+
+
+ Chat Sessions + {% if selected_data_source %} + for {{ selected_data_source.name }} + {% endif %} + {% if view != 'all' %}({{ view|title }}){% endif %} +
+ {{ page_obj.paginator.count }} sessions +
+
+ +
+
+ Loading... +
+

Loading data...

+
- -
- {% include "dashboard/partials/data_table.html" %} -
-
-
-
-
+ +
{% include "dashboard/partials/data_table.html" %}
+
+
+
+
- - {% if page_obj %} -
-
-
-
-
Summary
-
-
-
-
-
-
-
Total Sessions
-

{{ page_obj.paginator.count }}

-

Chat conversations

-
-
-
-
-
-
-
Avg Response Time
-

{{ avg_response_time|floatformat:2 }}s

-

Average response

-
-
-
-
-
-
-
Avg Messages
-

{{ avg_messages|floatformat:1 }}

-

Per conversation

-
-
-
-
-
-
-
Escalation Rate
-

{{ escalation_rate|floatformat:1 }}%

-

Escalated sessions

-
-
-
-
-
-
-
-
- {% endif %} + + {% if page_obj %} +
+
+
+
+
Summary
+
+
+
+
+
+
+
Total Sessions
+

{{ page_obj.paginator.count }}

+

Chat conversations

+
+
+
+
+
+
+
Avg Response Time
+

{{ avg_response_time|floatformat:2 }}s

+

Average response

+
+
+
+
+
+
+
Avg Messages
+

{{ avg_messages|floatformat:1 }}

+

Per conversation

+
+
+
+
+
+
+
Escalation Rate
+

{{ escalation_rate|floatformat:1 }}%

+

Escalated sessions

+
+
+
+
+
+
+
+
+ {% endif %} {% endblock %} - {% block extra_js %} - + {% endblock %} diff --git a/dashboard_project/templates/dashboard/no_company.html b/dashboard_project/templates/dashboard/no_company.html index edfbfd5..86eabc5 100644 --- a/dashboard_project/templates/dashboard/no_company.html +++ b/dashboard_project/templates/dashboard/no_company.html @@ -1,44 +1,37 @@ -{% extends 'base.html' %} - -{% block title %}No Company | Chat Analytics{% endblock %} - +{% extends 'base.html' %} {% block title %}No Company | Chat Analytics{% endblock %} {% block content %} -
-

No Company Association

-
+
+

No Company Association

+
-
-
-
-
-
Account Not Associated with a Company
-
-
-
- -

You are not currently associated with any company

-

- You need to be associated with a company to access the dashboard. -

-
+
+
+
+
+
Account Not Associated with a Company
+
+
+
+ +

You are not currently associated with any company

+

You need to be associated with a company to access the dashboard.

+
-

- 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. +

- -
-
-
-
+ +
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/partials/data_table.html b/dashboard_project/templates/dashboard/partials/data_table.html index 19cfbb8..f8b248e 100644 --- a/dashboard_project/templates/dashboard/partials/data_table.html +++ b/dashboard_project/templates/dashboard/partials/data_table.html @@ -1,160 +1,156 @@
- - - - - - - - - - - - - - - - {% for session in page_obj %} - - - - - - - - - - - - {% empty %} - - - - {% endfor %} - -
Session IDStart TimeCountryLanguageMessagesSentimentResponse TimeCategoryActions
{{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }}{{ session.country|default:"N/A" }}{{ session.language|default:"N/A" }}{{ session.messages_sent }} - {% if session.sentiment %} - {% if 'positive' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'negative' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'neutral' in session.sentiment|lower %} - {{ session.sentiment }} - {% else %} - {{ session.sentiment }} - {% endif %} - {% else %} - N/A - {% endif %} - {{ session.avg_response_time|floatformat:2 }}s{{ session.category|default:"N/A" }} - {% if session.session_id %} - - - - {% else %} - - {% endif %} -
No chat sessions found.
+ + + + + + + + + + + + + + + + {% for session in page_obj %} + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
Session IDStart TimeCountryLanguageMessagesSentimentResponse TimeCategoryActions
{{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }}{{ session.country|default:"N/A" }}{{ session.language|default:"N/A" }}{{ session.messages_sent }} + {% if session.sentiment %} + {% if 'positive' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'negative' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'neutral' in session.sentiment|lower %} + {{ session.sentiment }} + {% else %} + {{ session.sentiment }} + {% endif %} + {% else %} + N/A + {% endif %} + {{ session.avg_response_time|floatformat:2 }}s{{ session.category|default:"N/A" }} + {% if session.session_id %} + + + + {% else %} + + {% endif %} +
No chat sessions found.
{% if page_obj.paginator.num_pages > 1 %} - + {% endif %} diff --git a/dashboard_project/templates/dashboard/partials/search_results_table.html b/dashboard_project/templates/dashboard/partials/search_results_table.html index 8df2601..85725be 100644 --- a/dashboard_project/templates/dashboard/partials/search_results_table.html +++ b/dashboard_project/templates/dashboard/partials/search_results_table.html @@ -1,168 +1,160 @@
- - - - - - - - - - - - - - - - {% for session in page_obj %} - - - - - - - - - - - - {% empty %} - - - - {% endfor %} - -
Session IDStart TimeData SourceCountryLanguageSentimentMessagesCategoryActions
{{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }} - {{ session.data_source.name|truncatechars:15 }} - {{ session.country }}{{ session.language }} - {% if session.sentiment %} - {% if 'positive' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'negative' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'neutral' in session.sentiment|lower %} - {{ session.sentiment }} - {% else %} - {{ session.sentiment }} - {% endif %} - {% else %} - N/A - {% endif %} - {{ session.messages_sent }}{{ session.category|default:"N/A" }} - {% if session.session_id %} - - - - {% else %} - - {% endif %} -
- No chat sessions found matching your criteria. -
+ + + + + + + + + + + + + + + + {% for session in page_obj %} + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
Session IDStart TimeData SourceCountryLanguageSentimentMessagesCategoryActions
{{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }} + {{ session.data_source.name|truncatechars:15 }} + {{ session.country }}{{ session.language }} + {% if session.sentiment %} + {% if 'positive' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'negative' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'neutral' in session.sentiment|lower %} + {{ session.sentiment }} + {% else %} + {{ session.sentiment }} + {% endif %} + {% else %} + N/A + {% endif %} + {{ session.messages_sent }}{{ session.category|default:"N/A" }} + {% if session.session_id %} + + + + {% else %} + + {% endif %} +
No chat sessions found matching your criteria.
{% if page_obj.paginator.num_pages > 1 %} - + {% endif %} diff --git a/dashboard_project/templates/dashboard/search_results.html b/dashboard_project/templates/dashboard/search_results.html index 37b3811..a3f8b46 100644 --- a/dashboard_project/templates/dashboard/search_results.html +++ b/dashboard_project/templates/dashboard/search_results.html @@ -1,93 +1,81 @@ -{% extends 'base.html' %} - -{% block title %}Search Results | Chat Analytics{% endblock %} - +{% extends 'base.html' %} {% block title %}Search Results | Chat Analytics{% endblock %} {% block content %} -
-

Search Results

- -
+
+

Search Results

+ +
-
-
-
-
-
Search Chat Sessions
-
-
-
-
- - {% if data_source %} - - {% endif %} - -
-
- Search by session ID, country, language, sentiment, category, or - message content. -
-
-
-
-
-
+
+
+
+
+
Search Chat Sessions
+
+
+
+
+ + {% if data_source %} + + {% endif %} + +
+
+ Search by session ID, country, language, sentiment, category, or message + content. +
+
+
+
+
+
-
-
-
-
-
- Results {% if query %}for "{{ query }}"{% endif %} - {% if data_source %}in {{ data_source.name }}{% endif %} - ({{ page_obj.paginator.count }}) -
-
-
- -
-
- Loading... -
-

Loading data...

-
+
+
+
+
+
+ Results {% if query %}for "{{ query }}"{% endif %} + {% if data_source %}in {{ data_source.name }}{% endif %} + ({{ page_obj.paginator.count }}) +
+
+
+ +
+
+ Loading... +
+

Loading data...

+
- -
- {% include "dashboard/partials/search_results_table.html" %} -
-
-
-
-
+ +
+ {% include "dashboard/partials/search_results_table.html" %} +
+
+
+
+
{% endblock %} - {% block extra_js %} - + {% endblock %} diff --git a/dashboard_project/templates/dashboard/upload.html b/dashboard_project/templates/dashboard/upload.html index d87ff89..5bd74fe 100644 --- a/dashboard_project/templates/dashboard/upload.html +++ b/dashboard_project/templates/dashboard/upload.html @@ -1,197 +1,195 @@ -{% extends 'base.html' %} -{% load crispy_forms_tags %} -{% load dashboard_extras %} - -{% block title %}Upload Data | Chat Analytics{% endblock %} - -{% block content %} -
-

Upload Data

- -
- -
-
-
-
-
Upload CSV File
-
-
-
- {% csrf_token %} {{ form|crispy }} -
- -
-
-
-
-
- -
-
-
-
CSV File Format
-
-
-

The CSV file should contain the following columns:

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ColumnDescriptionType
session_idUnique identifier for the chat sessionString
start_timeWhen the session startedDatetime
end_timeWhen the session endedDatetime
ip_addressIP address of the userString
countryCountry of the userString
languageLanguage used in the conversationString
messages_sentNumber of messages in the conversationInteger
sentimentSentiment analysis of the conversationString
escalatedWhether the conversation was escalatedBoolean
forwarded_hrWhether the conversation was forwarded to HRBoolean
full_transcriptFull transcript of the conversationText
avg_response_timeAverage response time in secondsFloat
tokensTotal number of tokens usedInteger
tokens_eurCost of tokens in EURFloat
categoryCategory of the conversationString
initial_msgFirst message from the userText
user_ratingUser rating of the conversationString
-
-
-
-
- - {% if data_sources %} -
-
-
-
-
Uploaded Data Sources
-
-
-
- - - - - - - - - - - - - {% for data_source in data_sources %} - - - - - - - - - {% endfor %} - -
NameDescriptionUploadedFileSessionsActions
{{ data_source.name }}{{ data_source.description|truncatechars:50 }}{{ data_source.uploaded_at|date:"M d, Y H:i" }}{{ data_source.file.name|split:"/"|last }}{{ data_source.chat_sessions.count }} - - - - - - -
-
-
-
-
-
- {% endif %} +{% extends 'base.html' %} {% load crispy_forms_tags %} {% load dashboard_extras %} +{% block title %} + Upload Data | Chat Analytics +{% endblock %} +{% block content %} +
+

Upload Data

+ +
+ +
+
+
+
+
Upload CSV File
+
+
+
+ {% csrf_token %} {{ form|crispy }} +
+ +
+
+
+
+
+ +
+
+
+
CSV File Format
+
+
+

The CSV file should contain the following columns:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ColumnDescriptionType
session_idUnique identifier for the chat sessionString
start_timeWhen the session startedDatetime
end_timeWhen the session endedDatetime
ip_addressIP address of the userString
countryCountry of the userString
languageLanguage used in the conversationString
messages_sentNumber of messages in the conversationInteger
sentimentSentiment analysis of the conversationString
escalatedWhether the conversation was escalatedBoolean
forwarded_hrWhether the conversation was forwarded to HRBoolean
full_transcriptFull transcript of the conversationText
avg_response_timeAverage response time in secondsFloat
tokensTotal number of tokens usedInteger
tokens_eurCost of tokens in EURFloat
categoryCategory of the conversationString
initial_msgFirst message from the userText
user_ratingUser rating of the conversationString
+
+
+
+
+ + {% if data_sources %} +
+
+
+
+
Uploaded Data Sources
+
+
+
+ + + + + + + + + + + + + {% for data_source in data_sources %} + + + + + + + + + {% endfor %} + +
NameDescriptionUploadedFileSessionsActions
{{ data_source.name }}{{ data_source.description|truncatechars:50 }}{{ data_source.uploaded_at|date:"M d, Y H:i" }}{{ data_source.file.name|split:"/"|last }}{{ data_source.chat_sessions.count }} + + + + + + +
+
+
+
+
+
+ {% endif %} {% endblock %} diff --git a/package.json b/package.json index 0c42507..57679ee 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,6 @@ { - "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" - } + "devDependencies": { + "prettier": "^3.5.3", + "prettier-plugin-jinja-template": "^2.1.0" + } }