mirror of
https://github.com/kjanat/livegraphs-django.git
synced 2026-01-16 09:22:09 +01:00
Refactor HTML templates for improved readability and consistency
- Updated search_results_table.html to enhance formatting and maintain consistent indentation. - Refined search_results.html layout for better structure and clarity. - Improved upload.html for better organization and readability of the upload form and data source table. - Removed unnecessary lines in package.json and streamlined devDependencies section.
This commit is contained in:
@ -18,8 +18,8 @@ indent_size = 4
|
|||||||
|
|
||||||
# HTML and Django/Jinja2 template files
|
# HTML and Django/Jinja2 template files
|
||||||
[*.{html,htm}]
|
[*.{html,htm}]
|
||||||
indent_style = tab
|
indent_size = 2
|
||||||
indent_size = 4
|
|
||||||
# Allow prettier to format Django/Jinja templates properly
|
# Allow prettier to format Django/Jinja templates properly
|
||||||
# The following comment options can be used in individual files if needed:
|
# The following comment options can be used in individual files if needed:
|
||||||
# <!-- prettier-ignore -->
|
# <!-- prettier-ignore -->
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -407,6 +407,7 @@ pyrightconfig.json
|
|||||||
*Zone.Identifier
|
*Zone.Identifier
|
||||||
examples/
|
examples/
|
||||||
**/migrations/[0-9]**.py
|
**/migrations/[0-9]**.py
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
# UV specific
|
# UV specific
|
||||||
.uv/
|
.uv/
|
||||||
|
|||||||
@ -3,6 +3,7 @@ default_install_hook_types:
|
|||||||
- post-checkout
|
- post-checkout
|
||||||
- post-merge
|
- post-merge
|
||||||
- post-rewrite
|
- post-rewrite
|
||||||
|
|
||||||
repos:
|
repos:
|
||||||
# uv hooks for dependency management
|
# uv hooks for dependency management
|
||||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||||
@ -33,18 +34,18 @@ repos:
|
|||||||
# rev: 3.0.7
|
# rev: 3.0.7
|
||||||
# hooks:
|
# hooks:
|
||||||
# - id: djhtml
|
# - id: djhtml
|
||||||
# entry: djhtml --tabwidth 4 --
|
# entry: djhtml --tabwidth 4
|
||||||
# - id: djcss
|
|
||||||
# - id: djjs
|
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
rev: v4.0.0-alpha.8
|
rev: v3.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types_or: [javascript, jsx, ts, tsx, css, scss, html, json, yaml, markdown]
|
types_or: [javascript, jsx, ts, tsx, css, scss, html, json, yaml, markdown]
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- prettier
|
- prettier
|
||||||
- prettier-plugin-jinja-template
|
- prettier-plugin-jinja-template
|
||||||
|
# types_or: [javascript, jsx, ts, tsx, css, scss, json, yaml, markdown]
|
||||||
|
# exclude: '.*\.html$'
|
||||||
|
|
||||||
# Ruff for linting and formatting
|
# Ruff for linting and formatting
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
|||||||
@ -47,3 +47,6 @@ docker-compose.override.yml
|
|||||||
.vscode/
|
.vscode/
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
|
||||||
|
# Ignore all SQLite3 files:
|
||||||
|
**/*.sqlite3
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
"requirePragma": false,
|
"requirePragma": false,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"useTabs": true,
|
"useTabs": false,
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": ["*.html"],
|
"files": ["*.html"],
|
||||||
|
|||||||
90
.vscode/settings.json
vendored
90
.vscode/settings.json
vendored
@ -1,47 +1,47 @@
|
|||||||
{
|
{
|
||||||
"[css]": {
|
"[css]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"[python]": {
|
"[python]": {
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": "explicit",
|
"source.fixAll": "explicit",
|
||||||
"source.organizeImports": "explicit"
|
"source.organizeImports": "explicit"
|
||||||
},
|
},
|
||||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"[toml]": {
|
"[toml]": {
|
||||||
"editor.defaultFormatter": "tamasfe.even-better-toml"
|
"editor.defaultFormatter": "tamasfe.even-better-toml"
|
||||||
},
|
},
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"emmet.includeLanguages": {
|
"emmet.includeLanguages": {
|
||||||
"django-html": "html",
|
"django-html": "html",
|
||||||
"jinja-html": "html"
|
"jinja-html": "html"
|
||||||
},
|
},
|
||||||
"emmet.syntaxProfiles": {
|
"emmet.syntaxProfiles": {
|
||||||
"html": {
|
"html": {
|
||||||
"inline_break": 2
|
"inline_break": 2
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.html": "html"
|
"*.html": "html"
|
||||||
},
|
},
|
||||||
"html.format.wrapAttributes": "auto",
|
"html.format.wrapAttributes": "auto",
|
||||||
"html.format.wrapLineLength": 100,
|
"html.format.wrapLineLength": 100,
|
||||||
"notebook.codeActionsOnSave": {
|
"notebook.codeActionsOnSave": {
|
||||||
"notebook.source.fixAll": "explicit",
|
"notebook.source.fixAll": "explicit",
|
||||||
"notebook.source.organizeImports": "explicit"
|
"notebook.source.organizeImports": "explicit"
|
||||||
},
|
},
|
||||||
"notebook.formatOnSave.enabled": true,
|
"notebook.formatOnSave.enabled": true,
|
||||||
"prettier.requireConfig": true
|
"prettier.requireConfig": true
|
||||||
}
|
}
|
||||||
|
|||||||
11
Makefile
11
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
|
# Create a virtual environment
|
||||||
venv:
|
venv:
|
||||||
@ -25,13 +25,9 @@ format:
|
|||||||
uv run -m ruff format dashboard_project
|
uv run -m ruff format dashboard_project
|
||||||
uv run -m black 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.js dependencies
|
||||||
setup-node:
|
setup-node:
|
||||||
npm install
|
npm install --include=dev
|
||||||
|
|
||||||
# Clean Python cache files
|
# Clean Python cache files
|
||||||
clean:
|
clean:
|
||||||
@ -45,6 +41,9 @@ clean:
|
|||||||
find . -type d -name ".coverage" -exec rm -rf {} +
|
find . -type d -name ".coverage" -exec rm -rf {} +
|
||||||
find . -type d -name "htmlcov" -exec rm -rf {} +
|
find . -type d -name "htmlcov" -exec rm -rf {} +
|
||||||
find . -type d -name ".ruff_cache" -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 build/
|
||||||
rm -rf dist/
|
rm -rf dist/
|
||||||
|
|
||||||
|
|||||||
@ -64,12 +64,12 @@ For VSCode users, install the Prettier extension and add these settings to your
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"[html]": {
|
"[html]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
},
|
},
|
||||||
"prettier.requireConfig": true
|
"prettier.requireConfig": true
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
86
TODO.md
86
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?
|
## Dashboard UI Improvements
|
||||||
- Add export functionality to the dashboard:
|
|
||||||
- File formats:
|
### Responsiveness
|
||||||
- CSV
|
|
||||||
- Excel
|
- [ ] Fix dashboard graphs scaling/adjustment when zooming (currently requires page refresh)
|
||||||
- JSON
|
|
||||||
- XML
|
### Theming
|
||||||
- HTML
|
|
||||||
- PDF
|
- [ ] Add dark mode/light mode toggle
|
||||||
- Make the export button a dropdown with the following options:
|
- [ ] Add Notso AI branding elements
|
||||||
- Export as CSV
|
- [ ] Implement responsive table design (reduce rows to fit screen)
|
||||||
- Export as Excel
|
|
||||||
- Export as JSON
|
### Data Export
|
||||||
- Export as XML
|
|
||||||
- Export as HTML
|
- [ ] Implement multi-format export functionality
|
||||||
- Export as PDF
|
- [ ] CSV format
|
||||||
- Make the export data section folded by default and only show the export button.
|
- [ ] Excel format
|
||||||
- Adjust the downloaded file name to include the company name, date and time of the export.
|
- [ ] JSON format
|
||||||
- Add a button to download the CSV file for the selected company.
|
- [ ] XML format
|
||||||
- Make it possible to modify the column names in the CSV file through the admin interface.
|
- [ ] HTML format
|
||||||
- Add possibility to add a company logo in the admin interface.
|
- [ ] PDF format
|
||||||
- Add periodic download from <https://proto.notso.ai/XY/chats> possibility for the XY company.
|
- [ ] Create dropdown menu for export options
|
||||||
- Authentication: Basic Auth
|
- [ ] Make export data section collapsible (folded by default)
|
||||||
- URL: <https://proto.notso.ai/XY/chats>
|
- [ ] Add company name, date and timestamp to exported filenames
|
||||||
- Username: xxxx
|
|
||||||
- Password: xxxx
|
## Admin Interface Enhancements
|
||||||
- Reduce amount of rows in the table to fit the screen.
|
|
||||||
- Add dark mode/theming to the dashboard.
|
### Company Management
|
||||||
- Add Notso AI branding to the dashboard.
|
|
||||||
|
- [ ] Add company logo upload functionality
|
||||||
|
- [ ] Add direct CSV download button for each company (superusers only)
|
||||||
|
- [ ] Include company name, date and timestamp in filename
|
||||||
|
- [ ] Add UI for customizing CSV column names
|
||||||
|
|
||||||
|
## Data Integration
|
||||||
|
|
||||||
|
### External Data Sources
|
||||||
|
|
||||||
|
- [ ] Implement periodic data download from external API
|
||||||
|
- [ ] Source: <https://proto.notso.ai/XY/chats>
|
||||||
|
- [ ] Authentication: Basic Auth
|
||||||
|
- [ ] Credentials: [stored securely]
|
||||||
|
- [ ] Add scheduling options for data refresh
|
||||||
|
|
||||||
|
## Technical Debt
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
|
||||||
|
- [ ] Profile and optimize dashboard rendering
|
||||||
|
- [ ] Implement lazy loading for dashboard elements
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- [ ] Add unit tests for export functionality
|
||||||
|
- [ ] Add integration tests for data import process
|
||||||
|
|||||||
@ -91,8 +91,8 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
LANGUAGE_CODE = "en-us"
|
LANGUAGE_CODE = "nl"
|
||||||
TIME_ZONE = "UTC"
|
TIME_ZONE = "Europe/Amsterdam"
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|||||||
@ -4,311 +4,311 @@
|
|||||||
|
|
||||||
/* Dashboard grid layout */
|
/* Dashboard grid layout */
|
||||||
.dashboard-grid {
|
.dashboard-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
|
||||||
/* Slightly larger minmax for widgets */
|
/* Slightly larger minmax for widgets */
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
|
|
||||||
/* Increased gap */
|
/* Increased gap */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dashboard widget cards */
|
/* Dashboard widget cards */
|
||||||
.dashboard-widget {
|
.dashboard-widget {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
/* Allow flex for content alignment */
|
/* Allow flex for content alignment */
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
/* Stack header, body, footer vertically */
|
/* Stack header, body, footer vertically */
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
/* Ensure widgets fill grid cell height */
|
/* Ensure widgets fill grid cell height */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-widget .card-header {
|
.dashboard-widget .card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-widget .card-header .widget-title {
|
.dashboard-widget .card-header .widget-title {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
|
||||||
/* Slightly larger widget titles */
|
/* Slightly larger widget titles */
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-widget .card-header .widget-actions {
|
.dashboard-widget .card-header .widget-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-widget .card-header .widget-actions .btn {
|
.dashboard-widget .card-header .widget-actions .btn {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
|
||||||
/* Slightly larger action buttons */
|
/* Slightly larger action buttons */
|
||||||
height: 32px;
|
height: 32px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-widget .card-header .widget-actions .btn:hover {
|
.dashboard-widget .card-header .widget-actions .btn:hover {
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
border-color: #e0e0e0;
|
border-color: #e0e0e0;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-widget .card-body {
|
.dashboard-widget .card-body {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|
||||||
/* Allow card body to take available space */
|
/* Allow card body to take available space */
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
|
|
||||||
/* Consistent padding */
|
/* Consistent padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chart widgets */
|
/* Chart widgets */
|
||||||
.chart-widget .card-body {
|
.chart-widget .card-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-widget .chart-container {
|
.chart-widget .chart-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 250px;
|
min-height: 250px;
|
||||||
|
|
||||||
/* Adjusted min-height */
|
/* Adjusted min-height */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
/* Ensure it takes full width of card body */
|
/* Ensure it takes full width of card body */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stat widgets / Stat Cards */
|
/* Stat widgets / Stat Cards */
|
||||||
.stat-card {
|
.stat-card {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
|
|
||||||
/* Generous padding */
|
/* Generous padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card .stat-icon {
|
.stat-card .stat-icon {
|
||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
|
|
||||||
/* Larger icon */
|
/* Larger icon */
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 4.5rem;
|
width: 4.5rem;
|
||||||
height: 4.5rem;
|
height: 4.5rem;
|
||||||
line-height: 4.5rem;
|
line-height: 4.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #e9f2ff;
|
background-color: #e9f2ff;
|
||||||
|
|
||||||
/* Light blue background for icon */
|
/* Light blue background for icon */
|
||||||
color: #007bff;
|
color: #007bff;
|
||||||
|
|
||||||
/* Primary color for icon */
|
/* Primary color for icon */
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card .stat-value {
|
.stat-card .stat-value {
|
||||||
font-size: 2.25rem;
|
font-size: 2.25rem;
|
||||||
|
|
||||||
/* Larger stat value */
|
/* Larger stat value */
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
|
|
||||||
/* Reduced margin */
|
/* Reduced margin */
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
color: #212529;
|
color: #212529;
|
||||||
|
|
||||||
/* Darker color for value */
|
/* Darker color for value */
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card .stat-label {
|
.stat-card .stat-label {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
|
||||||
/* Slightly larger label */
|
/* Slightly larger label */
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dashboard theme variations */
|
/* Dashboard theme variations */
|
||||||
.dashboard-theme-light .card {
|
.dashboard-theme-light .card {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-theme-dark {
|
.dashboard-theme-dark {
|
||||||
background-color: #212529;
|
background-color: #212529;
|
||||||
color: #f8f9fa;
|
color: #f8f9fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-theme-dark .card {
|
.dashboard-theme-dark .card {
|
||||||
background-color: #343a40;
|
background-color: #343a40;
|
||||||
color: #f8f9fa;
|
color: #f8f9fa;
|
||||||
border-color: #495057;
|
border-color: #495057;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-theme-dark .card-header {
|
.dashboard-theme-dark .card-header {
|
||||||
background-color: #495057;
|
background-color: #495057;
|
||||||
border-bottom-color: #6c757d;
|
border-bottom-color: #6c757d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-theme-dark .stat-card .stat-label {
|
.dashboard-theme-dark .stat-card .stat-label {
|
||||||
color: #adb5bd;
|
color: #adb5bd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Time period selector */
|
/* Time period selector */
|
||||||
.time-period-selector {
|
.time-period-selector {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
|
||||||
/* Increased gap */
|
/* Increased gap */
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
/* Increased margin */
|
/* Increased margin */
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-period-selector .btn-group {
|
.time-period-selector .btn-group {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-period-selector .btn {
|
.time-period-selector .btn {
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
|
|
||||||
/* Bootstrap-like padding */
|
/* Bootstrap-like padding */
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom metric selector */
|
/* Custom metric selector */
|
||||||
.metric-selector {
|
.metric-selector {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-selector .nav-link {
|
.metric-selector .nav-link {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-selector .nav-link.active {
|
.metric-selector .nav-link.active {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dashboard loading states */
|
/* Dashboard loading states */
|
||||||
.widget-placeholder {
|
.widget-placeholder {
|
||||||
min-height: 300px;
|
min-height: 300px;
|
||||||
background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%);
|
background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%);
|
||||||
|
|
||||||
/* Lighter gradient */
|
/* Lighter gradient */
|
||||||
background-size: 200% 100%;
|
background-size: 200% 100%;
|
||||||
animation: loading 1.8s infinite ease-in-out;
|
animation: loading 1.8s infinite ease-in-out;
|
||||||
|
|
||||||
/* Smoother animation */
|
/* Smoother animation */
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
/* Consistent with cards */
|
/* Consistent with cards */
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes loading {
|
@keyframes loading {
|
||||||
0% {
|
0% {
|
||||||
background-position: 200% 0;
|
background-position: 200% 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
background-position: -200% 0;
|
background-position: -200% 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dashboard empty states */
|
/* Dashboard empty states */
|
||||||
.empty-state {
|
.empty-state {
|
||||||
padding: 2.5rem;
|
padding: 2.5rem;
|
||||||
|
|
||||||
/* Increased padding */
|
/* Increased padding */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
|
|
||||||
/* Light background for empty state */
|
/* Light background for empty state */
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
border: 1px dashed #ced4da;
|
border: 1px dashed #ced4da;
|
||||||
|
|
||||||
/* Dashed border */
|
/* Dashed border */
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state .empty-state-icon {
|
.empty-state .empty-state-icon {
|
||||||
font-size: 3.5rem;
|
font-size: 3.5rem;
|
||||||
|
|
||||||
/* Larger icon */
|
/* Larger icon */
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
opacity: 0.4;
|
opacity: 0.4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state .empty-state-message {
|
.empty-state .empty-state-message {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
|
|
||||||
/* Slightly larger message */
|
/* Slightly larger message */
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state .btn {
|
.empty-state .btn {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive adjustments */
|
/* Responsive adjustments */
|
||||||
@media (width <=767.98px) {
|
@media (width <=767.98px) {
|
||||||
.dashboard-grid {
|
.dashboard-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card .stat-icon {
|
.stat-card .stat-icon {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
line-height: 3rem;
|
line-height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-card .stat-value {
|
.stat-card .stat-value {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Stat Boxes Alignment Fix (Bottom Align, No Overlap) --- */
|
/* --- Stat Boxes Alignment Fix (Bottom Align, No Overlap) --- */
|
||||||
.stats-row {
|
.stats-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-card {
|
.stats-card {
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
/* Push content to bottom */
|
/* Push content to bottom */
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
/* Remove min-height/height for natural stretch */
|
/* Remove min-height/height for natural stretch */
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,361 +4,361 @@
|
|||||||
|
|
||||||
/* General Styles */
|
/* General Styles */
|
||||||
body {
|
body {
|
||||||
font-family:
|
font-family:
|
||||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif,
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif,
|
||||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
background-color: #f4f7f9;
|
background-color: #f4f7f9;
|
||||||
|
|
||||||
/* Lighter, cleaner background */
|
/* Lighter, cleaner background */
|
||||||
color: #333;
|
color: #333;
|
||||||
|
|
||||||
/* Darker text for better contrast */
|
/* Darker text for better contrast */
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
/* Added for sticky footer */
|
/* Added for sticky footer */
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
/* Added for sticky footer */
|
/* Added for sticky footer */
|
||||||
min-height: 100vh;
|
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 adjustments (if needed, Bootstrap usually handles this well) */
|
||||||
.navbar {
|
.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 */
|
/* Helper Classes */
|
||||||
.text-truncate-2 {
|
.text-truncate-2 {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
line-clamp: 2;
|
line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cursor-pointer {
|
.cursor-pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.min-w-150 {
|
.min-w-150 {
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card styles */
|
/* Card styles */
|
||||||
.card {
|
.card {
|
||||||
border: 1px solid #e0e5e9;
|
border: 1px solid #e0e5e9;
|
||||||
|
|
||||||
/* Lighter border */
|
/* Lighter border */
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
|
|
||||||
/* Slightly more rounded corners */
|
/* Slightly more rounded corners */
|
||||||
box-shadow: 0 4px 12px rgb(0 0 0 / 8%);
|
box-shadow: 0 4px 12px rgb(0 0 0 / 8%);
|
||||||
|
|
||||||
/* Softer, more modern shadow */
|
/* Softer, more modern shadow */
|
||||||
transition:
|
transition:
|
||||||
transform 0.2s ease-in-out,
|
transform 0.2s ease-in-out,
|
||||||
box-shadow 0.2s ease-in-out;
|
box-shadow 0.2s ease-in-out;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
/* Consistent margin */
|
/* Consistent margin */
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-hover:hover {
|
.card-hover:hover {
|
||||||
transform: translateY(-3px);
|
transform: translateY(-3px);
|
||||||
box-shadow: 0 6px 16px rgb(0 0 0 / 10%);
|
box-shadow: 0 6px 16px rgb(0 0 0 / 10%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
/* Clean white header */
|
/* Clean white header */
|
||||||
border-bottom: 1px solid #e0e5e9;
|
border-bottom: 1px solid #e0e5e9;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
||||||
/* Slightly bolder header text */
|
/* Slightly bolder header text */
|
||||||
padding: 0.75rem 1.25rem;
|
padding: 0.75rem 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
|
|
||||||
/* Adjusted card title size */
|
/* Adjusted card title size */
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar enhancements */
|
/* Sidebar enhancements */
|
||||||
.sidebar {
|
.sidebar {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
/* White sidebar for a cleaner look */
|
/* White sidebar for a cleaner look */
|
||||||
border-right: 1px solid #e0e5e9;
|
border-right: 1px solid #e0e5e9;
|
||||||
box-shadow: 2px 0 5px rgb(0 0 0 / 3%);
|
box-shadow: 2px 0 5px rgb(0 0 0 / 3%);
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-sticky {
|
.sidebar-sticky {
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .nav-link {
|
.sidebar .nav-link {
|
||||||
color: #4a5568;
|
color: #4a5568;
|
||||||
|
|
||||||
/* Softer link color */
|
/* Softer link color */
|
||||||
padding: 0.65rem 1.25rem;
|
padding: 0.65rem 1.25rem;
|
||||||
|
|
||||||
/* Adjusted padding */
|
/* Adjusted padding */
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
|
|
||||||
/* Bootstrap-like rounded corners for links */
|
/* Bootstrap-like rounded corners for links */
|
||||||
margin: 0.1rem 0.5rem;
|
margin: 0.1rem 0.5rem;
|
||||||
|
|
||||||
/* Margin around links */
|
/* Margin around links */
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .nav-link:hover {
|
.sidebar .nav-link:hover {
|
||||||
color: #007bff;
|
color: #007bff;
|
||||||
|
|
||||||
/* Primary color on hover */
|
/* Primary color on hover */
|
||||||
background-color: #e9f2ff;
|
background-color: #e9f2ff;
|
||||||
|
|
||||||
/* Light blue background on hover */
|
/* Light blue background on hover */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .nav-link.active {
|
.sidebar .nav-link.active {
|
||||||
color: #007bff;
|
color: #007bff;
|
||||||
background-color: #d6e4ff;
|
background-color: #d6e4ff;
|
||||||
|
|
||||||
/* Slightly darker blue for active */
|
/* Slightly darker blue for active */
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .nav-link i.me-2 {
|
.sidebar .nav-link i.me-2 {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
|
||||||
/* Ensure icons align well */
|
/* Ensure icons align well */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-right: 0.75rem !important;
|
margin-right: 0.75rem !important;
|
||||||
|
|
||||||
/* Consistent icon spacing */
|
/* Consistent icon spacing */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .nav-header {
|
.sidebar .nav-header {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
color: #718096;
|
color: #718096;
|
||||||
|
|
||||||
/* Softer header color */
|
/* Softer header color */
|
||||||
padding: 0.5rem 1.25rem;
|
padding: 0.5rem 1.25rem;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dashboard stats cards */
|
/* Dashboard stats cards */
|
||||||
.stats-card {
|
.stats-card {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-card h3 {
|
.stats-card h3 {
|
||||||
font-size: 1.75rem;
|
font-size: 1.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-card p {
|
.stats-card p {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chart containers */
|
/* Chart containers */
|
||||||
.chart-container {
|
.chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loading overlay */
|
/* Loading overlay */
|
||||||
.loading-overlay {
|
.loading-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgb(255 255 255 / 70%);
|
background-color: rgb(255 255 255 / 70%);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table enhancements */
|
/* Table enhancements */
|
||||||
.table {
|
.table {
|
||||||
border-color: #e0e5e9;
|
border-color: #e0e5e9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table th {
|
.table th {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
/* Bolder table headers */
|
/* Bolder table headers */
|
||||||
color: #4a5568;
|
color: #4a5568;
|
||||||
background-color: #f8f9fc;
|
background-color: #f8f9fc;
|
||||||
|
|
||||||
/* Light background for headers */
|
/* Light background for headers */
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-striped tbody tr:nth-of-type(odd) {
|
.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 {
|
.table-hover tbody tr:hover {
|
||||||
background-color: #e9f2ff;
|
background-color: #e9f2ff;
|
||||||
|
|
||||||
/* Consistent hover with sidebar */
|
/* Consistent hover with sidebar */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Form improvements */
|
/* Form improvements */
|
||||||
.form-control,
|
.form-control,
|
||||||
.form-select {
|
.form-select {
|
||||||
border-color: #ced4da;
|
border-color: #ced4da;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
|
|
||||||
/* Consistent border radius */
|
/* Consistent border radius */
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
|
|
||||||
/* Adjusted padding */
|
/* Adjusted padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control:focus,
|
.form-control:focus,
|
||||||
.form-select:focus {
|
.form-select:focus {
|
||||||
border-color: #86b7fe;
|
border-color: #86b7fe;
|
||||||
|
|
||||||
/* Bootstrap focus color */
|
/* Bootstrap focus color */
|
||||||
box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%);
|
box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%);
|
||||||
|
|
||||||
/* Bootstrap focus shadow */
|
/* Bootstrap focus shadow */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Button styling */
|
/* Button styling */
|
||||||
.btn {
|
.btn {
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
|
|
||||||
/* Consistent border radius */
|
/* Consistent border radius */
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
|
|
||||||
/* Standard button padding */
|
/* Standard button padding */
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
transition:
|
transition:
|
||||||
background-color 0.15s ease-in-out,
|
background-color 0.15s ease-in-out,
|
||||||
border-color 0.15s ease-in-out,
|
border-color 0.15s ease-in-out,
|
||||||
box-shadow 0.15s ease-in-out;
|
box-shadow 0.15s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
border-color: #007bff;
|
border-color: #007bff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.btn-primary:hover {
|
||||||
background-color: #0069d9;
|
background-color: #0069d9;
|
||||||
border-color: #0062cc;
|
border-color: #0062cc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
background-color: #6c757d;
|
background-color: #6c757d;
|
||||||
border-color: #6c757d;
|
border-color: #6c757d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:hover {
|
.btn-secondary:hover {
|
||||||
background-color: #5a6268;
|
background-color: #5a6268;
|
||||||
border-color: #545b62;
|
border-color: #545b62;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Alert styling */
|
/* Alert styling */
|
||||||
.alert {
|
.alert {
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
padding: 0.9rem 1.25rem;
|
padding: 0.9rem 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat transcript styling */
|
/* Chat transcript styling */
|
||||||
.chat-transcript {
|
.chat-transcript {
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
border: 1px solid #e9ecef;
|
border: 1px solid #e9ecef;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-transcript pre {
|
.chat-transcript pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Footer styling */
|
/* Footer styling */
|
||||||
footer {
|
footer {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|
||||||
/* White footer */
|
/* White footer */
|
||||||
border-top: 1px solid #e0e5e9;
|
border-top: 1px solid #e0e5e9;
|
||||||
padding: 1.5rem 0;
|
padding: 1.5rem 0;
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
|
|
||||||
/* Added for sticky footer */
|
/* Added for sticky footer */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive adjustments */
|
/* Responsive adjustments */
|
||||||
@media (width <=767.98px) {
|
@media (width <=767.98px) {
|
||||||
.main-content {
|
.main-content {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-card h3 {
|
.stats-card h3 {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
height: 250px;
|
height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-title {
|
.card-title {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Print styles */
|
/* Print styles */
|
||||||
@media print {
|
@media print {
|
||||||
.sidebar,
|
.sidebar,
|
||||||
.navbar,
|
.navbar,
|
||||||
.btn,
|
.btn,
|
||||||
footer {
|
footer {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.card {
|
||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
break-inside: avoid;
|
break-inside: avoid;
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,269 +6,269 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Only initialize if AJAX navigation is enabled
|
// Only initialize if AJAX navigation is enabled
|
||||||
if (typeof ENABLE_AJAX_NAVIGATION !== "undefined" && ENABLE_AJAX_NAVIGATION) {
|
if (typeof ENABLE_AJAX_NAVIGATION !== "undefined" && ENABLE_AJAX_NAVIGATION) {
|
||||||
setupAjaxNavigation();
|
setupAjaxNavigation();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to set up AJAX navigation for the application
|
// Function to set up AJAX navigation for the application
|
||||||
function setupAjaxNavigation() {
|
function setupAjaxNavigation() {
|
||||||
// Configuration
|
// Configuration
|
||||||
const config = {
|
const config = {
|
||||||
mainContentSelector: "#main-content", // Selector for the main content area
|
mainContentSelector: "#main-content", // Selector for the main content area
|
||||||
navLinkSelector: ".ajax-nav-link", // Selector for links to handle with AJAX
|
navLinkSelector: ".ajax-nav-link", // Selector for links to handle with AJAX
|
||||||
loadingIndicatorId: "nav-loading-indicator", // ID of the loading indicator
|
loadingIndicatorId: "nav-loading-indicator", // ID of the loading indicator
|
||||||
excludePatterns: [
|
excludePatterns: [
|
||||||
// URL patterns to exclude from AJAX navigation
|
// URL patterns to exclude from AJAX navigation
|
||||||
/\.(pdf|xlsx?|docx?|csv|zip|png|jpe?g|gif|svg)$/i, // File downloads
|
/\.(pdf|xlsx?|docx?|csv|zip|png|jpe?g|gif|svg)$/i, // File downloads
|
||||||
/\/admin\//, // Admin pages
|
/\/admin\//, // Admin pages
|
||||||
/\/accounts\/logout\//, // Logout page
|
/\/accounts\/logout\//, // Logout page
|
||||||
/\/api\//, // API endpoints
|
/\/api\//, // API endpoints
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create and insert the loading indicator
|
// Create and insert the loading indicator
|
||||||
if (!document.getElementById(config.loadingIndicatorId)) {
|
if (!document.getElementById(config.loadingIndicatorId)) {
|
||||||
const loadingIndicator = document.createElement("div");
|
const loadingIndicator = document.createElement("div");
|
||||||
loadingIndicator.id = config.loadingIndicatorId;
|
loadingIndicator.id = config.loadingIndicatorId;
|
||||||
loadingIndicator.className = "position-fixed top-0 start-0 end-0";
|
loadingIndicator.className = "position-fixed top-0 start-0 end-0";
|
||||||
loadingIndicator.innerHTML =
|
loadingIndicator.innerHTML =
|
||||||
'<div class="progress" style="height: 3px; border-radius: 0;"><div class="progress-bar progress-bar-striped progress-bar-animated bg-primary" style="width: 100%"></div></div>';
|
'<div class="progress" style="height: 3px; border-radius: 0;"><div class="progress-bar progress-bar-striped progress-bar-animated bg-primary" style="width: 100%"></div></div>';
|
||||||
loadingIndicator.style.display = "none";
|
loadingIndicator.style.display = "none";
|
||||||
loadingIndicator.style.zIndex = "9999";
|
loadingIndicator.style.zIndex = "9999";
|
||||||
document.body.appendChild(loadingIndicator);
|
document.body.appendChild(loadingIndicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the loading indicator element
|
// Get the loading indicator element
|
||||||
const loadingIndicator = document.getElementById(config.loadingIndicatorId);
|
const loadingIndicator = document.getElementById(config.loadingIndicatorId);
|
||||||
|
|
||||||
// Get the main content container
|
// Get the main content container
|
||||||
const mainContent = document.querySelector(config.mainContentSelector);
|
const mainContent = document.querySelector(config.mainContentSelector);
|
||||||
if (!mainContent) {
|
if (!mainContent) {
|
||||||
console.warn("Main content container not found. AJAX navigation disabled.");
|
console.warn("Main content container not found. AJAX navigation disabled.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to check if a URL should be excluded from AJAX navigation
|
// Function to check if a URL should be excluded from AJAX navigation
|
||||||
function shouldExcludeUrl(url) {
|
function shouldExcludeUrl(url) {
|
||||||
for (const pattern of config.excludePatterns) {
|
for (const pattern of config.excludePatterns) {
|
||||||
if (pattern.test(url)) {
|
if (pattern.test(url)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to show the loading indicator
|
// Function to show the loading indicator
|
||||||
function showLoading() {
|
function showLoading() {
|
||||||
loadingIndicator.style.display = "block";
|
loadingIndicator.style.display = "block";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to hide the loading indicator
|
// Function to hide the loading indicator
|
||||||
function hideLoading() {
|
function hideLoading() {
|
||||||
loadingIndicator.style.display = "none";
|
loadingIndicator.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to handle AJAX page navigation
|
// Function to handle AJAX page navigation
|
||||||
function handlePageNavigation(url, pushState = true) {
|
function handlePageNavigation(url, pushState = true) {
|
||||||
if (shouldExcludeUrl(url)) {
|
if (shouldExcludeUrl(url)) {
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showLoading();
|
showLoading();
|
||||||
const currentScrollPos = window.scrollY;
|
const currentScrollPos = window.scrollY;
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
"X-AJAX-Navigation": "true",
|
"X-AJAX-Navigation": "true",
|
||||||
Accept: "text/html",
|
Accept: "text/html",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response.ok)
|
if (!response.ok)
|
||||||
throw new Error(`Network response was not ok: ${response.status}`);
|
throw new Error(`Network response was not ok: ${response.status}`);
|
||||||
return response.text();
|
return response.text();
|
||||||
})
|
})
|
||||||
.then((html) => {
|
.then((html) => {
|
||||||
// Parse the HTML and extract #main-content
|
// Parse the HTML and extract #main-content
|
||||||
const tempDiv = document.createElement("div");
|
const tempDiv = document.createElement("div");
|
||||||
tempDiv.innerHTML = html;
|
tempDiv.innerHTML = html;
|
||||||
const newContent = tempDiv.querySelector(config.mainContentSelector);
|
const newContent = tempDiv.querySelector(config.mainContentSelector);
|
||||||
if (!newContent) throw new Error("Could not find main content in the response");
|
if (!newContent) throw new Error("Could not find main content in the response");
|
||||||
mainContent.innerHTML = newContent.innerHTML;
|
mainContent.innerHTML = newContent.innerHTML;
|
||||||
// Update the page title
|
// Update the page title
|
||||||
const titleMatch = html.match(/<title>(.*?)<\/title>/i);
|
const titleMatch = html.match(/<title>(.*?)<\/title>/i);
|
||||||
if (titleMatch) document.title = titleMatch[1];
|
if (titleMatch) document.title = titleMatch[1];
|
||||||
// Re-initialize dynamic content
|
// Re-initialize dynamic content
|
||||||
reloadScripts(mainContent);
|
reloadScripts(mainContent);
|
||||||
attachEventListeners();
|
attachEventListeners();
|
||||||
initializePageScripts();
|
initializePageScripts();
|
||||||
if (pushState) {
|
if (pushState) {
|
||||||
history.pushState(
|
history.pushState(
|
||||||
{ url: url, title: document.title, scrollPos: currentScrollPos },
|
{ url: url, title: document.title, scrollPos: currentScrollPos },
|
||||||
document.title,
|
document.title,
|
||||||
url,
|
url,
|
||||||
);
|
);
|
||||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||||
} else if (window.history.state && window.history.state.scrollPos) {
|
} else if (window.history.state && window.history.state.scrollPos) {
|
||||||
window.scrollTo({ top: window.history.state.scrollPos });
|
window.scrollTo({ top: window.history.state.scrollPos });
|
||||||
}
|
}
|
||||||
hideLoading();
|
hideLoading();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error during AJAX navigation:", error);
|
console.error("Error during AJAX navigation:", error);
|
||||||
hideLoading();
|
hideLoading();
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to reload and execute scripts in new content
|
// Function to reload and execute scripts in new content
|
||||||
function reloadScripts(container) {
|
function reloadScripts(container) {
|
||||||
const scripts = container.getElementsByTagName("script");
|
const scripts = container.getElementsByTagName("script");
|
||||||
for (let script of scripts) {
|
for (let script of scripts) {
|
||||||
const newScript = document.createElement("script");
|
const newScript = document.createElement("script");
|
||||||
|
|
||||||
// Copy all attributes
|
// Copy all attributes
|
||||||
Array.from(script.attributes).forEach((attr) => {
|
Array.from(script.attributes).forEach((attr) => {
|
||||||
newScript.setAttribute(attr.name, attr.value);
|
newScript.setAttribute(attr.name, attr.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy inline script content
|
// Copy inline script content
|
||||||
newScript.textContent = script.textContent;
|
newScript.textContent = script.textContent;
|
||||||
|
|
||||||
// Replace old script with new one
|
// Replace old script with new one
|
||||||
script.parentNode.replaceChild(newScript, script);
|
script.parentNode.replaceChild(newScript, script);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to handle form submissions
|
// Function to handle form submissions
|
||||||
function handleFormSubmission(form, e) {
|
function handleFormSubmission(form, e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Show loading indicator
|
// Show loading indicator
|
||||||
showLoading();
|
showLoading();
|
||||||
|
|
||||||
// Get form data
|
// Get form data
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
const method = form.method.toLowerCase();
|
const method = form.method.toLowerCase();
|
||||||
const url = form.action || window.location.href;
|
const url = form.action || window.location.href;
|
||||||
|
|
||||||
// Configure fetch options
|
// Configure fetch options
|
||||||
const fetchOptions = {
|
const fetchOptions = {
|
||||||
method: method,
|
method: method,
|
||||||
headers: {
|
headers: {
|
||||||
"X-AJAX-Navigation": "true",
|
"X-AJAX-Navigation": "true",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle different HTTP methods
|
// Handle different HTTP methods
|
||||||
if (method === "get") {
|
if (method === "get") {
|
||||||
const queryParams = new URLSearchParams(formData).toString();
|
const queryParams = new URLSearchParams(formData).toString();
|
||||||
handlePageNavigation(url + (queryParams ? "?" + queryParams : ""));
|
handlePageNavigation(url + (queryParams ? "?" + queryParams : ""));
|
||||||
} else {
|
} else {
|
||||||
fetchOptions.body = formData;
|
fetchOptions.body = formData;
|
||||||
|
|
||||||
fetch(url, fetchOptions)
|
fetch(url, fetchOptions)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.redirect) {
|
if (data.redirect) {
|
||||||
// Handle server-side redirects
|
// Handle server-side redirects
|
||||||
handlePageNavigation(data.redirect, true);
|
handlePageNavigation(data.redirect, true);
|
||||||
} else {
|
} else {
|
||||||
// Update page content
|
// Update page content
|
||||||
mainContent.innerHTML = data.html;
|
mainContent.innerHTML = data.html;
|
||||||
document.title = data.title || document.title;
|
document.title = data.title || document.title;
|
||||||
|
|
||||||
// Re-initialize dynamic content
|
// Re-initialize dynamic content
|
||||||
reloadScripts(mainContent);
|
reloadScripts(mainContent);
|
||||||
attachEventListeners();
|
attachEventListeners();
|
||||||
initializePageScripts();
|
initializePageScripts();
|
||||||
|
|
||||||
// Update URL if needed
|
// Update URL if needed
|
||||||
if (data.url) {
|
if (data.url) {
|
||||||
history.pushState({ url: data.url }, document.title, data.url);
|
history.pushState({ url: data.url }, document.title, data.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Form submission error:", error);
|
console.error("Form submission error:", error);
|
||||||
// Fallback to traditional form submission
|
// Fallback to traditional form submission
|
||||||
form.submit();
|
form.submit();
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to initialize scripts needed for the new page content
|
// Function to initialize scripts needed for the new page content
|
||||||
function initializePageScripts() {
|
function initializePageScripts() {
|
||||||
// Re-initialize any custom scripts that might be needed
|
// Re-initialize any custom scripts that might be needed
|
||||||
if (typeof setupAjaxPagination === "function") {
|
if (typeof setupAjaxPagination === "function") {
|
||||||
setupAjaxPagination();
|
setupAjaxPagination();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Bootstrap tooltips, popovers, etc.
|
// Initialize Bootstrap tooltips, popovers, etc.
|
||||||
if (typeof bootstrap !== "undefined") {
|
if (typeof bootstrap !== "undefined") {
|
||||||
// Initialize tooltips
|
// Initialize tooltips
|
||||||
const tooltipTriggerList = [].slice.call(
|
const tooltipTriggerList = [].slice.call(
|
||||||
document.querySelectorAll('[data-bs-toggle="tooltip"]'),
|
document.querySelectorAll('[data-bs-toggle="tooltip"]'),
|
||||||
);
|
);
|
||||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize popovers
|
// Initialize popovers
|
||||||
const popoverTriggerList = [].slice.call(
|
const popoverTriggerList = [].slice.call(
|
||||||
document.querySelectorAll('[data-bs-toggle="popover"]'),
|
document.querySelectorAll('[data-bs-toggle="popover"]'),
|
||||||
);
|
);
|
||||||
popoverTriggerList.map(function (popoverTriggerEl) {
|
popoverTriggerList.map(function (popoverTriggerEl) {
|
||||||
return new bootstrap.Popover(popoverTriggerEl);
|
return new bootstrap.Popover(popoverTriggerEl);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to attach event listeners to forms and links
|
// Function to attach event listeners to forms and links
|
||||||
function attachEventListeners() {
|
function attachEventListeners() {
|
||||||
// Handle AJAX navigation links
|
// Handle AJAX navigation links
|
||||||
document.querySelectorAll(config.navLinkSelector).forEach((link) => {
|
document.querySelectorAll(config.navLinkSelector).forEach((link) => {
|
||||||
if (!link.dataset.ajaxNavInitialized) {
|
if (!link.dataset.ajaxNavInitialized) {
|
||||||
link.addEventListener("click", function (e) {
|
link.addEventListener("click", function (e) {
|
||||||
if (e.ctrlKey || e.metaKey || e.shiftKey || shouldExcludeUrl(this.href)) {
|
if (e.ctrlKey || e.metaKey || e.shiftKey || shouldExcludeUrl(this.href)) {
|
||||||
return; // Let the browser handle these cases
|
return; // Let the browser handle these cases
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handlePageNavigation(this.href);
|
handlePageNavigation(this.href);
|
||||||
});
|
});
|
||||||
link.dataset.ajaxNavInitialized = "true";
|
link.dataset.ajaxNavInitialized = "true";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle forms with AJAX
|
// Handle forms with AJAX
|
||||||
document
|
document
|
||||||
.querySelectorAll("form.ajax-form, form.search-form, form.filter-form")
|
.querySelectorAll("form.ajax-form, form.search-form, form.filter-form")
|
||||||
.forEach((form) => {
|
.forEach((form) => {
|
||||||
if (!form.dataset.ajaxFormInitialized) {
|
if (!form.dataset.ajaxFormInitialized) {
|
||||||
form.addEventListener("submit", (e) => handleFormSubmission(form, e));
|
form.addEventListener("submit", (e) => handleFormSubmission(form, e));
|
||||||
form.dataset.ajaxFormInitialized = "true";
|
form.dataset.ajaxFormInitialized = "true";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial attachment of event listeners
|
// Initial attachment of event listeners
|
||||||
attachEventListeners();
|
attachEventListeners();
|
||||||
|
|
||||||
// Handle browser back/forward buttons
|
// Handle browser back/forward buttons
|
||||||
window.addEventListener("popstate", function (event) {
|
window.addEventListener("popstate", function (event) {
|
||||||
if (event.state && event.state.url) {
|
if (event.state && event.state.url) {
|
||||||
handlePageNavigation(event.state.url, false);
|
handlePageNavigation(event.state.url, false);
|
||||||
} else {
|
} else {
|
||||||
// Fallback to current URL if no state
|
// Fallback to current URL if no state
|
||||||
handlePageNavigation(window.location.href, false);
|
handlePageNavigation(window.location.href, false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,101 +6,101 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Initialize AJAX pagination
|
// Initialize AJAX pagination
|
||||||
setupAjaxPagination();
|
setupAjaxPagination();
|
||||||
|
|
||||||
// Function to set up AJAX pagination for the entire application
|
// Function to set up AJAX pagination for the entire application
|
||||||
function setupAjaxPagination() {
|
function setupAjaxPagination() {
|
||||||
// Configuration - can be customized per page if needed
|
// Configuration - can be customized per page if needed
|
||||||
const config = {
|
const config = {
|
||||||
contentContainerId: "ajax-content-container", // ID of the container to update
|
contentContainerId: "ajax-content-container", // ID of the container to update
|
||||||
loadingSpinnerId: "ajax-loading-spinner", // ID of the loading spinner
|
loadingSpinnerId: "ajax-loading-spinner", // ID of the loading spinner
|
||||||
paginationLinkClass: "pagination-link", // Class for pagination links
|
paginationLinkClass: "pagination-link", // Class for pagination links
|
||||||
retryMessage: "An error occurred while loading data. Please try again.",
|
retryMessage: "An error occurred while loading data. Please try again.",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get container elements
|
// Get container elements
|
||||||
const contentContainer = document.getElementById(config.contentContainerId);
|
const contentContainer = document.getElementById(config.contentContainerId);
|
||||||
const loadingSpinner = document.getElementById(config.loadingSpinnerId);
|
const loadingSpinner = document.getElementById(config.loadingSpinnerId);
|
||||||
|
|
||||||
// Exit if the page doesn't have the required elements
|
// Exit if the page doesn't have the required elements
|
||||||
if (!contentContainer || !loadingSpinner) return;
|
if (!contentContainer || !loadingSpinner) return;
|
||||||
|
|
||||||
// Function to handle pagination clicks
|
// Function to handle pagination clicks
|
||||||
function setupPaginationListeners() {
|
function setupPaginationListeners() {
|
||||||
document.querySelectorAll("." + config.paginationLinkClass).forEach((link) => {
|
document.querySelectorAll("." + config.paginationLinkClass).forEach((link) => {
|
||||||
link.addEventListener("click", function (e) {
|
link.addEventListener("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleAjaxNavigation(this.href);
|
handleAjaxNavigation(this.href);
|
||||||
|
|
||||||
// Get the page number if available
|
// Get the page number if available
|
||||||
const page = this.getAttribute("data-page");
|
const page = this.getAttribute("data-page");
|
||||||
|
|
||||||
// Update browser URL without refreshing
|
// Update browser URL without refreshing
|
||||||
const newUrl = this.href;
|
const newUrl = this.href;
|
||||||
history.pushState({ url: newUrl, page: page }, "", newUrl);
|
history.pushState({ url: newUrl, page: page }, "", newUrl);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to handle AJAX navigation
|
// Function to handle AJAX navigation
|
||||||
function handleAjaxNavigation(url) {
|
function handleAjaxNavigation(url) {
|
||||||
// Show loading spinner
|
// Show loading spinner
|
||||||
contentContainer.classList.add("d-none");
|
contentContainer.classList.add("d-none");
|
||||||
loadingSpinner.classList.remove("d-none");
|
loadingSpinner.classList.remove("d-none");
|
||||||
|
|
||||||
// Fetch data via AJAX
|
// Fetch data via AJAX
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Network response was not ok: ${response.status}`);
|
throw new Error(`Network response was not ok: ${response.status}`);
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.status === "success") {
|
if (data.status === "success") {
|
||||||
// Update the content
|
// Update the content
|
||||||
contentContainer.innerHTML = data.html_data;
|
contentContainer.innerHTML = data.html_data;
|
||||||
|
|
||||||
// Re-attach event listeners to new pagination links
|
// Re-attach event listeners to new pagination links
|
||||||
setupPaginationListeners();
|
setupPaginationListeners();
|
||||||
|
|
||||||
// Update any summary data if present and the page provides it
|
// Update any summary data if present and the page provides it
|
||||||
if (typeof updateSummary === "function" && data.summary) {
|
if (typeof updateSummary === "function" && data.summary) {
|
||||||
updateSummary(data);
|
updateSummary(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide loading spinner, show content
|
// Hide loading spinner, show content
|
||||||
loadingSpinner.classList.add("d-none");
|
loadingSpinner.classList.add("d-none");
|
||||||
contentContainer.classList.remove("d-none");
|
contentContainer.classList.remove("d-none");
|
||||||
|
|
||||||
// Scroll to top of the content container
|
// Scroll to top of the content container
|
||||||
contentContainer.scrollIntoView({ behavior: "smooth", block: "start" });
|
contentContainer.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error fetching data:", error);
|
console.error("Error fetching data:", error);
|
||||||
loadingSpinner.classList.add("d-none");
|
loadingSpinner.classList.add("d-none");
|
||||||
contentContainer.classList.remove("d-none");
|
contentContainer.classList.remove("d-none");
|
||||||
alert(config.retryMessage);
|
alert(config.retryMessage);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initial setup of event listeners
|
// Initial setup of event listeners
|
||||||
setupPaginationListeners();
|
setupPaginationListeners();
|
||||||
|
|
||||||
// Handle browser back/forward buttons
|
// Handle browser back/forward buttons
|
||||||
window.addEventListener("popstate", function (event) {
|
window.addEventListener("popstate", function (event) {
|
||||||
if (event.state && event.state.url) {
|
if (event.state && event.state.url) {
|
||||||
handleAjaxNavigation(event.state.url);
|
handleAjaxNavigation(event.state.url);
|
||||||
} else {
|
} else {
|
||||||
// If no state, fetch current URL
|
// If no state, fetch current URL
|
||||||
handleAjaxNavigation(window.location.href);
|
handleAjaxNavigation(window.location.href);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,272 +7,272 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Chart responsiveness
|
// Chart responsiveness
|
||||||
function resizeCharts() {
|
function resizeCharts() {
|
||||||
const charts = document.querySelectorAll(".chart-container");
|
const charts = document.querySelectorAll(".chart-container");
|
||||||
charts.forEach((chart) => {
|
charts.forEach((chart) => {
|
||||||
if (chart.id && window.Plotly) {
|
if (chart.id && window.Plotly) {
|
||||||
Plotly.relayout(chart.id, {
|
Plotly.relayout(chart.id, {
|
||||||
"xaxis.automargin": true,
|
"xaxis.automargin": true,
|
||||||
"yaxis.automargin": true,
|
"yaxis.automargin": true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle window resize
|
// Handle window resize
|
||||||
window.addEventListener("resize", function () {
|
window.addEventListener("resize", function () {
|
||||||
if (window.Plotly) {
|
if (window.Plotly) {
|
||||||
resizeCharts();
|
resizeCharts();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Time range filtering
|
// Time range filtering
|
||||||
const timeRangeDropdown = document.getElementById("timeRangeDropdown");
|
const timeRangeDropdown = document.getElementById("timeRangeDropdown");
|
||||||
if (timeRangeDropdown) {
|
if (timeRangeDropdown) {
|
||||||
const timeRangeLinks = timeRangeDropdown.querySelectorAll(".dropdown-item");
|
const timeRangeLinks = timeRangeDropdown.querySelectorAll(".dropdown-item");
|
||||||
timeRangeLinks.forEach((link) => {
|
timeRangeLinks.forEach((link) => {
|
||||||
link.addEventListener("click", function (e) {
|
link.addEventListener("click", function (e) {
|
||||||
const url = new URL(this.href);
|
const url = new URL(this.href);
|
||||||
const dashboardId = url.searchParams.get("dashboard_id");
|
const dashboardId = url.searchParams.get("dashboard_id");
|
||||||
const timeRange = url.searchParams.get("time_range");
|
const timeRange = url.searchParams.get("time_range");
|
||||||
|
|
||||||
// Fetch updated data via AJAX
|
// Fetch updated data via AJAX
|
||||||
if (dashboardId) {
|
if (dashboardId) {
|
||||||
fetchDashboardData(dashboardId, timeRange);
|
fetchDashboardData(dashboardId, timeRange);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to fetch dashboard data
|
// Function to fetch dashboard data
|
||||||
function fetchDashboardData(dashboardId, timeRange) {
|
function fetchDashboardData(dashboardId, timeRange) {
|
||||||
const loadingOverlay = document.createElement("div");
|
const loadingOverlay = document.createElement("div");
|
||||||
loadingOverlay.className = "loading-overlay";
|
loadingOverlay.className = "loading-overlay";
|
||||||
loadingOverlay.innerHTML =
|
loadingOverlay.innerHTML =
|
||||||
'<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>';
|
'<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>';
|
||||||
document.querySelector("main").appendChild(loadingOverlay);
|
document.querySelector("main").appendChild(loadingOverlay);
|
||||||
|
|
||||||
fetch(`/dashboard/api/dashboard/${dashboardId}/data/?time_range=${timeRange || "all"}`)
|
fetch(`/dashboard/api/dashboard/${dashboardId}/data/?time_range=${timeRange || "all"}`)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Network response was not ok: ${response.status}`);
|
throw new Error(`Network response was not ok: ${response.status}`);
|
||||||
}
|
}
|
||||||
return response.json();
|
return response.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log("Dashboard API response:", data);
|
console.log("Dashboard API response:", data);
|
||||||
updateDashboardStats(data);
|
updateDashboardStats(data);
|
||||||
updateDashboardCharts(data);
|
updateDashboardCharts(data);
|
||||||
|
|
||||||
// Update URL without page reload
|
// Update URL without page reload
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.set("dashboard_id", dashboardId);
|
url.searchParams.set("dashboard_id", dashboardId);
|
||||||
if (timeRange) {
|
if (timeRange) {
|
||||||
url.searchParams.set("time_range", timeRange);
|
url.searchParams.set("time_range", timeRange);
|
||||||
}
|
}
|
||||||
window.history.pushState({}, "", url);
|
window.history.pushState({}, "", url);
|
||||||
|
|
||||||
document.querySelector(".loading-overlay").remove();
|
document.querySelector(".loading-overlay").remove();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error fetching dashboard data:", error);
|
console.error("Error fetching dashboard data:", error);
|
||||||
document.querySelector(".loading-overlay").remove();
|
document.querySelector(".loading-overlay").remove();
|
||||||
|
|
||||||
// Show error message
|
// Show error message
|
||||||
const alertElement = document.createElement("div");
|
const alertElement = document.createElement("div");
|
||||||
alertElement.className = "alert alert-danger alert-dismissible fade show";
|
alertElement.className = "alert alert-danger alert-dismissible fade show";
|
||||||
alertElement.setAttribute("role", "alert");
|
alertElement.setAttribute("role", "alert");
|
||||||
alertElement.innerHTML = `
|
alertElement.innerHTML = `
|
||||||
Error loading dashboard data. Please try again.
|
Error loading dashboard data. Please try again.
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<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 to update dashboard statistics
|
||||||
function updateDashboardStats(data) {
|
function updateDashboardStats(data) {
|
||||||
// Update total sessions
|
// Update total sessions
|
||||||
const totalSessionsElement = document.querySelector(".stats-card:nth-child(1) h3");
|
const totalSessionsElement = document.querySelector(".stats-card:nth-child(1) h3");
|
||||||
if (totalSessionsElement) {
|
if (totalSessionsElement) {
|
||||||
totalSessionsElement.textContent = data.total_sessions;
|
totalSessionsElement.textContent = data.total_sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update average response time
|
// Update average response time
|
||||||
const avgResponseTimeElement = document.querySelector(".stats-card:nth-child(2) h3");
|
const avgResponseTimeElement = document.querySelector(".stats-card:nth-child(2) h3");
|
||||||
if (avgResponseTimeElement) {
|
if (avgResponseTimeElement) {
|
||||||
avgResponseTimeElement.textContent = data.avg_response_time + "s";
|
avgResponseTimeElement.textContent = data.avg_response_time + "s";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total tokens
|
// Update total tokens
|
||||||
const totalTokensElement = document.querySelector(".stats-card:nth-child(3) h3");
|
const totalTokensElement = document.querySelector(".stats-card:nth-child(3) h3");
|
||||||
if (totalTokensElement) {
|
if (totalTokensElement) {
|
||||||
totalTokensElement.textContent = data.total_tokens;
|
totalTokensElement.textContent = data.total_tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total cost
|
// Update total cost
|
||||||
const totalCostElement = document.querySelector(".stats-card:nth-child(4) h3");
|
const totalCostElement = document.querySelector(".stats-card:nth-child(4) h3");
|
||||||
if (totalCostElement) {
|
if (totalCostElement) {
|
||||||
totalCostElement.textContent = "€" + data.total_cost;
|
totalCostElement.textContent = "€" + data.total_cost;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to update dashboard charts
|
// Function to update dashboard charts
|
||||||
function updateDashboardCharts(data) {
|
function updateDashboardCharts(data) {
|
||||||
// Check if Plotly is available
|
// Check if Plotly is available
|
||||||
if (!window.Plotly) {
|
if (!window.Plotly) {
|
||||||
console.error("Plotly library not loaded!");
|
console.error("Plotly library not loaded!");
|
||||||
document.querySelectorAll(".chart-container").forEach((container) => {
|
document.querySelectorAll(".chart-container").forEach((container) => {
|
||||||
container.innerHTML =
|
container.innerHTML =
|
||||||
'<div class="text-center py-5"><p class="text-danger">Chart library not available. Please refresh the page.</p></div>';
|
'<div class="text-center py-5"><p class="text-danger">Chart library not available. Please refresh the page.</p></div>';
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update sessions over time chart
|
// Update sessions over time chart
|
||||||
const timeSeriesData = data.time_series_data;
|
const timeSeriesData = data.time_series_data;
|
||||||
if (timeSeriesData && timeSeriesData.length > 0) {
|
if (timeSeriesData && timeSeriesData.length > 0) {
|
||||||
try {
|
try {
|
||||||
const timeSeriesX = timeSeriesData.map((item) => item.date);
|
const timeSeriesX = timeSeriesData.map((item) => item.date);
|
||||||
const timeSeriesY = timeSeriesData.map((item) => item.count);
|
const timeSeriesY = timeSeriesData.map((item) => item.count);
|
||||||
|
|
||||||
Plotly.react(
|
Plotly.react(
|
||||||
"sessions-time-chart",
|
"sessions-time-chart",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
x: timeSeriesX,
|
x: timeSeriesX,
|
||||||
y: timeSeriesY,
|
y: timeSeriesY,
|
||||||
type: "scatter",
|
type: "scatter",
|
||||||
mode: "lines+markers",
|
mode: "lines+markers",
|
||||||
line: {
|
line: {
|
||||||
color: "rgb(75, 192, 192)",
|
color: "rgb(75, 192, 192)",
|
||||||
width: 2,
|
width: 2,
|
||||||
},
|
},
|
||||||
marker: {
|
marker: {
|
||||||
color: "rgb(75, 192, 192)",
|
color: "rgb(75, 192, 192)",
|
||||||
size: 6,
|
size: 6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
margin: { t: 10, r: 10, b: 40, l: 40 },
|
margin: { t: 10, r: 10, b: 40, l: 40 },
|
||||||
xaxis: {
|
xaxis: {
|
||||||
title: "Date",
|
title: "Date",
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
title: "Number of Sessions",
|
title: "Number of Sessions",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error rendering time series chart:", error);
|
console.error("Error rendering time series chart:", error);
|
||||||
document.getElementById("sessions-time-chart").innerHTML =
|
document.getElementById("sessions-time-chart").innerHTML =
|
||||||
'<div class="text-center py-5"><p class="text-danger">Error rendering chart.</p></div>';
|
'<div class="text-center py-5"><p class="text-danger">Error rendering chart.</p></div>';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("sessions-time-chart").innerHTML =
|
document.getElementById("sessions-time-chart").innerHTML =
|
||||||
'<div class="text-center py-5"><p class="text-muted">No time series data available</p></div>';
|
'<div class="text-center py-5"><p class="text-muted">No time series data available</p></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update sentiment chart
|
// Update sentiment chart
|
||||||
const sentimentData = data.sentiment_data;
|
const sentimentData = data.sentiment_data;
|
||||||
if (sentimentData && sentimentData.length > 0 && window.Plotly) {
|
if (sentimentData && sentimentData.length > 0 && window.Plotly) {
|
||||||
const sentimentLabels = sentimentData.map((item) => item.sentiment);
|
const sentimentLabels = sentimentData.map((item) => item.sentiment);
|
||||||
const sentimentValues = sentimentData.map((item) => item.count);
|
const sentimentValues = sentimentData.map((item) => item.count);
|
||||||
const sentimentColors = sentimentLabels.map((sentiment) => {
|
const sentimentColors = sentimentLabels.map((sentiment) => {
|
||||||
if (sentiment.toLowerCase().includes("positive")) return "rgb(75, 192, 92)";
|
if (sentiment.toLowerCase().includes("positive")) return "rgb(75, 192, 92)";
|
||||||
if (sentiment.toLowerCase().includes("negative")) return "rgb(255, 99, 132)";
|
if (sentiment.toLowerCase().includes("negative")) return "rgb(255, 99, 132)";
|
||||||
if (sentiment.toLowerCase().includes("neutral")) return "rgb(255, 205, 86)";
|
if (sentiment.toLowerCase().includes("neutral")) return "rgb(255, 205, 86)";
|
||||||
return "rgb(201, 203, 207)";
|
return "rgb(201, 203, 207)";
|
||||||
});
|
});
|
||||||
|
|
||||||
Plotly.react(
|
Plotly.react(
|
||||||
"sentiment-chart",
|
"sentiment-chart",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
values: sentimentValues,
|
values: sentimentValues,
|
||||||
labels: sentimentLabels,
|
labels: sentimentLabels,
|
||||||
type: "pie",
|
type: "pie",
|
||||||
marker: {
|
marker: {
|
||||||
colors: sentimentColors,
|
colors: sentimentColors,
|
||||||
},
|
},
|
||||||
hole: 0.4,
|
hole: 0.4,
|
||||||
textinfo: "label+percent",
|
textinfo: "label+percent",
|
||||||
insidetextorientation: "radial",
|
insidetextorientation: "radial",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
margin: { t: 10, r: 10, b: 10, l: 10 },
|
margin: { t: 10, r: 10, b: 10, l: 10 },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update country chart
|
// Update country chart
|
||||||
const countryData = data.country_data;
|
const countryData = data.country_data;
|
||||||
if (countryData && countryData.length > 0 && window.Plotly) {
|
if (countryData && countryData.length > 0 && window.Plotly) {
|
||||||
const countryLabels = countryData.map((item) => item.country);
|
const countryLabels = countryData.map((item) => item.country);
|
||||||
const countryValues = countryData.map((item) => item.count);
|
const countryValues = countryData.map((item) => item.count);
|
||||||
|
|
||||||
Plotly.react(
|
Plotly.react(
|
||||||
"country-chart",
|
"country-chart",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
x: countryValues,
|
x: countryValues,
|
||||||
y: countryLabels,
|
y: countryLabels,
|
||||||
type: "bar",
|
type: "bar",
|
||||||
orientation: "h",
|
orientation: "h",
|
||||||
marker: {
|
marker: {
|
||||||
color: "rgb(54, 162, 235)",
|
color: "rgb(54, 162, 235)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
margin: { t: 10, r: 10, b: 40, l: 100 },
|
margin: { t: 10, r: 10, b: 40, l: 100 },
|
||||||
xaxis: {
|
xaxis: {
|
||||||
title: "Number of Sessions",
|
title: "Number of Sessions",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update category chart
|
// Update category chart
|
||||||
const categoryData = data.category_data;
|
const categoryData = data.category_data;
|
||||||
if (categoryData && categoryData.length > 0 && window.Plotly) {
|
if (categoryData && categoryData.length > 0 && window.Plotly) {
|
||||||
const categoryLabels = categoryData.map((item) => item.category);
|
const categoryLabels = categoryData.map((item) => item.category);
|
||||||
const categoryValues = categoryData.map((item) => item.count);
|
const categoryValues = categoryData.map((item) => item.count);
|
||||||
|
|
||||||
Plotly.react(
|
Plotly.react(
|
||||||
"category-chart",
|
"category-chart",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
labels: categoryLabels,
|
labels: categoryLabels,
|
||||||
values: categoryValues,
|
values: categoryValues,
|
||||||
type: "pie",
|
type: "pie",
|
||||||
textinfo: "label+percent",
|
textinfo: "label+percent",
|
||||||
insidetextorientation: "radial",
|
insidetextorientation: "radial",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
margin: { t: 10, r: 10, b: 10, l: 10 },
|
margin: { t: 10, r: 10, b: 10, l: 10 },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dashboard selector
|
// Dashboard selector
|
||||||
const dashboardSelector = document.querySelectorAll('a[href^="?dashboard_id="]');
|
const dashboardSelector = document.querySelectorAll('a[href^="?dashboard_id="]');
|
||||||
dashboardSelector.forEach((link) => {
|
dashboardSelector.forEach((link) => {
|
||||||
link.addEventListener("click", function (e) {
|
link.addEventListener("click", function (e) {
|
||||||
const url = new URL(this.href);
|
const url = new URL(this.href);
|
||||||
const dashboardId = url.searchParams.get("dashboard_id");
|
const dashboardId = url.searchParams.get("dashboard_id");
|
||||||
|
|
||||||
// Fetch updated data via AJAX
|
// Fetch updated data via AJAX
|
||||||
if (dashboardId) {
|
if (dashboardId) {
|
||||||
fetchDashboardData(dashboardId);
|
fetchDashboardData(dashboardId);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -6,147 +6,147 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
// Initialize tooltips
|
// Initialize tooltips
|
||||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize popovers
|
// Initialize popovers
|
||||||
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
|
||||||
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
|
var popoverList = popoverTriggerList.map(function (popoverTriggerEl) {
|
||||||
return new bootstrap.Popover(popoverTriggerEl);
|
return new bootstrap.Popover(popoverTriggerEl);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle sidebar on mobile
|
// Toggle sidebar on mobile
|
||||||
const sidebarToggle = document.querySelector("#sidebarToggle");
|
const sidebarToggle = document.querySelector("#sidebarToggle");
|
||||||
if (sidebarToggle) {
|
if (sidebarToggle) {
|
||||||
sidebarToggle.addEventListener("click", function () {
|
sidebarToggle.addEventListener("click", function () {
|
||||||
document.querySelector(".sidebar").classList.toggle("show");
|
document.querySelector(".sidebar").classList.toggle("show");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-dismiss alerts after 5 seconds
|
// Auto-dismiss alerts after 5 seconds
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
var alerts = document.querySelectorAll(".alert:not(.alert-important)");
|
var alerts = document.querySelectorAll(".alert:not(.alert-important)");
|
||||||
alerts.forEach(function (alert) {
|
alerts.forEach(function (alert) {
|
||||||
if (alert && bootstrap.Alert.getInstance(alert)) {
|
if (alert && bootstrap.Alert.getInstance(alert)) {
|
||||||
bootstrap.Alert.getInstance(alert).close();
|
bootstrap.Alert.getInstance(alert).close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
// Form validation
|
// Form validation
|
||||||
const forms = document.querySelectorAll(".needs-validation");
|
const forms = document.querySelectorAll(".needs-validation");
|
||||||
forms.forEach(function (form) {
|
forms.forEach(function (form) {
|
||||||
form.addEventListener(
|
form.addEventListener(
|
||||||
"submit",
|
"submit",
|
||||||
function (event) {
|
function (event) {
|
||||||
if (!form.checkValidity()) {
|
if (!form.checkValidity()) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
form.classList.add("was-validated");
|
form.classList.add("was-validated");
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Confirm dialogs
|
// Confirm dialogs
|
||||||
const confirmButtons = document.querySelectorAll("[data-confirm]");
|
const confirmButtons = document.querySelectorAll("[data-confirm]");
|
||||||
confirmButtons.forEach(function (button) {
|
confirmButtons.forEach(function (button) {
|
||||||
button.addEventListener("click", function (event) {
|
button.addEventListener("click", function (event) {
|
||||||
if (!confirm(this.dataset.confirm || "Are you sure?")) {
|
if (!confirm(this.dataset.confirm || "Are you sure?")) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Back button
|
// Back button
|
||||||
const backButtons = document.querySelectorAll(".btn-back");
|
const backButtons = document.querySelectorAll(".btn-back");
|
||||||
backButtons.forEach(function (button) {
|
backButtons.forEach(function (button) {
|
||||||
button.addEventListener("click", function (event) {
|
button.addEventListener("click", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
window.history.back();
|
window.history.back();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// File input customization
|
// File input customization
|
||||||
const fileInputs = document.querySelectorAll(".custom-file-input");
|
const fileInputs = document.querySelectorAll(".custom-file-input");
|
||||||
fileInputs.forEach(function (input) {
|
fileInputs.forEach(function (input) {
|
||||||
input.addEventListener("change", function (e) {
|
input.addEventListener("change", function (e) {
|
||||||
const fileName = this.files[0]?.name || "Choose file";
|
const fileName = this.files[0]?.name || "Choose file";
|
||||||
const nextSibling = this.nextElementSibling;
|
const nextSibling = this.nextElementSibling;
|
||||||
if (nextSibling) {
|
if (nextSibling) {
|
||||||
nextSibling.innerText = fileName;
|
nextSibling.innerText = fileName;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Search form submit on enter
|
// Search form submit on enter
|
||||||
const searchInputs = document.querySelectorAll(".search-input");
|
const searchInputs = document.querySelectorAll(".search-input");
|
||||||
searchInputs.forEach(function (input) {
|
searchInputs.forEach(function (input) {
|
||||||
input.addEventListener("keypress", function (e) {
|
input.addEventListener("keypress", function (e) {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.closest("form").submit();
|
this.closest("form").submit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Toggle password visibility
|
// Toggle password visibility
|
||||||
const togglePasswordButtons = document.querySelectorAll(".toggle-password");
|
const togglePasswordButtons = document.querySelectorAll(".toggle-password");
|
||||||
togglePasswordButtons.forEach(function (button) {
|
togglePasswordButtons.forEach(function (button) {
|
||||||
button.addEventListener("click", function () {
|
button.addEventListener("click", function () {
|
||||||
const target = document.querySelector(this.dataset.target);
|
const target = document.querySelector(this.dataset.target);
|
||||||
if (target) {
|
if (target) {
|
||||||
const type = target.getAttribute("type") === "password" ? "text" : "password";
|
const type = target.getAttribute("type") === "password" ? "text" : "password";
|
||||||
target.setAttribute("type", type);
|
target.setAttribute("type", type);
|
||||||
this.querySelector("i").classList.toggle("fa-eye");
|
this.querySelector("i").classList.toggle("fa-eye");
|
||||||
this.querySelector("i").classList.toggle("fa-eye-slash");
|
this.querySelector("i").classList.toggle("fa-eye-slash");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Dropdown menu positioning
|
// Dropdown menu positioning
|
||||||
const dropdowns = document.querySelectorAll(".dropdown-menu");
|
const dropdowns = document.querySelectorAll(".dropdown-menu");
|
||||||
dropdowns.forEach(function (dropdown) {
|
dropdowns.forEach(function (dropdown) {
|
||||||
dropdown.addEventListener("click", function (e) {
|
dropdown.addEventListener("click", function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Responsive table handling
|
// Responsive table handling
|
||||||
const tables = document.querySelectorAll(".table-responsive");
|
const tables = document.querySelectorAll(".table-responsive");
|
||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
tables.forEach(function (table) {
|
tables.forEach(function (table) {
|
||||||
table.classList.add("table-responsive-force");
|
table.classList.add("table-responsive-force");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle special links (printable views, exports)
|
// Handle special links (printable views, exports)
|
||||||
const printLinks = document.querySelectorAll(".print-link");
|
const printLinks = document.querySelectorAll(".print-link");
|
||||||
printLinks.forEach(function (link) {
|
printLinks.forEach(function (link) {
|
||||||
link.addEventListener("click", function (e) {
|
link.addEventListener("click", function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.print();
|
window.print();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const exportLinks = document.querySelectorAll("[data-export]");
|
const exportLinks = document.querySelectorAll("[data-export]");
|
||||||
exportLinks.forEach(function (link) {
|
exportLinks.forEach(function (link) {
|
||||||
link.addEventListener("click", function (e) {
|
link.addEventListener("click", function (e) {
|
||||||
// Handle export functionality if needed
|
// Handle export functionality if needed
|
||||||
console.log("Export requested:", this.dataset.export);
|
console.log("Export requested:", this.dataset.export);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle sidebar collapse on small screens
|
// Handle sidebar collapse on small screens
|
||||||
function handleSidebarOnResize() {
|
function handleSidebarOnResize() {
|
||||||
if (window.innerWidth < 768) {
|
if (window.innerWidth < 768) {
|
||||||
document.querySelector(".sidebar")?.classList.remove("show");
|
document.querySelector(".sidebar")?.classList.remove("show");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("resize", handleSidebarOnResize);
|
window.addEventListener("resize", handleSidebarOnResize);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,31 +1,27 @@
|
|||||||
<!-- templates/accounts/login.html -->
|
<!-- templates/accounts/login.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% load crispy_forms_tags %}
|
||||||
{% load crispy_forms_tags %}
|
{% block title %}
|
||||||
|
Login | Chat Analytics
|
||||||
{% block title %}Login | Chat Analytics{% endblock %}
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
{% block content %}
|
<div class="row justify-content-center">
|
||||||
<div class="row justify-content-center">
|
<div class="col-md-6">
|
||||||
<div class="col-md-6">
|
<div class="card mt-4">
|
||||||
<div class="card mt-4">
|
<div class="card-header">
|
||||||
<div class="card-header">
|
<h4 class="card-title mb-0">Login</h4>
|
||||||
<h4 class="card-title mb-0">Login</h4>
|
</div>
|
||||||
</div>
|
<div class="card-body">
|
||||||
<div class="card-body">
|
<form method="post">
|
||||||
<form method="post">
|
{% csrf_token %} {{ form|crispy }}
|
||||||
{% csrf_token %}
|
<div class="d-grid gap-2">
|
||||||
{{ form|crispy }}
|
<button type="submit" class="btn btn-primary">Login</button>
|
||||||
<div class="d-grid gap-2">
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Login</button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div class="card-footer text-center">
|
||||||
</div>
|
<p class="mb-0">Don't have an account? <a href="{% url 'register' %}">Register</a></p>
|
||||||
<div class="card-footer text-center">
|
</div>
|
||||||
<p class="mb-0">
|
</div>
|
||||||
Don't have an account? <a href="{% url 'register' %}">Register</a>
|
</div>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,37 +1,34 @@
|
|||||||
<!-- templates/accounts/password_change.html -->
|
<!-- templates/accounts/password_change.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load crispy_forms_tags %}
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Change Password | Chat Analytics{% endblock %}
|
{% block title %}Change Password | Chat Analytics{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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>
|
<h1 class="h2">Change Password</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary">
|
<a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Profile
|
<i class="fas fa-arrow-left"></i> Back to Profile
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Change Your Password</h5>
|
<h5 class="card-title mb-0">Change Your Password</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %} {{ form|crispy }}
|
||||||
{{ form|crispy }}
|
<div class="d-grid gap-2">
|
||||||
<div class="d-grid gap-2">
|
<button type="submit" class="btn btn-primary">Change Password</button>
|
||||||
<button type="submit" class="btn btn-primary">Change Password</button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,44 +1,38 @@
|
|||||||
<!-- templates/accounts/password_change_done.html -->
|
<!-- templates/accounts/password_change_done.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% block title %}Password Changed | Chat Analytics{% endblock %}
|
||||||
|
|
||||||
{% block title %}Password Changed | Chat Analytics{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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>
|
<h1 class="h2">Password Changed</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary">
|
<a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Profile
|
<i class="fas fa-arrow-left"></i> Back to Profile
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header bg-success text-white">
|
<div class="card-header bg-success text-white">
|
||||||
<h5 class="card-title mb-0">Password Changed Successfully</h5>
|
<h5 class="card-title mb-0">Password Changed Successfully</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<i class="fas fa-check-circle fa-4x text-success mb-3"></i>
|
<i class="fas fa-check-circle fa-4x text-success mb-3"></i>
|
||||||
<h4>Your password has been changed successfully!</h4>
|
<h4>Your password has been changed successfully!</h4>
|
||||||
<p>
|
<p>Your new password is now active. You can use it the next time you log in.</p>
|
||||||
Your new password is now active. You can use it the next time you log
|
</div>
|
||||||
in.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a href="{% url 'profile' %}" class="btn btn-primary">Return to Profile</a>
|
<a href="{% url 'profile' %}" class="btn btn-primary">Return to Profile</a>
|
||||||
<a href="{% url 'dashboard' %}" class="btn btn-outline-secondary ms-2"
|
<a href="{% url 'dashboard' %}" class="btn btn-outline-secondary ms-2"
|
||||||
>Go to Dashboard</a
|
>Go to Dashboard</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,213 +1,187 @@
|
|||||||
<!-- templates/accounts/profile.html -->
|
<!-- templates/accounts/profile.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block title %}My Profile | Chat Analytics{% endblock %}
|
{% block title %}My Profile | Chat Analytics{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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>
|
<h1 class="h2">My Profile</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary">
|
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Account Information</h5>
|
<h5 class="card-title mb-0">Account Information</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Username:</div>
|
<div class="col-md-4 fw-bold">Username:</div>
|
||||||
<div class="col-md-8">{{ user.username }}</div>
|
<div class="col-md-8">{{ user.username }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Email:</div>
|
<div class="col-md-4 fw-bold">Email:</div>
|
||||||
<div class="col-md-8">{{ user.email }}</div>
|
<div class="col-md-8">{{ user.email }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Company:</div>
|
<div class="col-md-4 fw-bold">Company:</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
{% if user.company %}
|
{% if user.company %}
|
||||||
{{ user.company.name }}
|
{{ user.company.name }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">Not assigned to a company</span>
|
<span class="text-muted">Not assigned to a company</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Role:</div>
|
<div class="col-md-4 fw-bold">Role:</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<span class="badge bg-danger">Admin</span>
|
<span class="badge bg-danger">Admin</span>
|
||||||
{% elif user.is_company_admin %}
|
{% elif user.is_company_admin %}
|
||||||
<span class="badge bg-primary">Company Admin</span>
|
<span class="badge bg-primary">Company Admin</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">User</span>
|
<span class="badge bg-secondary">User</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Last Login:</div>
|
<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 class="col-md-8">{{ user.last_login|date:"F d, Y H:i" }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Date Joined:</div>
|
<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 class="col-md-8">{{ user.date_joined|date:"F d, Y H:i" }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<a href="{% url 'password_change' %}" class="btn btn-primary"
|
<a href="{% url 'password_change' %}" class="btn btn-primary">Change Password</a>
|
||||||
>Change Password</a
|
</div>
|
||||||
>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if user.company %}
|
{% if user.company %}
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Company Information</h5>
|
<h5 class="card-title mb-0">Company Information</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Company Name:</div>
|
<div class="col-md-4 fw-bold">Company Name:</div>
|
||||||
<div class="col-md-8">{{ user.company.name }}</div>
|
<div class="col-md-8">{{ user.company.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Description:</div>
|
<div class="col-md-4 fw-bold">Description:</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
{{ user.company.description|default:"No description available." }}
|
{{ user.company.description|default:"No description available." }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Created:</div>
|
<div class="col-md-4 fw-bold">Created:</div>
|
||||||
<div class="col-md-8">{{ user.company.created_at|date:"F d, Y" }}</div>
|
<div class="col-md-8">{{ user.company.created_at|date:"F d, Y" }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Total Employees:</div>
|
<div class="col-md-4 fw-bold">Total Employees:</div>
|
||||||
<div class="col-md-8">{{ user.company.employees.count }}</div>
|
<div class="col-md-8">{{ user.company.employees.count }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-4 fw-bold">Data Sources:</div>
|
<div class="col-md-4 fw-bold">Data Sources:</div>
|
||||||
<div class="col-md-8">{{ user.company.data_sources.count }}</div>
|
<div class="col-md-8">{{ user.company.data_sources.count }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if user.is_company_admin or user.is_staff %}
|
{% if user.is_company_admin or user.is_staff %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Admin Actions</h5>
|
<h5 class="card-title mb-0">Admin Actions</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<div class="col-md-4 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<h5 class="card-title">Manage Users</h5>
|
<h5 class="card-title">Manage Users</h5>
|
||||||
<p class="card-text">
|
<p class="card-text">Manage users and assign them to companies.</p>
|
||||||
Manage users and assign them to companies.
|
<a
|
||||||
</p>
|
href="{% url 'admin:accounts_customuser_changelist' %}"
|
||||||
<a
|
class="btn btn-primary"
|
||||||
href="{% url 'admin:accounts_customuser_changelist' %}"
|
>Manage Users</a
|
||||||
class="btn btn-primary"
|
>
|
||||||
>Manage Users</a
|
</div>
|
||||||
>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-md-4 mb-3">
|
||||||
</div>
|
<div class="card h-100">
|
||||||
<div class="col-md-4 mb-3">
|
<div class="card-body text-center">
|
||||||
<div class="card h-100">
|
<h5 class="card-title">Manage Companies</h5>
|
||||||
<div class="card-body text-center">
|
<p class="card-text">Create and edit companies in the system.</p>
|
||||||
<h5 class="card-title">Manage Companies</h5>
|
<a
|
||||||
<p class="card-text">
|
href="{% url 'admin:accounts_company_changelist' %}"
|
||||||
Create and edit companies in the system.
|
class="btn btn-primary"
|
||||||
</p>
|
>Manage Companies</a
|
||||||
<a
|
>
|
||||||
href="{% url 'admin:accounts_company_changelist' %}"
|
</div>
|
||||||
class="btn btn-primary"
|
</div>
|
||||||
>Manage Companies</a
|
</div>
|
||||||
>
|
<div class="col-md-4 mb-3">
|
||||||
</div>
|
<div class="card h-100">
|
||||||
</div>
|
<div class="card-body text-center">
|
||||||
</div>
|
<h5 class="card-title">Admin Dashboard</h5>
|
||||||
<div class="col-md-4 mb-3">
|
<p class="card-text">Go to the full admin dashboard.</p>
|
||||||
<div class="card h-100">
|
<a href="{% url 'admin:index' %}" class="btn btn-primary">Admin Dashboard</a>
|
||||||
<div class="card-body text-center">
|
</div>
|
||||||
<h5 class="card-title">Admin Dashboard</h5>
|
</div>
|
||||||
<p class="card-text">Go to the full admin dashboard.</p>
|
</div>
|
||||||
<a
|
{% elif user.is_company_admin %}
|
||||||
href="{% url 'admin:index' %}"
|
<div class="col-md-4 mb-3">
|
||||||
class="btn btn-primary"
|
<div class="card h-100">
|
||||||
>Admin Dashboard</a
|
<div class="card-body text-center">
|
||||||
>
|
<h5 class="card-title">Manage Dashboards</h5>
|
||||||
</div>
|
<p class="card-text">Create and edit dashboards for your company.</p>
|
||||||
</div>
|
<a href="{% url 'create_dashboard' %}" class="btn btn-primary"
|
||||||
</div>
|
>Manage Dashboards</a
|
||||||
{% elif user.is_company_admin %}
|
>
|
||||||
<div class="col-md-4 mb-3">
|
</div>
|
||||||
<div class="card h-100">
|
</div>
|
||||||
<div class="card-body text-center">
|
</div>
|
||||||
<h5 class="card-title">Manage Dashboards</h5>
|
<div class="col-md-4 mb-3">
|
||||||
<p class="card-text">
|
<div class="card h-100">
|
||||||
Create and edit dashboards for your company.
|
<div class="card-body text-center">
|
||||||
</p>
|
<h5 class="card-title">Upload Data</h5>
|
||||||
<a
|
<p class="card-text">Upload and manage data sources for analysis.</p>
|
||||||
href="{% url 'create_dashboard' %}"
|
<a href="{% url 'upload_data' %}" class="btn btn-primary">Upload Data</a>
|
||||||
class="btn btn-primary"
|
</div>
|
||||||
>Manage Dashboards</a
|
</div>
|
||||||
>
|
</div>
|
||||||
</div>
|
<div class="col-md-4 mb-3">
|
||||||
</div>
|
<div class="card h-100">
|
||||||
</div>
|
<div class="card-body text-center">
|
||||||
<div class="col-md-4 mb-3">
|
<h5 class="card-title">Search Sessions</h5>
|
||||||
<div class="card h-100">
|
<p class="card-text">Search and analyze chat sessions.</p>
|
||||||
<div class="card-body text-center">
|
<a href="{% url 'search_chat_sessions' %}" class="btn btn-primary"
|
||||||
<h5 class="card-title">Upload Data</h5>
|
>Search Sessions</a
|
||||||
<p class="card-text">
|
>
|
||||||
Upload and manage data sources for analysis.
|
</div>
|
||||||
</p>
|
</div>
|
||||||
<a
|
</div>
|
||||||
href="{% url 'upload_data' %}"
|
{% endif %}
|
||||||
class="btn btn-primary"
|
</div>
|
||||||
>Upload Data</a
|
</div>
|
||||||
>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
<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 %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,31 +1,27 @@
|
|||||||
<!-- templates/accounts/register.html -->
|
<!-- templates/accounts/register.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% load crispy_forms_tags %}
|
||||||
{% load crispy_forms_tags %}
|
{% block title %}
|
||||||
|
Register | Chat Analytics
|
||||||
{% block title %}Register | Chat Analytics{% endblock %}
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
{% block content %}
|
<div class="row justify-content-center">
|
||||||
<div class="row justify-content-center">
|
<div class="col-md-6">
|
||||||
<div class="col-md-6">
|
<div class="card mt-4">
|
||||||
<div class="card mt-4">
|
<div class="card-header">
|
||||||
<div class="card-header">
|
<h4 class="card-title mb-0">Register</h4>
|
||||||
<h4 class="card-title mb-0">Register</h4>
|
</div>
|
||||||
</div>
|
<div class="card-body">
|
||||||
<div class="card-body">
|
<form method="post">
|
||||||
<form method="post">
|
{% csrf_token %} {{ form|crispy }}
|
||||||
{% csrf_token %}
|
<div class="d-grid gap-2">
|
||||||
{{ form|crispy }}
|
<button type="submit" class="btn btn-primary">Register</button>
|
||||||
<div class="d-grid gap-2">
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Register</button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
<div class="card-footer text-center">
|
||||||
</div>
|
<p class="mb-0">Already have an account? <a href="{% url 'login' %}">Login</a></p>
|
||||||
<div class="card-footer text-center">
|
</div>
|
||||||
<p class="mb-0">
|
</div>
|
||||||
Already have an account? <a href="{% url 'login' %}">Login</a>
|
</div>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -2,339 +2,315 @@
|
|||||||
{% load static %}
|
{% load static %}
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>{% block title %}Chat Analytics Dashboard{% endblock %}</title>
|
<title>{% block title %}Chat Analytics Dashboard{% endblock %}</title>
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
<link
|
<link
|
||||||
href="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css"
|
href="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/css/bootstrap.min.css"
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Font Awesome -->
|
<!-- Font Awesome -->
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@latest/css/all.min.css"
|
href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@latest/css/all.min.css"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Plotly.js -->
|
<!-- Plotly.js -->
|
||||||
<script
|
<script
|
||||||
src="https://cdn.jsdelivr.net/npm/plotly.js@latest/dist/plotly.min.js"
|
src="https://cdn.jsdelivr.net/npm/plotly.js@latest/dist/plotly.min.js"
|
||||||
charset="utf-8"
|
charset="utf-8"
|
||||||
></script>
|
></script>
|
||||||
|
|
||||||
<!-- Custom CSS -->
|
<!-- Custom CSS -->
|
||||||
<link rel="stylesheet" href="{% static 'css/style.css' %}" />
|
<link rel="stylesheet" href="{% static 'css/style.css' %}" />
|
||||||
<link rel="stylesheet" href="{% static 'css/dashboard.css' %}" />
|
<link rel="stylesheet" href="{% static 'css/dashboard.css' %}" />
|
||||||
|
|
||||||
{% block extra_css %}{% endblock %}
|
{% block extra_css %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<!-- Navbar -->
|
<!-- Navbar -->
|
||||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark absolute-top">
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark absolute-top">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="{% url 'dashboard' %}">Chat Analytics</a>
|
<a class="navbar-brand" href="{% url 'dashboard' %}">Chat Analytics</a>
|
||||||
<button
|
<button
|
||||||
class="navbar-toggler"
|
class="navbar-toggler"
|
||||||
type="button"
|
type="button"
|
||||||
data-bs-toggle="collapse"
|
data-bs-toggle="collapse"
|
||||||
data-bs-target="#navbarCollapse"
|
data-bs-target="#navbarCollapse"
|
||||||
>
|
>
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||||
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
<ul class="navbar-nav me-auto mb-2 mb-md-0">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a
|
||||||
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}"
|
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}"
|
||||||
href="{% url 'dashboard' %}"
|
href="{% url 'dashboard' %}"
|
||||||
>Dashboard</a
|
>Dashboard</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a
|
||||||
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'upload_data' %}active{% endif %}"
|
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'upload_data' %}active{% endif %}"
|
||||||
href="{% url 'upload_data' %}"
|
href="{% url 'upload_data' %}"
|
||||||
>Upload Data</a
|
>Upload Data</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a
|
||||||
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'search_chat_sessions' %}active{% endif %}"
|
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'search_chat_sessions' %}active{% endif %}"
|
||||||
href="{% url 'search_chat_sessions' %}"
|
href="{% url 'search_chat_sessions' %}"
|
||||||
>Search</a
|
>Search</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-light dropdown-toggle"
|
class="btn btn-outline-light dropdown-toggle"
|
||||||
type="button"
|
type="button"
|
||||||
id="userDropdown"
|
id="userDropdown"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
{% if user.company %}
|
{% if user.company %}
|
||||||
<span class="badge bg-info me-1"
|
<span class="badge bg-info me-1">{{ user.company.name }}</span>
|
||||||
>{{ user.company.name }}</span
|
{% endif %}
|
||||||
>
|
{{ user.username }}
|
||||||
{% endif %}
|
</button>
|
||||||
{{ user.username }}
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
|
||||||
</button>
|
<li>
|
||||||
<ul
|
<a class="dropdown-item ajax-nav-link" href="{% url 'profile' %}">Profile</a>
|
||||||
class="dropdown-menu dropdown-menu-end"
|
</li>
|
||||||
aria-labelledby="userDropdown"
|
{% if user.is_staff %}
|
||||||
>
|
<li>
|
||||||
<li>
|
<a class="dropdown-item" href="{% url 'admin:index' %}">Admin</a>
|
||||||
<a
|
</li>
|
||||||
class="dropdown-item ajax-nav-link"
|
{% endif %}
|
||||||
href="{% url 'profile' %}"
|
<li>
|
||||||
>Profile</a
|
<hr class="dropdown-divider" />
|
||||||
>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
{% if user.is_staff %}
|
<a class="dropdown-item" href="{% url 'logout' %}">Logout</a>
|
||||||
<li>
|
</li>
|
||||||
<a class="dropdown-item" href="{% url 'admin:index' %}"
|
</ul>
|
||||||
>Admin</a
|
</div>
|
||||||
>
|
{% else %}
|
||||||
</li>
|
<a href="{% url 'login' %}" class="btn btn-outline-light me-2">Login</a>
|
||||||
{% endif %}
|
<a href="{% url 'register' %}" class="btn btn-light">Register</a>
|
||||||
<li>
|
{% endif %}
|
||||||
<hr class="dropdown-divider" />
|
</div>
|
||||||
</li>
|
</div>
|
||||||
<li>
|
</div>
|
||||||
<a class="dropdown-item" href="{% url 'logout' %}"
|
</nav>
|
||||||
>Logout</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'login' %}" class="btn btn-outline-light me-2">Login</a>
|
|
||||||
<a href="{% url 'register' %}" class="btn btn-light">Register</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<nav
|
<nav
|
||||||
id="sidebarMenu"
|
id="sidebarMenu"
|
||||||
class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse sticky-top h-100 p-0"
|
class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse sticky-top h-100 p-0"
|
||||||
>
|
>
|
||||||
<div class="sidebar-sticky pt-3">
|
<div class="sidebar-sticky pt-3">
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a
|
||||||
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}"
|
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'dashboard' %}active{% endif %}"
|
||||||
href="{% url 'dashboard' %}"
|
href="{% url 'dashboard' %}"
|
||||||
>
|
>
|
||||||
<i class="fas fa-tachometer-alt me-2"></i>
|
<i class="fas fa-tachometer-alt me-2"></i>
|
||||||
Dashboard
|
Dashboard
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a
|
||||||
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'upload_data' %}active{% endif %}"
|
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'upload_data' %}active{% endif %}"
|
||||||
href="{% url 'upload_data' %}"
|
href="{% url 'upload_data' %}"
|
||||||
>
|
>
|
||||||
<i class="fas fa-upload me-2"></i>
|
<i class="fas fa-upload me-2"></i>
|
||||||
Upload Data
|
Upload Data
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a
|
||||||
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'search_chat_sessions' %}active{% endif %}"
|
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'search_chat_sessions' %}active{% endif %}"
|
||||||
href="{% url 'search_chat_sessions' %}"
|
href="{% url 'search_chat_sessions' %}"
|
||||||
>
|
>
|
||||||
<i class="fas fa-search me-2"></i>
|
<i class="fas fa-search me-2"></i>
|
||||||
Search
|
Search
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a
|
||||||
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'data_view' %}active{% endif %}"
|
class="nav-link ajax-nav-link {% if request.resolver_match.url_name == 'data_view' %}active{% endif %}"
|
||||||
href="{% url 'data_view' %}"
|
href="{% url 'data_view' %}"
|
||||||
>
|
>
|
||||||
<i class="fas fa-table me-2"></i>
|
<i class="fas fa-table me-2"></i>
|
||||||
Data View
|
Data View
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% if user.is_authenticated and user.company %}
|
{% if user.is_authenticated and user.company %}
|
||||||
{% if dashboards %}
|
{% if dashboards %}
|
||||||
<li class="nav-header"><strong>Dashboards</strong></li>
|
<li class="nav-header"><strong>Dashboards</strong></li>
|
||||||
{% for dashboard in dashboards %}
|
{% for dashboard in dashboards %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a
|
||||||
class="nav-link {% if selected_dashboard.id == dashboard.id %}active{% endif %}"
|
class="nav-link {% if selected_dashboard.id == dashboard.id %}active{% endif %}"
|
||||||
href="{% url 'dashboard' %}?dashboard_id={{ dashboard.id }}"
|
href="{% url 'dashboard' %}?dashboard_id={{ dashboard.id }}"
|
||||||
>
|
>
|
||||||
<i class="fas fa-chart-line me-2"></i>
|
<i class="fas fa-chart-line me-2"></i>
|
||||||
{{ dashboard.name }}
|
{{ dashboard.name }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'create_dashboard' %}">
|
<a class="nav-link" href="{% url 'create_dashboard' %}">
|
||||||
<i class="fas fa-plus-circle me-2"></i>
|
<i class="fas fa-plus-circle me-2"></i>
|
||||||
New Dashboard
|
New Dashboard
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if data_sources %}
|
{% if data_sources %}
|
||||||
<li class="nav-header"><strong>Data Sources</strong></li>
|
<li class="nav-header"><strong>Data Sources</strong></li>
|
||||||
{% for data_source in data_sources %}
|
{% for data_source in data_sources %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a
|
<a class="nav-link" href="{% url 'data_source_detail' data_source.id %}">
|
||||||
class="nav-link"
|
<i class="fas fa-database me-2"></i>
|
||||||
href="{% url 'data_source_detail' data_source.id %}"
|
{{ data_source.name }}
|
||||||
>
|
</a>
|
||||||
<i class="fas fa-database me-2"></i>
|
</li>
|
||||||
{{ data_source.name }}
|
{% endfor %}
|
||||||
</a>
|
{% endif %}
|
||||||
</li>
|
{% endif %}
|
||||||
{% endfor %}
|
</ul>
|
||||||
{% endif %}
|
{% endblock %}
|
||||||
{% endif %}
|
</div>
|
||||||
</ul>
|
</nav>
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Main content -->
|
<!-- Main content -->
|
||||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
|
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
|
||||||
{# {% if messages %} #}
|
{# {% if messages %} #}
|
||||||
{# <div class="messages mt-3"> #}
|
{# <div class="messages mt-3"> #}
|
||||||
{# {% for message in messages %} #}
|
{# {% for message in messages %} #}
|
||||||
{# <div class="alert {% if message.tags == 'error' %}alert-danger{% elif message.tags == 'success' %}alert-success{% elif message.tags == 'warning' %}alert-warning{% else %}alert-info{% endif %} alert-dismissible fade show" role="alert"> #}
|
{# <div class="alert {% if message.tags == 'error' %}alert-danger{% elif message.tags == 'success' %}alert-success{% elif message.tags == 'warning' %}alert-warning{% else %}alert-info{% endif %} alert-dismissible fade show" role="alert"> #}
|
||||||
{# {{ message }} #}
|
{# {{ message }} #}
|
||||||
{# <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> #}
|
{# <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> #}
|
||||||
{# </div> #}
|
{# </div> #}
|
||||||
{# {% endfor %} #}
|
{# {% endfor %} #}
|
||||||
{# </div> #}
|
{# </div> #}
|
||||||
{# {% endif %} #}
|
{# {% endif %} #}
|
||||||
|
|
||||||
<div id="main-content">
|
<div id="main-content">{% block content %}{% endblock %}</div>
|
||||||
{% block content %}
|
</main>
|
||||||
{% endblock %}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<p>© {% now "Y" %} KJANAT All rights reserved. | Chat Analytics Dashboard.</p>
|
<p>© {% now "Y" %} KJANAT All rights reserved. | Chat Analytics Dashboard.</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
<!-- Bootstrap JS -->
|
||||||
<script
|
<script
|
||||||
src="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/js/bootstrap.bundle.min.js"
|
src="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/js/bootstrap.bundle.min.js"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
></script>
|
></script>
|
||||||
<!-- jQuery (for Ajax) -->
|
<!-- jQuery (for Ajax) -->
|
||||||
<script
|
<script
|
||||||
src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"
|
src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
></script>
|
></script>
|
||||||
|
|
||||||
<!-- Custom JavaScript -->
|
<!-- Custom JavaScript -->
|
||||||
<script src="{% static 'js/main.js' %}"></script>
|
<script src="{% static 'js/main.js' %}"></script>
|
||||||
<script src="{% static 'js/ajax-pagination.js' %}"></script>
|
<script src="{% static 'js/ajax-pagination.js' %}"></script>
|
||||||
<script src="{% static 'js/ajax-navigation.js' %}"></script>
|
<script src="{% static 'js/ajax-navigation.js' %}"></script>
|
||||||
|
|
||||||
<!-- Enable AJAX Navigation -->
|
<!-- Enable AJAX Navigation -->
|
||||||
<script>
|
<script>
|
||||||
// Enable AJAX navigation for the entire application
|
// Enable AJAX navigation for the entire application
|
||||||
var ENABLE_AJAX_NAVIGATION = true;
|
var ENABLE_AJAX_NAVIGATION = true;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Check if Plotly loaded successfully -->
|
<!-- Check if Plotly loaded successfully -->
|
||||||
<script>
|
<script>
|
||||||
if (typeof Plotly === "undefined") {
|
if (typeof Plotly === "undefined") {
|
||||||
console.error("Plotly library failed to load. Will attempt to load fallback.");
|
console.error("Plotly library failed to load. Will attempt to load fallback.");
|
||||||
// Try to load Plotly from alternative source
|
// Try to load Plotly from alternative source
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.src = "https://cdn.jsdelivr.net/npm/plotly.js@latest/dist/plotly.min.js";
|
script.src = "https://cdn.jsdelivr.net/npm/plotly.js@latest/dist/plotly.min.js";
|
||||||
script.async = true;
|
script.async = true;
|
||||||
script.crossOrigin = "anonymous";
|
script.crossOrigin = "anonymous";
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1100;">
|
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1100">
|
||||||
<!-- Toasts will be appended here -->
|
<!-- Toasts will be appended here -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<!-- Pre-render message data that will be used by JavaScript -->
|
<!-- Pre-render message data that will be used by JavaScript -->
|
||||||
<script type="application/json" id="message-data-{{ forloop.counter }}">
|
<script type="application/json" id="message-data-{{ forloop.counter }}">
|
||||||
{
|
{
|
||||||
"message": "{{ message|escapejs }}",
|
"message": "{{ message|escapejs }}",
|
||||||
"tags": "{{ message.tags|default:'' }}"
|
"tags": "{{ message.tags|default:'' }}"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const toastContainer = document.querySelector(".toast-container");
|
const toastContainer = document.querySelector(".toast-container");
|
||||||
if (!toastContainer) return;
|
if (!toastContainer) return;
|
||||||
|
|
||||||
// Find all message data elements
|
// Find all message data elements
|
||||||
const messageDataElements = document.querySelectorAll(
|
const messageDataElements = document.querySelectorAll('script[id^="message-data-"]');
|
||||||
'script[id^="message-data-"]',
|
messageDataElements.forEach(function (dataElement) {
|
||||||
);
|
try {
|
||||||
messageDataElements.forEach(function (dataElement) {
|
const messageData = JSON.parse(dataElement.textContent);
|
||||||
try {
|
createToast(messageData.message, messageData.tags);
|
||||||
const messageData = JSON.parse(dataElement.textContent);
|
} catch (e) {
|
||||||
createToast(messageData.message, messageData.tags);
|
console.error("Error parsing message data:", e);
|
||||||
} catch (e) {
|
}
|
||||||
console.error("Error parsing message data:", e);
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function createToast(messageText, messageTags) {
|
function createToast(messageText, messageTags) {
|
||||||
let toastClass = "";
|
let toastClass = "";
|
||||||
let autohide = true;
|
let autohide = true;
|
||||||
let delay = 5000;
|
let delay = 5000;
|
||||||
|
|
||||||
if (messageTags.includes("debug")) {
|
if (messageTags.includes("debug")) {
|
||||||
toastClass = "bg-secondary text-white";
|
toastClass = "bg-secondary text-white";
|
||||||
} else if (messageTags.includes("info")) {
|
} else if (messageTags.includes("info")) {
|
||||||
toastClass = "bg-info text-dark";
|
toastClass = "bg-info text-dark";
|
||||||
} else if (messageTags.includes("success")) {
|
} else if (messageTags.includes("success")) {
|
||||||
toastClass = "bg-success text-white";
|
toastClass = "bg-success text-white";
|
||||||
} else if (messageTags.includes("warning")) {
|
} else if (messageTags.includes("warning")) {
|
||||||
toastClass = "bg-warning text-dark";
|
toastClass = "bg-warning text-dark";
|
||||||
autohide = false;
|
autohide = false;
|
||||||
} else if (messageTags.includes("error")) {
|
} else if (messageTags.includes("error")) {
|
||||||
toastClass = "bg-danger text-white";
|
toastClass = "bg-danger text-white";
|
||||||
autohide = false;
|
autohide = false;
|
||||||
} else {
|
} else {
|
||||||
toastClass = "bg-light text-dark";
|
toastClass = "bg-light text-dark";
|
||||||
}
|
}
|
||||||
|
|
||||||
const toastId =
|
const toastId =
|
||||||
"toast-" +
|
"toast-" + Date.now() + "-" + Math.random().toString(36).substring(2, 11);
|
||||||
Date.now() +
|
const toastHtml = `
|
||||||
"-" +
|
|
||||||
Math.random().toString(36).substring(2, 11);
|
|
||||||
const toastHtml = `
|
|
||||||
<div id="${toastId}" class="toast ${toastClass}" role="alert" aria-live="assertive" aria-atomic="true">
|
<div id="${toastId}" class="toast ${toastClass}" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
<div class="toast-header">
|
<div class="toast-header">
|
||||||
<strong class="me-auto">Notification</strong>
|
<strong class="me-auto">Notification</strong>
|
||||||
@ -346,25 +322,25 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
toastContainer.insertAdjacentHTML("beforeend", toastHtml);
|
toastContainer.insertAdjacentHTML("beforeend", toastHtml);
|
||||||
|
|
||||||
const toastElement = document.getElementById(toastId);
|
const toastElement = document.getElementById(toastId);
|
||||||
if (toastElement) {
|
if (toastElement) {
|
||||||
const toast = new bootstrap.Toast(toastElement, {
|
const toast = new bootstrap.Toast(toastElement, {
|
||||||
autohide: autohide,
|
autohide: autohide,
|
||||||
delay: autohide ? delay : undefined,
|
delay: autohide ? delay : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
toastElement.addEventListener("hidden.bs.toast", function () {
|
toastElement.addEventListener("hidden.bs.toast", function () {
|
||||||
toastElement.remove();
|
toastElement.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.show();
|
toast.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,145 +1,133 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
{% block title %}
|
||||||
{% block title %}Chat Session {{ session.session_id }} | Chat Analytics{% endblock %}
|
Chat Session {{ session.session_id }} | Chat Analytics
|
||||||
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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">Chat Session: {{ session.session_id }}</h1>
|
<h1 class="h2">Chat Session: {{ session.session_id }}</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<a
|
<a
|
||||||
href="{% url 'data_source_detail' session.data_source.id %}"
|
href="{% url 'data_source_detail' session.data_source.id %}"
|
||||||
class="btn btn-sm btn-outline-secondary"
|
class="btn btn-sm btn-outline-secondary"
|
||||||
>
|
>
|
||||||
<i class="fas fa-arrow-left"></i> Back to Data Source
|
<i class="fas fa-arrow-left"></i> Back to Data Source
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Session Information</h5>
|
<h5 class="card-title mb-0">Session Information</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<p><strong>Session ID:</strong> {{ session.session_id }}</p>
|
<p><strong>Session ID:</strong> {{ session.session_id }}</p>
|
||||||
<p>
|
<p>
|
||||||
<strong>Start Time:</strong>
|
<strong>Start Time:</strong>
|
||||||
{{ session.start_time|date:"F d, Y H:i" }}
|
{{ session.start_time|date:"F d, Y H:i" }}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p><strong>End Time:</strong> {{ session.end_time|date:"F d, Y H:i" }}</p>
|
||||||
<strong>End Time:</strong> {{ session.end_time|date:"F d, Y H:i" }}
|
<p><strong>IP Address:</strong> {{ session.ip_address|default:"N/A" }}</p>
|
||||||
</p>
|
<p><strong>Country:</strong> {{ session.country|default:"N/A" }}</p>
|
||||||
<p>
|
<p><strong>Language:</strong> {{ session.language|default:"N/A" }}</p>
|
||||||
<strong>IP Address:</strong> {{ session.ip_address|default:"N/A" }}
|
</div>
|
||||||
</p>
|
<div class="col-md-6">
|
||||||
<p><strong>Country:</strong> {{ session.country|default:"N/A" }}</p>
|
<p><strong>Messages Sent:</strong> {{ session.messages_sent }}</p>
|
||||||
<p><strong>Language:</strong> {{ session.language|default:"N/A" }}</p>
|
<p>
|
||||||
</div>
|
<strong>Average Response Time:</strong>
|
||||||
<div class="col-md-6">
|
{{ session.avg_response_time|floatformat:2 }}s
|
||||||
<p><strong>Messages Sent:</strong> {{ session.messages_sent }}</p>
|
</p>
|
||||||
<p>
|
<p><strong>Tokens:</strong> {{ session.tokens }}</p>
|
||||||
<strong>Average Response Time:</strong>
|
<p><strong>Token Cost:</strong> €{{ session.tokens_eur|floatformat:2 }}</p>
|
||||||
{{ session.avg_response_time|floatformat:2 }}s
|
<p><strong>Category:</strong> {{ session.category|default:"N/A" }}</p>
|
||||||
</p>
|
<p>
|
||||||
<p><strong>Tokens:</strong> {{ session.tokens }}</p>
|
<strong>Sentiment:</strong>
|
||||||
<p>
|
{% if session.sentiment %}
|
||||||
<strong>Token Cost:</strong> €{{ session.tokens_eur|floatformat:2 }}
|
{% if 'positive' in session.sentiment|lower %}
|
||||||
</p>
|
<span class="badge bg-success">{{ session.sentiment }}</span>
|
||||||
<p><strong>Category:</strong> {{ session.category|default:"N/A" }}</p>
|
{% elif 'negative' in session.sentiment|lower %}
|
||||||
<p>
|
<span class="badge bg-danger">{{ session.sentiment }}</span>
|
||||||
<strong>Sentiment:</strong>
|
{% elif 'neutral' in session.sentiment|lower %}
|
||||||
{% if session.sentiment %}
|
<span class="badge bg-warning">{{ session.sentiment }}</span>
|
||||||
{% if 'positive' in session.sentiment|lower %}
|
{% else %}
|
||||||
<span class="badge bg-success"
|
<span class="badge bg-secondary">{{ session.sentiment }}</span>
|
||||||
>{{ session.sentiment }}</span
|
{% endif %}
|
||||||
>
|
{% else %}
|
||||||
{% elif 'negative' in session.sentiment|lower %}
|
<span class="text-muted">N/A</span>
|
||||||
<span class="badge bg-danger">{{ session.sentiment }}</span>
|
{% endif %}
|
||||||
{% elif 'neutral' in session.sentiment|lower %}
|
</p>
|
||||||
<span class="badge bg-warning"
|
</div>
|
||||||
>{{ session.sentiment }}</span
|
</div>
|
||||||
>
|
<div class="row mt-3">
|
||||||
{% else %}
|
<div class="col-12">
|
||||||
<span class="badge bg-secondary"
|
<p class="mb-2"><strong>Initial Message:</strong></p>
|
||||||
>{{ session.sentiment }}</span
|
<div class="card bg-light">
|
||||||
>
|
<div class="card-body">
|
||||||
{% endif %}
|
<p class="mb-0">{{ session.initial_msg|default:"N/A" }}</p>
|
||||||
{% else %}
|
</div>
|
||||||
<span class="text-muted">N/A</span>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mt-3">
|
</div>
|
||||||
<div class="col-12">
|
<div class="col-md-4">
|
||||||
<p class="mb-2"><strong>Initial Message:</strong></p>
|
<div class="card">
|
||||||
<div class="card bg-light">
|
<div class="card-header">
|
||||||
<div class="card-body">
|
<h5 class="card-title mb-0">Additional Info</h5>
|
||||||
<p class="mb-0">{{ session.initial_msg|default:"N/A" }}</p>
|
</div>
|
||||||
</div>
|
<div class="card-body">
|
||||||
</div>
|
<p>
|
||||||
</div>
|
<strong>Escalated:</strong> {% if session.escalated %}
|
||||||
</div>
|
<span class="badge bg-danger">Yes</span>
|
||||||
</div>
|
{% else %}
|
||||||
</div>
|
<span class="badge bg-success">No</span>
|
||||||
</div>
|
{% endif %}
|
||||||
<div class="col-md-4">
|
</p>
|
||||||
<div class="card">
|
<p>
|
||||||
<div class="card-header">
|
<strong>Forwarded to HR:</strong> {% if session.forwarded_hr %}
|
||||||
<h5 class="card-title mb-0">Additional Info</h5>
|
<span class="badge bg-danger">Yes</span>
|
||||||
</div>
|
{% else %}
|
||||||
<div class="card-body">
|
<span class="badge bg-success">No</span>
|
||||||
<p>
|
{% endif %}
|
||||||
<strong>Escalated:</strong> {% if session.escalated %}
|
</p>
|
||||||
<span class="badge bg-danger">Yes</span>
|
<p><strong>User Rating:</strong> {{ session.user_rating|default:"N/A" }}</p>
|
||||||
{% else %}
|
<hr />
|
||||||
<span class="badge bg-success">No</span>
|
<p>
|
||||||
{% endif %}
|
<strong>Data Source:</strong>
|
||||||
</p>
|
<a href="{% url 'data_source_detail' session.data_source.id %}"
|
||||||
<p>
|
>{{ session.data_source.name }}</a
|
||||||
<strong>Forwarded to HR:</strong> {% if session.forwarded_hr %}
|
>
|
||||||
<span class="badge bg-danger">Yes</span>
|
</p>
|
||||||
{% else %}
|
<p><strong>Company:</strong> {{ session.data_source.company.name }}</p>
|
||||||
<span class="badge bg-success">No</span>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
</p>
|
</div>
|
||||||
<p><strong>User Rating:</strong> {{ session.user_rating|default:"N/A" }}</p>
|
</div>
|
||||||
<hr />
|
|
||||||
<p>
|
<div class="row">
|
||||||
<strong>Data Source:</strong>
|
<div class="col-12">
|
||||||
<a href="{% url 'data_source_detail' session.data_source.id %}"
|
<div class="card">
|
||||||
>{{ session.data_source.name }}</a
|
<div class="card-header">
|
||||||
>
|
<h5 class="card-title mb-0">Full Transcript</h5>
|
||||||
</p>
|
</div>
|
||||||
<p><strong>Company:</strong> {{ session.data_source.company.name }}</p>
|
<div class="card-body">
|
||||||
</div>
|
{% if session.full_transcript %}
|
||||||
</div>
|
<div class="chat-transcript" style="max-height: 500px; overflow-y: auto">
|
||||||
</div>
|
<pre style="white-space: pre-wrap; font-family: inherit">
|
||||||
</div>
|
{{ session.full_transcript }}</pre
|
||||||
|
>
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col-12">
|
{% else %}
|
||||||
<div class="card">
|
<p class="text-center text-muted">No transcript available.</p>
|
||||||
<div class="card-header">
|
{% endif %}
|
||||||
<h5 class="card-title mb-0">Full Transcript</h5>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
</div>
|
||||||
{% if session.full_transcript %}
|
</div>
|
||||||
<div class="chat-transcript" style="max-height: 500px; overflow-y: auto;">
|
|
||||||
<pre style="white-space: pre-wrap; font-family: inherit;">
|
|
||||||
{{ session.full_transcript }}</pre
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p class="text-center text-muted">No transcript available.</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,329 +1,320 @@
|
|||||||
<!-- templates/dashboard/dashboard.html -->
|
<!-- templates/dashboard/dashboard.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% load static %}
|
||||||
{% load static %}
|
{% block title %}
|
||||||
|
Dashboard | Chat Analytics
|
||||||
{% block title %}Dashboard | Chat Analytics{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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">{{ selected_dashboard.name }}</h1>
|
<h1 class="h2">{{ selected_dashboard.name }}</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<div class="btn-group me-2">
|
<div class="btn-group me-2">
|
||||||
<a
|
<a
|
||||||
href="{% url 'edit_dashboard' selected_dashboard.id %}"
|
href="{% url 'edit_dashboard' selected_dashboard.id %}"
|
||||||
class="btn btn-sm btn-outline-secondary"
|
class="btn btn-sm btn-outline-secondary"
|
||||||
>
|
>
|
||||||
<i class="fas fa-edit"></i> Edit
|
<i class="fas fa-edit"></i> Edit
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="{% url 'delete_dashboard' selected_dashboard.id %}"
|
href="{% url 'delete_dashboard' selected_dashboard.id %}"
|
||||||
class="btn btn-sm btn-outline-danger"
|
class="btn btn-sm btn-outline-danger"
|
||||||
>
|
>
|
||||||
<i class="fas fa-trash"></i> Delete
|
<i class="fas fa-trash"></i> Delete
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="{% url 'export_chats_csv' %}?dashboard_id={{ selected_dashboard.id }}"
|
href="{% url 'export_chats_csv' %}?dashboard_id={{ selected_dashboard.id }}"
|
||||||
class="btn btn-sm btn-outline-success"
|
class="btn btn-sm btn-outline-success"
|
||||||
>
|
>
|
||||||
<i class="fas fa-file-csv"></i> Export CSV
|
<i class="fas fa-file-csv"></i> Export CSV
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-outline-primary dropdown-toggle"
|
class="btn btn-sm btn-outline-primary dropdown-toggle"
|
||||||
type="button"
|
type="button"
|
||||||
id="timeRangeDropdown"
|
id="timeRangeDropdown"
|
||||||
data-bs-toggle="dropdown"
|
data-bs-toggle="dropdown"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
>
|
>
|
||||||
<i class="fas fa-calendar"></i> Time Range
|
<i class="fas fa-calendar"></i> Time Range
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu" aria-labelledby="timeRangeDropdown">
|
<ul class="dropdown-menu" aria-labelledby="timeRangeDropdown">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a class="dropdown-item" href="?dashboard_id={{ selected_dashboard.id }}&time_range=7"
|
||||||
class="dropdown-item"
|
>Last 7 days</a
|
||||||
href="?dashboard_id={{ selected_dashboard.id }}&time_range=7"
|
>
|
||||||
>Last 7 days</a
|
</li>
|
||||||
>
|
<li>
|
||||||
</li>
|
<a class="dropdown-item" href="?dashboard_id={{ selected_dashboard.id }}&time_range=30"
|
||||||
<li>
|
>Last 30 days</a
|
||||||
<a
|
>
|
||||||
class="dropdown-item"
|
</li>
|
||||||
href="?dashboard_id={{ selected_dashboard.id }}&time_range=30"
|
<li>
|
||||||
>Last 30 days</a
|
<a class="dropdown-item" href="?dashboard_id={{ selected_dashboard.id }}&time_range=90"
|
||||||
>
|
>Last 90 days</a
|
||||||
</li>
|
>
|
||||||
<li>
|
</li>
|
||||||
<a
|
<li>
|
||||||
class="dropdown-item"
|
<a class="dropdown-item" href="?dashboard_id={{ selected_dashboard.id }}&time_range=all"
|
||||||
href="?dashboard_id={{ selected_dashboard.id }}&time_range=90"
|
>All time</a
|
||||||
>Last 90 days</a
|
>
|
||||||
>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
<li>
|
</div>
|
||||||
<a
|
</div>
|
||||||
class="dropdown-item"
|
</div>
|
||||||
href="?dashboard_id={{ selected_dashboard.id }}&time_range=all"
|
|
||||||
>All time</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card stats-card bg-primary text-white">
|
<div class="card stats-card bg-primary text-white">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Total Sessions</h6>
|
<h6 class="card-title">Total Sessions</h6>
|
||||||
<h3>{{ dashboard_data.total_sessions }}</h3>
|
<h3>{{ dashboard_data.total_sessions }}</h3>
|
||||||
<p>Chat conversations</p>
|
<p>Chat conversations</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card stats-card bg-success text-white">
|
<div class="card stats-card bg-success text-white">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Avg Response Time</h6>
|
<h6 class="card-title">Avg Response Time</h6>
|
||||||
<h3>{{ dashboard_data.avg_response_time }}s</h3>
|
<h3>{{ dashboard_data.avg_response_time }}s</h3>
|
||||||
<p>Average response</p>
|
<p>Average response</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card stats-card bg-info text-white">
|
<div class="card stats-card bg-info text-white">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Total Tokens</h6>
|
<h6 class="card-title">Total Tokens</h6>
|
||||||
<h3>{{ dashboard_data.total_tokens }}</h3>
|
<h3>{{ dashboard_data.total_tokens }}</h3>
|
||||||
<p>Total usage</p>
|
<p>Total usage</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card stats-card bg-warning text-white">
|
<div class="card stats-card bg-warning text-white">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Total Cost</h6>
|
<h6 class="card-title">Total Cost</h6>
|
||||||
<h3>€{{ dashboard_data.total_cost }}</h3>
|
<h3>€{{ dashboard_data.total_cost }}</h3>
|
||||||
<p>Token cost</p>
|
<p>Token cost</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Sessions Over Time</h5>
|
<h5 class="card-title mb-0">Sessions Over Time</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="sessions-time-chart" class="chart-container"></div>
|
<div id="sessions-time-chart" class="chart-container"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Sentiment Analysis</h5>
|
<h5 class="card-title mb-0">Sentiment Analysis</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="sentiment-chart" class="chart-container"></div>
|
<div id="sentiment-chart" class="chart-container"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Top Countries</h5>
|
<h5 class="card-title mb-0">Top Countries</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="country-chart" class="chart-container"></div>
|
<div id="country-chart" class="chart-container"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Categories</h5>
|
<h5 class="card-title mb-0">Categories</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div id="category-chart" class="chart-container"></div>
|
<div id="category-chart" class="chart-container"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
<!-- Store the JSON data in script tags to avoid parsing issues -->
|
<!-- Store the JSON data in script tags to avoid parsing issues -->
|
||||||
<script type="application/json" id="time-series-data">{{ time_series_data_json|safe }}</script>
|
<script type="application/json" id="time-series-data">
|
||||||
<script type="application/json" id="sentiment-data">{{ sentiment_data_json|safe }}</script>
|
{{ time_series_data_json|safe }}
|
||||||
<script type="application/json" id="country-data">{{ country_data_json|safe }}</script>
|
</script>
|
||||||
<script type="application/json" id="category-data">{{ category_data_json|safe }}</script>
|
<script type="application/json" id="sentiment-data">
|
||||||
<!-- prettier-ignore-end -->
|
{{ sentiment_data_json|safe }}
|
||||||
|
</script>
|
||||||
|
<script type="application/json" id="country-data">
|
||||||
|
{{ country_data_json|safe }}
|
||||||
|
</script>
|
||||||
|
<script type="application/json" id="category-data">
|
||||||
|
{{ category_data_json|safe }}
|
||||||
|
</script>
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
try {
|
try {
|
||||||
// Parse the dashboard data components from script tags
|
// Parse the dashboard data components from script tags
|
||||||
const timeSeriesData = JSON.parse(
|
const timeSeriesData = JSON.parse(document.getElementById("time-series-data").textContent);
|
||||||
document.getElementById("time-series-data").textContent,
|
const sentimentData = JSON.parse(document.getElementById("sentiment-data").textContent);
|
||||||
);
|
const countryData = JSON.parse(document.getElementById("country-data").textContent);
|
||||||
const sentimentData = JSON.parse(
|
const categoryData = JSON.parse(document.getElementById("category-data").textContent);
|
||||||
document.getElementById("sentiment-data").textContent,
|
|
||||||
);
|
|
||||||
const countryData = JSON.parse(document.getElementById("country-data").textContent);
|
|
||||||
const categoryData = JSON.parse(
|
|
||||||
document.getElementById("category-data").textContent,
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log("Time series data loaded:", timeSeriesData);
|
console.log("Time series data loaded:", timeSeriesData);
|
||||||
console.log("Sentiment data loaded:", sentimentData);
|
console.log("Sentiment data loaded:", sentimentData);
|
||||||
console.log("Country data loaded:", countryData);
|
console.log("Country data loaded:", countryData);
|
||||||
console.log("Category data loaded:", categoryData);
|
console.log("Category data loaded:", categoryData);
|
||||||
|
|
||||||
// Sessions over time chart
|
// Sessions over time chart
|
||||||
if (timeSeriesData && timeSeriesData.length > 0) {
|
if (timeSeriesData && timeSeriesData.length > 0) {
|
||||||
const timeSeriesX = timeSeriesData.map((item) => item.date);
|
const timeSeriesX = timeSeriesData.map((item) => item.date);
|
||||||
const timeSeriesY = timeSeriesData.map((item) => item.count);
|
const timeSeriesY = timeSeriesData.map((item) => item.count);
|
||||||
|
|
||||||
Plotly.newPlot(
|
Plotly.newPlot(
|
||||||
"sessions-time-chart",
|
"sessions-time-chart",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
x: timeSeriesX,
|
x: timeSeriesX,
|
||||||
y: timeSeriesY,
|
y: timeSeriesY,
|
||||||
type: "scatter",
|
type: "scatter",
|
||||||
mode: "lines+markers",
|
mode: "lines+markers",
|
||||||
line: {
|
line: {
|
||||||
color: "rgb(75, 192, 192)",
|
color: "rgb(75, 192, 192)",
|
||||||
width: 2,
|
width: 2,
|
||||||
},
|
},
|
||||||
marker: {
|
marker: {
|
||||||
color: "rgb(75, 192, 192)",
|
color: "rgb(75, 192, 192)",
|
||||||
size: 6,
|
size: 6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
margin: { t: 10, r: 10, b: 40, l: 40 },
|
margin: { t: 10, r: 10, b: 40, l: 40 },
|
||||||
xaxis: {
|
xaxis: {
|
||||||
title: "Date",
|
title: "Date",
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
title: "Number of Sessions",
|
title: "Number of Sessions",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("sessions-time-chart").innerHTML =
|
document.getElementById("sessions-time-chart").innerHTML =
|
||||||
'<div class="text-center py-5"><p class="text-muted">No time series data available</p></div>';
|
'<div class="text-center py-5"><p class="text-muted">No time series data available</p></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sentiment analysis chart
|
// Sentiment analysis chart
|
||||||
if (sentimentData && sentimentData.length > 0) {
|
if (sentimentData && sentimentData.length > 0) {
|
||||||
const sentimentLabels = sentimentData.map((item) => item.sentiment);
|
const sentimentLabels = sentimentData.map((item) => item.sentiment);
|
||||||
const sentimentValues = sentimentData.map((item) => item.count);
|
const sentimentValues = sentimentData.map((item) => item.count);
|
||||||
const sentimentColors = sentimentLabels.map((sentiment) => {
|
const sentimentColors = sentimentLabels.map((sentiment) => {
|
||||||
if (sentiment.toLowerCase().includes("positive")) return "rgb(75, 192, 92)";
|
if (sentiment.toLowerCase().includes("positive")) return "rgb(75, 192, 92)";
|
||||||
if (sentiment.toLowerCase().includes("negative"))
|
if (sentiment.toLowerCase().includes("negative")) return "rgb(255, 99, 132)";
|
||||||
return "rgb(255, 99, 132)";
|
if (sentiment.toLowerCase().includes("neutral")) return "rgb(255, 205, 86)";
|
||||||
if (sentiment.toLowerCase().includes("neutral")) return "rgb(255, 205, 86)";
|
return "rgb(201, 203, 207)";
|
||||||
return "rgb(201, 203, 207)";
|
});
|
||||||
});
|
|
||||||
|
|
||||||
Plotly.newPlot(
|
Plotly.newPlot(
|
||||||
"sentiment-chart",
|
"sentiment-chart",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
values: sentimentValues,
|
values: sentimentValues,
|
||||||
labels: sentimentLabels,
|
labels: sentimentLabels,
|
||||||
type: "pie",
|
type: "pie",
|
||||||
marker: {
|
marker: {
|
||||||
colors: sentimentColors,
|
colors: sentimentColors,
|
||||||
},
|
},
|
||||||
hole: 0.4,
|
hole: 0.4,
|
||||||
textinfo: "label+percent",
|
textinfo: "label+percent",
|
||||||
insidetextorientation: "radial",
|
insidetextorientation: "radial",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
margin: { t: 10, r: 10, b: 10, l: 10 },
|
margin: { t: 10, r: 10, b: 10, l: 10 },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("sentiment-chart").innerHTML =
|
document.getElementById("sentiment-chart").innerHTML =
|
||||||
'<div class="text-center py-5"><p class="text-muted">No sentiment data available</p></div>';
|
'<div class="text-center py-5"><p class="text-muted">No sentiment data available</p></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Country chart
|
// Country chart
|
||||||
if (countryData && countryData.length > 0) {
|
if (countryData && countryData.length > 0) {
|
||||||
const countryLabels = countryData.map((item) => item.country);
|
const countryLabels = countryData.map((item) => item.country);
|
||||||
const countryValues = countryData.map((item) => item.count);
|
const countryValues = countryData.map((item) => item.count);
|
||||||
|
|
||||||
Plotly.newPlot(
|
Plotly.newPlot(
|
||||||
"country-chart",
|
"country-chart",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
x: countryValues,
|
x: countryValues,
|
||||||
y: countryLabels,
|
y: countryLabels,
|
||||||
type: "bar",
|
type: "bar",
|
||||||
orientation: "h",
|
orientation: "h",
|
||||||
marker: {
|
marker: {
|
||||||
color: "rgb(54, 162, 235)",
|
color: "rgb(54, 162, 235)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
margin: { t: 10, r: 10, b: 40, l: 100 },
|
margin: { t: 10, r: 10, b: 40, l: 100 },
|
||||||
xaxis: {
|
xaxis: {
|
||||||
title: "Number of Sessions",
|
title: "Number of Sessions",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("country-chart").innerHTML =
|
document.getElementById("country-chart").innerHTML =
|
||||||
'<div class="text-center py-5"><p class="text-muted">No country data available</p></div>';
|
'<div class="text-center py-5"><p class="text-muted">No country data available</p></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category chart
|
// Category chart
|
||||||
if (categoryData && categoryData.length > 0) {
|
if (categoryData && categoryData.length > 0) {
|
||||||
const categoryLabels = categoryData.map((item) => item.category);
|
const categoryLabels = categoryData.map((item) => item.category);
|
||||||
const categoryValues = categoryData.map((item) => item.count);
|
const categoryValues = categoryData.map((item) => item.count);
|
||||||
|
|
||||||
Plotly.newPlot(
|
Plotly.newPlot(
|
||||||
"category-chart",
|
"category-chart",
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
labels: categoryLabels,
|
labels: categoryLabels,
|
||||||
values: categoryValues,
|
values: categoryValues,
|
||||||
type: "pie",
|
type: "pie",
|
||||||
textinfo: "label+percent",
|
textinfo: "label+percent",
|
||||||
insidetextorientation: "radial",
|
insidetextorientation: "radial",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
margin: { t: 10, r: 10, b: 10, l: 10 },
|
margin: { t: 10, r: 10, b: 10, l: 10 },
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
document.getElementById("category-chart").innerHTML =
|
document.getElementById("category-chart").innerHTML =
|
||||||
'<div class="text-center py-5"><p class="text-muted">No category data available</p></div>';
|
'<div class="text-center py-5"><p class="text-muted">No category data available</p></div>';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error rendering charts:", error);
|
console.error("Error rendering charts:", error);
|
||||||
document.querySelectorAll(".chart-container").forEach((container) => {
|
document.querySelectorAll(".chart-container").forEach((container) => {
|
||||||
container.innerHTML =
|
container.innerHTML =
|
||||||
'<div class="text-center py-5"><p class="text-danger">Error loading chart data. Please refresh the page.</p></div>';
|
'<div class="text-center py-5"><p class="text-danger">Error loading chart data. Please refresh the page.</p></div>';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,44 +1,40 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% block title %}Delete Dashboard | Chat Analytics{% endblock %}
|
||||||
|
|
||||||
{% block title %}Delete Dashboard | Chat Analytics{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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">Delete Dashboard</h1>
|
<h1 class="h2">Delete Dashboard</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary">
|
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-danger">
|
<div class="card border-danger">
|
||||||
<div class="card-header bg-danger text-white">
|
<div class="card-header bg-danger text-white">
|
||||||
<h5 class="card-title mb-0">Confirm Deletion</h5>
|
<h5 class="card-title mb-0">Confirm Deletion</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
Are you sure you want to delete the dashboard
|
Are you sure you want to delete the dashboard "<strong>{{ dashboard.name }}</strong>"?
|
||||||
"<strong>{{ dashboard.name }}</strong>"?
|
</p>
|
||||||
</p>
|
<p>
|
||||||
<p>
|
This action cannot be undone. The dashboard will be permanently deleted, but the
|
||||||
This action cannot be undone. The dashboard will be permanently deleted, but
|
underlying data sources will remain intact.
|
||||||
the underlying data sources will remain intact.
|
</p>
|
||||||
</p>
|
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="d-flex justify-content-between mt-4">
|
<div class="d-flex justify-content-between mt-4">
|
||||||
<a href="{% url 'dashboard' %}" class="btn btn-secondary">Cancel</a>
|
<a href="{% url 'dashboard' %}" class="btn btn-secondary">Cancel</a>
|
||||||
<button type="submit" class="btn btn-danger">Delete Dashboard</button>
|
<button type="submit" class="btn btn-danger">Delete Dashboard</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,43 +1,43 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% load crispy_forms_tags %}
|
||||||
{% load crispy_forms_tags %}
|
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}
|
{% if is_create %}
|
||||||
| Chat Analytics
|
Create Dashboard
|
||||||
|
{% else %}
|
||||||
|
Edit Dashboard
|
||||||
|
{% endif %}
|
||||||
|
| Chat Analytics
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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">{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}</h1>
|
<h1 class="h2">{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary">
|
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">
|
<h5 class="card-title mb-0">
|
||||||
{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}
|
{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %} {{ form|crispy }}
|
||||||
{{ form|crispy }}
|
<div class="d-grid gap-2">
|
||||||
<div class="d-grid gap-2">
|
<button type="submit" class="btn btn-primary">
|
||||||
<button type="submit" class="btn btn-primary">
|
{% if is_create %}Create Dashboard{% else %}Update Dashboard{% endif %}
|
||||||
{% if is_create %}Create Dashboard{% else %}Update Dashboard{% endif %}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,53 +1,47 @@
|
|||||||
<!-- templates/dashboard/data_source_confirm_delete.html -->
|
<!-- templates/dashboard/data_source_confirm_delete.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% block title %}Delete Data Source | Chat Analytics{% endblock %}
|
||||||
|
|
||||||
{% block title %}Delete Data Source | Chat Analytics{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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">Delete Data Source</h1>
|
<h1 class="h2">Delete Data Source</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<a
|
<a
|
||||||
href="{% url 'data_source_detail' data_source.id %}"
|
href="{% url 'data_source_detail' data_source.id %}"
|
||||||
class="btn btn-sm btn-outline-secondary"
|
class="btn btn-sm btn-outline-secondary"
|
||||||
>
|
>
|
||||||
<i class="fas fa-arrow-left"></i> Back to Data Source
|
<i class="fas fa-arrow-left"></i> Back to Data Source
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card border-danger">
|
<div class="card border-danger">
|
||||||
<div class="card-header bg-danger text-white">
|
<div class="card-header bg-danger text-white">
|
||||||
<h5 class="card-title mb-0">Confirm Deletion</h5>
|
<h5 class="card-title mb-0">Confirm Deletion</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="lead">
|
<p class="lead">
|
||||||
Are you sure you want to delete the data source
|
Are you sure you want to delete the data source
|
||||||
"<strong>{{ data_source.name }}</strong>"?
|
"<strong>{{ data_source.name }}</strong>"?
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This action cannot be undone. The data source and all associated chat
|
This action cannot be undone. The data source and all associated chat sessions
|
||||||
sessions ({{ data_source.chat_sessions.count }} sessions) will be
|
({{ data_source.chat_sessions.count }} sessions) will be permanently deleted.
|
||||||
permanently deleted.
|
</p>
|
||||||
</p>
|
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="d-flex justify-content-between mt-4">
|
<div class="d-flex justify-content-between mt-4">
|
||||||
<a
|
<a href="{% url 'data_source_detail' data_source.id %}" class="btn btn-secondary"
|
||||||
href="{% url 'data_source_detail' data_source.id %}"
|
>Cancel</a
|
||||||
class="btn btn-secondary"
|
>
|
||||||
>Cancel</a
|
<button type="submit" class="btn btn-danger">Delete Data Source</button>
|
||||||
>
|
</div>
|
||||||
<button type="submit" class="btn btn-danger">Delete Data Source</button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,255 +1,229 @@
|
|||||||
<!-- templates/dashboard/data_source_detail.html -->
|
<!-- templates/dashboard/data_source_detail.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% load dashboard_extras %}
|
||||||
{% load dashboard_extras %}
|
{% block title %}
|
||||||
|
{{ data_source.name }}
|
||||||
{% block title %}{{ data_source.name }} | Chat Analytics{% endblock %}
|
| Chat Analytics
|
||||||
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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">{{ data_source.name }}</h1>
|
<h1 class="h2">{{ data_source.name }}</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<a href="{% url 'upload_data' %}" class="btn btn-sm btn-outline-secondary me-2">
|
<a href="{% url 'upload_data' %}" class="btn btn-sm btn-outline-secondary me-2">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Data Sources
|
<i class="fas fa-arrow-left"></i> Back to Data Sources
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="{% url 'export_chats_csv' %}?data_source_id={{ data_source.id }}"
|
href="{% url 'export_chats_csv' %}?data_source_id={{ data_source.id }}"
|
||||||
class="btn btn-sm btn-outline-success me-2"
|
class="btn btn-sm btn-outline-success me-2"
|
||||||
>
|
>
|
||||||
<i class="fas fa-file-csv"></i> Export CSV
|
<i class="fas fa-file-csv"></i> Export CSV
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a href="{% url 'delete_data_source' data_source.id %}" class="btn btn-sm btn-outline-danger">
|
||||||
href="{% url 'delete_data_source' data_source.id %}"
|
<i class="fas fa-trash"></i> Delete
|
||||||
class="btn btn-sm btn-outline-danger"
|
</a>
|
||||||
>
|
</div>
|
||||||
<i class="fas fa-trash"></i> Delete
|
</div>
|
||||||
</a>
|
|
||||||
</div>
|
<div class="row mb-4">
|
||||||
</div>
|
<div class="col-md-8">
|
||||||
|
<div class="card">
|
||||||
<div class="row mb-4">
|
<div class="card-header">
|
||||||
<div class="col-md-8">
|
<h5 class="card-title mb-0">Data Source Details</h5>
|
||||||
<div class="card">
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-body">
|
||||||
<h5 class="card-title mb-0">Data Source Details</h5>
|
<div class="row">
|
||||||
</div>
|
<div class="col-md-6">
|
||||||
<div class="card-body">
|
<p><strong>Name:</strong> {{ data_source.name }}</p>
|
||||||
<div class="row">
|
<p>
|
||||||
<div class="col-md-6">
|
<strong>Uploaded At:</strong>
|
||||||
<p><strong>Name:</strong> {{ data_source.name }}</p>
|
{{ data_source.uploaded_at|date:"F d, Y H:i" }}
|
||||||
<p>
|
</p>
|
||||||
<strong>Uploaded At:</strong>
|
<p><strong>File:</strong> {{ data_source.file.name|split:"/"|last }}</p>
|
||||||
{{ data_source.uploaded_at|date:"F d, Y H:i" }}
|
</div>
|
||||||
</p>
|
<div class="col-md-6">
|
||||||
<p><strong>File:</strong> {{ data_source.file.name|split:"/"|last }}</p>
|
<p><strong>Company:</strong> {{ data_source.company.name }}</p>
|
||||||
</div>
|
<p><strong>Total Sessions:</strong> {{ page_obj.paginator.count }}</p>
|
||||||
<div class="col-md-6">
|
<p><strong>Description:</strong> {{ data_source.description }}</p>
|
||||||
<p><strong>Company:</strong> {{ data_source.company.name }}</p>
|
</div>
|
||||||
<p><strong>Total Sessions:</strong> {{ page_obj.paginator.count }}</p>
|
</div>
|
||||||
<p><strong>Description:</strong> {{ data_source.description }}</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="col-md-4">
|
||||||
</div>
|
<div class="card">
|
||||||
</div>
|
<div class="card-header">
|
||||||
<div class="col-md-4">
|
<h5 class="card-title mb-0">Filter Sessions</h5>
|
||||||
<div class="card">
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-body">
|
||||||
<h5 class="card-title mb-0">Filter Sessions</h5>
|
<form method="get" action="{% url 'search_chat_sessions' %}">
|
||||||
</div>
|
<div class="input-group mb-3">
|
||||||
<div class="card-body">
|
<input
|
||||||
<form method="get" action="{% url 'search_chat_sessions' %}">
|
type="text"
|
||||||
<div class="input-group mb-3">
|
name="q"
|
||||||
<input
|
class="form-control"
|
||||||
type="text"
|
placeholder="Search sessions..."
|
||||||
name="q"
|
aria-label="Search sessions"
|
||||||
class="form-control"
|
/>
|
||||||
placeholder="Search sessions..."
|
<input type="hidden" name="data_source_id" value="{{ data_source.id }}" />
|
||||||
aria-label="Search sessions"
|
<button class="btn btn-outline-primary" type="submit">
|
||||||
/>
|
<i class="fas fa-search"></i>
|
||||||
<input
|
</button>
|
||||||
type="hidden"
|
</div>
|
||||||
name="data_source_id"
|
</form>
|
||||||
value="{{ data_source.id }}"
|
</div>
|
||||||
/>
|
</div>
|
||||||
<button class="btn btn-outline-primary" type="submit">
|
</div>
|
||||||
<i class="fas fa-search"></i>
|
</div>
|
||||||
</button>
|
|
||||||
</div>
|
<div class="row">
|
||||||
</form>
|
<div class="col-12">
|
||||||
</div>
|
<div class="card">
|
||||||
</div>
|
<div class="card-header">
|
||||||
</div>
|
<h5 class="card-title mb-0">Chat Sessions ({{ page_obj.paginator.count }})</h5>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="table-responsive">
|
||||||
<div class="col-12">
|
<table class="table table-striped table-hover">
|
||||||
<div class="card">
|
<thead>
|
||||||
<div class="card-header">
|
<tr>
|
||||||
<h5 class="card-title mb-0">Chat Sessions ({{ page_obj.paginator.count }})</h5>
|
<th>Session ID</th>
|
||||||
</div>
|
<th>Start Time</th>
|
||||||
<div class="card-body">
|
<th>Country</th>
|
||||||
<div class="table-responsive">
|
<th>Language</th>
|
||||||
<table class="table table-striped table-hover">
|
<th>Sentiment</th>
|
||||||
<thead>
|
<th>Messages</th>
|
||||||
<tr>
|
<th>Tokens</th>
|
||||||
<th>Session ID</th>
|
<th>Category</th>
|
||||||
<th>Start Time</th>
|
<th>Actions</th>
|
||||||
<th>Country</th>
|
</tr>
|
||||||
<th>Language</th>
|
</thead>
|
||||||
<th>Sentiment</th>
|
<tbody>
|
||||||
<th>Messages</th>
|
{% for session in page_obj %}
|
||||||
<th>Tokens</th>
|
<tr>
|
||||||
<th>Category</th>
|
<td>{{ session.session_id|truncatechars:10 }}</td>
|
||||||
<th>Actions</th>
|
<td>{{ session.start_time|date:"M d, Y H:i" }}</td>
|
||||||
</tr>
|
<td>{{ session.country }}</td>
|
||||||
</thead>
|
<td>{{ session.language }}</td>
|
||||||
<tbody>
|
<td>
|
||||||
{% for session in page_obj %}
|
{% if session.sentiment %}
|
||||||
<tr>
|
{% if 'positive' in session.sentiment|lower %}
|
||||||
<td>{{ session.session_id|truncatechars:10 }}</td>
|
<span class="badge bg-success">{{ session.sentiment }}</span>
|
||||||
<td>{{ session.start_time|date:"M d, Y H:i" }}</td>
|
{% elif 'negative' in session.sentiment|lower %}
|
||||||
<td>{{ session.country }}</td>
|
<span class="badge bg-danger">{{ session.sentiment }}</span>
|
||||||
<td>{{ session.language }}</td>
|
{% elif 'neutral' in session.sentiment|lower %}
|
||||||
<td>
|
<span class="badge bg-warning">{{ session.sentiment }}</span>
|
||||||
{% if session.sentiment %}
|
{% else %}
|
||||||
{% if 'positive' in session.sentiment|lower %}
|
<span class="badge bg-secondary">{{ session.sentiment }}</span>
|
||||||
<span class="badge bg-success"
|
{% endif %}
|
||||||
>{{ session.sentiment }}</span
|
{% else %}
|
||||||
>
|
<span class="text-muted">N/A</span>
|
||||||
{% elif 'negative' in session.sentiment|lower %}
|
{% endif %}
|
||||||
<span class="badge bg-danger"
|
</td>
|
||||||
>{{ session.sentiment }}</span
|
<td>{{ session.messages_sent }}</td>
|
||||||
>
|
<td>{{ session.tokens }}</td>
|
||||||
{% elif 'neutral' in session.sentiment|lower %}
|
<td>{{ session.category|default:"N/A" }}</td>
|
||||||
<span class="badge bg-warning"
|
<td>
|
||||||
>{{ session.sentiment }}</span
|
{% if session.session_id %}
|
||||||
>
|
<a
|
||||||
{% else %}
|
href="{% url 'chat_session_detail' session.session_id %}"
|
||||||
<span class="badge bg-secondary"
|
class="btn btn-sm btn-outline-primary"
|
||||||
>{{ session.sentiment }}</span
|
>
|
||||||
>
|
<i class="fas fa-eye"></i>
|
||||||
{% endif %}
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">N/A</span>
|
<button class="btn btn-sm btn-outline-secondary" disabled>
|
||||||
{% endif %}
|
<i class="fas fa-eye-slash"></i>
|
||||||
</td>
|
</button>
|
||||||
<td>{{ session.messages_sent }}</td>
|
{% endif %}
|
||||||
<td>{{ session.tokens }}</td>
|
</td>
|
||||||
<td>{{ session.category|default:"N/A" }}</td>
|
</tr>
|
||||||
<td>
|
{% empty %}
|
||||||
{% if session.session_id %}
|
<tr>
|
||||||
<a
|
<td colspan="9" class="text-center">No chat sessions found.</td>
|
||||||
href="{% url 'chat_session_detail' session.session_id %}"
|
</tr>
|
||||||
class="btn btn-sm btn-outline-primary"
|
{% endfor %}
|
||||||
>
|
</tbody>
|
||||||
<i class="fas fa-eye"></i>
|
</table>
|
||||||
</a>
|
</div>
|
||||||
{% else %}
|
|
||||||
<button
|
{% if page_obj.paginator.num_pages > 1 %}
|
||||||
class="btn btn-sm btn-outline-secondary"
|
<nav aria-label="Page navigation" class="mt-4">
|
||||||
disabled
|
<ul class="pagination justify-content-center">
|
||||||
>
|
{% if page_obj.has_previous %}
|
||||||
<i class="fas fa-eye-slash"></i>
|
<li class="page-item">
|
||||||
</button>
|
<a class="page-link" href="?page=1" aria-label="First">
|
||||||
{% endif %}
|
<span aria-hidden="true">««</span>
|
||||||
</td>
|
</a>
|
||||||
</tr>
|
</li>
|
||||||
{% empty %}
|
<li class="page-item">
|
||||||
<tr>
|
<a
|
||||||
<td colspan="9" class="text-center">
|
class="page-link"
|
||||||
No chat sessions found.
|
href="?page={{ page_obj.previous_page_number }}"
|
||||||
</td>
|
aria-label="Previous"
|
||||||
</tr>
|
>
|
||||||
{% endfor %}
|
<span aria-hidden="true">«</span>
|
||||||
</tbody>
|
</a>
|
||||||
</table>
|
</li>
|
||||||
</div>
|
{% else %}
|
||||||
|
<li class="page-item disabled">
|
||||||
{% if page_obj.paginator.num_pages > 1 %}
|
<a class="page-link" href="#" aria-label="First">
|
||||||
<nav aria-label="Page navigation" class="mt-4">
|
<span aria-hidden="true">««</span>
|
||||||
<ul class="pagination justify-content-center">
|
</a>
|
||||||
{% if page_obj.has_previous %}
|
</li>
|
||||||
<li class="page-item">
|
<li class="page-item disabled">
|
||||||
<a class="page-link" href="?page=1" aria-label="First">
|
<a class="page-link" href="#" aria-label="Previous">
|
||||||
<span aria-hidden="true">««</span>
|
<span aria-hidden="true">«</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item">
|
{% endif %}
|
||||||
<a
|
{% for num in page_obj.paginator.page_range %}
|
||||||
class="page-link"
|
{% if page_obj.number == num %}
|
||||||
href="?page={{ page_obj.previous_page_number }}"
|
<li class="page-item active">
|
||||||
aria-label="Previous"
|
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
|
||||||
>
|
</li>
|
||||||
<span aria-hidden="true">«</span>
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||||
</a>
|
<li class="page-item">
|
||||||
</li>
|
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
|
||||||
{% else %}
|
</li>
|
||||||
<li class="page-item disabled">
|
{% endif %}
|
||||||
<a class="page-link" href="#" aria-label="First">
|
{% endfor %}
|
||||||
<span aria-hidden="true">««</span>
|
{% if page_obj.has_next %}
|
||||||
</a>
|
<li class="page-item">
|
||||||
</li>
|
<a
|
||||||
<li class="page-item disabled">
|
class="page-link"
|
||||||
<a class="page-link" href="#" aria-label="Previous">
|
href="?page={{ page_obj.next_page_number }}"
|
||||||
<span aria-hidden="true">«</span>
|
aria-label="Next"
|
||||||
</a>
|
>
|
||||||
</li>
|
<span aria-hidden="true">»</span>
|
||||||
{% endif %}
|
</a>
|
||||||
|
</li>
|
||||||
{% for num in page_obj.paginator.page_range %}
|
<li class="page-item">
|
||||||
{% if page_obj.number == num %}
|
<a
|
||||||
<li class="page-item active">
|
class="page-link"
|
||||||
<a class="page-link" href="?page={{ num }}"
|
href="?page={{ page_obj.paginator.num_pages }}"
|
||||||
>{{ num }}</a
|
aria-label="Last"
|
||||||
>
|
>
|
||||||
</li>
|
<span aria-hidden="true">»»</span>
|
||||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
</a>
|
||||||
<li class="page-item">
|
</li>
|
||||||
<a class="page-link" href="?page={{ num }}"
|
{% else %}
|
||||||
>{{ num }}</a
|
<li class="page-item disabled">
|
||||||
>
|
<a class="page-link" href="#" aria-label="Next">
|
||||||
</li>
|
<span aria-hidden="true">»</span>
|
||||||
{% endif %}
|
</a>
|
||||||
{% endfor %}
|
</li>
|
||||||
|
<li class="page-item disabled">
|
||||||
{% if page_obj.has_next %}
|
<a class="page-link" href="#" aria-label="Last">
|
||||||
<li class="page-item">
|
<span aria-hidden="true">»»</span>
|
||||||
<a
|
</a>
|
||||||
class="page-link"
|
</li>
|
||||||
href="?page={{ page_obj.next_page_number }}"
|
{% endif %}
|
||||||
aria-label="Next"
|
</ul>
|
||||||
>
|
</nav>
|
||||||
<span aria-hidden="true">»</span>
|
{% endif %}
|
||||||
</a>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
<li class="page-item">
|
</div>
|
||||||
<a
|
</div>
|
||||||
class="page-link"
|
|
||||||
href="?page={{ page_obj.paginator.num_pages }}"
|
|
||||||
aria-label="Last"
|
|
||||||
>
|
|
||||||
<span aria-hidden="true">»»</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<a class="page-link" href="#" aria-label="Next">
|
|
||||||
<span aria-hidden="true">»</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item disabled">
|
|
||||||
<a class="page-link" href="#" aria-label="Last">
|
|
||||||
<span aria-hidden="true">»»</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,320 +1,276 @@
|
|||||||
<!-- templates/dashboard/data_view.html -->
|
<!-- templates/dashboard/data_view.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% load dashboard_extras %}
|
||||||
{% load dashboard_extras %}
|
{% block title %}
|
||||||
|
Data View | Chat Analytics
|
||||||
{% block title %}Data View | Chat Analytics{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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">Data View</h1>
|
<h1 class="h2">Data View</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<div class="btn-group me-2">
|
<div class="btn-group me-2">
|
||||||
<a
|
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary ajax-nav-link">
|
||||||
href="{% url 'dashboard' %}"
|
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||||
class="btn btn-sm btn-outline-secondary ajax-nav-link"
|
</a>
|
||||||
>
|
{% if selected_data_source %}
|
||||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
<a
|
||||||
</a>
|
href="{% url 'data_source_detail' selected_data_source.id %}"
|
||||||
{% if selected_data_source %}
|
class="btn btn-sm btn-outline-secondary ajax-nav-link"
|
||||||
<a
|
>
|
||||||
href="{% url 'data_source_detail' selected_data_source.id %}"
|
<i class="fas fa-database"></i> View Source
|
||||||
class="btn btn-sm btn-outline-secondary ajax-nav-link"
|
</a>
|
||||||
>
|
{% endif %}
|
||||||
<i class="fas fa-database"></i> View Source
|
</div>
|
||||||
</a>
|
<div class="dropdown">
|
||||||
{% endif %}
|
<button
|
||||||
</div>
|
class="btn btn-sm btn-outline-primary dropdown-toggle"
|
||||||
<div class="dropdown">
|
type="button"
|
||||||
<button
|
id="dataViewDropdown"
|
||||||
class="btn btn-sm btn-outline-primary dropdown-toggle"
|
data-bs-toggle="dropdown"
|
||||||
type="button"
|
aria-expanded="false"
|
||||||
id="dataViewDropdown"
|
>
|
||||||
data-bs-toggle="dropdown"
|
<i class="fas fa-filter"></i> Filter
|
||||||
aria-expanded="false"
|
</button>
|
||||||
>
|
<ul class="dropdown-menu" aria-labelledby="dataViewDropdown">
|
||||||
<i class="fas fa-filter"></i> Filter
|
<li>
|
||||||
</button>
|
<a class="dropdown-item ajax-nav-link" href="?view=all">All Sessions</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="dataViewDropdown">
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item ajax-nav-link" href="?view=all">All Sessions</a>
|
<a class="dropdown-item ajax-nav-link" href="?view=recent">Recent Sessions</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a class="dropdown-item ajax-nav-link" href="?view=recent"
|
<a class="dropdown-item ajax-nav-link" href="?view=positive">Positive Sentiment</a>
|
||||||
>Recent Sessions</a
|
</li>
|
||||||
>
|
<li>
|
||||||
</li>
|
<a class="dropdown-item ajax-nav-link" href="?view=negative">Negative Sentiment</a>
|
||||||
<li>
|
</li>
|
||||||
<a class="dropdown-item ajax-nav-link" href="?view=positive"
|
<li>
|
||||||
>Positive Sentiment</a
|
<a class="dropdown-item ajax-nav-link" href="?view=escalated">Escalated Sessions</a>
|
||||||
>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
<li>
|
</div>
|
||||||
<a class="dropdown-item ajax-nav-link" href="?view=negative"
|
</div>
|
||||||
>Negative Sentiment</a
|
</div>
|
||||||
>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item ajax-nav-link" href="?view=escalated"
|
|
||||||
>Escalated Sessions</a
|
|
||||||
>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Data Source Selection -->
|
<!-- Data Source Selection -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Data Source Selection</h5>
|
<h5 class="card-title mb-0">Data Source Selection</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form method="get" class="row g-3 align-items-center filter-form">
|
<form method="get" class="row g-3 align-items-center filter-form">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<select
|
<select name="data_source_id" class="form-select" aria-label="Select Data Source">
|
||||||
name="data_source_id"
|
<option value="">All Data Sources</option>
|
||||||
class="form-select"
|
{% for ds in data_sources %}
|
||||||
aria-label="Select Data Source"
|
<option
|
||||||
>
|
value="{{ ds.id }}"
|
||||||
<option value="">All Data Sources</option>
|
{% if selected_data_source.id == ds.id %}
|
||||||
{% for ds in data_sources %}
|
selected
|
||||||
<option
|
{% endif %}
|
||||||
value="{{ ds.id }}"
|
>
|
||||||
{% if selected_data_source.id == ds.id %}selected{% endif %}
|
{{ ds.name }}
|
||||||
>
|
</option>
|
||||||
{{ ds.name }}
|
{% endfor %}
|
||||||
</option>
|
</select>
|
||||||
{% endfor %}
|
</div>
|
||||||
</select>
|
<div class="col-md-4">
|
||||||
</div>
|
<select name="view" class="form-select" aria-label="Select View">
|
||||||
<div class="col-md-4">
|
<option value="all" {% if view == 'all' %}selected{% endif %}>All Sessions</option>
|
||||||
<select name="view" class="form-select" aria-label="Select View">
|
<option value="recent" {% if view == 'recent' %}selected{% endif %}>
|
||||||
<option value="all" {% if view == 'all' %}selected{% endif %}>
|
Recent Sessions
|
||||||
All Sessions
|
</option>
|
||||||
</option>
|
<option value="positive" {% if view == 'positive' %}selected{% endif %}>
|
||||||
<option value="recent" {% if view == 'recent' %}selected{% endif %}>
|
Positive Sentiment
|
||||||
Recent Sessions
|
</option>
|
||||||
</option>
|
<option value="negative" {% if view == 'negative' %}selected{% endif %}>
|
||||||
<option
|
Negative Sentiment
|
||||||
value="positive"
|
</option>
|
||||||
{% if view == 'positive' %}selected{% endif %}
|
<option value="escalated" {% if view == 'escalated' %}selected{% endif %}>
|
||||||
>
|
Escalated Sessions
|
||||||
Positive Sentiment
|
</option>
|
||||||
</option>
|
</select>
|
||||||
<option
|
</div>
|
||||||
value="negative"
|
<div class="col-md-2">
|
||||||
{% if view == 'negative' %}selected{% endif %}
|
<button type="submit" class="btn btn-primary w-100">Apply</button>
|
||||||
>
|
</div>
|
||||||
Negative Sentiment
|
</form>
|
||||||
</option>
|
</div>
|
||||||
<option
|
</div>
|
||||||
value="escalated"
|
</div>
|
||||||
{% if view == 'escalated' %}selected{% endif %}
|
</div>
|
||||||
>
|
|
||||||
Escalated Sessions
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<button type="submit" class="btn btn-primary w-100">Apply</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Export to CSV -->
|
<!-- Export to CSV -->
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Export Data</h5>
|
<h5 class="card-title mb-0">Export Data</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form
|
<form id="export-form" method="get" action="{% url 'export_chats_csv' %}" class="row g-3">
|
||||||
id="export-form"
|
<!-- Pass current filters to export -->
|
||||||
method="get"
|
<input type="hidden" name="data_source_id" value="{{ selected_data_source.id }}" />
|
||||||
action="{% url 'export_chats_csv' %}"
|
<input type="hidden" name="view" value="{{ view }}" />
|
||||||
class="row g-3"
|
|
||||||
>
|
|
||||||
<!-- Pass current filters to export -->
|
|
||||||
<input
|
|
||||||
type="hidden"
|
|
||||||
name="data_source_id"
|
|
||||||
value="{{ selected_data_source.id }}"
|
|
||||||
/>
|
|
||||||
<input type="hidden" name="view" value="{{ view }}" />
|
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<label for="start_date" class="form-label">Start Date</label>
|
<label for="start_date" class="form-label">Start Date</label>
|
||||||
<input
|
<input type="date" name="start_date" id="start_date" class="form-control" />
|
||||||
type="date"
|
</div>
|
||||||
name="start_date"
|
<div class="col-md-3">
|
||||||
id="start_date"
|
<label for="end_date" class="form-label">End Date</label>
|
||||||
class="form-control"
|
<input type="date" name="end_date" id="end_date" class="form-control" />
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div class="col-md-3">
|
||||||
<div class="col-md-3">
|
<label for="country" class="form-label">Country</label>
|
||||||
<label for="end_date" class="form-label">End Date</label>
|
<input
|
||||||
<input type="date" name="end_date" id="end_date" class="form-control" />
|
type="text"
|
||||||
</div>
|
name="country"
|
||||||
<div class="col-md-3">
|
id="country"
|
||||||
<label for="country" class="form-label">Country</label>
|
class="form-control"
|
||||||
<input
|
placeholder="Country"
|
||||||
type="text"
|
/>
|
||||||
name="country"
|
</div>
|
||||||
id="country"
|
<div class="col-md-3">
|
||||||
class="form-control"
|
<label for="sentiment" class="form-label">Sentiment</label>
|
||||||
placeholder="Country"
|
<select name="sentiment" id="sentiment" class="form-select">
|
||||||
/>
|
<option value="">All</option>
|
||||||
</div>
|
<option value="positive">Positive</option>
|
||||||
<div class="col-md-3">
|
<option value="negative">Negative</option>
|
||||||
<label for="sentiment" class="form-label">Sentiment</label>
|
<option value="neutral">Neutral</option>
|
||||||
<select name="sentiment" id="sentiment" class="form-select">
|
</select>
|
||||||
<option value="">All</option>
|
</div>
|
||||||
<option value="positive">Positive</option>
|
<div class="col-md-3">
|
||||||
<option value="negative">Negative</option>
|
<label for="escalated" class="form-label">Escalated</label>
|
||||||
<option value="neutral">Neutral</option>
|
<select name="escalated" id="escalated" class="form-select">
|
||||||
</select>
|
<option value="">All</option>
|
||||||
</div>
|
<option value="true">Yes</option>
|
||||||
<div class="col-md-3">
|
<option value="false">No</option>
|
||||||
<label for="escalated" class="form-label">Escalated</label>
|
</select>
|
||||||
<select name="escalated" id="escalated" class="form-select">
|
</div>
|
||||||
<option value="">All</option>
|
<div class="col-md-3 d-flex align-items-end">
|
||||||
<option value="true">Yes</option>
|
<button type="submit" class="btn btn-success w-100">
|
||||||
<option value="false">No</option>
|
<i class="fas fa-file-csv me-1"></i> Export to CSV
|
||||||
</select>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 d-flex align-items-end">
|
</form>
|
||||||
<button type="submit" class="btn btn-success w-100">
|
</div>
|
||||||
<i class="fas fa-file-csv me-1"></i> Export to CSV
|
</div>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Data Table -->
|
<!-- Data Table -->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="card-title mb-0">
|
<h5 class="card-title mb-0">
|
||||||
Chat Sessions
|
Chat Sessions
|
||||||
{% if selected_data_source %}
|
{% if selected_data_source %}
|
||||||
for {{ selected_data_source.name }}
|
for {{ selected_data_source.name }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if view != 'all' %}
|
{% if view != 'all' %}({{ view|title }}){% endif %}
|
||||||
({{ view|title }})
|
</h5>
|
||||||
{% endif %}
|
<span class="badge bg-primary">{{ page_obj.paginator.count }} sessions</span>
|
||||||
</h5>
|
</div>
|
||||||
<span class="badge bg-primary">{{ page_obj.paginator.count }} sessions</span>
|
<div class="card-body">
|
||||||
</div>
|
<!-- Loading spinner shown during AJAX requests -->
|
||||||
<div class="card-body">
|
<div id="ajax-loading-spinner" class="text-center py-4 d-none">
|
||||||
<!-- Loading spinner shown during AJAX requests -->
|
<div class="spinner-border text-primary" role="status">
|
||||||
<div id="ajax-loading-spinner" class="text-center py-4 d-none">
|
<span class="visually-hidden">Loading...</span>
|
||||||
<div class="spinner-border text-primary" role="status">
|
</div>
|
||||||
<span class="visually-hidden">Loading...</span>
|
<p class="mt-2">Loading data...</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2">Loading data...</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Data table container that will be updated via AJAX -->
|
<!-- Data table container that will be updated via AJAX -->
|
||||||
<div id="ajax-content-container">
|
<div id="ajax-content-container">{% include "dashboard/partials/data_table.html" %}</div>
|
||||||
{% include "dashboard/partials/data_table.html" %}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Data Summary -->
|
<!-- Data Summary -->
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<div class="row mt-4">
|
<div class="row mt-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Summary</h5>
|
<h5 class="card-title mb-0">Summary</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card stats-card bg-light">
|
<div class="card stats-card bg-light">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Total Sessions</h6>
|
<h6 class="card-title">Total Sessions</h6>
|
||||||
<h3>{{ page_obj.paginator.count }}</h3>
|
<h3>{{ page_obj.paginator.count }}</h3>
|
||||||
<p>Chat conversations</p>
|
<p>Chat conversations</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card stats-card bg-light">
|
<div class="card stats-card bg-light">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Avg Response Time</h6>
|
<h6 class="card-title">Avg Response Time</h6>
|
||||||
<h3>{{ avg_response_time|floatformat:2 }}s</h3>
|
<h3>{{ avg_response_time|floatformat:2 }}s</h3>
|
||||||
<p>Average response</p>
|
<p>Average response</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card stats-card bg-light">
|
<div class="card stats-card bg-light">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Avg Messages</h6>
|
<h6 class="card-title">Avg Messages</h6>
|
||||||
<h3>{{ avg_messages|floatformat:1 }}</h3>
|
<h3>{{ avg_messages|floatformat:1 }}</h3>
|
||||||
<p>Per conversation</p>
|
<p>Per conversation</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<div class="card stats-card bg-light">
|
<div class="card stats-card bg-light">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h6 class="card-title">Escalation Rate</h6>
|
<h6 class="card-title">Escalation Rate</h6>
|
||||||
<h3>{{ escalation_rate|floatformat:1 }}%</h3>
|
<h3>{{ escalation_rate|floatformat:1 }}%</h3>
|
||||||
<p>Escalated sessions</p>
|
<p>Escalated sessions</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
// Function to update the summary section with new data
|
// Function to update the summary section with new data
|
||||||
function updateSummary(data) {
|
function updateSummary(data) {
|
||||||
if (document.querySelector(".stats-card h3:nth-of-type(1)")) {
|
if (document.querySelector(".stats-card h3:nth-of-type(1)")) {
|
||||||
document.querySelector(".stats-card h3:nth-of-type(1)").textContent =
|
document.querySelector(".stats-card h3:nth-of-type(1)").textContent =
|
||||||
data.page_obj.paginator.count;
|
data.page_obj.paginator.count;
|
||||||
}
|
}
|
||||||
if (document.querySelector(".stats-card h3:nth-of-type(2)")) {
|
if (document.querySelector(".stats-card h3:nth-of-type(2)")) {
|
||||||
document.querySelector(".stats-card h3:nth-of-type(2)").textContent =
|
document.querySelector(".stats-card h3:nth-of-type(2)").textContent =
|
||||||
data.avg_response_time !== null && data.avg_response_time !== undefined
|
data.avg_response_time !== null && data.avg_response_time !== undefined
|
||||||
? data.avg_response_time.toFixed(2) + "s"
|
? data.avg_response_time.toFixed(2) + "s"
|
||||||
: "0.00s";
|
: "0.00s";
|
||||||
}
|
}
|
||||||
if (document.querySelector(".stats-card h3:nth-of-type(3)")) {
|
if (document.querySelector(".stats-card h3:nth-of-type(3)")) {
|
||||||
document.querySelector(".stats-card h3:nth-of-type(3)").textContent =
|
document.querySelector(".stats-card h3:nth-of-type(3)").textContent =
|
||||||
data.avg_messages !== null && data.avg_messages !== undefined
|
data.avg_messages !== null && data.avg_messages !== undefined
|
||||||
? data.avg_messages.toFixed(1)
|
? data.avg_messages.toFixed(1)
|
||||||
: "0.0";
|
: "0.0";
|
||||||
}
|
}
|
||||||
if (document.querySelector(".stats-card h3:nth-of-type(4)")) {
|
if (document.querySelector(".stats-card h3:nth-of-type(4)")) {
|
||||||
document.querySelector(".stats-card h3:nth-of-type(4)").textContent =
|
document.querySelector(".stats-card h3:nth-of-type(4)").textContent =
|
||||||
data.escalation_rate !== null && data.escalation_rate !== undefined
|
data.escalation_rate !== null && data.escalation_rate !== undefined
|
||||||
? data.escalation_rate.toFixed(1) + "%"
|
? data.escalation_rate.toFixed(1) + "%"
|
||||||
: "0.0%";
|
: "0.0%";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,44 +1,37 @@
|
|||||||
<!-- templates/dashboard/no_company.html -->
|
<!-- templates/dashboard/no_company.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% block title %}No Company | Chat Analytics{% endblock %}
|
||||||
|
|
||||||
{% block title %}No Company | Chat Analytics{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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">No Company Association</h1>
|
<h1 class="h2">No Company Association</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header bg-warning text-dark">
|
<div class="card-header bg-warning text-dark">
|
||||||
<h5 class="card-title mb-0">Account Not Associated with a Company</h5>
|
<h5 class="card-title mb-0">Account Not Associated with a Company</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body text-center">
|
<div class="card-body text-center">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<i class="fas fa-building fa-4x text-warning mb-3"></i>
|
<i class="fas fa-building fa-4x text-warning mb-3"></i>
|
||||||
<h4>You are not currently associated with any company</h4>
|
<h4>You are not currently associated with any company</h4>
|
||||||
<p class="lead">
|
<p class="lead">You need to be associated with a company to access the dashboard.</p>
|
||||||
You need to be associated with a company to access the dashboard.
|
</div>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Please contact an administrator to have your account assigned to a company.
|
Please contact an administrator to have your account assigned to a company. Once your
|
||||||
Once your account is associated with a company, you'll be able to access the
|
account is associated with a company, you'll be able to access the dashboard and its
|
||||||
dashboard and its features.
|
features.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<a href="{% url 'profile' %}" class="btn btn-primary">View Your Profile</a>
|
<a href="{% url 'profile' %}" class="btn btn-primary">View Your Profile</a>
|
||||||
<a href="{% url 'logout' %}" class="btn btn-outline-secondary ms-2"
|
<a href="{% url 'logout' %}" class="btn btn-outline-secondary ms-2">Logout</a>
|
||||||
>Logout</a
|
</div>
|
||||||
>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,160 +1,156 @@
|
|||||||
<!-- templates/dashboard/partials/data_table.html -->
|
<!-- templates/dashboard/partials/data_table.html -->
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Session ID</th>
|
<th>Session ID</th>
|
||||||
<th>Start Time</th>
|
<th>Start Time</th>
|
||||||
<th>Country</th>
|
<th>Country</th>
|
||||||
<th>Language</th>
|
<th>Language</th>
|
||||||
<th>Messages</th>
|
<th>Messages</th>
|
||||||
<th>Sentiment</th>
|
<th>Sentiment</th>
|
||||||
<th>Response Time</th>
|
<th>Response Time</th>
|
||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for session in page_obj %}
|
{% for session in page_obj %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ session.session_id|truncatechars:10 }}</td>
|
<td>{{ session.session_id|truncatechars:10 }}</td>
|
||||||
<td>{{ session.start_time|date:"M d, Y H:i" }}</td>
|
<td>{{ session.start_time|date:"M d, Y H:i" }}</td>
|
||||||
<td>{{ session.country|default:"N/A" }}</td>
|
<td>{{ session.country|default:"N/A" }}</td>
|
||||||
<td>{{ session.language|default:"N/A" }}</td>
|
<td>{{ session.language|default:"N/A" }}</td>
|
||||||
<td>{{ session.messages_sent }}</td>
|
<td>{{ session.messages_sent }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if session.sentiment %}
|
{% if session.sentiment %}
|
||||||
{% if 'positive' in session.sentiment|lower %}
|
{% if 'positive' in session.sentiment|lower %}
|
||||||
<span class="badge bg-success">{{ session.sentiment }}</span>
|
<span class="badge bg-success">{{ session.sentiment }}</span>
|
||||||
{% elif 'negative' in session.sentiment|lower %}
|
{% elif 'negative' in session.sentiment|lower %}
|
||||||
<span class="badge bg-danger">{{ session.sentiment }}</span>
|
<span class="badge bg-danger">{{ session.sentiment }}</span>
|
||||||
{% elif 'neutral' in session.sentiment|lower %}
|
{% elif 'neutral' in session.sentiment|lower %}
|
||||||
<span class="badge bg-warning">{{ session.sentiment }}</span>
|
<span class="badge bg-warning">{{ session.sentiment }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">{{ session.sentiment }}</span>
|
<span class="badge bg-secondary">{{ session.sentiment }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">N/A</span>
|
<span class="text-muted">N/A</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ session.avg_response_time|floatformat:2 }}s</td>
|
<td>{{ session.avg_response_time|floatformat:2 }}s</td>
|
||||||
<td>{{ session.category|default:"N/A" }}</td>
|
<td>{{ session.category|default:"N/A" }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if session.session_id %}
|
{% if session.session_id %}
|
||||||
<a
|
<a
|
||||||
href="{% url 'chat_session_detail' session.session_id %}"
|
href="{% url 'chat_session_detail' session.session_id %}"
|
||||||
class="btn btn-sm btn-outline-primary ajax-nav-link"
|
class="btn btn-sm btn-outline-primary ajax-nav-link"
|
||||||
>
|
>
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="btn btn-sm btn-outline-secondary" disabled>
|
<button class="btn btn-sm btn-outline-secondary" disabled>
|
||||||
<i class="fas fa-eye-slash"></i>
|
<i class="fas fa-eye-slash"></i>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="9" class="text-center">No chat sessions found.</td>
|
<td colspan="9" class="text-center">No chat sessions found.</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if page_obj.paginator.num_pages > 1 %}
|
{% if page_obj.paginator.num_pages > 1 %}
|
||||||
<nav aria-label="Page navigation" class="mt-4" id="pagination-container">
|
<nav aria-label="Page navigation" class="mt-4" id="pagination-container">
|
||||||
<ul class="pagination justify-content-center">
|
<ul class="pagination justify-content-center">
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a
|
<a
|
||||||
class="page-link pagination-link"
|
class="page-link pagination-link"
|
||||||
data-page="1"
|
data-page="1"
|
||||||
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page=1"
|
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page=1"
|
||||||
aria-label="First"
|
aria-label="First"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">««</span>
|
<span aria-hidden="true">««</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a
|
<a
|
||||||
class="page-link pagination-link"
|
class="page-link pagination-link"
|
||||||
data-page="{{ page_obj.previous_page_number }}"
|
data-page="{{ page_obj.previous_page_number }}"
|
||||||
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ page_obj.previous_page_number }}"
|
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ page_obj.previous_page_number }}"
|
||||||
aria-label="Previous"
|
aria-label="Previous"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">«</span>
|
<span aria-hidden="true">«</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<a class="page-link" href="#" aria-label="First">
|
<a class="page-link" href="#" aria-label="First">
|
||||||
<span aria-hidden="true">««</span>
|
<span aria-hidden="true">««</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<a class="page-link" href="#" aria-label="Previous">
|
<a class="page-link" href="#" aria-label="Previous">
|
||||||
<span aria-hidden="true">«</span>
|
<span aria-hidden="true">«</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% for num in page_obj.paginator.page_range %}{% if page_obj.number == num %}
|
||||||
{% for num in page_obj.paginator.page_range %}
|
<li class="page-item active">
|
||||||
{% if page_obj.number == num %}
|
<a
|
||||||
<li class="page-item active">
|
class="page-link pagination-link"
|
||||||
<a
|
data-page="{{ num }}"
|
||||||
class="page-link pagination-link"
|
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ num }}"
|
||||||
data-page="{{ num }}"
|
>{{ num }}</a
|
||||||
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ num }}"
|
>
|
||||||
>{{ num }}</a
|
</li>
|
||||||
>
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||||
</li>
|
<li class="page-item">
|
||||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
<a
|
||||||
<li class="page-item">
|
class="page-link pagination-link"
|
||||||
<a
|
data-page="{{ num }}"
|
||||||
class="page-link pagination-link"
|
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ num }}"
|
||||||
data-page="{{ num }}"
|
>{{ num }}</a
|
||||||
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ num }}"
|
>
|
||||||
>{{ num }}</a
|
</li>
|
||||||
>
|
{% endif %}{% endfor %}
|
||||||
</li>
|
{% if page_obj.has_next %}
|
||||||
{% endif %}
|
<li class="page-item">
|
||||||
{% endfor %}
|
<a
|
||||||
|
class="page-link pagination-link"
|
||||||
{% if page_obj.has_next %}
|
data-page="{{ page_obj.next_page_number }}"
|
||||||
<li class="page-item">
|
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ page_obj.next_page_number }}"
|
||||||
<a
|
aria-label="Next"
|
||||||
class="page-link pagination-link"
|
>
|
||||||
data-page="{{ page_obj.next_page_number }}"
|
<span aria-hidden="true">»</span>
|
||||||
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ page_obj.next_page_number }}"
|
</a>
|
||||||
aria-label="Next"
|
</li>
|
||||||
>
|
<li class="page-item">
|
||||||
<span aria-hidden="true">»</span>
|
<a
|
||||||
</a>
|
class="page-link pagination-link"
|
||||||
</li>
|
data-page="{{ page_obj.paginator.num_pages }}"
|
||||||
<li class="page-item">
|
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ page_obj.paginator.num_pages }}"
|
||||||
<a
|
aria-label="Last"
|
||||||
class="page-link pagination-link"
|
>
|
||||||
data-page="{{ page_obj.paginator.num_pages }}"
|
<span aria-hidden="true">»»</span>
|
||||||
href="?{% if selected_data_source %}data_source_id={{ selected_data_source.id }}&{% endif %}{% if view %}view={{ view }}&{% endif %}page={{ page_obj.paginator.num_pages }}"
|
</a>
|
||||||
aria-label="Last"
|
</li>
|
||||||
>
|
{% else %}
|
||||||
<span aria-hidden="true">»»</span>
|
<li class="page-item disabled">
|
||||||
</a>
|
<a class="page-link" href="#" aria-label="Next">
|
||||||
</li>
|
<span aria-hidden="true">»</span>
|
||||||
{% else %}
|
</a>
|
||||||
<li class="page-item disabled">
|
</li>
|
||||||
<a class="page-link" href="#" aria-label="Next">
|
<li class="page-item disabled">
|
||||||
<span aria-hidden="true">»</span>
|
<a class="page-link" href="#" aria-label="Last">
|
||||||
</a>
|
<span aria-hidden="true">»»</span>
|
||||||
</li>
|
</a>
|
||||||
<li class="page-item disabled">
|
</li>
|
||||||
<a class="page-link" href="#" aria-label="Last">
|
{% endif %}
|
||||||
<span aria-hidden="true">»»</span>
|
</ul>
|
||||||
</a>
|
</nav>
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,168 +1,160 @@
|
|||||||
<!-- templates/dashboard/partials/search_results_table.html -->
|
<!-- templates/dashboard/partials/search_results_table.html -->
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Session ID</th>
|
<th>Session ID</th>
|
||||||
<th>Start Time</th>
|
<th>Start Time</th>
|
||||||
<th>Data Source</th>
|
<th>Data Source</th>
|
||||||
<th>Country</th>
|
<th>Country</th>
|
||||||
<th>Language</th>
|
<th>Language</th>
|
||||||
<th>Sentiment</th>
|
<th>Sentiment</th>
|
||||||
<th>Messages</th>
|
<th>Messages</th>
|
||||||
<th>Category</th>
|
<th>Category</th>
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for session in page_obj %}
|
{% for session in page_obj %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ session.session_id|truncatechars:10 }}</td>
|
<td>{{ session.session_id|truncatechars:10 }}</td>
|
||||||
<td>{{ session.start_time|date:"M d, Y H:i" }}</td>
|
<td>{{ session.start_time|date:"M d, Y H:i" }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a href="{% url 'data_source_detail' session.data_source.id %}" class="ajax-nav-link"
|
||||||
href="{% url 'data_source_detail' session.data_source.id %}"
|
>{{ session.data_source.name|truncatechars:15 }}</a
|
||||||
class="ajax-nav-link"
|
>
|
||||||
>{{ session.data_source.name|truncatechars:15 }}</a
|
</td>
|
||||||
>
|
<td>{{ session.country }}</td>
|
||||||
</td>
|
<td>{{ session.language }}</td>
|
||||||
<td>{{ session.country }}</td>
|
<td>
|
||||||
<td>{{ session.language }}</td>
|
{% if session.sentiment %}
|
||||||
<td>
|
{% if 'positive' in session.sentiment|lower %}
|
||||||
{% if session.sentiment %}
|
<span class="badge bg-success">{{ session.sentiment }}</span>
|
||||||
{% if 'positive' in session.sentiment|lower %}
|
{% elif 'negative' in session.sentiment|lower %}
|
||||||
<span class="badge bg-success">{{ session.sentiment }}</span>
|
<span class="badge bg-danger">{{ session.sentiment }}</span>
|
||||||
{% elif 'negative' in session.sentiment|lower %}
|
{% elif 'neutral' in session.sentiment|lower %}
|
||||||
<span class="badge bg-danger">{{ session.sentiment }}</span>
|
<span class="badge bg-warning">{{ session.sentiment }}</span>
|
||||||
{% elif 'neutral' in session.sentiment|lower %}
|
{% else %}
|
||||||
<span class="badge bg-warning">{{ session.sentiment }}</span>
|
<span class="badge bg-secondary">{{ session.sentiment }}</span>
|
||||||
{% else %}
|
{% endif %}
|
||||||
<span class="badge bg-secondary">{{ session.sentiment }}</span>
|
{% else %}
|
||||||
{% endif %}
|
<span class="text-muted">N/A</span>
|
||||||
{% else %}
|
{% endif %}
|
||||||
<span class="text-muted">N/A</span>
|
</td>
|
||||||
{% endif %}
|
<td>{{ session.messages_sent }}</td>
|
||||||
</td>
|
<td>{{ session.category|default:"N/A" }}</td>
|
||||||
<td>{{ session.messages_sent }}</td>
|
<td>
|
||||||
<td>{{ session.category|default:"N/A" }}</td>
|
{% if session.session_id %}
|
||||||
<td>
|
<a
|
||||||
{% if session.session_id %}
|
href="{% url 'chat_session_detail' session.session_id %}"
|
||||||
<a
|
class="btn btn-sm btn-outline-primary"
|
||||||
href="{% url 'chat_session_detail' session.session_id %}"
|
>
|
||||||
class="btn btn-sm btn-outline-primary"
|
<i class="fas fa-eye"></i>
|
||||||
>
|
</a>
|
||||||
<i class="fas fa-eye"></i>
|
{% else %}
|
||||||
</a>
|
<button class="btn btn-sm btn-outline-secondary" disabled>
|
||||||
{% else %}
|
<i class="fas fa-eye-slash"></i>
|
||||||
<button class="btn btn-sm btn-outline-secondary" disabled>
|
</button>
|
||||||
<i class="fas fa-eye-slash"></i>
|
{% endif %}
|
||||||
</button>
|
</td>
|
||||||
{% endif %}
|
</tr>
|
||||||
</td>
|
{% empty %}
|
||||||
</tr>
|
<tr>
|
||||||
{% empty %}
|
<td colspan="9" class="text-center">No chat sessions found matching your criteria.</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td colspan="9" class="text-center">
|
{% endfor %}
|
||||||
No chat sessions found matching your criteria.
|
</tbody>
|
||||||
</td>
|
</table>
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if page_obj.paginator.num_pages > 1 %}
|
{% if page_obj.paginator.num_pages > 1 %}
|
||||||
<nav aria-label="Page navigation" class="mt-4" id="pagination-container">
|
<nav aria-label="Page navigation" class="mt-4" id="pagination-container">
|
||||||
<ul class="pagination justify-content-center">
|
<ul class="pagination justify-content-center">
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a
|
<a
|
||||||
class="page-link pagination-link"
|
class="page-link pagination-link"
|
||||||
data-page="1"
|
data-page="1"
|
||||||
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page=1"
|
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page=1"
|
||||||
aria-label="First"
|
aria-label="First"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">««</span>
|
<span aria-hidden="true">««</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item">
|
<li class="page-item">
|
||||||
<a
|
<a
|
||||||
class="page-link pagination-link"
|
class="page-link pagination-link"
|
||||||
data-page="{{ page_obj.previous_page_number }}"
|
data-page="{{ page_obj.previous_page_number }}"
|
||||||
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ page_obj.previous_page_number }}"
|
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ page_obj.previous_page_number }}"
|
||||||
aria-label="Previous"
|
aria-label="Previous"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">«</span>
|
<span aria-hidden="true">«</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<a class="page-link" href="#" aria-label="First">
|
<a class="page-link" href="#" aria-label="First">
|
||||||
<span aria-hidden="true">««</span>
|
<span aria-hidden="true">««</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="page-item disabled">
|
<li class="page-item disabled">
|
||||||
<a class="page-link" href="#" aria-label="Previous">
|
<a class="page-link" href="#" aria-label="Previous">
|
||||||
<span aria-hidden="true">«</span>
|
<span aria-hidden="true">«</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% for num in page_obj.paginator.page_range %}{% if page_obj.number == num %}
|
||||||
{% for num in page_obj.paginator.page_range %}
|
<li class="page-item active">
|
||||||
{% if page_obj.number == num %}
|
<a
|
||||||
<li class="page-item active">
|
class="page-link pagination-link"
|
||||||
<a
|
data-page="{{ num }}"
|
||||||
class="page-link pagination-link"
|
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ num }}"
|
||||||
data-page="{{ num }}"
|
>{{ num }}</a
|
||||||
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ num }}"
|
>
|
||||||
>{{ num }}</a
|
</li>
|
||||||
>
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||||
</li>
|
<li class="page-item">
|
||||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
<a
|
||||||
<li class="page-item">
|
class="page-link pagination-link"
|
||||||
<a
|
data-page="{{ num }}"
|
||||||
class="page-link pagination-link"
|
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ num }}"
|
||||||
data-page="{{ num }}"
|
>{{ num }}</a
|
||||||
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ num }}"
|
>
|
||||||
>{{ num }}</a
|
</li>
|
||||||
>
|
{% endif %}{% endfor %}
|
||||||
</li>
|
{% if page_obj.has_next %}
|
||||||
{% endif %}
|
<li class="page-item">
|
||||||
{% endfor %}
|
<a
|
||||||
|
class="page-link pagination-link"
|
||||||
{% if page_obj.has_next %}
|
data-page="{{ page_obj.next_page_number }}"
|
||||||
<li class="page-item">
|
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ page_obj.next_page_number }}"
|
||||||
<a
|
aria-label="Next"
|
||||||
class="page-link pagination-link"
|
>
|
||||||
data-page="{{ page_obj.next_page_number }}"
|
<span aria-hidden="true">»</span>
|
||||||
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ page_obj.next_page_number }}"
|
</a>
|
||||||
aria-label="Next"
|
</li>
|
||||||
>
|
<li class="page-item">
|
||||||
<span aria-hidden="true">»</span>
|
<a
|
||||||
</a>
|
class="page-link pagination-link"
|
||||||
</li>
|
data-page="{{ page_obj.paginator.num_pages }}"
|
||||||
<li class="page-item">
|
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ page_obj.paginator.num_pages }}"
|
||||||
<a
|
aria-label="Last"
|
||||||
class="page-link pagination-link"
|
>
|
||||||
data-page="{{ page_obj.paginator.num_pages }}"
|
<span aria-hidden="true">»»</span>
|
||||||
href="?{% if query %}q={{ query }}&{% endif %}{% if data_source %}data_source_id={{ data_source.id }}&{% endif %}page={{ page_obj.paginator.num_pages }}"
|
</a>
|
||||||
aria-label="Last"
|
</li>
|
||||||
>
|
{% else %}
|
||||||
<span aria-hidden="true">»»</span>
|
<li class="page-item disabled">
|
||||||
</a>
|
<a class="page-link" href="#" aria-label="Next">
|
||||||
</li>
|
<span aria-hidden="true">»</span>
|
||||||
{% else %}
|
</a>
|
||||||
<li class="page-item disabled">
|
</li>
|
||||||
<a class="page-link" href="#" aria-label="Next">
|
<li class="page-item disabled">
|
||||||
<span aria-hidden="true">»</span>
|
<a class="page-link" href="#" aria-label="Last">
|
||||||
</a>
|
<span aria-hidden="true">»»</span>
|
||||||
</li>
|
</a>
|
||||||
<li class="page-item disabled">
|
</li>
|
||||||
<a class="page-link" href="#" aria-label="Last">
|
{% endif %}
|
||||||
<span aria-hidden="true">»»</span>
|
</ul>
|
||||||
</a>
|
</nav>
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -1,93 +1,81 @@
|
|||||||
<!-- templates/dashboard/search_results.html -->
|
<!-- templates/dashboard/search_results.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% block title %}Search Results | Chat Analytics{% endblock %}
|
||||||
|
|
||||||
{% block title %}Search Results | Chat Analytics{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div
|
<div
|
||||||
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
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">Search Results</h1>
|
<h1 class="h2">Search Results</h1>
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary ajax-nav-link">
|
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary ajax-nav-link">
|
||||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">Search Chat Sessions</h5>
|
<h5 class="card-title mb-0">Search Chat Sessions</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form
|
<form method="get" action="{% url 'search_chat_sessions' %}" class="search-form">
|
||||||
method="get"
|
<div class="input-group">
|
||||||
action="{% url 'search_chat_sessions' %}"
|
<input
|
||||||
class="search-form"
|
type="text"
|
||||||
>
|
name="q"
|
||||||
<div class="input-group">
|
class="form-control"
|
||||||
<input
|
placeholder="Search sessions..."
|
||||||
type="text"
|
value="{{ query }}"
|
||||||
name="q"
|
aria-label="Search sessions"
|
||||||
class="form-control"
|
/>
|
||||||
placeholder="Search sessions..."
|
{% if data_source %}
|
||||||
value="{{ query }}"
|
<input type="hidden" name="data_source_id" value="{{ data_source.id }}" />
|
||||||
aria-label="Search sessions"
|
{% endif %}
|
||||||
/>
|
<button class="btn btn-outline-primary" type="submit">
|
||||||
{% if data_source %}
|
<i class="fas fa-search"></i> Search
|
||||||
<input
|
</button>
|
||||||
type="hidden"
|
</div>
|
||||||
name="data_source_id"
|
<div class="mt-2 text-muted">
|
||||||
value="{{ data_source.id }}"
|
<small
|
||||||
/>
|
>Search by session ID, country, language, sentiment, category, or message
|
||||||
{% endif %}
|
content.</small
|
||||||
<button class="btn btn-outline-primary" type="submit">
|
>
|
||||||
<i class="fas fa-search"></i> Search
|
</div>
|
||||||
</button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 text-muted">
|
</div>
|
||||||
<small
|
</div>
|
||||||
>Search by session ID, country, language, sentiment, category, or
|
</div>
|
||||||
message content.</small
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title mb-0">
|
<h5 class="card-title mb-0">
|
||||||
Results {% if query %}for "{{ query }}"{% endif %}
|
Results {% if query %}for "{{ query }}"{% endif %}
|
||||||
{% if data_source %}in {{ data_source.name }}{% endif %}
|
{% if data_source %}in {{ data_source.name }}{% endif %}
|
||||||
({{ page_obj.paginator.count }})
|
({{ page_obj.paginator.count }})
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<!-- Loading spinner shown during AJAX requests -->
|
<!-- Loading spinner shown during AJAX requests -->
|
||||||
<div id="ajax-loading-spinner" class="text-center py-4 d-none">
|
<div id="ajax-loading-spinner" class="text-center py-4 d-none">
|
||||||
<div class="spinner-border text-primary" role="status">
|
<div class="spinner-border text-primary" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2">Loading data...</p>
|
<p class="mt-2">Loading data...</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search results container that will be updated via AJAX -->
|
<!-- Search results container that will be updated via AJAX -->
|
||||||
<div id="ajax-content-container">
|
<div id="ajax-content-container">
|
||||||
{% include "dashboard/partials/search_results_table.html" %}
|
{% include "dashboard/partials/search_results_table.html" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<!-- No need for extra JavaScript here, using common ajax-pagination.js -->
|
<!-- No need for extra JavaScript here, using common ajax-pagination.js -->
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,197 +1,195 @@
|
|||||||
<!-- templates/dashboard/upload.html -->
|
<!-- templates/dashboard/upload.html -->
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %} {% load crispy_forms_tags %} {% load dashboard_extras %}
|
||||||
{% load crispy_forms_tags %}
|
{% block title %}
|
||||||
{% load dashboard_extras %}
|
Upload Data | Chat Analytics
|
||||||
|
{% endblock %}
|
||||||
{% block title %}Upload Data | Chat Analytics{% endblock %}
|
{% block content %}
|
||||||
|
<div
|
||||||
{% block content %}
|
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"
|
||||||
<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">Upload Data</h1>
|
||||||
>
|
<div class="btn-toolbar mb-2 mb-md-0">
|
||||||
<h1 class="h2">Upload Data</h1>
|
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary">
|
||||||
<div class="btn-toolbar mb-2 mb-md-0">
|
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
||||||
<a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary">
|
</a>
|
||||||
<i class="fas fa-arrow-left"></i> Back to Dashboard
|
</div>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
<div class="row">
|
<div class="card">
|
||||||
<div class="col-md-6">
|
<div class="card-header">
|
||||||
<div class="card">
|
<h5 class="card-title mb-0">Upload CSV File</h5>
|
||||||
<div class="card-header">
|
</div>
|
||||||
<h5 class="card-title mb-0">Upload CSV File</h5>
|
<div class="card-body">
|
||||||
</div>
|
<form method="post" enctype="multipart/form-data">
|
||||||
<div class="card-body">
|
{% csrf_token %} {{ form|crispy }}
|
||||||
<form method="post" enctype="multipart/form-data">
|
<div class="d-grid gap-2">
|
||||||
{% csrf_token %} {{ form|crispy }}
|
<button type="submit" class="btn btn-primary">Upload</button>
|
||||||
<div class="d-grid gap-2">
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Upload</button>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<div class="col-md-6">
|
||||||
|
<div class="card">
|
||||||
<div class="col-md-6">
|
<div class="card-header">
|
||||||
<div class="card">
|
<h5 class="card-title mb-0">CSV File Format</h5>
|
||||||
<div class="card-header">
|
</div>
|
||||||
<h5 class="card-title mb-0">CSV File Format</h5>
|
<div class="card-body">
|
||||||
</div>
|
<p>The CSV file should contain the following columns:</p>
|
||||||
<div class="card-body">
|
<table class="table table-sm">
|
||||||
<p>The CSV file should contain the following columns:</p>
|
<thead>
|
||||||
<table class="table table-sm">
|
<tr>
|
||||||
<thead>
|
<th>Column</th>
|
||||||
<tr>
|
<th>Description</th>
|
||||||
<th>Column</th>
|
<th>Type</th>
|
||||||
<th>Description</th>
|
</tr>
|
||||||
<th>Type</th>
|
</thead>
|
||||||
</tr>
|
<tbody>
|
||||||
</thead>
|
<tr>
|
||||||
<tbody>
|
<td>session_id</td>
|
||||||
<tr>
|
<td>Unique identifier for the chat session</td>
|
||||||
<td>session_id</td>
|
<td>String</td>
|
||||||
<td>Unique identifier for the chat session</td>
|
</tr>
|
||||||
<td>String</td>
|
<tr>
|
||||||
</tr>
|
<td>start_time</td>
|
||||||
<tr>
|
<td>When the session started</td>
|
||||||
<td>start_time</td>
|
<td>Datetime</td>
|
||||||
<td>When the session started</td>
|
</tr>
|
||||||
<td>Datetime</td>
|
<tr>
|
||||||
</tr>
|
<td>end_time</td>
|
||||||
<tr>
|
<td>When the session ended</td>
|
||||||
<td>end_time</td>
|
<td>Datetime</td>
|
||||||
<td>When the session ended</td>
|
</tr>
|
||||||
<td>Datetime</td>
|
<tr>
|
||||||
</tr>
|
<td>ip_address</td>
|
||||||
<tr>
|
<td>IP address of the user</td>
|
||||||
<td>ip_address</td>
|
<td>String</td>
|
||||||
<td>IP address of the user</td>
|
</tr>
|
||||||
<td>String</td>
|
<tr>
|
||||||
</tr>
|
<td>country</td>
|
||||||
<tr>
|
<td>Country of the user</td>
|
||||||
<td>country</td>
|
<td>String</td>
|
||||||
<td>Country of the user</td>
|
</tr>
|
||||||
<td>String</td>
|
<tr>
|
||||||
</tr>
|
<td>language</td>
|
||||||
<tr>
|
<td>Language used in the conversation</td>
|
||||||
<td>language</td>
|
<td>String</td>
|
||||||
<td>Language used in the conversation</td>
|
</tr>
|
||||||
<td>String</td>
|
<tr>
|
||||||
</tr>
|
<td>messages_sent</td>
|
||||||
<tr>
|
<td>Number of messages in the conversation</td>
|
||||||
<td>messages_sent</td>
|
<td>Integer</td>
|
||||||
<td>Number of messages in the conversation</td>
|
</tr>
|
||||||
<td>Integer</td>
|
<tr>
|
||||||
</tr>
|
<td>sentiment</td>
|
||||||
<tr>
|
<td>Sentiment analysis of the conversation</td>
|
||||||
<td>sentiment</td>
|
<td>String</td>
|
||||||
<td>Sentiment analysis of the conversation</td>
|
</tr>
|
||||||
<td>String</td>
|
<tr>
|
||||||
</tr>
|
<td>escalated</td>
|
||||||
<tr>
|
<td>Whether the conversation was escalated</td>
|
||||||
<td>escalated</td>
|
<td>Boolean</td>
|
||||||
<td>Whether the conversation was escalated</td>
|
</tr>
|
||||||
<td>Boolean</td>
|
<tr>
|
||||||
</tr>
|
<td>forwarded_hr</td>
|
||||||
<tr>
|
<td>Whether the conversation was forwarded to HR</td>
|
||||||
<td>forwarded_hr</td>
|
<td>Boolean</td>
|
||||||
<td>Whether the conversation was forwarded to HR</td>
|
</tr>
|
||||||
<td>Boolean</td>
|
<tr>
|
||||||
</tr>
|
<td>full_transcript</td>
|
||||||
<tr>
|
<td>Full transcript of the conversation</td>
|
||||||
<td>full_transcript</td>
|
<td>Text</td>
|
||||||
<td>Full transcript of the conversation</td>
|
</tr>
|
||||||
<td>Text</td>
|
<tr>
|
||||||
</tr>
|
<td>avg_response_time</td>
|
||||||
<tr>
|
<td>Average response time in seconds</td>
|
||||||
<td>avg_response_time</td>
|
<td>Float</td>
|
||||||
<td>Average response time in seconds</td>
|
</tr>
|
||||||
<td>Float</td>
|
<tr>
|
||||||
</tr>
|
<td>tokens</td>
|
||||||
<tr>
|
<td>Total number of tokens used</td>
|
||||||
<td>tokens</td>
|
<td>Integer</td>
|
||||||
<td>Total number of tokens used</td>
|
</tr>
|
||||||
<td>Integer</td>
|
<tr>
|
||||||
</tr>
|
<td>tokens_eur</td>
|
||||||
<tr>
|
<td>Cost of tokens in EUR</td>
|
||||||
<td>tokens_eur</td>
|
<td>Float</td>
|
||||||
<td>Cost of tokens in EUR</td>
|
</tr>
|
||||||
<td>Float</td>
|
<tr>
|
||||||
</tr>
|
<td>category</td>
|
||||||
<tr>
|
<td>Category of the conversation</td>
|
||||||
<td>category</td>
|
<td>String</td>
|
||||||
<td>Category of the conversation</td>
|
</tr>
|
||||||
<td>String</td>
|
<tr>
|
||||||
</tr>
|
<td>initial_msg</td>
|
||||||
<tr>
|
<td>First message from the user</td>
|
||||||
<td>initial_msg</td>
|
<td>Text</td>
|
||||||
<td>First message from the user</td>
|
</tr>
|
||||||
<td>Text</td>
|
<tr>
|
||||||
</tr>
|
<td>user_rating</td>
|
||||||
<tr>
|
<td>User rating of the conversation</td>
|
||||||
<td>user_rating</td>
|
<td>String</td>
|
||||||
<td>User rating of the conversation</td>
|
</tr>
|
||||||
<td>String</td>
|
</tbody>
|
||||||
</tr>
|
</table>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
{% if data_sources %}
|
||||||
|
<div class="row mt-4">
|
||||||
{% if data_sources %}
|
<div class="col-12">
|
||||||
<div class="row mt-4">
|
<div class="card">
|
||||||
<div class="col-12">
|
<div class="card-header">
|
||||||
<div class="card">
|
<h5 class="card-title mb-0">Uploaded Data Sources</h5>
|
||||||
<div class="card-header">
|
</div>
|
||||||
<h5 class="card-title mb-0">Uploaded Data Sources</h5>
|
<div class="card-body">
|
||||||
</div>
|
<div class="table-responsive">
|
||||||
<div class="card-body">
|
<table class="table table-striped table-hover">
|
||||||
<div class="table-responsive">
|
<thead>
|
||||||
<table class="table table-striped table-hover">
|
<tr>
|
||||||
<thead>
|
<th>Name</th>
|
||||||
<tr>
|
<th>Description</th>
|
||||||
<th>Name</th>
|
<th>Uploaded</th>
|
||||||
<th>Description</th>
|
<th>File</th>
|
||||||
<th>Uploaded</th>
|
<th>Sessions</th>
|
||||||
<th>File</th>
|
<th>Actions</th>
|
||||||
<th>Sessions</th>
|
</tr>
|
||||||
<th>Actions</th>
|
</thead>
|
||||||
</tr>
|
<tbody>
|
||||||
</thead>
|
{% for data_source in data_sources %}
|
||||||
<tbody>
|
<tr>
|
||||||
{% for data_source in data_sources %}
|
<td>{{ data_source.name }}</td>
|
||||||
<tr>
|
<td>{{ data_source.description|truncatechars:50 }}</td>
|
||||||
<td>{{ data_source.name }}</td>
|
<td>{{ data_source.uploaded_at|date:"M d, Y H:i" }}</td>
|
||||||
<td>{{ data_source.description|truncatechars:50 }}</td>
|
<td>{{ data_source.file.name|split:"/"|last }}</td>
|
||||||
<td>{{ data_source.uploaded_at|date:"M d, Y H:i" }}</td>
|
<td>{{ data_source.chat_sessions.count }}</td>
|
||||||
<td>{{ data_source.file.name|split:"/"|last }}</td>
|
<td>
|
||||||
<td>{{ data_source.chat_sessions.count }}</td>
|
<a
|
||||||
<td>
|
href="{% url 'data_source_detail' data_source.id %}"
|
||||||
<a
|
class="btn btn-sm btn-outline-primary"
|
||||||
href="{% url 'data_source_detail' data_source.id %}"
|
>
|
||||||
class="btn btn-sm btn-outline-primary"
|
<i class="fas fa-eye"></i>
|
||||||
>
|
</a>
|
||||||
<i class="fas fa-eye"></i>
|
<a
|
||||||
</a>
|
href="{% url 'delete_data_source' data_source.id %}"
|
||||||
<a
|
class="btn btn-sm btn-outline-danger"
|
||||||
href="{% url 'delete_data_source' data_source.id %}"
|
>
|
||||||
class="btn btn-sm btn-outline-danger"
|
<i class="fas fa-trash"></i>
|
||||||
>
|
</a>
|
||||||
<i class="fas fa-trash"></i>
|
</td>
|
||||||
</a>
|
</tr>
|
||||||
</td>
|
{% endfor %}
|
||||||
</tr>
|
</tbody>
|
||||||
{% endfor %}
|
</table>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
19
package.json
19
package.json
@ -1,17 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "livegraphsdjango",
|
"devDependencies": {
|
||||||
"version": "0.1.0",
|
"prettier": "^3.5.3",
|
||||||
"description": "Live Graphs Django Dashboard",
|
"prettier-plugin-jinja-template": "^2.1.0"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user