Spaces:
Sleeping
Sleeping
IZERE HIRWA Roger
commited on
Commit
·
c024705
1
Parent(s):
2873069
ishingiro
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .env +39 -0
- .gitignore +0 -0
- AIMHSA_Samples.postman_collection.json +104 -0
- Dockerfile +36 -0
- add_sample_data.py +225 -0
- app.py +0 -0
- chatbot/admin.css +2280 -0
- chatbot/admin.js +1281 -0
- chatbot/admin_advanced.js +0 -0
- chatbot/admin_dashboard.html +1138 -0
- chatbot/app.js +958 -0
- chatbot/auth.css +585 -0
- chatbot/auth.html +66 -0
- chatbot/auth.js +162 -0
- chatbot/config-ui.js +419 -0
- chatbot/config.js +369 -0
- chatbot/index.html +83 -0
- chatbot/js/api.js +165 -0
- chatbot/js/config.js +136 -0
- chatbot/landing.css +1761 -0
- chatbot/landing.html +470 -0
- chatbot/landing.js +642 -0
- chatbot/login.html +130 -0
- chatbot/login.js +563 -0
- chatbot/professional.css +2446 -0
- chatbot/professional.js +1439 -0
- chatbot/professional_advanced.js +1587 -0
- chatbot/professional_dashboard.html +762 -0
- chatbot/professional_dashboard_clean.html +822 -0
- chatbot/register.html +133 -0
- chatbot/register.js +647 -0
- chatbot/style.css +612 -0
- check_professional_data.py +83 -0
- config.py +84 -0
- data/mental-health-overview.txt +14 -0
- data/ptsd-trauma-guide.txt +14 -0
- data/rwanda-helplines.txt +48 -0
- data/rwanda-mental-health-policy.txt +13 -0
- data/rwanda_mental_health_hospitals.txt +35 -0
- data/self-help-coping.txt +20 -0
- data/youth-mental-health.txt +13 -0
- email_config.env +11 -0
- ingest.py +114 -0
- init_database.py +307 -0
- install.py +83 -0
- production.env +37 -0
- requirements-cloud.txt +22 -0
- requirements-minimal.txt +14 -0
- requirements.txt +134 -0
- requirements_ollama.txt +12 -0
.env
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment Configuration
|
| 2 |
+
FLASK_ENV=development
|
| 3 |
+
DEBUG=True
|
| 4 |
+
|
| 5 |
+
# Server Configuration
|
| 6 |
+
SERVER_HOST=0.0.0.0
|
| 7 |
+
SERVER_PORT=7060
|
| 8 |
+
|
| 9 |
+
# For production hosting (Heroku, Railway, etc.)
|
| 10 |
+
PORT=7060
|
| 11 |
+
|
| 12 |
+
# API Configuration (leave empty for relative URLs)
|
| 13 |
+
API_BASE_URL=
|
| 14 |
+
FRONTEND_URL=
|
| 15 |
+
|
| 16 |
+
# Database Configuration
|
| 17 |
+
DB_FILE=storage/conversations.db
|
| 18 |
+
STORAGE_DIR=storage
|
| 19 |
+
DATA_DIR=data
|
| 20 |
+
|
| 21 |
+
# Email Configuration for AI Mental Health Chatbot
|
| 22 |
+
SMTP_SERVER=smtp.gmail.com
|
| 23 |
+
SMTP_PORT=587
|
| 24 |
+
SMTP_USERNAME=it.elias38@gmail.com
|
| 25 |
+
SMTP_PASSWORD=your-app-password-here
|
| 26 |
+
FROM_EMAIL=noreply@aimhsa.rw
|
| 27 |
+
|
| 28 |
+
# SMS Configuration
|
| 29 |
+
HDEV_SMS_API_ID=HDEV-23fb1b59-aec0-4aef-a351-bfc1c3aa3c52-ID
|
| 30 |
+
HDEV_SMS_API_KEY=HDEV-6e36c286-19bb-4b45-838e-8b5cd0240857-KEY
|
| 31 |
+
|
| 32 |
+
# Chat Model Configuration
|
| 33 |
+
CHAT_MODEL=meta-llama/llama-3.1-8b-instruct
|
| 34 |
+
EMBED_MODEL=nomic-embed-text
|
| 35 |
+
SENT_EMBED_MODEL=nomic-embed-text
|
| 36 |
+
OLLAMA_BASE_URL=https://openrouter.ai/api/v1
|
| 37 |
+
OLLAMA_API_KEY=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
|
| 38 |
+
|
| 39 |
+
# Security Note: Never commit this file with real credentials to version control
|
.gitignore
ADDED
|
Binary file (70 Bytes). View file
|
|
|
AIMHSA_Samples.postman_collection.json
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"info": {
|
| 3 |
+
"name": "AIMHSA RAG Chatbot - Sample Queries",
|
| 4 |
+
"_postman_id": "feza-aimhsa-rag-002",
|
| 5 |
+
"description": "Postman collection to test AIMHSA RAG chatbot with sample queries for Rwanda mental health.",
|
| 6 |
+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
| 7 |
+
},
|
| 8 |
+
"item": [
|
| 9 |
+
{
|
| 10 |
+
"name": "Health Check",
|
| 11 |
+
"request": {
|
| 12 |
+
"method": "GET",
|
| 13 |
+
"header": [],
|
| 14 |
+
"url": {
|
| 15 |
+
"raw": "http://127.0.0.1:5057/healthz",
|
| 16 |
+
"protocol": "http",
|
| 17 |
+
"host": ["127.0.0.1"],
|
| 18 |
+
"port": "5057",
|
| 19 |
+
"path": ["healthz"]
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"name": "Ask: PTSD Symptoms",
|
| 25 |
+
"request": {
|
| 26 |
+
"method": "POST",
|
| 27 |
+
"header": [
|
| 28 |
+
{"key": "Content-Type", "value": "application/json"}
|
| 29 |
+
],
|
| 30 |
+
"body": {
|
| 31 |
+
"mode": "raw",
|
| 32 |
+
"raw": "{\n \"query\": \"What are the common symptoms of PTSD in Rwanda?\"\n}"
|
| 33 |
+
},
|
| 34 |
+
"url": {
|
| 35 |
+
"raw": "http://127.0.0.1:5057/ask",
|
| 36 |
+
"protocol": "http",
|
| 37 |
+
"host": ["127.0.0.1"],
|
| 38 |
+
"port": "5057",
|
| 39 |
+
"path": ["ask"]
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"name": "Ask: Coping with Anxiety",
|
| 45 |
+
"request": {
|
| 46 |
+
"method": "POST",
|
| 47 |
+
"header": [
|
| 48 |
+
{"key": "Content-Type", "value": "application/json"}
|
| 49 |
+
],
|
| 50 |
+
"body": {
|
| 51 |
+
"mode": "raw",
|
| 52 |
+
"raw": "{\n \"query\": \"How can someone in Rwanda cope with anxiety?\"\n}"
|
| 53 |
+
},
|
| 54 |
+
"url": {
|
| 55 |
+
"raw": "http://127.0.0.1:5057/ask",
|
| 56 |
+
"protocol": "http",
|
| 57 |
+
"host": ["127.0.0.1"],
|
| 58 |
+
"port": "5057",
|
| 59 |
+
"path": ["ask"]
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"name": "Ask: Depression Support",
|
| 65 |
+
"request": {
|
| 66 |
+
"method": "POST",
|
| 67 |
+
"header": [
|
| 68 |
+
{"key": "Content-Type", "value": "application/json"}
|
| 69 |
+
],
|
| 70 |
+
"body": {
|
| 71 |
+
"mode": "raw",
|
| 72 |
+
"raw": "{\n \"query\": \"What support services exist for depression in Rwanda?\"\n}"
|
| 73 |
+
},
|
| 74 |
+
"url": {
|
| 75 |
+
"raw": "http://127.0.0.1:5057/ask",
|
| 76 |
+
"protocol": "http",
|
| 77 |
+
"host": ["127.0.0.1"],
|
| 78 |
+
"port": "5057",
|
| 79 |
+
"path": ["ask"]
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"name": "Ask: Mental Health Policy",
|
| 85 |
+
"request": {
|
| 86 |
+
"method": "POST",
|
| 87 |
+
"header": [
|
| 88 |
+
{"key": "Content-Type", "value": "application/json"}
|
| 89 |
+
],
|
| 90 |
+
"body": {
|
| 91 |
+
"mode": "raw",
|
| 92 |
+
"raw": "{\n \"query\": \"Summarize Rwanda's National Mental Health Policy.\"\n}"
|
| 93 |
+
},
|
| 94 |
+
"url": {
|
| 95 |
+
"raw": "http://127.0.0.1:5057/ask",
|
| 96 |
+
"protocol": "http",
|
| 97 |
+
"host": ["127.0.0.1"],
|
| 98 |
+
"port": "5057",
|
| 99 |
+
"path": ["ask"]
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
]
|
| 104 |
+
}
|
Dockerfile
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
# Create writable directories
|
| 11 |
+
RUN mkdir -p /app/instance && chmod -R 777 /app/instance
|
| 12 |
+
ENV HF_HOME=/app/transformers_cache
|
| 13 |
+
RUN mkdir -p /app/transformers_cache && chmod -R 777 /app/transformers_cache
|
| 14 |
+
|
| 15 |
+
# Create ../data directory for vector store
|
| 16 |
+
RUN mkdir -p /app/data && chmod -R 777 /app/data
|
| 17 |
+
RUN mkdir -p /data && chmod -R 777 /data
|
| 18 |
+
|
| 19 |
+
# Create uploads directory
|
| 20 |
+
RUN mkdir -p /app/uploads && chmod -R 777 /app/uploads
|
| 21 |
+
|
| 22 |
+
# Create logs directory
|
| 23 |
+
RUN mkdir -p /app/logs && chmod -R 777 /app/logs
|
| 24 |
+
RUN chmod -R 777 /data
|
| 25 |
+
RUN chmod -R 777 /storage
|
| 26 |
+
|
| 27 |
+
# Set NLTK data directory and download punkt
|
| 28 |
+
ENV NLTK_DATA=/usr/local/nltk_data
|
| 29 |
+
RUN mkdir -p $NLTK_DATA && python -m nltk.downloader -d $NLTK_DATA punkt
|
| 30 |
+
|
| 31 |
+
# Pre-download sentence-transformers model
|
| 32 |
+
RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')"
|
| 33 |
+
|
| 34 |
+
EXPOSE 7860
|
| 35 |
+
|
| 36 |
+
CMD ["python", "app.py"]
|
add_sample_data.py
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Add sample data to the database for testing
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sqlite3
|
| 7 |
+
import time
|
| 8 |
+
import hashlib
|
| 9 |
+
import uuid
|
| 10 |
+
|
| 11 |
+
# Database file path
|
| 12 |
+
DB_FILE = "aimhsa.db"
|
| 13 |
+
|
| 14 |
+
def hash_password(password):
|
| 15 |
+
"""Hash a password using SHA-256"""
|
| 16 |
+
return hashlib.sha256(password.encode()).hexdigest()
|
| 17 |
+
|
| 18 |
+
def add_sample_data():
|
| 19 |
+
"""Add sample data to the database"""
|
| 20 |
+
|
| 21 |
+
print("="*60)
|
| 22 |
+
print("ADDING SAMPLE DATA")
|
| 23 |
+
print("="*60)
|
| 24 |
+
|
| 25 |
+
conn = sqlite3.connect(DB_FILE)
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
current_time = time.time()
|
| 29 |
+
|
| 30 |
+
# Add sample user
|
| 31 |
+
print("Adding sample user...")
|
| 32 |
+
conn.execute("""
|
| 33 |
+
INSERT OR REPLACE INTO users (
|
| 34 |
+
username, password_hash, created_ts, email, fullname,
|
| 35 |
+
telephone, province, district, created_at
|
| 36 |
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 37 |
+
""", (
|
| 38 |
+
'Mugisha',
|
| 39 |
+
hash_password('password123'),
|
| 40 |
+
current_time,
|
| 41 |
+
'mugisha@example.com',
|
| 42 |
+
'Mugisha DUKUZUMUREMYI',
|
| 43 |
+
'0785354935',
|
| 44 |
+
'Eastern',
|
| 45 |
+
'Kirehe',
|
| 46 |
+
current_time
|
| 47 |
+
))
|
| 48 |
+
print("✅ Sample user 'Mugisha' added")
|
| 49 |
+
|
| 50 |
+
# Add sample professional
|
| 51 |
+
print("Adding sample professional...")
|
| 52 |
+
conn.execute("""
|
| 53 |
+
INSERT OR REPLACE INTO professionals (
|
| 54 |
+
username, password_hash, first_name, last_name, email, phone,
|
| 55 |
+
license_number, specialization, expertise_areas, languages,
|
| 56 |
+
qualifications, availability_schedule, location_latitude,
|
| 57 |
+
location_longitude, location_address, district, max_patients_per_day,
|
| 58 |
+
consultation_fee, experience_years, bio, profile_picture,
|
| 59 |
+
is_active, created_ts, updated_ts
|
| 60 |
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 61 |
+
""", (
|
| 62 |
+
'jean.ntwari',
|
| 63 |
+
hash_password('password123'),
|
| 64 |
+
'Jean',
|
| 65 |
+
'Ntwari',
|
| 66 |
+
'jean.ntwari@example.com',
|
| 67 |
+
'+250788123456',
|
| 68 |
+
'LIC123456',
|
| 69 |
+
'Clinical Psychologist',
|
| 70 |
+
'["Depression", "Anxiety", "Trauma", "Stress Management"]',
|
| 71 |
+
'["English", "Kinyarwanda", "French"]',
|
| 72 |
+
'["PhD in Clinical Psychology", "Licensed Clinical Psychologist"]',
|
| 73 |
+
'{"monday": "09:00-17:00", "tuesday": "09:00-17:00", "wednesday": "09:00-17:00", "thursday": "09:00-17:00", "friday": "09:00-17:00"}',
|
| 74 |
+
-1.9441,
|
| 75 |
+
30.0619,
|
| 76 |
+
'Kigali, Rwanda',
|
| 77 |
+
'Kigali',
|
| 78 |
+
10,
|
| 79 |
+
50000.0,
|
| 80 |
+
8,
|
| 81 |
+
'Experienced clinical psychologist specializing in trauma therapy and cognitive behavioral therapy.',
|
| 82 |
+
None,
|
| 83 |
+
1,
|
| 84 |
+
current_time,
|
| 85 |
+
current_time
|
| 86 |
+
))
|
| 87 |
+
print("✅ Sample professional 'Jean Ntwari' added")
|
| 88 |
+
|
| 89 |
+
# Get professional ID
|
| 90 |
+
professional = conn.execute("SELECT id FROM professionals WHERE username = 'jean.ntwari'").fetchone()
|
| 91 |
+
professional_id = professional[0]
|
| 92 |
+
|
| 93 |
+
# Add sample conversation
|
| 94 |
+
conv_id = str(uuid.uuid4())
|
| 95 |
+
print("Adding sample conversation...")
|
| 96 |
+
conn.execute("""
|
| 97 |
+
INSERT OR REPLACE INTO conversations (
|
| 98 |
+
conv_id, owner_key, preview, ts
|
| 99 |
+
) VALUES (?, ?, ?, ?)
|
| 100 |
+
""", (
|
| 101 |
+
conv_id,
|
| 102 |
+
'Mugisha',
|
| 103 |
+
'User is feeling overwhelmed and struggling with low mood. Needs support and resources.',
|
| 104 |
+
current_time
|
| 105 |
+
))
|
| 106 |
+
print("✅ Sample conversation added")
|
| 107 |
+
|
| 108 |
+
# Add sample messages
|
| 109 |
+
print("Adding sample messages...")
|
| 110 |
+
messages = [
|
| 111 |
+
(conv_id, 'user', 'I am feeling overwhelmed and tired. I need help.', current_time - 3600),
|
| 112 |
+
(conv_id, 'assistant', 'I understand you\'re feeling overwhelmed and tired. That sounds really difficult. Can you tell me more about what\'s been going on?', current_time - 3500),
|
| 113 |
+
(conv_id, 'user', 'I have been struggling with low mood and lack of motivation. Everything feels too much.', current_time - 3400),
|
| 114 |
+
(conv_id, 'assistant', 'I hear that you\'re experiencing low mood and feeling like everything is too much. These feelings are valid and it\'s important that you\'re reaching out for support. Would you like me to help you connect with a mental health professional?', current_time - 3300),
|
| 115 |
+
(conv_id, 'user', 'Yes, I think I need professional help. I am in Rwanda and not sure where to find good mental health support.', current_time - 3200),
|
| 116 |
+
(conv_id, 'assistant', 'I can help you connect with a qualified mental health professional in Rwanda. Based on your needs, I\'ll create a booking for you with a professional who specializes in mood disorders and stress management.', current_time - 3100)
|
| 117 |
+
]
|
| 118 |
+
|
| 119 |
+
for msg in messages:
|
| 120 |
+
conn.execute("""
|
| 121 |
+
INSERT OR REPLACE INTO messages (
|
| 122 |
+
conv_id, role, content, ts
|
| 123 |
+
) VALUES (?, ?, ?, ?)
|
| 124 |
+
""", msg)
|
| 125 |
+
|
| 126 |
+
print("✅ Sample messages added")
|
| 127 |
+
|
| 128 |
+
# Add sample automated booking
|
| 129 |
+
booking_id = 'd63a7794-a89c-452c-80a6-24691e3cb848'
|
| 130 |
+
print("Adding sample automated booking...")
|
| 131 |
+
conn.execute("""
|
| 132 |
+
INSERT OR REPLACE INTO automated_bookings (
|
| 133 |
+
booking_id, conv_id, user_account, user_ip, professional_id,
|
| 134 |
+
risk_level, risk_score, detected_indicators, conversation_summary,
|
| 135 |
+
booking_status, scheduled_datetime, session_type, location_preference,
|
| 136 |
+
notes, created_ts, updated_ts
|
| 137 |
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
| 138 |
+
""", (
|
| 139 |
+
booking_id,
|
| 140 |
+
conv_id,
|
| 141 |
+
'Mugisha',
|
| 142 |
+
'127.0.0.1',
|
| 143 |
+
professional_id,
|
| 144 |
+
'medium',
|
| 145 |
+
0.5,
|
| 146 |
+
'["user_requested_booking"]',
|
| 147 |
+
'**Professional Summary:**\n\n**Client:** Female, Rwandan national\n\n**Presenting Concerns:** Feeling overwhelmed, tired, and struggling with low mood.\n\n**Emotional State:** The client is expressing emotional distress, feeling not good, and acknowledging the need for support. She appears to be experiencing significant stress and is open to exploring resources for mental health support in Rwanda.\n\n**Risk Factors:** None explicitly mentioned, but the context suggests a history of challenges faced by Rwanda, which may contribute to the client\'s current emotional state.\n\n**Key Issues:**\n\n* Feeling overwhelmed and tired\n* Low mood and lack of motivation\n* Difficulty in accessing mental health resources in Rwanda\n* Interest in exploring relaxation techniques, journaling, or grounding exercises as coping mechanisms\n\nThis conversation highlights the importance of acknowledging and addressing the client\'s emotional pain, while also providing her with information and support to access necessary resources. Further guidance on managing stress and improving mental well-being is warranted.\n\n**Recommendations:**\n\n* Schedule a follow-up appointment to assess the client\'s progress and provide ongoing support.\n* Explore additional coping mechanisms, such as cognitive-behavioral therapy (CBT) or mindfulness-based interventions.\n* Connect the client with local resources, such as mental health hotlines or counseling services, for continued support.\n\n**Next Steps:**\n\n* Coordinate follow-up appointments to ensure the client\'s ongoing progress and well-being.\n* Monitor the client\'s emotional state and adjust treatment plans as needed.',
|
| 148 |
+
'confirmed',
|
| 149 |
+
current_time + 86400, # Tomorrow
|
| 150 |
+
'urgent',
|
| 151 |
+
'Kigali',
|
| 152 |
+
'Client needs immediate support for mood and stress management.',
|
| 153 |
+
current_time,
|
| 154 |
+
current_time + 200 # Updated 200 seconds later
|
| 155 |
+
))
|
| 156 |
+
print("✅ Sample automated booking added")
|
| 157 |
+
|
| 158 |
+
# Add sample conversation messages
|
| 159 |
+
print("Adding conversation messages...")
|
| 160 |
+
conv_messages = [
|
| 161 |
+
(conv_id, 'user', 'I am feeling overwhelmed and tired. I need help.', current_time - 3600),
|
| 162 |
+
(conv_id, 'assistant', 'I understand you\'re feeling overwhelmed and tired. That sounds really difficult. Can you tell me more about what\'s been going on?', current_time - 3500),
|
| 163 |
+
(conv_id, 'user', 'I have been struggling with low mood and lack of motivation. Everything feels too much.', current_time - 3400),
|
| 164 |
+
(conv_id, 'assistant', 'I hear that you\'re experiencing low mood and feeling like everything is too much. These feelings are valid and it\'s important that you\'re reaching out for support. Would you like me to help you connect with a mental health professional?', current_time - 3300),
|
| 165 |
+
(conv_id, 'user', 'Yes, I think I need professional help. I am in Rwanda and not sure where to find good mental health support.', current_time - 3200),
|
| 166 |
+
(conv_id, 'assistant', 'I can help you connect with a qualified mental health professional in Rwanda. Based on your needs, I\'ll create a booking for you with a professional who specializes in mood disorders and stress management.', current_time - 3100)
|
| 167 |
+
]
|
| 168 |
+
|
| 169 |
+
for msg in conv_messages:
|
| 170 |
+
conn.execute("""
|
| 171 |
+
INSERT OR REPLACE INTO conversation_messages (
|
| 172 |
+
conv_id, sender, content, timestamp
|
| 173 |
+
) VALUES (?, ?, ?, ?)
|
| 174 |
+
""", msg)
|
| 175 |
+
|
| 176 |
+
print("✅ Conversation messages added")
|
| 177 |
+
|
| 178 |
+
# Add sample professional notification
|
| 179 |
+
print("Adding sample professional notification...")
|
| 180 |
+
conn.execute("""
|
| 181 |
+
INSERT OR REPLACE INTO professional_notifications (
|
| 182 |
+
professional_id, booking_id, notification_type, title, message,
|
| 183 |
+
is_read, priority, created_ts
|
| 184 |
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
| 185 |
+
""", (
|
| 186 |
+
professional_id,
|
| 187 |
+
booking_id,
|
| 188 |
+
'new_booking',
|
| 189 |
+
'URGENT: MEDIUM Risk Case - Mugisha DUKUZUMUREMYI',
|
| 190 |
+
'Automated booking created for medium risk case. Risk indicators: user_requested_booking\n\nUser Contact Information:\nName: Mugisha DUKUZUMUREMYI\nPhone: 0785354935\nEmail: mugisha@example.com\nLocation: Kirehe, Eastern',
|
| 191 |
+
0,
|
| 192 |
+
'high',
|
| 193 |
+
current_time
|
| 194 |
+
))
|
| 195 |
+
print("✅ Sample professional notification added")
|
| 196 |
+
|
| 197 |
+
# Commit all changes
|
| 198 |
+
conn.commit()
|
| 199 |
+
|
| 200 |
+
print("\n" + "="*60)
|
| 201 |
+
print("SAMPLE DATA ADDITION COMPLETE!")
|
| 202 |
+
print("="*60)
|
| 203 |
+
|
| 204 |
+
# Verify data was added
|
| 205 |
+
user_count = conn.execute("SELECT COUNT(*) FROM users").fetchone()[0]
|
| 206 |
+
professional_count = conn.execute("SELECT COUNT(*) FROM professionals").fetchone()[0]
|
| 207 |
+
booking_count = conn.execute("SELECT COUNT(*) FROM automated_bookings").fetchone()[0]
|
| 208 |
+
message_count = conn.execute("SELECT COUNT(*) FROM messages").fetchone()[0]
|
| 209 |
+
|
| 210 |
+
print(f"\nData Summary:")
|
| 211 |
+
print(f" Users: {user_count}")
|
| 212 |
+
print(f" Professionals: {professional_count}")
|
| 213 |
+
print(f" Bookings: {booking_count}")
|
| 214 |
+
print(f" Messages: {message_count}")
|
| 215 |
+
|
| 216 |
+
conn.close()
|
| 217 |
+
|
| 218 |
+
except Exception as e:
|
| 219 |
+
print(f"❌ Error adding sample data: {e}")
|
| 220 |
+
conn.close()
|
| 221 |
+
raise
|
| 222 |
+
|
| 223 |
+
if __name__ == "__main__":
|
| 224 |
+
add_sample_data()
|
| 225 |
+
|
app.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
chatbot/admin.css
ADDED
|
@@ -0,0 +1,2280 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Admin Dashboard Styles with AdminLTE 4 Integration */
|
| 2 |
+
:root {
|
| 3 |
+
--primary: #7c3aed;
|
| 4 |
+
--primary-light: #a855f7;
|
| 5 |
+
--primary-dark: #5b21b6;
|
| 6 |
+
--background: #0f172a;
|
| 7 |
+
--surface: #1e293b;
|
| 8 |
+
--card: #334155;
|
| 9 |
+
--text: #f8fafc;
|
| 10 |
+
--text-secondary: #cbd5e1;
|
| 11 |
+
--text-muted: #94a3b8;
|
| 12 |
+
--border: #334155;
|
| 13 |
+
--border-light: #475569;
|
| 14 |
+
--success: #10b981;
|
| 15 |
+
--warning: #f59e0b;
|
| 16 |
+
--danger: #ef4444;
|
| 17 |
+
--critical: #dc2626;
|
| 18 |
+
--high: #ea580c;
|
| 19 |
+
--medium: #d97706;
|
| 20 |
+
--low: #059669;
|
| 21 |
+
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
|
| 22 |
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
/* AdminLTE 4 Compatibility Overrides */
|
| 26 |
+
body {
|
| 27 |
+
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
/* ========================================
|
| 31 |
+
EXPERTISE AREAS - ENHANCED DESIGN
|
| 32 |
+
======================================== */
|
| 33 |
+
|
| 34 |
+
.expertise-grid {
|
| 35 |
+
background: linear-gradient(135deg, rgba(124, 58, 237, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%);
|
| 36 |
+
border: 1px solid var(--border-light);
|
| 37 |
+
border-radius: 12px;
|
| 38 |
+
padding: 1.5rem;
|
| 39 |
+
margin-top: 0.5rem;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.expertise-category {
|
| 43 |
+
margin-bottom: 1.5rem;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.expertise-category:last-child {
|
| 47 |
+
margin-bottom: 0;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.category-title {
|
| 51 |
+
color: var(--text);
|
| 52 |
+
font-weight: 600;
|
| 53 |
+
font-size: 0.95rem;
|
| 54 |
+
margin-bottom: 1rem;
|
| 55 |
+
padding-bottom: 0.5rem;
|
| 56 |
+
border-bottom: 2px solid var(--border-light);
|
| 57 |
+
display: flex;
|
| 58 |
+
align-items: center;
|
| 59 |
+
gap: 0.5rem;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.category-title i {
|
| 63 |
+
font-size: 1.1rem;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.expertise-options {
|
| 67 |
+
display: flex;
|
| 68 |
+
flex-direction: column;
|
| 69 |
+
gap: 0.75rem;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.expertise-option {
|
| 73 |
+
position: relative;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.expertise-checkbox {
|
| 77 |
+
position: absolute;
|
| 78 |
+
opacity: 0;
|
| 79 |
+
cursor: pointer;
|
| 80 |
+
height: 0;
|
| 81 |
+
width: 0;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.expertise-label {
|
| 85 |
+
display: flex;
|
| 86 |
+
align-items: center;
|
| 87 |
+
padding: 0.875rem 1rem;
|
| 88 |
+
background: var(--surface);
|
| 89 |
+
border: 2px solid var(--border);
|
| 90 |
+
border-radius: 8px;
|
| 91 |
+
cursor: pointer;
|
| 92 |
+
transition: all 0.3s ease;
|
| 93 |
+
font-weight: 500;
|
| 94 |
+
color: var(--text-secondary);
|
| 95 |
+
position: relative;
|
| 96 |
+
overflow: hidden;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.expertise-label::before {
|
| 100 |
+
content: '';
|
| 101 |
+
position: absolute;
|
| 102 |
+
top: 0;
|
| 103 |
+
left: 0;
|
| 104 |
+
width: 4px;
|
| 105 |
+
height: 100%;
|
| 106 |
+
background: var(--primary);
|
| 107 |
+
transform: scaleY(0);
|
| 108 |
+
transition: transform 0.3s ease;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.expertise-label:hover {
|
| 112 |
+
background: rgba(124, 58, 237, 0.1);
|
| 113 |
+
border-color: var(--primary-light);
|
| 114 |
+
color: var(--text);
|
| 115 |
+
transform: translateY(-1px);
|
| 116 |
+
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.15);
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.expertise-label i {
|
| 120 |
+
margin-right: 0.75rem;
|
| 121 |
+
font-size: 1.1rem;
|
| 122 |
+
color: var(--primary-light);
|
| 123 |
+
transition: color 0.3s ease;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.expertise-label span {
|
| 127 |
+
flex: 1;
|
| 128 |
+
font-size: 0.95rem;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
/* Checked state */
|
| 132 |
+
.expertise-checkbox:checked + .expertise-label {
|
| 133 |
+
background: linear-gradient(135deg, rgba(124, 58, 237, 0.15) 0%, rgba(168, 85, 247, 0.15) 100%);
|
| 134 |
+
border-color: var(--primary);
|
| 135 |
+
color: var(--text);
|
| 136 |
+
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.2);
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
.expertise-checkbox:checked + .expertise-label::before {
|
| 140 |
+
transform: scaleY(1);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
.expertise-checkbox:checked + .expertise-label i {
|
| 144 |
+
color: var(--primary);
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
/* Focus state for accessibility */
|
| 148 |
+
.expertise-checkbox:focus + .expertise-label {
|
| 149 |
+
outline: 2px solid var(--primary-light);
|
| 150 |
+
outline-offset: 2px;
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
/* Select All Controls */
|
| 154 |
+
.expertise-controls {
|
| 155 |
+
display: flex;
|
| 156 |
+
align-items: center;
|
| 157 |
+
flex-wrap: wrap;
|
| 158 |
+
gap: 0.5rem;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.expertise-controls .btn {
|
| 162 |
+
border-radius: 6px;
|
| 163 |
+
font-weight: 500;
|
| 164 |
+
transition: all 0.3s ease;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
.expertise-controls .btn:hover {
|
| 168 |
+
transform: translateY(-1px);
|
| 169 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.expertise-count {
|
| 173 |
+
font-size: 0.9rem;
|
| 174 |
+
font-weight: 500;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
#selectedCount {
|
| 178 |
+
color: var(--primary);
|
| 179 |
+
font-weight: 600;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
/* Form validation states */
|
| 183 |
+
.expertise-grid.is-invalid {
|
| 184 |
+
border-color: var(--danger);
|
| 185 |
+
background: rgba(239, 68, 68, 0.05);
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.expertise-grid.is-valid {
|
| 189 |
+
border-color: var(--success);
|
| 190 |
+
background: rgba(16, 185, 129, 0.05);
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
/* Responsive design */
|
| 194 |
+
@media (max-width: 768px) {
|
| 195 |
+
.expertise-grid {
|
| 196 |
+
padding: 1rem;
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
.expertise-label {
|
| 200 |
+
padding: 0.75rem 0.875rem;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.expertise-label i {
|
| 204 |
+
margin-right: 0.5rem;
|
| 205 |
+
font-size: 1rem;
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
.expertise-label span {
|
| 209 |
+
font-size: 0.9rem;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
.expertise-controls {
|
| 213 |
+
flex-direction: column;
|
| 214 |
+
align-items: flex-start;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
.expertise-controls .btn {
|
| 218 |
+
width: 100%;
|
| 219 |
+
margin-bottom: 0.5rem;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.expertise-count {
|
| 223 |
+
margin-left: 0 !important;
|
| 224 |
+
margin-top: 0.5rem;
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
/* Animation for smooth interactions */
|
| 229 |
+
@keyframes expertiseSelect {
|
| 230 |
+
0% {
|
| 231 |
+
transform: scale(1);
|
| 232 |
+
}
|
| 233 |
+
50% {
|
| 234 |
+
transform: scale(1.02);
|
| 235 |
+
}
|
| 236 |
+
100% {
|
| 237 |
+
transform: scale(1);
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
.expertise-checkbox:checked + .expertise-label {
|
| 242 |
+
animation: expertiseSelect 0.3s ease;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
/* Dark theme enhancements */
|
| 246 |
+
@media (prefers-color-scheme: dark) {
|
| 247 |
+
.expertise-label {
|
| 248 |
+
background: rgba(30, 41, 59, 0.8);
|
| 249 |
+
border-color: rgba(71, 85, 105, 0.6);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.expertise-label:hover {
|
| 253 |
+
background: rgba(124, 58, 237, 0.2);
|
| 254 |
+
border-color: var(--primary-light);
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.expertise-checkbox:checked + .expertise-label {
|
| 258 |
+
background: linear-gradient(135deg, rgba(124, 58, 237, 0.25) 0%, rgba(168, 85, 247, 0.25) 100%);
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
/* ========================================
|
| 263 |
+
BOOKINGS SECTION - ENHANCED DESIGN
|
| 264 |
+
======================================== */
|
| 265 |
+
|
| 266 |
+
.bookings-card {
|
| 267 |
+
border: none;
|
| 268 |
+
box-shadow: var(--shadow-lg);
|
| 269 |
+
border-radius: 12px;
|
| 270 |
+
overflow: hidden;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.bookings-card .card-header {
|
| 274 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
|
| 275 |
+
border-bottom: none;
|
| 276 |
+
padding: 1.5rem;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
.bookings-card .card-title {
|
| 280 |
+
color: white;
|
| 281 |
+
font-weight: 600;
|
| 282 |
+
font-size: 1.25rem;
|
| 283 |
+
margin: 0;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
.bookings-card .card-title i {
|
| 287 |
+
margin-right: 0.5rem;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.bookings-filters {
|
| 291 |
+
background: rgba(124, 58, 237, 0.05);
|
| 292 |
+
border: 1px solid var(--border-light);
|
| 293 |
+
border-radius: 8px;
|
| 294 |
+
padding: 1.5rem;
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
.bookings-filters .form-label {
|
| 298 |
+
font-weight: 600;
|
| 299 |
+
color: var(--text);
|
| 300 |
+
margin-bottom: 0.5rem;
|
| 301 |
+
font-size: 0.9rem;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.bookings-filters .form-control {
|
| 305 |
+
border: 1px solid var(--border);
|
| 306 |
+
border-radius: 6px;
|
| 307 |
+
background: var(--surface);
|
| 308 |
+
color: var(--text);
|
| 309 |
+
transition: all 0.3s ease;
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
.bookings-filters .form-control:focus {
|
| 313 |
+
border-color: var(--primary);
|
| 314 |
+
box-shadow: 0 0 0 0.2rem rgba(124, 58, 237, 0.25);
|
| 315 |
+
background: var(--surface);
|
| 316 |
+
color: var(--text);
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
.bookings-filters .btn {
|
| 320 |
+
border-radius: 6px;
|
| 321 |
+
font-weight: 500;
|
| 322 |
+
transition: all 0.3s ease;
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
.bookings-filters .btn:hover {
|
| 326 |
+
transform: translateY(-1px);
|
| 327 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
/* Info Boxes */
|
| 331 |
+
.info-box {
|
| 332 |
+
border-radius: 8px;
|
| 333 |
+
box-shadow: var(--shadow);
|
| 334 |
+
transition: all 0.3s ease;
|
| 335 |
+
border: none;
|
| 336 |
+
overflow: hidden;
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
.info-box:hover {
|
| 340 |
+
transform: translateY(-2px);
|
| 341 |
+
box-shadow: var(--shadow-lg);
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
.info-box-icon {
|
| 345 |
+
display: flex;
|
| 346 |
+
align-items: center;
|
| 347 |
+
justify-content: center;
|
| 348 |
+
font-size: 2rem;
|
| 349 |
+
color: rgba(255, 255, 255, 0.8);
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
.info-box-content {
|
| 353 |
+
padding: 1rem;
|
| 354 |
+
}
|
| 355 |
+
|
| 356 |
+
.info-box-text {
|
| 357 |
+
font-size: 0.9rem;
|
| 358 |
+
font-weight: 500;
|
| 359 |
+
color: rgba(255, 255, 255, 0.9);
|
| 360 |
+
text-transform: uppercase;
|
| 361 |
+
letter-spacing: 0.5px;
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
.info-box-number {
|
| 365 |
+
font-size: 1.5rem;
|
| 366 |
+
font-weight: 700;
|
| 367 |
+
color: white;
|
| 368 |
+
margin-top: 0.25rem;
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
/* Gradient Backgrounds */
|
| 372 |
+
.bg-gradient-primary {
|
| 373 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
.bg-gradient-success {
|
| 377 |
+
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
.bg-gradient-warning {
|
| 381 |
+
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
.bg-gradient-danger {
|
| 385 |
+
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
| 386 |
+
}
|
| 387 |
+
|
| 388 |
+
/* Enhanced Table Styling */
|
| 389 |
+
#bookingsTable {
|
| 390 |
+
border: none;
|
| 391 |
+
border-radius: 8px;
|
| 392 |
+
overflow: hidden;
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
#bookingsTable thead th {
|
| 396 |
+
background: var(--surface);
|
| 397 |
+
color: var(--text);
|
| 398 |
+
font-weight: 600;
|
| 399 |
+
text-transform: uppercase;
|
| 400 |
+
font-size: 0.85rem;
|
| 401 |
+
letter-spacing: 0.5px;
|
| 402 |
+
border: none;
|
| 403 |
+
padding: 1rem 0.75rem;
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
#bookingsTable thead th i {
|
| 407 |
+
margin-right: 0.5rem;
|
| 408 |
+
color: var(--primary);
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
#bookingsTable tbody tr {
|
| 412 |
+
transition: all 0.3s ease;
|
| 413 |
+
border: none;
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
#bookingsTable tbody tr:hover {
|
| 417 |
+
background: rgba(124, 58, 237, 0.1);
|
| 418 |
+
transform: translateY(-1px);
|
| 419 |
+
box-shadow: 0 2px 8px rgba(124, 58, 237, 0.15);
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
#bookingsTable tbody td {
|
| 423 |
+
border: none;
|
| 424 |
+
padding: 1rem 0.75rem;
|
| 425 |
+
vertical-align: middle;
|
| 426 |
+
border-bottom: 1px solid var(--border-light);
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
/* User Details Styling */
|
| 430 |
+
.user-details {
|
| 431 |
+
display: flex;
|
| 432 |
+
align-items: center;
|
| 433 |
+
gap: 0.75rem;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
.user-avatar {
|
| 437 |
+
width: 40px;
|
| 438 |
+
height: 40px;
|
| 439 |
+
border-radius: 50%;
|
| 440 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
|
| 441 |
+
display: flex;
|
| 442 |
+
align-items: center;
|
| 443 |
+
justify-content: center;
|
| 444 |
+
color: white;
|
| 445 |
+
font-weight: 600;
|
| 446 |
+
font-size: 0.9rem;
|
| 447 |
+
flex-shrink: 0;
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
.user-info {
|
| 451 |
+
flex: 1;
|
| 452 |
+
min-width: 0;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
.user-name {
|
| 456 |
+
font-weight: 600;
|
| 457 |
+
color: var(--text);
|
| 458 |
+
margin: 0;
|
| 459 |
+
font-size: 0.95rem;
|
| 460 |
+
}
|
| 461 |
+
|
| 462 |
+
.user-contact {
|
| 463 |
+
font-size: 0.8rem;
|
| 464 |
+
color: var(--text-secondary);
|
| 465 |
+
margin: 0.25rem 0 0 0;
|
| 466 |
+
display: flex;
|
| 467 |
+
flex-direction: column;
|
| 468 |
+
gap: 0.25rem;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
.user-contact a {
|
| 472 |
+
color: var(--primary);
|
| 473 |
+
text-decoration: none;
|
| 474 |
+
transition: color 0.3s ease;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
.user-contact a:hover {
|
| 478 |
+
color: var(--primary-light);
|
| 479 |
+
text-decoration: underline;
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
.user-location {
|
| 483 |
+
font-size: 0.75rem;
|
| 484 |
+
color: var(--text-muted);
|
| 485 |
+
margin: 0.25rem 0 0 0;
|
| 486 |
+
display: flex;
|
| 487 |
+
align-items: center;
|
| 488 |
+
gap: 0.25rem;
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
.user-location i {
|
| 492 |
+
font-size: 0.7rem;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
/* Professional Info Styling */
|
| 496 |
+
.professional-info {
|
| 497 |
+
display: flex;
|
| 498 |
+
align-items: center;
|
| 499 |
+
gap: 0.5rem;
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
.professional-avatar {
|
| 503 |
+
width: 32px;
|
| 504 |
+
height: 32px;
|
| 505 |
+
border-radius: 50%;
|
| 506 |
+
background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
|
| 507 |
+
display: flex;
|
| 508 |
+
align-items: center;
|
| 509 |
+
justify-content: center;
|
| 510 |
+
color: white;
|
| 511 |
+
font-weight: 600;
|
| 512 |
+
font-size: 0.8rem;
|
| 513 |
+
flex-shrink: 0;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
.professional-name {
|
| 517 |
+
font-weight: 500;
|
| 518 |
+
color: var(--text);
|
| 519 |
+
margin: 0;
|
| 520 |
+
font-size: 0.9rem;
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
.professional-specialization {
|
| 524 |
+
font-size: 0.75rem;
|
| 525 |
+
color: var(--text-secondary);
|
| 526 |
+
margin: 0.25rem 0 0 0;
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
/* Risk Level Badges */
|
| 530 |
+
.risk-badge {
|
| 531 |
+
padding: 0.375rem 0.75rem;
|
| 532 |
+
border-radius: 20px;
|
| 533 |
+
font-size: 0.75rem;
|
| 534 |
+
font-weight: 600;
|
| 535 |
+
text-transform: uppercase;
|
| 536 |
+
letter-spacing: 0.5px;
|
| 537 |
+
display: inline-flex;
|
| 538 |
+
align-items: center;
|
| 539 |
+
gap: 0.25rem;
|
| 540 |
+
}
|
| 541 |
+
|
| 542 |
+
.risk-badge i {
|
| 543 |
+
font-size: 0.7rem;
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
.risk-badge.critical {
|
| 547 |
+
background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
|
| 548 |
+
color: white;
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
.risk-badge.high {
|
| 552 |
+
background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
|
| 553 |
+
color: white;
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
.risk-badge.medium {
|
| 557 |
+
background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
|
| 558 |
+
color: white;
|
| 559 |
+
}
|
| 560 |
+
|
| 561 |
+
.risk-badge.low {
|
| 562 |
+
background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
|
| 563 |
+
color: white;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
/* Status Badges */
|
| 567 |
+
.status-badge {
|
| 568 |
+
padding: 0.375rem 0.75rem;
|
| 569 |
+
border-radius: 20px;
|
| 570 |
+
font-size: 0.75rem;
|
| 571 |
+
font-weight: 600;
|
| 572 |
+
text-transform: uppercase;
|
| 573 |
+
letter-spacing: 0.5px;
|
| 574 |
+
display: inline-flex;
|
| 575 |
+
align-items: center;
|
| 576 |
+
gap: 0.25rem;
|
| 577 |
+
}
|
| 578 |
+
|
| 579 |
+
.status-badge i {
|
| 580 |
+
font-size: 0.7rem;
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
.status-badge.pending {
|
| 584 |
+
background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
|
| 585 |
+
color: white;
|
| 586 |
+
}
|
| 587 |
+
|
| 588 |
+
.status-badge.confirmed {
|
| 589 |
+
background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
|
| 590 |
+
color: white;
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
.status-badge.completed {
|
| 594 |
+
background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
|
| 595 |
+
color: white;
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
.status-badge.declined {
|
| 599 |
+
background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
|
| 600 |
+
color: white;
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
.status-badge.cancelled {
|
| 604 |
+
background: linear-gradient(135deg, #6b7280 0%, #9ca3af 100%);
|
| 605 |
+
color: white;
|
| 606 |
+
}
|
| 607 |
+
|
| 608 |
+
/* Action Buttons */
|
| 609 |
+
.action-buttons {
|
| 610 |
+
display: flex;
|
| 611 |
+
gap: 0.5rem;
|
| 612 |
+
align-items: center;
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
+
.action-btn {
|
| 616 |
+
width: 32px;
|
| 617 |
+
height: 32px;
|
| 618 |
+
border-radius: 6px;
|
| 619 |
+
border: none;
|
| 620 |
+
display: flex;
|
| 621 |
+
align-items: center;
|
| 622 |
+
justify-content: center;
|
| 623 |
+
transition: all 0.3s ease;
|
| 624 |
+
font-size: 0.8rem;
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
.action-btn:hover {
|
| 628 |
+
transform: translateY(-1px);
|
| 629 |
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
.action-btn.btn-info {
|
| 633 |
+
background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
|
| 634 |
+
color: white;
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
.action-btn.btn-warning {
|
| 638 |
+
background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
|
| 639 |
+
color: white;
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
.action-btn.btn-danger {
|
| 643 |
+
background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
|
| 644 |
+
color: white;
|
| 645 |
+
}
|
| 646 |
+
|
| 647 |
+
.action-btn.btn-success {
|
| 648 |
+
background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
|
| 649 |
+
color: white;
|
| 650 |
+
}
|
| 651 |
+
|
| 652 |
+
/* Responsive Design */
|
| 653 |
+
@media (max-width: 768px) {
|
| 654 |
+
.bookings-filters .row > div {
|
| 655 |
+
margin-bottom: 1rem;
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
.user-details {
|
| 659 |
+
flex-direction: column;
|
| 660 |
+
align-items: flex-start;
|
| 661 |
+
gap: 0.5rem;
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
.user-avatar {
|
| 665 |
+
width: 32px;
|
| 666 |
+
height: 32px;
|
| 667 |
+
font-size: 0.8rem;
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
.action-buttons {
|
| 671 |
+
flex-direction: column;
|
| 672 |
+
gap: 0.25rem;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
.action-btn {
|
| 676 |
+
width: 28px;
|
| 677 |
+
height: 28px;
|
| 678 |
+
font-size: 0.7rem;
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
.info-box-content {
|
| 682 |
+
padding: 0.75rem;
|
| 683 |
+
}
|
| 684 |
+
|
| 685 |
+
.info-box-number {
|
| 686 |
+
font-size: 1.25rem;
|
| 687 |
+
}
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
/* Loading States */
|
| 691 |
+
.bookings-loading {
|
| 692 |
+
text-align: center;
|
| 693 |
+
padding: 2rem;
|
| 694 |
+
color: var(--text-secondary);
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
.bookings-loading i {
|
| 698 |
+
font-size: 2rem;
|
| 699 |
+
margin-bottom: 1rem;
|
| 700 |
+
color: var(--primary);
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
/* Empty State */
|
| 704 |
+
.bookings-empty {
|
| 705 |
+
text-align: center;
|
| 706 |
+
padding: 3rem 1rem;
|
| 707 |
+
color: var(--text-secondary);
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
.bookings-empty i {
|
| 711 |
+
font-size: 3rem;
|
| 712 |
+
margin-bottom: 1rem;
|
| 713 |
+
color: var(--text-muted);
|
| 714 |
+
}
|
| 715 |
+
|
| 716 |
+
.bookings-empty h4 {
|
| 717 |
+
color: var(--text);
|
| 718 |
+
margin-bottom: 0.5rem;
|
| 719 |
+
}
|
| 720 |
+
|
| 721 |
+
.bookings-empty p {
|
| 722 |
+
color: var(--text-secondary);
|
| 723 |
+
margin-bottom: 1.5rem;
|
| 724 |
+
}
|
| 725 |
+
|
| 726 |
+
/* Large Avatar Styling for Modal */
|
| 727 |
+
.user-avatar-large {
|
| 728 |
+
width: 80px;
|
| 729 |
+
height: 80px;
|
| 730 |
+
border-radius: 50%;
|
| 731 |
+
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
|
| 732 |
+
display: flex;
|
| 733 |
+
align-items: center;
|
| 734 |
+
justify-content: center;
|
| 735 |
+
color: white;
|
| 736 |
+
font-weight: 700;
|
| 737 |
+
font-size: 1.5rem;
|
| 738 |
+
flex-shrink: 0;
|
| 739 |
+
box-shadow: var(--shadow);
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
+
.professional-avatar-large {
|
| 743 |
+
width: 80px;
|
| 744 |
+
height: 80px;
|
| 745 |
+
border-radius: 50%;
|
| 746 |
+
background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
|
| 747 |
+
display: flex;
|
| 748 |
+
align-items: center;
|
| 749 |
+
justify-content: center;
|
| 750 |
+
color: white;
|
| 751 |
+
font-weight: 700;
|
| 752 |
+
font-size: 1.5rem;
|
| 753 |
+
flex-shrink: 0;
|
| 754 |
+
box-shadow: var(--shadow);
|
| 755 |
+
}
|
| 756 |
+
|
| 757 |
+
/* Modal Enhancements */
|
| 758 |
+
.modal-xl {
|
| 759 |
+
max-width: 1200px;
|
| 760 |
+
}
|
| 761 |
+
|
| 762 |
+
.modal-content {
|
| 763 |
+
border: none;
|
| 764 |
+
border-radius: 12px;
|
| 765 |
+
box-shadow: var(--shadow-lg);
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
.modal-header {
|
| 769 |
+
border-bottom: 1px solid var(--border-light);
|
| 770 |
+
border-radius: 12px 12px 0 0;
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
.modal-body {
|
| 774 |
+
padding: 2rem;
|
| 775 |
+
max-height: 70vh;
|
| 776 |
+
overflow-y: auto;
|
| 777 |
+
}
|
| 778 |
+
|
| 779 |
+
.modal-footer {
|
| 780 |
+
border-top: 1px solid var(--border-light);
|
| 781 |
+
border-radius: 0 0 12px 12px;
|
| 782 |
+
}
|
| 783 |
+
|
| 784 |
+
/* Booking Details Modal Styling */
|
| 785 |
+
.booking-header {
|
| 786 |
+
border-bottom: 2px solid var(--border-light);
|
| 787 |
+
padding-bottom: 1rem;
|
| 788 |
+
}
|
| 789 |
+
|
| 790 |
+
.booking-header h4 {
|
| 791 |
+
color: var(--text);
|
| 792 |
+
font-weight: 600;
|
| 793 |
+
}
|
| 794 |
+
|
| 795 |
+
.booking-header code {
|
| 796 |
+
background: var(--surface);
|
| 797 |
+
color: var(--primary);
|
| 798 |
+
padding: 0.25rem 0.5rem;
|
| 799 |
+
border-radius: 4px;
|
| 800 |
+
font-size: 0.9rem;
|
| 801 |
+
}
|
| 802 |
+
|
| 803 |
+
/* Card Styling in Modal */
|
| 804 |
+
.modal-body .card {
|
| 805 |
+
border: 1px solid var(--border-light);
|
| 806 |
+
border-radius: 8px;
|
| 807 |
+
box-shadow: var(--shadow);
|
| 808 |
+
}
|
| 809 |
+
|
| 810 |
+
.modal-body .card-header {
|
| 811 |
+
border-bottom: 1px solid var(--border-light);
|
| 812 |
+
font-weight: 600;
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
.modal-body .card-body {
|
| 816 |
+
background: var(--surface);
|
| 817 |
+
}
|
| 818 |
+
|
| 819 |
+
.modal-body .card-body p {
|
| 820 |
+
margin-bottom: 0.75rem;
|
| 821 |
+
color: var(--text);
|
| 822 |
+
}
|
| 823 |
+
|
| 824 |
+
.modal-body .card-body strong {
|
| 825 |
+
color: var(--text);
|
| 826 |
+
font-weight: 600;
|
| 827 |
+
}
|
| 828 |
+
|
| 829 |
+
.modal-body .card-body a {
|
| 830 |
+
text-decoration: none;
|
| 831 |
+
transition: color 0.3s ease;
|
| 832 |
+
}
|
| 833 |
+
|
| 834 |
+
.modal-body .card-body a:hover {
|
| 835 |
+
text-decoration: underline;
|
| 836 |
+
}
|
| 837 |
+
|
| 838 |
+
/* Responsive Modal */
|
| 839 |
+
@media (max-width: 768px) {
|
| 840 |
+
.modal-xl {
|
| 841 |
+
max-width: 95%;
|
| 842 |
+
}
|
| 843 |
+
|
| 844 |
+
.modal-body {
|
| 845 |
+
padding: 1rem;
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
+
.user-avatar-large,
|
| 849 |
+
.professional-avatar-large {
|
| 850 |
+
width: 60px;
|
| 851 |
+
height: 60px;
|
| 852 |
+
font-size: 1.2rem;
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
.booking-header .row {
|
| 856 |
+
flex-direction: column;
|
| 857 |
+
}
|
| 858 |
+
|
| 859 |
+
.booking-header .col-md-4 {
|
| 860 |
+
text-align: left !important;
|
| 861 |
+
margin-top: 1rem;
|
| 862 |
+
}
|
| 863 |
+
}
|
| 864 |
+
|
| 865 |
+
/* Ensure AdminLTE components work with our dark theme */
|
| 866 |
+
.content-wrapper {
|
| 867 |
+
background-color: var(--background) !important;
|
| 868 |
+
color: var(--text) !important;
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
.main-header {
|
| 872 |
+
background-color: var(--surface) !important;
|
| 873 |
+
border-bottom: 1px solid var(--border) !important;
|
| 874 |
+
}
|
| 875 |
+
|
| 876 |
+
.navbar-nav .nav-link {
|
| 877 |
+
color: var(--text) !important;
|
| 878 |
+
}
|
| 879 |
+
|
| 880 |
+
.sidebar {
|
| 881 |
+
background-color: var(--surface) !important;
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
.sidebar .nav-link {
|
| 885 |
+
color: var(--text-secondary) !important;
|
| 886 |
+
}
|
| 887 |
+
|
| 888 |
+
.sidebar .nav-link:hover {
|
| 889 |
+
background-color: var(--card) !important;
|
| 890 |
+
color: var(--text) !important;
|
| 891 |
+
}
|
| 892 |
+
|
| 893 |
+
.card {
|
| 894 |
+
background-color: var(--card) !important;
|
| 895 |
+
border: 1px solid var(--border) !important;
|
| 896 |
+
color: var(--text) !important;
|
| 897 |
+
}
|
| 898 |
+
|
| 899 |
+
.card-header {
|
| 900 |
+
background-color: var(--surface) !important;
|
| 901 |
+
border-bottom: 1px solid var(--border) !important;
|
| 902 |
+
color: var(--text) !important;
|
| 903 |
+
}
|
| 904 |
+
|
| 905 |
+
.btn-primary {
|
| 906 |
+
background-color: var(--primary) !important;
|
| 907 |
+
border-color: var(--primary) !important;
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
.btn-primary:hover {
|
| 911 |
+
background-color: var(--primary-dark) !important;
|
| 912 |
+
border-color: var(--primary-dark) !important;
|
| 913 |
+
}
|
| 914 |
+
|
| 915 |
+
.btn-secondary {
|
| 916 |
+
background-color: var(--surface) !important;
|
| 917 |
+
border-color: var(--border) !important;
|
| 918 |
+
color: var(--text) !important;
|
| 919 |
+
}
|
| 920 |
+
|
| 921 |
+
.btn-secondary:hover {
|
| 922 |
+
background-color: var(--card) !important;
|
| 923 |
+
border-color: var(--border-light) !important;
|
| 924 |
+
}
|
| 925 |
+
|
| 926 |
+
.form-control {
|
| 927 |
+
background-color: var(--card) !important;
|
| 928 |
+
border: 1px solid var(--border) !important;
|
| 929 |
+
color: var(--text) !important;
|
| 930 |
+
}
|
| 931 |
+
|
| 932 |
+
.form-control:focus {
|
| 933 |
+
background-color: var(--card) !important;
|
| 934 |
+
border-color: var(--primary) !important;
|
| 935 |
+
color: var(--text) !important;
|
| 936 |
+
box-shadow: 0 0 0 0.2rem rgba(124, 58, 237, 0.25) !important;
|
| 937 |
+
}
|
| 938 |
+
|
| 939 |
+
.table {
|
| 940 |
+
color: var(--text) !important;
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
.table th {
|
| 944 |
+
background-color: var(--surface) !important;
|
| 945 |
+
border-color: var(--border) !important;
|
| 946 |
+
color: var(--text) !important;
|
| 947 |
+
}
|
| 948 |
+
|
| 949 |
+
.table td {
|
| 950 |
+
border-color: var(--border) !important;
|
| 951 |
+
}
|
| 952 |
+
|
| 953 |
+
.modal-content {
|
| 954 |
+
background-color: var(--card) !important;
|
| 955 |
+
border: 1px solid var(--border) !important;
|
| 956 |
+
color: var(--text) !important;
|
| 957 |
+
}
|
| 958 |
+
|
| 959 |
+
.modal-header {
|
| 960 |
+
border-bottom: 1px solid var(--border) !important;
|
| 961 |
+
}
|
| 962 |
+
|
| 963 |
+
.modal-footer {
|
| 964 |
+
border-top: 1px solid var(--border) !important;
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
* {
|
| 968 |
+
box-sizing: border-box;
|
| 969 |
+
margin: 0;
|
| 970 |
+
padding: 0;
|
| 971 |
+
}
|
| 972 |
+
|
| 973 |
+
body {
|
| 974 |
+
font-family: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
| 975 |
+
background: var(--background);
|
| 976 |
+
color: var(--text);
|
| 977 |
+
height: 100vh;
|
| 978 |
+
overflow: hidden;
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
.admin-container {
|
| 982 |
+
display: flex;
|
| 983 |
+
height: 100vh;
|
| 984 |
+
background: var(--background);
|
| 985 |
+
}
|
| 986 |
+
|
| 987 |
+
/* Navigation */
|
| 988 |
+
.admin-nav {
|
| 989 |
+
width: 280px;
|
| 990 |
+
background: linear-gradient(180deg, #111827 0%, #0f172a 60%);
|
| 991 |
+
border-right: 1px solid var(--border);
|
| 992 |
+
display: flex;
|
| 993 |
+
flex-direction: column;
|
| 994 |
+
flex-shrink: 0;
|
| 995 |
+
}
|
| 996 |
+
|
| 997 |
+
.nav-header {
|
| 998 |
+
padding: 24px 20px;
|
| 999 |
+
border-bottom: 1px solid var(--border);
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
.nav-header h1 {
|
| 1003 |
+
font-size: 24px;
|
| 1004 |
+
font-weight: 700;
|
| 1005 |
+
color: var(--primary-light);
|
| 1006 |
+
margin-bottom: 4px;
|
| 1007 |
+
}
|
| 1008 |
+
|
| 1009 |
+
.nav-header p {
|
| 1010 |
+
font-size: 14px;
|
| 1011 |
+
color: var(--text-muted);
|
| 1012 |
+
}
|
| 1013 |
+
|
| 1014 |
+
.nav-menu {
|
| 1015 |
+
flex: 1;
|
| 1016 |
+
list-style: none;
|
| 1017 |
+
padding: 20px 0;
|
| 1018 |
+
}
|
| 1019 |
+
|
| 1020 |
+
.nav-link {
|
| 1021 |
+
display: block;
|
| 1022 |
+
padding: 12px 20px;
|
| 1023 |
+
color: var(--text-secondary);
|
| 1024 |
+
text-decoration: none;
|
| 1025 |
+
transition: all 0.2s ease;
|
| 1026 |
+
border-left: 3px solid transparent;
|
| 1027 |
+
}
|
| 1028 |
+
|
| 1029 |
+
.nav-link:hover {
|
| 1030 |
+
background: rgba(124, 58, 237, 0.1);
|
| 1031 |
+
color: var(--primary-light);
|
| 1032 |
+
}
|
| 1033 |
+
|
| 1034 |
+
.nav-link.active {
|
| 1035 |
+
background: rgba(124, 58, 237, 0.15);
|
| 1036 |
+
color: var(--primary-light);
|
| 1037 |
+
border-left-color: var(--primary);
|
| 1038 |
+
}
|
| 1039 |
+
|
| 1040 |
+
.nav-footer {
|
| 1041 |
+
padding: 20px;
|
| 1042 |
+
border-top: 1px solid var(--border);
|
| 1043 |
+
}
|
| 1044 |
+
|
| 1045 |
+
.logout-btn {
|
| 1046 |
+
width: 100%;
|
| 1047 |
+
padding: 10px;
|
| 1048 |
+
background: transparent;
|
| 1049 |
+
border: 1px solid var(--border);
|
| 1050 |
+
color: var(--text-secondary);
|
| 1051 |
+
border-radius: 8px;
|
| 1052 |
+
cursor: pointer;
|
| 1053 |
+
transition: all 0.2s ease;
|
| 1054 |
+
}
|
| 1055 |
+
|
| 1056 |
+
.logout-btn:hover {
|
| 1057 |
+
background: var(--danger);
|
| 1058 |
+
border-color: var(--danger);
|
| 1059 |
+
color: white;
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
+
/* Main Content */
|
| 1063 |
+
.admin-content {
|
| 1064 |
+
flex: 1;
|
| 1065 |
+
overflow-y: auto;
|
| 1066 |
+
padding: 24px;
|
| 1067 |
+
}
|
| 1068 |
+
|
| 1069 |
+
/* Toolbar */
|
| 1070 |
+
.admin-toolbar { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:16px; }
|
| 1071 |
+
.admin-toolbar input{ flex:1; padding:10px 12px; background: var(--surface); border:1px solid var(--border); border-radius:8px; color: var(--text); }
|
| 1072 |
+
.admin-toolbar select{ padding:10px 12px; background: var(--surface); border:1px solid var(--border); border-radius:8px; color: var(--text); }
|
| 1073 |
+
|
| 1074 |
+
/* KPI cards */
|
| 1075 |
+
.kpi-grid{ display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap:16px; margin-bottom:24px; }
|
| 1076 |
+
.kpi-card{ background: var(--surface); border:1px solid var(--border); border-radius:12px; padding:16px; box-shadow: var(--shadow); }
|
| 1077 |
+
.kpi-label{ color: var(--text-muted); font-size:12px; margin-bottom:8px; }
|
| 1078 |
+
.kpi-value{ font-size:28px; font-weight:700; color: var(--text); }
|
| 1079 |
+
.kpi-trend{ font-size:12px; color: var(--text-secondary); margin-top:6px; }
|
| 1080 |
+
|
| 1081 |
+
/* Pager & FAB & Toast */
|
| 1082 |
+
.pager { display:flex; align-items:center; justify-content:flex-end; gap:10px; padding:12px 0; color: var(--text-secondary); }
|
| 1083 |
+
.pager-btn { padding:6px 10px; background: var(--surface); color: var(--text); border:1px solid var(--border); border-radius:6px; cursor:pointer; }
|
| 1084 |
+
.pager-btn:disabled { opacity:.5; cursor:not-allowed; }
|
| 1085 |
+
.pager-info { font-size:12px; }
|
| 1086 |
+
|
| 1087 |
+
.fab { position:fixed; right:20px; bottom:20px; background: var(--primary); color:#fff; border:none; border-radius:9999px; padding:12px 16px; box-shadow: var(--shadow-lg); cursor:pointer; font-weight:600; }
|
| 1088 |
+
.fab:hover { background: var(--primary-dark); }
|
| 1089 |
+
|
| 1090 |
+
.toast { background: rgba(30,41,59,.95); color:#e5e7eb; padding:10px 14px; border:1px solid var(--border); border-radius:8px; box-shadow: var(--shadow); transform: translateY(0); transition: opacity .2s ease, transform .2s ease; }
|
| 1091 |
+
.toast-success { border-color: rgba(16,185,129,.4); color:#10b981; }
|
| 1092 |
+
.toast-error { border-color: rgba(239,68,68,.4); color:#ef4444; }
|
| 1093 |
+
|
| 1094 |
+
.admin-section {
|
| 1095 |
+
display: none;
|
| 1096 |
+
}
|
| 1097 |
+
|
| 1098 |
+
.admin-section.active {
|
| 1099 |
+
display: block;
|
| 1100 |
+
}
|
| 1101 |
+
|
| 1102 |
+
.section-header {
|
| 1103 |
+
display: flex;
|
| 1104 |
+
justify-content: space-between;
|
| 1105 |
+
align-items: center;
|
| 1106 |
+
margin-bottom: 24px;
|
| 1107 |
+
padding-bottom: 16px;
|
| 1108 |
+
border-bottom: 1px solid var(--border);
|
| 1109 |
+
}
|
| 1110 |
+
|
| 1111 |
+
.section-header h2 {
|
| 1112 |
+
font-size: 28px;
|
| 1113 |
+
font-weight: 700;
|
| 1114 |
+
color: var(--text);
|
| 1115 |
+
}
|
| 1116 |
+
|
| 1117 |
+
.section-controls {
|
| 1118 |
+
display: flex;
|
| 1119 |
+
gap: 12px;
|
| 1120 |
+
align-items: center;
|
| 1121 |
+
}
|
| 1122 |
+
|
| 1123 |
+
.search-box input {
|
| 1124 |
+
padding: 8px 12px;
|
| 1125 |
+
background: var(--surface);
|
| 1126 |
+
border: 1px solid var(--border);
|
| 1127 |
+
border-radius: 6px;
|
| 1128 |
+
color: var(--text);
|
| 1129 |
+
width: 200px;
|
| 1130 |
+
}
|
| 1131 |
+
|
| 1132 |
+
.search-box input::placeholder {
|
| 1133 |
+
color: var(--text-muted);
|
| 1134 |
+
}
|
| 1135 |
+
|
| 1136 |
+
/* Buttons */
|
| 1137 |
+
.btn-primary {
|
| 1138 |
+
background: var(--primary);
|
| 1139 |
+
color: white;
|
| 1140 |
+
border: none;
|
| 1141 |
+
padding: 10px 16px;
|
| 1142 |
+
border-radius: 6px;
|
| 1143 |
+
cursor: pointer;
|
| 1144 |
+
font-weight: 500;
|
| 1145 |
+
transition: all 0.2s ease;
|
| 1146 |
+
}
|
| 1147 |
+
|
| 1148 |
+
.btn-primary:hover {
|
| 1149 |
+
background: var(--primary-dark);
|
| 1150 |
+
}
|
| 1151 |
+
|
| 1152 |
+
.btn-secondary {
|
| 1153 |
+
background: var(--surface);
|
| 1154 |
+
color: var(--text);
|
| 1155 |
+
border: 1px solid var(--border);
|
| 1156 |
+
padding: 10px 16px;
|
| 1157 |
+
border-radius: 6px;
|
| 1158 |
+
cursor: pointer;
|
| 1159 |
+
font-weight: 500;
|
| 1160 |
+
transition: all 0.2s ease;
|
| 1161 |
+
}
|
| 1162 |
+
|
| 1163 |
+
.btn-secondary:hover {
|
| 1164 |
+
background: var(--card);
|
| 1165 |
+
}
|
| 1166 |
+
|
| 1167 |
+
/* Professional Grid */
|
| 1168 |
+
.professionals-grid {
|
| 1169 |
+
display: grid;
|
| 1170 |
+
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
| 1171 |
+
gap: 20px;
|
| 1172 |
+
}
|
| 1173 |
+
|
| 1174 |
+
.professional-card {
|
| 1175 |
+
background: var(--surface);
|
| 1176 |
+
border: 1px solid var(--border);
|
| 1177 |
+
border-radius: 12px;
|
| 1178 |
+
padding: 20px;
|
| 1179 |
+
transition: all 0.2s ease;
|
| 1180 |
+
}
|
| 1181 |
+
|
| 1182 |
+
.professional-card:hover {
|
| 1183 |
+
border-color: var(--primary);
|
| 1184 |
+
box-shadow: var(--shadow-lg);
|
| 1185 |
+
}
|
| 1186 |
+
|
| 1187 |
+
.professional-header {
|
| 1188 |
+
display: flex;
|
| 1189 |
+
justify-content: space-between;
|
| 1190 |
+
align-items: flex-start;
|
| 1191 |
+
margin-bottom: 16px;
|
| 1192 |
+
}
|
| 1193 |
+
|
| 1194 |
+
.professional-name {
|
| 1195 |
+
font-size: 18px;
|
| 1196 |
+
font-weight: 600;
|
| 1197 |
+
color: var(--text);
|
| 1198 |
+
margin-bottom: 4px;
|
| 1199 |
+
}
|
| 1200 |
+
|
| 1201 |
+
.professional-specialization {
|
| 1202 |
+
font-size: 14px;
|
| 1203 |
+
color: var(--primary-light);
|
| 1204 |
+
font-weight: 500;
|
| 1205 |
+
}
|
| 1206 |
+
|
| 1207 |
+
.professional-status {
|
| 1208 |
+
padding: 4px 8px;
|
| 1209 |
+
border-radius: 4px;
|
| 1210 |
+
font-size: 12px;
|
| 1211 |
+
font-weight: 500;
|
| 1212 |
+
}
|
| 1213 |
+
|
| 1214 |
+
.status-active {
|
| 1215 |
+
background: rgba(16, 185, 129, 0.2);
|
| 1216 |
+
color: var(--success);
|
| 1217 |
+
}
|
| 1218 |
+
|
| 1219 |
+
.status-inactive {
|
| 1220 |
+
background: rgba(239, 68, 68, 0.2);
|
| 1221 |
+
color: var(--danger);
|
| 1222 |
+
}
|
| 1223 |
+
|
| 1224 |
+
.professional-details {
|
| 1225 |
+
margin-bottom: 16px;
|
| 1226 |
+
}
|
| 1227 |
+
|
| 1228 |
+
.professional-detail {
|
| 1229 |
+
display: flex;
|
| 1230 |
+
justify-content: space-between;
|
| 1231 |
+
margin-bottom: 8px;
|
| 1232 |
+
font-size: 14px;
|
| 1233 |
+
}
|
| 1234 |
+
|
| 1235 |
+
.professional-detail span:first-child {
|
| 1236 |
+
color: var(--text-muted);
|
| 1237 |
+
}
|
| 1238 |
+
|
| 1239 |
+
.professional-detail span:last-child {
|
| 1240 |
+
color: var(--text-secondary);
|
| 1241 |
+
}
|
| 1242 |
+
|
| 1243 |
+
.professional-actions {
|
| 1244 |
+
display: flex;
|
| 1245 |
+
gap: 8px;
|
| 1246 |
+
}
|
| 1247 |
+
|
| 1248 |
+
.btn-small {
|
| 1249 |
+
padding: 6px 12px;
|
| 1250 |
+
font-size: 12px;
|
| 1251 |
+
border-radius: 4px;
|
| 1252 |
+
}
|
| 1253 |
+
|
| 1254 |
+
.btn-danger {
|
| 1255 |
+
background: var(--danger);
|
| 1256 |
+
color: white;
|
| 1257 |
+
border: 1px solid var(--danger);
|
| 1258 |
+
}
|
| 1259 |
+
|
| 1260 |
+
.btn-danger:hover {
|
| 1261 |
+
background: #dc2626;
|
| 1262 |
+
border-color: #dc2626;
|
| 1263 |
+
}
|
| 1264 |
+
|
| 1265 |
+
/* Bookings Table */
|
| 1266 |
+
.bookings-table {
|
| 1267 |
+
background: var(--surface);
|
| 1268 |
+
border: 1px solid var(--border);
|
| 1269 |
+
border-radius: 12px;
|
| 1270 |
+
overflow: hidden;
|
| 1271 |
+
}
|
| 1272 |
+
|
| 1273 |
+
.bookings-table table {
|
| 1274 |
+
width: 100%;
|
| 1275 |
+
border-collapse: collapse;
|
| 1276 |
+
}
|
| 1277 |
+
|
| 1278 |
+
.bookings-table th {
|
| 1279 |
+
background: var(--card);
|
| 1280 |
+
padding: 16px;
|
| 1281 |
+
text-align: left;
|
| 1282 |
+
font-weight: 600;
|
| 1283 |
+
color: var(--text);
|
| 1284 |
+
border-bottom: 1px solid var(--border);
|
| 1285 |
+
}
|
| 1286 |
+
|
| 1287 |
+
.bookings-table td {
|
| 1288 |
+
padding: 16px;
|
| 1289 |
+
border-bottom: 1px solid var(--border);
|
| 1290 |
+
color: var(--text-secondary);
|
| 1291 |
+
}
|
| 1292 |
+
|
| 1293 |
+
.bookings-table tr:hover {
|
| 1294 |
+
background: rgba(124, 58, 237, 0.05);
|
| 1295 |
+
}
|
| 1296 |
+
|
| 1297 |
+
.risk-badge {
|
| 1298 |
+
padding: 4px 8px;
|
| 1299 |
+
border-radius: 4px;
|
| 1300 |
+
font-size: 12px;
|
| 1301 |
+
font-weight: 500;
|
| 1302 |
+
}
|
| 1303 |
+
|
| 1304 |
+
.risk-critical {
|
| 1305 |
+
background: rgba(220, 38, 38, 0.2);
|
| 1306 |
+
color: var(--critical);
|
| 1307 |
+
}
|
| 1308 |
+
|
| 1309 |
+
.risk-high {
|
| 1310 |
+
background: rgba(234, 88, 12, 0.2);
|
| 1311 |
+
color: var(--high);
|
| 1312 |
+
}
|
| 1313 |
+
|
| 1314 |
+
.risk-medium {
|
| 1315 |
+
background: rgba(217, 119, 6, 0.2);
|
| 1316 |
+
color: var(--medium);
|
| 1317 |
+
}
|
| 1318 |
+
|
| 1319 |
+
.risk-low {
|
| 1320 |
+
background: rgba(5, 150, 105, 0.2);
|
| 1321 |
+
color: var(--low);
|
| 1322 |
+
}
|
| 1323 |
+
|
| 1324 |
+
.status-badge {
|
| 1325 |
+
padding: 4px 8px;
|
| 1326 |
+
border-radius: 4px;
|
| 1327 |
+
font-size: 12px;
|
| 1328 |
+
font-weight: 500;
|
| 1329 |
+
}
|
| 1330 |
+
|
| 1331 |
+
.status-pending {
|
| 1332 |
+
background: rgba(245, 158, 11, 0.2);
|
| 1333 |
+
color: var(--warning);
|
| 1334 |
+
}
|
| 1335 |
+
|
| 1336 |
+
.status-confirmed {
|
| 1337 |
+
background: rgba(16, 185, 129, 0.2);
|
| 1338 |
+
color: var(--success);
|
| 1339 |
+
}
|
| 1340 |
+
|
| 1341 |
+
.status-completed {
|
| 1342 |
+
background: rgba(59, 130, 246, 0.2);
|
| 1343 |
+
color: #3b82f6;
|
| 1344 |
+
}
|
| 1345 |
+
|
| 1346 |
+
.status-declined {
|
| 1347 |
+
background: rgba(239, 68, 68, 0.2);
|
| 1348 |
+
color: var(--danger);
|
| 1349 |
+
}
|
| 1350 |
+
|
| 1351 |
+
/* Risk Dashboard */
|
| 1352 |
+
.risk-dashboard {
|
| 1353 |
+
display: grid;
|
| 1354 |
+
grid-template-columns: 1fr 1fr;
|
| 1355 |
+
gap: 24px;
|
| 1356 |
+
}
|
| 1357 |
+
|
| 1358 |
+
.risk-stats {
|
| 1359 |
+
display: grid;
|
| 1360 |
+
grid-template-columns: repeat(2, 1fr);
|
| 1361 |
+
gap: 16px;
|
| 1362 |
+
}
|
| 1363 |
+
|
| 1364 |
+
.stat-card {
|
| 1365 |
+
background: var(--surface);
|
| 1366 |
+
border: 1px solid var(--border);
|
| 1367 |
+
border-radius: 12px;
|
| 1368 |
+
padding: 24px;
|
| 1369 |
+
text-align: center;
|
| 1370 |
+
transition: all 0.2s ease;
|
| 1371 |
+
}
|
| 1372 |
+
|
| 1373 |
+
.stat-card:hover {
|
| 1374 |
+
transform: translateY(-2px);
|
| 1375 |
+
box-shadow: var(--shadow-lg);
|
| 1376 |
+
}
|
| 1377 |
+
|
| 1378 |
+
.stat-card h3 {
|
| 1379 |
+
font-size: 14px;
|
| 1380 |
+
color: var(--text-muted);
|
| 1381 |
+
margin-bottom: 8px;
|
| 1382 |
+
font-weight: 500;
|
| 1383 |
+
}
|
| 1384 |
+
|
| 1385 |
+
.stat-number {
|
| 1386 |
+
font-size: 32px;
|
| 1387 |
+
font-weight: 700;
|
| 1388 |
+
color: var(--text);
|
| 1389 |
+
}
|
| 1390 |
+
|
| 1391 |
+
.stat-card.critical {
|
| 1392 |
+
border-color: var(--critical);
|
| 1393 |
+
}
|
| 1394 |
+
|
| 1395 |
+
.stat-card.critical .stat-number {
|
| 1396 |
+
color: var(--critical);
|
| 1397 |
+
}
|
| 1398 |
+
|
| 1399 |
+
.stat-card.high {
|
| 1400 |
+
border-color: var(--high);
|
| 1401 |
+
}
|
| 1402 |
+
|
| 1403 |
+
.stat-card.high .stat-number {
|
| 1404 |
+
color: var(--high);
|
| 1405 |
+
}
|
| 1406 |
+
|
| 1407 |
+
.stat-card.medium {
|
| 1408 |
+
border-color: var(--medium);
|
| 1409 |
+
}
|
| 1410 |
+
|
| 1411 |
+
.stat-card.medium .stat-number {
|
| 1412 |
+
color: var(--medium);
|
| 1413 |
+
}
|
| 1414 |
+
|
| 1415 |
+
.stat-card.low {
|
| 1416 |
+
border-color: var(--low);
|
| 1417 |
+
}
|
| 1418 |
+
|
| 1419 |
+
.stat-card.low .stat-number {
|
| 1420 |
+
color: var(--low);
|
| 1421 |
+
}
|
| 1422 |
+
|
| 1423 |
+
.recent-assessments {
|
| 1424 |
+
background: var(--surface);
|
| 1425 |
+
border: 1px solid var(--border);
|
| 1426 |
+
border-radius: 12px;
|
| 1427 |
+
padding: 20px;
|
| 1428 |
+
}
|
| 1429 |
+
|
| 1430 |
+
.recent-assessments h3 {
|
| 1431 |
+
font-size: 18px;
|
| 1432 |
+
font-weight: 600;
|
| 1433 |
+
color: var(--text);
|
| 1434 |
+
margin-bottom: 16px;
|
| 1435 |
+
}
|
| 1436 |
+
|
| 1437 |
+
.assessment-item {
|
| 1438 |
+
display: flex;
|
| 1439 |
+
justify-content: space-between;
|
| 1440 |
+
align-items: center;
|
| 1441 |
+
padding: 12px 0;
|
| 1442 |
+
border-bottom: 1px solid var(--border);
|
| 1443 |
+
}
|
| 1444 |
+
|
| 1445 |
+
.assessment-item:last-child {
|
| 1446 |
+
border-bottom: none;
|
| 1447 |
+
}
|
| 1448 |
+
|
| 1449 |
+
.assessment-info {
|
| 1450 |
+
flex: 1;
|
| 1451 |
+
}
|
| 1452 |
+
|
| 1453 |
+
.assessment-query {
|
| 1454 |
+
font-size: 14px;
|
| 1455 |
+
color: var(--text-secondary);
|
| 1456 |
+
margin-bottom: 4px;
|
| 1457 |
+
}
|
| 1458 |
+
|
| 1459 |
+
.assessment-time {
|
| 1460 |
+
font-size: 12px;
|
| 1461 |
+
color: var(--text-muted);
|
| 1462 |
+
}
|
| 1463 |
+
|
| 1464 |
+
/* Analytics */
|
| 1465 |
+
.analytics-grid {
|
| 1466 |
+
display: grid;
|
| 1467 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 1468 |
+
gap: 20px;
|
| 1469 |
+
}
|
| 1470 |
+
|
| 1471 |
+
.analytics-card {
|
| 1472 |
+
background: var(--surface);
|
| 1473 |
+
border: 1px solid var(--border);
|
| 1474 |
+
border-radius: 12px;
|
| 1475 |
+
padding: 24px;
|
| 1476 |
+
text-align: center;
|
| 1477 |
+
}
|
| 1478 |
+
|
| 1479 |
+
.analytics-card h3 {
|
| 1480 |
+
font-size: 14px;
|
| 1481 |
+
color: var(--text-muted);
|
| 1482 |
+
margin-bottom: 12px;
|
| 1483 |
+
font-weight: 500;
|
| 1484 |
+
}
|
| 1485 |
+
|
| 1486 |
+
.analytics-number {
|
| 1487 |
+
font-size: 28px;
|
| 1488 |
+
font-weight: 700;
|
| 1489 |
+
color: var(--primary-light);
|
| 1490 |
+
}
|
| 1491 |
+
|
| 1492 |
+
/* Modal */
|
| 1493 |
+
.modal {
|
| 1494 |
+
display: none;
|
| 1495 |
+
position: fixed;
|
| 1496 |
+
z-index: 1000;
|
| 1497 |
+
left: 0;
|
| 1498 |
+
top: 0;
|
| 1499 |
+
width: 100%;
|
| 1500 |
+
height: 100%;
|
| 1501 |
+
background-color: rgba(0, 0, 0, 0.5);
|
| 1502 |
+
backdrop-filter: blur(4px);
|
| 1503 |
+
}
|
| 1504 |
+
|
| 1505 |
+
.modal-content {
|
| 1506 |
+
background: var(--surface);
|
| 1507 |
+
margin: 5% auto;
|
| 1508 |
+
padding: 24px;
|
| 1509 |
+
border: 1px solid var(--border);
|
| 1510 |
+
border-radius: 12px;
|
| 1511 |
+
width: 90%;
|
| 1512 |
+
max-width: 600px;
|
| 1513 |
+
max-height: 80vh;
|
| 1514 |
+
overflow-y: auto;
|
| 1515 |
+
}
|
| 1516 |
+
|
| 1517 |
+
.modal-content.large {
|
| 1518 |
+
max-width: 800px;
|
| 1519 |
+
}
|
| 1520 |
+
|
| 1521 |
+
.close {
|
| 1522 |
+
color: var(--text-muted);
|
| 1523 |
+
float: right;
|
| 1524 |
+
font-size: 28px;
|
| 1525 |
+
font-weight: bold;
|
| 1526 |
+
cursor: pointer;
|
| 1527 |
+
transition: color 0.2s ease;
|
| 1528 |
+
}
|
| 1529 |
+
|
| 1530 |
+
.close:hover {
|
| 1531 |
+
color: var(--text);
|
| 1532 |
+
}
|
| 1533 |
+
|
| 1534 |
+
/* Form Styles */
|
| 1535 |
+
.form-row {
|
| 1536 |
+
display: grid;
|
| 1537 |
+
grid-template-columns: 1fr 1fr;
|
| 1538 |
+
gap: 16px;
|
| 1539 |
+
margin-bottom: 16px;
|
| 1540 |
+
}
|
| 1541 |
+
|
| 1542 |
+
.form-group {
|
| 1543 |
+
margin-bottom: 16px;
|
| 1544 |
+
}
|
| 1545 |
+
|
| 1546 |
+
.form-group label {
|
| 1547 |
+
display: block;
|
| 1548 |
+
margin-bottom: 6px;
|
| 1549 |
+
font-weight: 500;
|
| 1550 |
+
color: var(--text);
|
| 1551 |
+
}
|
| 1552 |
+
|
| 1553 |
+
.form-group input,
|
| 1554 |
+
.form-group select,
|
| 1555 |
+
.form-group textarea {
|
| 1556 |
+
width: 100%;
|
| 1557 |
+
padding: 10px 12px;
|
| 1558 |
+
background: var(--background);
|
| 1559 |
+
border: 1px solid var(--border);
|
| 1560 |
+
border-radius: 6px;
|
| 1561 |
+
color: var(--text);
|
| 1562 |
+
font-size: 14px;
|
| 1563 |
+
}
|
| 1564 |
+
|
| 1565 |
+
.form-group input:focus,
|
| 1566 |
+
.form-group select:focus,
|
| 1567 |
+
.form-group textarea:focus {
|
| 1568 |
+
outline: none;
|
| 1569 |
+
border-color: var(--primary);
|
| 1570 |
+
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1);
|
| 1571 |
+
}
|
| 1572 |
+
|
| 1573 |
+
.checkbox-group {
|
| 1574 |
+
display: grid;
|
| 1575 |
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
| 1576 |
+
gap: 8px;
|
| 1577 |
+
}
|
| 1578 |
+
|
| 1579 |
+
.checkbox-group label {
|
| 1580 |
+
display: flex;
|
| 1581 |
+
align-items: center;
|
| 1582 |
+
gap: 8px;
|
| 1583 |
+
font-weight: normal;
|
| 1584 |
+
margin-bottom: 0;
|
| 1585 |
+
}
|
| 1586 |
+
|
| 1587 |
+
.checkbox-group input[type="checkbox"] {
|
| 1588 |
+
width: auto;
|
| 1589 |
+
margin: 0;
|
| 1590 |
+
}
|
| 1591 |
+
|
| 1592 |
+
.form-actions {
|
| 1593 |
+
display: flex;
|
| 1594 |
+
gap: 12px;
|
| 1595 |
+
justify-content: flex-end;
|
| 1596 |
+
margin-top: 24px;
|
| 1597 |
+
padding-top: 20px;
|
| 1598 |
+
border-top: 1px solid var(--border);
|
| 1599 |
+
}
|
| 1600 |
+
|
| 1601 |
+
/* Responsive */
|
| 1602 |
+
@media (max-width: 768px) {
|
| 1603 |
+
.admin-nav {
|
| 1604 |
+
width: 240px;
|
| 1605 |
+
}
|
| 1606 |
+
|
| 1607 |
+
.admin-content {
|
| 1608 |
+
padding: 16px;
|
| 1609 |
+
}
|
| 1610 |
+
|
| 1611 |
+
.section-header {
|
| 1612 |
+
flex-direction: column;
|
| 1613 |
+
align-items: flex-start;
|
| 1614 |
+
gap: 16px;
|
| 1615 |
+
}
|
| 1616 |
+
|
| 1617 |
+
.section-controls {
|
| 1618 |
+
width: 100%;
|
| 1619 |
+
justify-content: space-between;
|
| 1620 |
+
}
|
| 1621 |
+
|
| 1622 |
+
.form-row {
|
| 1623 |
+
grid-template-columns: 1fr;
|
| 1624 |
+
}
|
| 1625 |
+
|
| 1626 |
+
.risk-dashboard {
|
| 1627 |
+
grid-template-columns: 1fr;
|
| 1628 |
+
}
|
| 1629 |
+
|
| 1630 |
+
.risk-stats {
|
| 1631 |
+
grid-template-columns: 1fr;
|
| 1632 |
+
}
|
| 1633 |
+
|
| 1634 |
+
.modal-content {
|
| 1635 |
+
margin: 10% auto;
|
| 1636 |
+
width: 95%;
|
| 1637 |
+
}
|
| 1638 |
+
}
|
| 1639 |
+
|
| 1640 |
+
/* RAG Status Styles */
|
| 1641 |
+
.rag-status-grid {
|
| 1642 |
+
display: grid;
|
| 1643 |
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
| 1644 |
+
gap: 20px;
|
| 1645 |
+
margin-bottom: 30px;
|
| 1646 |
+
}
|
| 1647 |
+
|
| 1648 |
+
.rag-card {
|
| 1649 |
+
background: var(--card);
|
| 1650 |
+
border-radius: 12px;
|
| 1651 |
+
padding: 20px;
|
| 1652 |
+
border: 1px solid var(--border);
|
| 1653 |
+
}
|
| 1654 |
+
|
| 1655 |
+
.rag-card h3 {
|
| 1656 |
+
color: var(--text);
|
| 1657 |
+
font-size: 18px;
|
| 1658 |
+
font-weight: 600;
|
| 1659 |
+
margin-bottom: 16px;
|
| 1660 |
+
border-bottom: 1px solid var(--border);
|
| 1661 |
+
padding-bottom: 8px;
|
| 1662 |
+
}
|
| 1663 |
+
|
| 1664 |
+
.rag-info {
|
| 1665 |
+
display: flex;
|
| 1666 |
+
justify-content: space-between;
|
| 1667 |
+
align-items: center;
|
| 1668 |
+
margin-bottom: 12px;
|
| 1669 |
+
}
|
| 1670 |
+
|
| 1671 |
+
.rag-label {
|
| 1672 |
+
color: var(--text-secondary);
|
| 1673 |
+
font-size: 14px;
|
| 1674 |
+
}
|
| 1675 |
+
|
| 1676 |
+
.rag-value {
|
| 1677 |
+
color: var(--text);
|
| 1678 |
+
font-weight: 500;
|
| 1679 |
+
font-size: 14px;
|
| 1680 |
+
}
|
| 1681 |
+
|
| 1682 |
+
.rag-status-indicator {
|
| 1683 |
+
padding: 4px 8px;
|
| 1684 |
+
border-radius: 6px;
|
| 1685 |
+
font-size: 12px;
|
| 1686 |
+
font-weight: 500;
|
| 1687 |
+
}
|
| 1688 |
+
|
| 1689 |
+
.rag-status-indicator.active {
|
| 1690 |
+
background: var(--success);
|
| 1691 |
+
color: white;
|
| 1692 |
+
}
|
| 1693 |
+
|
| 1694 |
+
.rag-status-indicator.inactive {
|
| 1695 |
+
background: var(--danger);
|
| 1696 |
+
color: white;
|
| 1697 |
+
}
|
| 1698 |
+
|
| 1699 |
+
.rag-data-sources {
|
| 1700 |
+
background: var(--card);
|
| 1701 |
+
border-radius: 12px;
|
| 1702 |
+
padding: 20px;
|
| 1703 |
+
border: 1px solid var(--border);
|
| 1704 |
+
}
|
| 1705 |
+
|
| 1706 |
+
.rag-data-sources h3 {
|
| 1707 |
+
color: var(--text);
|
| 1708 |
+
font-size: 18px;
|
| 1709 |
+
font-weight: 600;
|
| 1710 |
+
margin-bottom: 16px;
|
| 1711 |
+
}
|
| 1712 |
+
|
| 1713 |
+
.data-sources-list {
|
| 1714 |
+
display: grid;
|
| 1715 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 1716 |
+
gap: 12px;
|
| 1717 |
+
}
|
| 1718 |
+
|
| 1719 |
+
.data-source-item {
|
| 1720 |
+
display: flex;
|
| 1721 |
+
justify-content: space-between;
|
| 1722 |
+
align-items: center;
|
| 1723 |
+
padding: 12px;
|
| 1724 |
+
background: var(--surface);
|
| 1725 |
+
border-radius: 8px;
|
| 1726 |
+
border: 1px solid var(--border-light);
|
| 1727 |
+
}
|
| 1728 |
+
|
| 1729 |
+
.source-name {
|
| 1730 |
+
color: var(--text);
|
| 1731 |
+
font-size: 14px;
|
| 1732 |
+
font-weight: 500;
|
| 1733 |
+
}
|
| 1734 |
+
|
| 1735 |
+
.source-status {
|
| 1736 |
+
padding: 4px 8px;
|
| 1737 |
+
border-radius: 6px;
|
| 1738 |
+
font-size: 12px;
|
| 1739 |
+
font-weight: 500;
|
| 1740 |
+
}
|
| 1741 |
+
|
| 1742 |
+
.source-status.active {
|
| 1743 |
+
background: var(--success);
|
| 1744 |
+
color: white;
|
| 1745 |
+
}
|
| 1746 |
+
|
| 1747 |
+
.source-status.inactive {
|
| 1748 |
+
background: var(--danger);
|
| 1749 |
+
color: white;
|
| 1750 |
+
}
|
| 1751 |
+
|
| 1752 |
+
/* AdminLTE 4 Additional Enhancements */
|
| 1753 |
+
/* Toast Notifications */
|
| 1754 |
+
.toast {
|
| 1755 |
+
background-color: var(--card) !important;
|
| 1756 |
+
border: 1px solid var(--border) !important;
|
| 1757 |
+
color: var(--text) !important;
|
| 1758 |
+
}
|
| 1759 |
+
|
| 1760 |
+
.toast-header {
|
| 1761 |
+
background-color: var(--surface) !important;
|
| 1762 |
+
border-bottom: 1px solid var(--border) !important;
|
| 1763 |
+
color: var(--text) !important;
|
| 1764 |
+
}
|
| 1765 |
+
|
| 1766 |
+
/* DataTables Integration */
|
| 1767 |
+
.dataTables_wrapper {
|
| 1768 |
+
color: var(--text) !important;
|
| 1769 |
+
}
|
| 1770 |
+
|
| 1771 |
+
.dataTables_wrapper .dataTables_length,
|
| 1772 |
+
.dataTables_wrapper .dataTables_filter,
|
| 1773 |
+
.dataTables_wrapper .dataTables_info,
|
| 1774 |
+
.dataTables_wrapper .dataTables_processing,
|
| 1775 |
+
.dataTables_wrapper .dataTables_paginate {
|
| 1776 |
+
color: var(--text) !important;
|
| 1777 |
+
}
|
| 1778 |
+
|
| 1779 |
+
.dataTables_wrapper .dataTables_filter input {
|
| 1780 |
+
background-color: var(--card) !important;
|
| 1781 |
+
border: 1px solid var(--border) !important;
|
| 1782 |
+
color: var(--text) !important;
|
| 1783 |
+
}
|
| 1784 |
+
|
| 1785 |
+
.dataTables_wrapper .dataTables_length select {
|
| 1786 |
+
background-color: var(--card) !important;
|
| 1787 |
+
border: 1px solid var(--border) !important;
|
| 1788 |
+
color: var(--text) !important;
|
| 1789 |
+
}
|
| 1790 |
+
|
| 1791 |
+
.dataTables_wrapper .dataTables_paginate .paginate_button {
|
| 1792 |
+
background-color: var(--surface) !important;
|
| 1793 |
+
border: 1px solid var(--border) !important;
|
| 1794 |
+
color: var(--text) !important;
|
| 1795 |
+
}
|
| 1796 |
+
|
| 1797 |
+
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
|
| 1798 |
+
background-color: var(--card) !important;
|
| 1799 |
+
color: var(--text) !important;
|
| 1800 |
+
}
|
| 1801 |
+
|
| 1802 |
+
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
|
| 1803 |
+
background-color: var(--primary) !important;
|
| 1804 |
+
color: white !important;
|
| 1805 |
+
}
|
| 1806 |
+
|
| 1807 |
+
/* Select2 Integration */
|
| 1808 |
+
.select2-container--default .select2-selection--single {
|
| 1809 |
+
background-color: var(--card) !important;
|
| 1810 |
+
border: 1px solid var(--border) !important;
|
| 1811 |
+
color: var(--text) !important;
|
| 1812 |
+
}
|
| 1813 |
+
|
| 1814 |
+
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
| 1815 |
+
color: var(--text) !important;
|
| 1816 |
+
}
|
| 1817 |
+
|
| 1818 |
+
.select2-container--default .select2-dropdown {
|
| 1819 |
+
background-color: var(--card) !important;
|
| 1820 |
+
border: 1px solid var(--border) !important;
|
| 1821 |
+
}
|
| 1822 |
+
|
| 1823 |
+
.select2-container--default .select2-results__option {
|
| 1824 |
+
color: var(--text) !important;
|
| 1825 |
+
}
|
| 1826 |
+
|
| 1827 |
+
.select2-container--default .select2-results__option--highlighted[aria-selected] {
|
| 1828 |
+
background-color: var(--primary) !important;
|
| 1829 |
+
color: white !important;
|
| 1830 |
+
}
|
| 1831 |
+
|
| 1832 |
+
/* Chart.js Integration */
|
| 1833 |
+
.chart-container {
|
| 1834 |
+
background-color: var(--card) !important;
|
| 1835 |
+
border: 1px solid var(--border) !important;
|
| 1836 |
+
border-radius: 8px;
|
| 1837 |
+
padding: 20px;
|
| 1838 |
+
}
|
| 1839 |
+
|
| 1840 |
+
/* SweetAlert2 Integration */
|
| 1841 |
+
.swal2-popup {
|
| 1842 |
+
background-color: var(--card) !important;
|
| 1843 |
+
color: var(--text) !important;
|
| 1844 |
+
}
|
| 1845 |
+
|
| 1846 |
+
.swal2-title {
|
| 1847 |
+
color: var(--text) !important;
|
| 1848 |
+
}
|
| 1849 |
+
|
| 1850 |
+
.swal2-content {
|
| 1851 |
+
color: var(--text-secondary) !important;
|
| 1852 |
+
}
|
| 1853 |
+
|
| 1854 |
+
.swal2-confirm {
|
| 1855 |
+
background-color: var(--primary) !important;
|
| 1856 |
+
}
|
| 1857 |
+
|
| 1858 |
+
.swal2-cancel {
|
| 1859 |
+
background-color: var(--surface) !important;
|
| 1860 |
+
color: var(--text) !important;
|
| 1861 |
+
}
|
| 1862 |
+
|
| 1863 |
+
/* Loading States */
|
| 1864 |
+
.loading {
|
| 1865 |
+
opacity: 0.6;
|
| 1866 |
+
pointer-events: none;
|
| 1867 |
+
}
|
| 1868 |
+
|
| 1869 |
+
.spinner-border {
|
| 1870 |
+
color: var(--primary) !important;
|
| 1871 |
+
}
|
| 1872 |
+
|
| 1873 |
+
/* Enhanced Animations */
|
| 1874 |
+
.fade-in {
|
| 1875 |
+
animation: fadeIn 0.3s ease-in;
|
| 1876 |
+
}
|
| 1877 |
+
|
| 1878 |
+
@keyframes fadeIn {
|
| 1879 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 1880 |
+
to { opacity: 1; transform: translateY(0); }
|
| 1881 |
+
}
|
| 1882 |
+
|
| 1883 |
+
.slide-in {
|
| 1884 |
+
animation: slideIn 0.3s ease-out;
|
| 1885 |
+
}
|
| 1886 |
+
|
| 1887 |
+
@keyframes slideIn {
|
| 1888 |
+
from { transform: translateX(-100%); }
|
| 1889 |
+
to { transform: translateX(0); }
|
| 1890 |
+
}
|
| 1891 |
+
|
| 1892 |
+
/* Responsive Enhancements */
|
| 1893 |
+
@media (max-width: 768px) {
|
| 1894 |
+
.admin-nav {
|
| 1895 |
+
transform: translateX(-100%);
|
| 1896 |
+
transition: transform 0.3s ease;
|
| 1897 |
+
}
|
| 1898 |
+
|
| 1899 |
+
.admin-nav.mobile-open {
|
| 1900 |
+
transform: translateX(0);
|
| 1901 |
+
}
|
| 1902 |
+
|
| 1903 |
+
.admin-content {
|
| 1904 |
+
margin-left: 0;
|
| 1905 |
+
}
|
| 1906 |
+
|
| 1907 |
+
.mobile-menu-toggle {
|
| 1908 |
+
display: block;
|
| 1909 |
+
}
|
| 1910 |
+
}
|
| 1911 |
+
|
| 1912 |
+
.mobile-menu-toggle {
|
| 1913 |
+
display: none;
|
| 1914 |
+
position: fixed;
|
| 1915 |
+
top: 20px;
|
| 1916 |
+
left: 20px;
|
| 1917 |
+
z-index: 1000;
|
| 1918 |
+
background: var(--primary);
|
| 1919 |
+
color: white;
|
| 1920 |
+
border: none;
|
| 1921 |
+
padding: 10px;
|
| 1922 |
+
border-radius: 5px;
|
| 1923 |
+
cursor: pointer;
|
| 1924 |
+
}
|
| 1925 |
+
|
| 1926 |
+
/* Print Styles */
|
| 1927 |
+
@media print {
|
| 1928 |
+
.admin-nav,
|
| 1929 |
+
.admin-toolbar,
|
| 1930 |
+
.btn,
|
| 1931 |
+
.modal {
|
| 1932 |
+
display: none !important;
|
| 1933 |
+
}
|
| 1934 |
+
|
| 1935 |
+
.admin-content {
|
| 1936 |
+
margin-left: 0 !important;
|
| 1937 |
+
width: 100% !important;
|
| 1938 |
+
}
|
| 1939 |
+
|
| 1940 |
+
.card {
|
| 1941 |
+
border: 1px solid #000 !important;
|
| 1942 |
+
box-shadow: none !important;
|
| 1943 |
+
}
|
| 1944 |
+
}
|
| 1945 |
+
|
| 1946 |
+
/* Form Validation Styles */
|
| 1947 |
+
.form-control.is-invalid {
|
| 1948 |
+
border-color: #dc3545;
|
| 1949 |
+
box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
|
| 1950 |
+
}
|
| 1951 |
+
|
| 1952 |
+
.form-control.is-valid {
|
| 1953 |
+
border-color: #28a745;
|
| 1954 |
+
box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
|
| 1955 |
+
}
|
| 1956 |
+
|
| 1957 |
+
/* Modal Improvements */
|
| 1958 |
+
.modal {
|
| 1959 |
+
z-index: 1050;
|
| 1960 |
+
}
|
| 1961 |
+
|
| 1962 |
+
.modal-backdrop {
|
| 1963 |
+
z-index: 1040;
|
| 1964 |
+
}
|
| 1965 |
+
|
| 1966 |
+
/* Professional Form Styles */
|
| 1967 |
+
#professionalForm .form-group {
|
| 1968 |
+
margin-bottom: 1rem;
|
| 1969 |
+
}
|
| 1970 |
+
|
| 1971 |
+
#professionalForm .form-check {
|
| 1972 |
+
margin-bottom: 0.5rem;
|
| 1973 |
+
}
|
| 1974 |
+
|
| 1975 |
+
#professionalForm .form-check-input {
|
| 1976 |
+
margin-top: 0.25rem;
|
| 1977 |
+
}
|
| 1978 |
+
|
| 1979 |
+
/* Button States */
|
| 1980 |
+
.btn:disabled {
|
| 1981 |
+
opacity: 0.6;
|
| 1982 |
+
cursor: not-allowed;
|
| 1983 |
+
}
|
| 1984 |
+
|
| 1985 |
+
/* Professional Card Improvements */
|
| 1986 |
+
.professional-card {
|
| 1987 |
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
| 1988 |
+
}
|
| 1989 |
+
|
| 1990 |
+
.professional-card:hover {
|
| 1991 |
+
transform: translateY(-2px);
|
| 1992 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
| 1993 |
+
}
|
| 1994 |
+
|
| 1995 |
+
.professional-actions {
|
| 1996 |
+
display: flex;
|
| 1997 |
+
gap: 0.5rem;
|
| 1998 |
+
flex-wrap: wrap;
|
| 1999 |
+
}
|
| 2000 |
+
|
| 2001 |
+
.btn-small {
|
| 2002 |
+
padding: 0.25rem 0.5rem;
|
| 2003 |
+
font-size: 0.875rem;
|
| 2004 |
+
border-radius: 0.25rem;
|
| 2005 |
+
}
|
| 2006 |
+
|
| 2007 |
+
/* Form Input Focus States */
|
| 2008 |
+
.form-control:focus {
|
| 2009 |
+
border-color: var(--primary);
|
| 2010 |
+
box-shadow: 0 0 0 0.2rem rgba(124, 58, 237, 0.25);
|
| 2011 |
+
}
|
| 2012 |
+
|
| 2013 |
+
/* Professional Form Specific Styles */
|
| 2014 |
+
#professionalForm {
|
| 2015 |
+
max-height: 70vh;
|
| 2016 |
+
overflow-y: auto;
|
| 2017 |
+
}
|
| 2018 |
+
|
| 2019 |
+
/* Force input functionality */
|
| 2020 |
+
#professionalForm input,
|
| 2021 |
+
#professionalForm select,
|
| 2022 |
+
#professionalForm textarea {
|
| 2023 |
+
background-color: #fff !important;
|
| 2024 |
+
color: #495057 !important;
|
| 2025 |
+
border: 1px solid #ced4da !important;
|
| 2026 |
+
padding: 0.375rem 0.75rem !important;
|
| 2027 |
+
font-size: 1rem !important;
|
| 2028 |
+
line-height: 1.5 !important;
|
| 2029 |
+
border-radius: 0.25rem !important;
|
| 2030 |
+
width: 100% !important;
|
| 2031 |
+
display: block !important;
|
| 2032 |
+
pointer-events: auto !important;
|
| 2033 |
+
user-select: text !important;
|
| 2034 |
+
-webkit-user-select: text !important;
|
| 2035 |
+
-moz-user-select: text !important;
|
| 2036 |
+
-ms-user-select: text !important;
|
| 2037 |
+
}
|
| 2038 |
+
|
| 2039 |
+
#professionalForm input:focus,
|
| 2040 |
+
#professionalForm select:focus,
|
| 2041 |
+
#professionalForm textarea:focus {
|
| 2042 |
+
border-color: #7c3aed !important;
|
| 2043 |
+
box-shadow: 0 0 0 0.2rem rgba(124, 58, 237, 0.25) !important;
|
| 2044 |
+
outline: 0 !important;
|
| 2045 |
+
background-color: #fff !important;
|
| 2046 |
+
color: #495057 !important;
|
| 2047 |
+
}
|
| 2048 |
+
|
| 2049 |
+
#professionalForm input:not(:disabled):not([readonly]) {
|
| 2050 |
+
background-color: #fff !important;
|
| 2051 |
+
color: #495057 !important;
|
| 2052 |
+
cursor: text !important;
|
| 2053 |
+
}
|
| 2054 |
+
|
| 2055 |
+
/* Ensure form controls work */
|
| 2056 |
+
#professionalForm .form-control {
|
| 2057 |
+
background-color: #fff !important;
|
| 2058 |
+
color: #495057 !important;
|
| 2059 |
+
border: 1px solid #ced4da !important;
|
| 2060 |
+
padding: 0.375rem 0.75rem !important;
|
| 2061 |
+
font-size: 1rem !important;
|
| 2062 |
+
line-height: 1.5 !important;
|
| 2063 |
+
border-radius: 0.25rem !important;
|
| 2064 |
+
pointer-events: auto !important;
|
| 2065 |
+
user-select: text !important;
|
| 2066 |
+
}
|
| 2067 |
+
|
| 2068 |
+
#professionalForm .form-control:focus {
|
| 2069 |
+
border-color: #7c3aed !important;
|
| 2070 |
+
box-shadow: 0 0 0 0.2rem rgba(124, 58, 237, 0.25) !important;
|
| 2071 |
+
outline: 0 !important;
|
| 2072 |
+
background-color: #fff !important;
|
| 2073 |
+
color: #495057 !important;
|
| 2074 |
+
}
|
| 2075 |
+
|
| 2076 |
+
/* Modal input fixes */
|
| 2077 |
+
#professionalModal input,
|
| 2078 |
+
#professionalModal select,
|
| 2079 |
+
#professionalModal textarea {
|
| 2080 |
+
background-color: #fff !important;
|
| 2081 |
+
color: #495057 !important;
|
| 2082 |
+
border: 1px solid #ced4da !important;
|
| 2083 |
+
padding: 0.375rem 0.75rem !important;
|
| 2084 |
+
font-size: 1rem !important;
|
| 2085 |
+
line-height: 1.5 !important;
|
| 2086 |
+
border-radius: 0.25rem !important;
|
| 2087 |
+
width: 100% !important;
|
| 2088 |
+
display: block !important;
|
| 2089 |
+
pointer-events: auto !important;
|
| 2090 |
+
user-select: text !important;
|
| 2091 |
+
}
|
| 2092 |
+
|
| 2093 |
+
#professionalModal input:focus,
|
| 2094 |
+
#professionalModal select:focus,
|
| 2095 |
+
#professionalModal textarea:focus {
|
| 2096 |
+
border-color: #7c3aed !important;
|
| 2097 |
+
box-shadow: 0 0 0 0.2rem rgba(124, 58, 237, 0.25) !important;
|
| 2098 |
+
outline: 0 !important;
|
| 2099 |
+
background-color: #fff !important;
|
| 2100 |
+
color: #495057 !important;
|
| 2101 |
+
}
|
| 2102 |
+
|
| 2103 |
+
#professionalForm .form-control {
|
| 2104 |
+
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
| 2105 |
+
background-color: #fff !important;
|
| 2106 |
+
color: #495057 !important;
|
| 2107 |
+
border: 1px solid #ced4da !important;
|
| 2108 |
+
padding: 0.375rem 0.75rem !important;
|
| 2109 |
+
font-size: 1rem !important;
|
| 2110 |
+
line-height: 1.5 !important;
|
| 2111 |
+
border-radius: 0.25rem !important;
|
| 2112 |
+
}
|
| 2113 |
+
|
| 2114 |
+
#professionalForm .form-control:focus {
|
| 2115 |
+
border-color: var(--primary) !important;
|
| 2116 |
+
box-shadow: 0 0 0 0.2rem rgba(124, 58, 237, 0.25) !important;
|
| 2117 |
+
outline: 0 !important;
|
| 2118 |
+
background-color: #fff !important;
|
| 2119 |
+
color: #495057 !important;
|
| 2120 |
+
}
|
| 2121 |
+
|
| 2122 |
+
#professionalForm .form-control:not(:disabled):not([readonly]) {
|
| 2123 |
+
background-color: #fff !important;
|
| 2124 |
+
color: #495057 !important;
|
| 2125 |
+
}
|
| 2126 |
+
|
| 2127 |
+
#professionalForm input[type="text"],
|
| 2128 |
+
#professionalForm input[type="email"],
|
| 2129 |
+
#professionalForm input[type="tel"],
|
| 2130 |
+
#professionalForm input[type="password"],
|
| 2131 |
+
#professionalForm input[type="number"],
|
| 2132 |
+
#professionalForm select,
|
| 2133 |
+
#professionalForm textarea {
|
| 2134 |
+
background-color: #fff !important;
|
| 2135 |
+
color: #495057 !important;
|
| 2136 |
+
border: 1px solid #ced4da !important;
|
| 2137 |
+
padding: 0.375rem 0.75rem !important;
|
| 2138 |
+
font-size: 1rem !important;
|
| 2139 |
+
line-height: 1.5 !important;
|
| 2140 |
+
border-radius: 0.25rem !important;
|
| 2141 |
+
width: 100% !important;
|
| 2142 |
+
display: block !important;
|
| 2143 |
+
}
|
| 2144 |
+
|
| 2145 |
+
#professionalForm input:focus,
|
| 2146 |
+
#professionalForm select:focus,
|
| 2147 |
+
#professionalForm textarea:focus {
|
| 2148 |
+
border-color: var(--primary) !important;
|
| 2149 |
+
box-shadow: 0 0 0 0.2rem rgba(124, 58, 237, 0.25) !important;
|
| 2150 |
+
outline: 0 !important;
|
| 2151 |
+
background-color: #fff !important;
|
| 2152 |
+
color: #495057 !important;
|
| 2153 |
+
}
|
| 2154 |
+
|
| 2155 |
+
#professionalForm .form-control:focus {
|
| 2156 |
+
border-color: var(--primary);
|
| 2157 |
+
box-shadow: 0 0 0 0.2rem rgba(124, 58, 237, 0.25);
|
| 2158 |
+
}
|
| 2159 |
+
|
| 2160 |
+
#professionalForm .form-control.is-valid {
|
| 2161 |
+
border-color: #28a745;
|
| 2162 |
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='m2.3 6.73.94-.94 1.44 1.44 3.16-3.16.94.94-4.1 4.1z'/%3e%3c/svg%3e");
|
| 2163 |
+
background-repeat: no-repeat;
|
| 2164 |
+
background-position: right calc(0.375em + 0.1875rem) center;
|
| 2165 |
+
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
| 2166 |
+
}
|
| 2167 |
+
|
| 2168 |
+
#professionalForm .form-control.is-invalid {
|
| 2169 |
+
border-color: #dc3545;
|
| 2170 |
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath d='m5.8 4.6 1.4 1.4 1.4-1.4'/%3e%3c/svg%3e");
|
| 2171 |
+
background-repeat: no-repeat;
|
| 2172 |
+
background-position: right calc(0.375em + 0.1875rem) center;
|
| 2173 |
+
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
|
| 2174 |
+
}
|
| 2175 |
+
|
| 2176 |
+
/* Expertise Areas Styling */
|
| 2177 |
+
#professionalForm .form-check {
|
| 2178 |
+
margin-bottom: 0.5rem;
|
| 2179 |
+
padding-left: 1.5rem;
|
| 2180 |
+
}
|
| 2181 |
+
|
| 2182 |
+
#professionalForm .form-check-input {
|
| 2183 |
+
margin-top: 0.25rem;
|
| 2184 |
+
margin-left: -1.5rem;
|
| 2185 |
+
}
|
| 2186 |
+
|
| 2187 |
+
#professionalForm .form-check-label {
|
| 2188 |
+
cursor: pointer;
|
| 2189 |
+
user-select: none;
|
| 2190 |
+
}
|
| 2191 |
+
|
| 2192 |
+
/* Modal Improvements */
|
| 2193 |
+
.modal-dialog {
|
| 2194 |
+
margin: 1.75rem auto;
|
| 2195 |
+
}
|
| 2196 |
+
|
| 2197 |
+
.modal-content {
|
| 2198 |
+
border: none;
|
| 2199 |
+
border-radius: 0.5rem;
|
| 2200 |
+
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
| 2201 |
+
}
|
| 2202 |
+
|
| 2203 |
+
.modal-header {
|
| 2204 |
+
border-bottom: 1px solid #dee2e6;
|
| 2205 |
+
padding: 1rem 1.5rem;
|
| 2206 |
+
}
|
| 2207 |
+
|
| 2208 |
+
.modal-body {
|
| 2209 |
+
padding: 1.5rem;
|
| 2210 |
+
}
|
| 2211 |
+
|
| 2212 |
+
.modal-footer {
|
| 2213 |
+
border-top: 1px solid #dee2e6;
|
| 2214 |
+
padding: 1rem 1.5rem;
|
| 2215 |
+
}
|
| 2216 |
+
|
| 2217 |
+
/* Button Improvements */
|
| 2218 |
+
.btn:disabled {
|
| 2219 |
+
opacity: 0.6;
|
| 2220 |
+
cursor: not-allowed;
|
| 2221 |
+
}
|
| 2222 |
+
|
| 2223 |
+
.btn-primary:disabled {
|
| 2224 |
+
background-color: #6c757d;
|
| 2225 |
+
border-color: #6c757d;
|
| 2226 |
+
}
|
| 2227 |
+
|
| 2228 |
+
/* Loading State */
|
| 2229 |
+
.btn.loading {
|
| 2230 |
+
position: relative;
|
| 2231 |
+
color: transparent;
|
| 2232 |
+
}
|
| 2233 |
+
|
| 2234 |
+
.btn.loading::after {
|
| 2235 |
+
content: "";
|
| 2236 |
+
position: absolute;
|
| 2237 |
+
width: 16px;
|
| 2238 |
+
height: 16px;
|
| 2239 |
+
top: 50%;
|
| 2240 |
+
left: 50%;
|
| 2241 |
+
margin-left: -8px;
|
| 2242 |
+
margin-top: -8px;
|
| 2243 |
+
border: 2px solid transparent;
|
| 2244 |
+
border-top-color: #ffffff;
|
| 2245 |
+
border-radius: 50%;
|
| 2246 |
+
animation: spin 1s linear infinite;
|
| 2247 |
+
}
|
| 2248 |
+
|
| 2249 |
+
@keyframes spin {
|
| 2250 |
+
0% { transform: rotate(0deg); }
|
| 2251 |
+
100% { transform: rotate(360deg); }
|
| 2252 |
+
}
|
| 2253 |
+
|
| 2254 |
+
/* Loading States */
|
| 2255 |
+
.loading {
|
| 2256 |
+
opacity: 0.6;
|
| 2257 |
+
pointer-events: none;
|
| 2258 |
+
}
|
| 2259 |
+
|
| 2260 |
+
/* Toast Notifications */
|
| 2261 |
+
.toast-container {
|
| 2262 |
+
position: fixed;
|
| 2263 |
+
top: 20px;
|
| 2264 |
+
right: 20px;
|
| 2265 |
+
z-index: 1060;
|
| 2266 |
+
}
|
| 2267 |
+
|
| 2268 |
+
.toast {
|
| 2269 |
+
background: var(--surface);
|
| 2270 |
+
border: 1px solid var(--border);
|
| 2271 |
+
color: var(--text);
|
| 2272 |
+
}
|
| 2273 |
+
|
| 2274 |
+
.toast-success {
|
| 2275 |
+
border-left: 4px solid var(--success);
|
| 2276 |
+
}
|
| 2277 |
+
|
| 2278 |
+
.toast-error {
|
| 2279 |
+
border-left: 4px solid var(--danger);
|
| 2280 |
+
}
|
chatbot/admin.js
ADDED
|
@@ -0,0 +1,1281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(() => {
|
| 2 |
+
// Get API URL from configuration
|
| 3 |
+
const getAPIRoot = () => {
|
| 4 |
+
if (window.AIMHSA && window.AIMHSA.Config) {
|
| 5 |
+
return window.AIMHSA.Config.getApiBaseUrl();
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
// Fallback to intelligent detection
|
| 9 |
+
try {
|
| 10 |
+
const loc = window.location;
|
| 11 |
+
if (loc.port === '8000') {
|
| 12 |
+
return `${loc.protocol}//${loc.hostname}:5057`;
|
| 13 |
+
} else if (loc.port === '5057' || loc.port === '') {
|
| 14 |
+
return loc.origin;
|
| 15 |
+
} else {
|
| 16 |
+
return 'http://localhost:5057';
|
| 17 |
+
}
|
| 18 |
+
} catch (_) {
|
| 19 |
+
return 'http://localhost:5057';
|
| 20 |
+
}
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
const API_ROOT = getAPIRoot();
|
| 24 |
+
const API_BASE_URL = API_ROOT; // For compatibility
|
| 25 |
+
|
| 26 |
+
// Simple inline message helper
|
| 27 |
+
function showMessage(text, type = 'error') {
|
| 28 |
+
const existing = document.querySelector('.inline-message');
|
| 29 |
+
if (existing) existing.remove();
|
| 30 |
+
const message = document.createElement('div');
|
| 31 |
+
message.className = `inline-message ${type}`;
|
| 32 |
+
message.textContent = text;
|
| 33 |
+
message.style.cssText = `
|
| 34 |
+
position: fixed; left: 50%; transform: translateX(-50%);
|
| 35 |
+
top: 12px; z-index: 10000; padding: 10px 14px; border-radius: 8px;
|
| 36 |
+
font-size: 14px; font-weight: 500;
|
| 37 |
+
${type === 'success'
|
| 38 |
+
? 'background: rgba(16,185,129,0.12); color:#047857; border:1px solid rgba(16,185,129,0.3);'
|
| 39 |
+
: 'background: rgba(239,68,68,0.12); color:#991b1b; border:1px solid rgba(239,68,68,0.3);'}
|
| 40 |
+
`;
|
| 41 |
+
document.body.appendChild(message);
|
| 42 |
+
setTimeout(() => message.remove(), 3500);
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// Check admin authentication
|
| 46 |
+
const adminData = localStorage.getItem('aimhsa_admin');
|
| 47 |
+
if (!adminData) {
|
| 48 |
+
// Check if they're logged in as a different type of user
|
| 49 |
+
const userData = localStorage.getItem('aimhsa_account');
|
| 50 |
+
const professionalData = localStorage.getItem('aimhsa_professional');
|
| 51 |
+
|
| 52 |
+
if (userData && userData !== 'null') {
|
| 53 |
+
alert('You are logged in as a regular user. Please logout and login as an admin.');
|
| 54 |
+
window.location.href = '/';
|
| 55 |
+
return;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
if (professionalData) {
|
| 59 |
+
alert('You are logged in as a professional. Please logout and login as an admin.');
|
| 60 |
+
window.location.href = '/professional_dashboard.html';
|
| 61 |
+
return;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
window.location.href = '/login';
|
| 65 |
+
return;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
// Elements
|
| 69 |
+
const navLinks = document.querySelectorAll('.nav-link');
|
| 70 |
+
const adminSections = document.querySelectorAll('.admin-section');
|
| 71 |
+
const addProfessionalBtn = document.getElementById('addProfessionalBtn');
|
| 72 |
+
const professionalModal = document.getElementById('professionalModal');
|
| 73 |
+
const professionalForm = document.getElementById('professionalForm');
|
| 74 |
+
const professionalsGrid = document.getElementById('professionalsGrid');
|
| 75 |
+
const bookingsTableBody = document.getElementById('bookingsTableBody');
|
| 76 |
+
const statusFilter = document.getElementById('statusFilter');
|
| 77 |
+
const riskLevelFilter = document.getElementById('riskLevelFilter');
|
| 78 |
+
const refreshStatsBtn = document.getElementById('refreshStatsBtn');
|
| 79 |
+
const recentAssessments = document.getElementById('recentAssessments');
|
| 80 |
+
const logoutBtn = document.getElementById('logoutBtn');
|
| 81 |
+
|
| 82 |
+
// State
|
| 83 |
+
let professionals = [];
|
| 84 |
+
let filteredProfessionals = [];
|
| 85 |
+
let professionalsPage = 1;
|
| 86 |
+
const PRO_PAGE_SIZE = 8;
|
| 87 |
+
|
| 88 |
+
let bookings = [];
|
| 89 |
+
let filteredBookings = [];
|
| 90 |
+
let bookingsPage = 1;
|
| 91 |
+
const BOOK_PAGE_SIZE = 10;
|
| 92 |
+
|
| 93 |
+
let assessments = [];
|
| 94 |
+
|
| 95 |
+
// API Helper
|
| 96 |
+
async function api(path, opts = {}) {
|
| 97 |
+
const url = API_ROOT + path;
|
| 98 |
+
const res = await fetch(url, {
|
| 99 |
+
headers: {
|
| 100 |
+
'Content-Type': 'application/json',
|
| 101 |
+
...opts.headers
|
| 102 |
+
},
|
| 103 |
+
...opts
|
| 104 |
+
});
|
| 105 |
+
|
| 106 |
+
if (!res.ok) {
|
| 107 |
+
const txt = await res.text();
|
| 108 |
+
throw new Error(txt || res.statusText);
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
return res.json();
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
// Navigation
|
| 115 |
+
navLinks.forEach(link => {
|
| 116 |
+
link.addEventListener('click', (e) => {
|
| 117 |
+
e.preventDefault();
|
| 118 |
+
const target = link.getAttribute('href').substring(1);
|
| 119 |
+
|
| 120 |
+
// Update active nav link
|
| 121 |
+
navLinks.forEach(l => l.classList.remove('active'));
|
| 122 |
+
link.classList.add('active');
|
| 123 |
+
|
| 124 |
+
// Show target section
|
| 125 |
+
adminSections.forEach(section => {
|
| 126 |
+
section.classList.remove('active');
|
| 127 |
+
if (section.id === target) {
|
| 128 |
+
section.classList.add('active');
|
| 129 |
+
|
| 130 |
+
// Load section data
|
| 131 |
+
switch(target) {
|
| 132 |
+
case 'professionals':
|
| 133 |
+
loadProfessionals();
|
| 134 |
+
break;
|
| 135 |
+
case 'bookings':
|
| 136 |
+
loadBookings();
|
| 137 |
+
break;
|
| 138 |
+
case 'risk-monitor':
|
| 139 |
+
loadRiskStats();
|
| 140 |
+
loadRecentAssessments();
|
| 141 |
+
break;
|
| 142 |
+
case 'analytics':
|
| 143 |
+
loadAnalytics();
|
| 144 |
+
break;
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
});
|
| 148 |
+
});
|
| 149 |
+
});
|
| 150 |
+
|
| 151 |
+
// Toolbar actions (global search and refresh)
|
| 152 |
+
const globalSearch = document.getElementById('globalSearch');
|
| 153 |
+
const timeRange = document.getElementById('timeRange');
|
| 154 |
+
const refreshAllBtn = document.getElementById('refreshAllBtn');
|
| 155 |
+
if (globalSearch) {
|
| 156 |
+
globalSearch.addEventListener('input', debounce(() => {
|
| 157 |
+
const q = globalSearch.value.toLowerCase().trim();
|
| 158 |
+
if (document.getElementById('professionals').classList.contains('active')) {
|
| 159 |
+
filteredProfessionals = professionals.filter(p => (
|
| 160 |
+
`${p.first_name} ${p.last_name}`.toLowerCase().includes(q) ||
|
| 161 |
+
(p.specialization || '').toLowerCase().includes(q) ||
|
| 162 |
+
(p.email || '').toLowerCase().includes(q)
|
| 163 |
+
));
|
| 164 |
+
professionalsPage = 1; renderProfessionals();
|
| 165 |
+
} else if (document.getElementById('bookings').classList.contains('active')) {
|
| 166 |
+
filteredBookings = bookings.filter(b => (
|
| 167 |
+
(b.user_account || '').toLowerCase().includes(q) ||
|
| 168 |
+
`${b.first_name} ${b.last_name}`.toLowerCase().includes(q) ||
|
| 169 |
+
(b.risk_level || '').toLowerCase().includes(q)
|
| 170 |
+
));
|
| 171 |
+
bookingsPage = 1; renderBookings();
|
| 172 |
+
}
|
| 173 |
+
}, 250));
|
| 174 |
+
}
|
| 175 |
+
if (refreshAllBtn) {
|
| 176 |
+
refreshAllBtn.addEventListener('click', () => {
|
| 177 |
+
loadProfessionals();
|
| 178 |
+
loadBookings();
|
| 179 |
+
loadRiskStats();
|
| 180 |
+
loadRecentAssessments();
|
| 181 |
+
loadAnalytics();
|
| 182 |
+
showToast('Data refreshed', 'success');
|
| 183 |
+
});
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
// Utility: Toast notifications
|
| 187 |
+
function showToast(message, type = 'info') {
|
| 188 |
+
const host = document.body;
|
| 189 |
+
let container = document.getElementById('toastContainer');
|
| 190 |
+
if (!container) {
|
| 191 |
+
container = document.createElement('div');
|
| 192 |
+
container.id = 'toastContainer';
|
| 193 |
+
container.style.cssText = 'position:fixed;right:16px;bottom:16px;display:flex;flex-direction:column;gap:10px;z-index:9999';
|
| 194 |
+
host.appendChild(container);
|
| 195 |
+
}
|
| 196 |
+
const el = document.createElement('div');
|
| 197 |
+
el.className = `toast toast-${type}`;
|
| 198 |
+
el.textContent = message;
|
| 199 |
+
container.appendChild(el);
|
| 200 |
+
setTimeout(() => { el.style.opacity = '0'; el.style.transform = 'translateY(10px)'; }, 10);
|
| 201 |
+
setTimeout(() => container.removeChild(el), 3000);
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
// Debounce helper
|
| 205 |
+
function debounce(fn, wait = 300) {
|
| 206 |
+
let t;
|
| 207 |
+
return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), wait); };
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
// Load Professionals
|
| 211 |
+
async function loadProfessionals() {
|
| 212 |
+
try {
|
| 213 |
+
const response = await api('/admin/professionals');
|
| 214 |
+
professionals = response.professionals || [];
|
| 215 |
+
filteredProfessionals = professionals.slice();
|
| 216 |
+
professionalsPage = 1;
|
| 217 |
+
renderProfessionals();
|
| 218 |
+
} catch (error) {
|
| 219 |
+
console.error('Failed to load professionals:', error);
|
| 220 |
+
showMessage('Failed to load professionals', 'error');
|
| 221 |
+
showToast('Failed to load professionals', 'error');
|
| 222 |
+
}
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
function renderProfessionals() {
|
| 226 |
+
professionalsGrid.innerHTML = '';
|
| 227 |
+
|
| 228 |
+
if (filteredProfessionals.length === 0) {
|
| 229 |
+
professionalsGrid.innerHTML = '<p class="no-data">No professionals found</p>';
|
| 230 |
+
return;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
// Paging
|
| 234 |
+
const start = (professionalsPage - 1) * PRO_PAGE_SIZE;
|
| 235 |
+
const end = start + PRO_PAGE_SIZE;
|
| 236 |
+
const pageItems = filteredProfessionals.slice(start, end);
|
| 237 |
+
|
| 238 |
+
pageItems.forEach(prof => {
|
| 239 |
+
const card = document.createElement('div');
|
| 240 |
+
card.className = 'professional-card';
|
| 241 |
+
|
| 242 |
+
const expertiseAreas = prof.expertise_areas.join(', ');
|
| 243 |
+
const languages = prof.languages.join(', ');
|
| 244 |
+
|
| 245 |
+
card.innerHTML = `
|
| 246 |
+
<div class="professional-header">
|
| 247 |
+
<div>
|
| 248 |
+
<div class="professional-name">${prof.first_name} ${prof.last_name}</div>
|
| 249 |
+
<div class="professional-specialization">${prof.specialization}</div>
|
| 250 |
+
</div>
|
| 251 |
+
<div class="professional-status ${prof.is_active ? 'status-active' : 'status-inactive'}">
|
| 252 |
+
${prof.is_active ? 'Active' : 'Inactive'}
|
| 253 |
+
</div>
|
| 254 |
+
</div>
|
| 255 |
+
|
| 256 |
+
<div class="professional-details">
|
| 257 |
+
<div class="professional-detail">
|
| 258 |
+
<span>Email:</span>
|
| 259 |
+
<span>${prof.email}</span>
|
| 260 |
+
</div>
|
| 261 |
+
<div class="professional-detail">
|
| 262 |
+
<span>Phone:</span>
|
| 263 |
+
<span>${prof.phone || 'Not provided'}</span>
|
| 264 |
+
</div>
|
| 265 |
+
<div class="professional-detail">
|
| 266 |
+
<span>Experience:</span>
|
| 267 |
+
<span>${prof.experience_years} years</span>
|
| 268 |
+
</div>
|
| 269 |
+
<div class="professional-detail">
|
| 270 |
+
<span>District:</span>
|
| 271 |
+
<span>${prof.district || 'Not specified'}</span>
|
| 272 |
+
</div>
|
| 273 |
+
<div class="professional-detail">
|
| 274 |
+
<span>Expertise:</span>
|
| 275 |
+
<span>${expertiseAreas || 'Not specified'}</span>
|
| 276 |
+
</div>
|
| 277 |
+
<div class="professional-detail">
|
| 278 |
+
<span>Languages:</span>
|
| 279 |
+
<span>${languages}</span>
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
| 282 |
+
|
| 283 |
+
<div class="professional-actions">
|
| 284 |
+
<button class="btn-small btn-secondary" onclick="editProfessional(${prof.id})">Edit</button>
|
| 285 |
+
<button class="btn-small btn-secondary" onclick="toggleProfessionalStatus(${prof.id}, ${prof.is_active})">
|
| 286 |
+
${prof.is_active ? 'Deactivate' : 'Activate'}
|
| 287 |
+
</button>
|
| 288 |
+
<button class="btn-small btn-danger" onclick="deleteProfessional(${prof.id}, '${prof.first_name} ${prof.last_name}')">Delete</button>
|
| 289 |
+
</div>
|
| 290 |
+
`;
|
| 291 |
+
|
| 292 |
+
professionalsGrid.appendChild(card);
|
| 293 |
+
});
|
| 294 |
+
|
| 295 |
+
// Pagination controls
|
| 296 |
+
renderProPagination();
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
function renderProPagination() {
|
| 300 |
+
const totalPages = Math.max(1, Math.ceil(filteredProfessionals.length / PRO_PAGE_SIZE));
|
| 301 |
+
let pager = document.getElementById('proPager');
|
| 302 |
+
if (!pager) {
|
| 303 |
+
pager = document.createElement('div');
|
| 304 |
+
pager.id = 'proPager';
|
| 305 |
+
pager.className = 'pager';
|
| 306 |
+
professionalsGrid.parentElement.appendChild(pager);
|
| 307 |
+
}
|
| 308 |
+
pager.innerHTML = '';
|
| 309 |
+
const prev = document.createElement('button'); prev.textContent = 'Prev'; prev.className = 'pager-btn'; prev.disabled = professionalsPage <= 1;
|
| 310 |
+
const next = document.createElement('button'); next.textContent = 'Next'; next.className = 'pager-btn'; next.disabled = professionalsPage >= totalPages;
|
| 311 |
+
const info = document.createElement('span'); info.className = 'pager-info'; info.textContent = `Page ${professionalsPage} of ${totalPages}`;
|
| 312 |
+
prev.onclick = () => { professionalsPage = Math.max(1, professionalsPage - 1); renderProfessionals(); };
|
| 313 |
+
next.onclick = () => { professionalsPage = Math.min(totalPages, professionalsPage + 1); renderProfessionals(); };
|
| 314 |
+
pager.appendChild(prev); pager.appendChild(info); pager.appendChild(next);
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
// Load Bookings
|
| 318 |
+
async function loadBookings() {
|
| 319 |
+
try {
|
| 320 |
+
const params = new URLSearchParams();
|
| 321 |
+
if (statusFilter.value) params.append('status', statusFilter.value);
|
| 322 |
+
if (riskLevelFilter.value) params.append('risk_level', riskLevelFilter.value);
|
| 323 |
+
|
| 324 |
+
const response = await api(`/admin/bookings?${params}`);
|
| 325 |
+
bookings = response.bookings || [];
|
| 326 |
+
filteredBookings = bookings.slice();
|
| 327 |
+
bookingsPage = 1;
|
| 328 |
+
renderBookings();
|
| 329 |
+
} catch (error) {
|
| 330 |
+
console.error('Failed to load bookings:', error);
|
| 331 |
+
showMessage('Failed to load bookings', 'error');
|
| 332 |
+
showToast('Failed to load bookings', 'error');
|
| 333 |
+
}
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
function renderBookings() {
|
| 337 |
+
bookingsTableBody.innerHTML = '';
|
| 338 |
+
|
| 339 |
+
if (filteredBookings.length === 0) {
|
| 340 |
+
bookingsTableBody.innerHTML = '<tr><td colspan="7" class="no-data">No bookings found</td></tr>';
|
| 341 |
+
return;
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
const start = (bookingsPage - 1) * BOOK_PAGE_SIZE;
|
| 345 |
+
const end = start + BOOK_PAGE_SIZE;
|
| 346 |
+
const pageItems = filteredBookings.slice(start, end);
|
| 347 |
+
|
| 348 |
+
pageItems.forEach(booking => {
|
| 349 |
+
const row = document.createElement('tr');
|
| 350 |
+
|
| 351 |
+
const scheduledTime = new Date(booking.scheduled_datetime * 1000).toLocaleString();
|
| 352 |
+
const userInfo = booking.user_account || `IP: ${booking.user_ip}`;
|
| 353 |
+
|
| 354 |
+
row.innerHTML = `
|
| 355 |
+
<td>${booking.booking_id.substring(0, 8)}...</td>
|
| 356 |
+
<td>${userInfo}</td>
|
| 357 |
+
<td>${booking.first_name} ${booking.last_name}</td>
|
| 358 |
+
<td><span class="risk-badge risk-${booking.risk_level}">${booking.risk_level.toUpperCase()}</span></td>
|
| 359 |
+
<td>${scheduledTime}</td>
|
| 360 |
+
<td><span class="status-badge status-${booking.booking_status}">${booking.booking_status.toUpperCase()}</span></td>
|
| 361 |
+
<td>
|
| 362 |
+
<button class="btn-small btn-secondary" onclick="viewBookingDetails('${booking.booking_id}')">View</button>
|
| 363 |
+
</td>
|
| 364 |
+
`;
|
| 365 |
+
|
| 366 |
+
bookingsTableBody.appendChild(row);
|
| 367 |
+
});
|
| 368 |
+
|
| 369 |
+
renderBookingsPagination();
|
| 370 |
+
ensureExportButton();
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
function renderBookingsPagination() {
|
| 374 |
+
const totalPages = Math.max(1, Math.ceil(filteredBookings.length / BOOK_PAGE_SIZE));
|
| 375 |
+
let pager = document.getElementById('bookPager');
|
| 376 |
+
if (!pager) {
|
| 377 |
+
pager = document.createElement('div');
|
| 378 |
+
pager.id = 'bookPager';
|
| 379 |
+
pager.className = 'pager';
|
| 380 |
+
const table = document.getElementById('bookingsTable');
|
| 381 |
+
table.parentElement.appendChild(pager);
|
| 382 |
+
}
|
| 383 |
+
pager.innerHTML = '';
|
| 384 |
+
const prev = document.createElement('button'); prev.textContent = 'Prev'; prev.className = 'pager-btn'; prev.disabled = bookingsPage <= 1;
|
| 385 |
+
const next = document.createElement('button'); next.textContent = 'Next'; next.className = 'pager-btn'; next.disabled = bookingsPage >= totalPages;
|
| 386 |
+
const info = document.createElement('span'); info.className = 'pager-info'; info.textContent = `Page ${bookingsPage} of ${totalPages}`;
|
| 387 |
+
prev.onclick = () => { bookingsPage = Math.max(1, bookingsPage - 1); renderBookings(); };
|
| 388 |
+
next.onclick = () => { bookingsPage = Math.min(totalPages, bookingsPage + 1); renderBookings(); };
|
| 389 |
+
pager.appendChild(prev); pager.appendChild(info); pager.appendChild(next);
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
// Export CSV for current bookings view
|
| 393 |
+
function ensureExportButton() {
|
| 394 |
+
let btn = document.getElementById('exportBookingsBtn');
|
| 395 |
+
if (!btn) {
|
| 396 |
+
btn = document.createElement('button');
|
| 397 |
+
btn.id = 'exportBookingsBtn';
|
| 398 |
+
btn.className = 'fab';
|
| 399 |
+
btn.title = 'Export current bookings to CSV';
|
| 400 |
+
btn.textContent = '⇩ CSV';
|
| 401 |
+
document.body.appendChild(btn);
|
| 402 |
+
btn.addEventListener('click', exportBookingsCsv);
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
function exportBookingsCsv() {
|
| 407 |
+
const headers = ['Booking ID','User','Professional','Risk Level','Scheduled Time','Status'];
|
| 408 |
+
const rows = filteredBookings.map(b => [
|
| 409 |
+
b.booking_id,
|
| 410 |
+
b.user_account || `IP: ${b.user_ip}`,
|
| 411 |
+
`${b.first_name} ${b.last_name}`,
|
| 412 |
+
b.risk_level,
|
| 413 |
+
new Date(b.scheduled_datetime * 1000).toISOString(),
|
| 414 |
+
b.booking_status
|
| 415 |
+
]);
|
| 416 |
+
const csv = [headers, ...rows].map(r => r.map(x => `"${String(x).replace(/"/g,'""')}"`).join(',')).join('\n');
|
| 417 |
+
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
| 418 |
+
const url = URL.createObjectURL(blob);
|
| 419 |
+
const a = document.createElement('a');
|
| 420 |
+
a.href = url; a.download = `bookings_${Date.now()}.csv`; a.click();
|
| 421 |
+
URL.revokeObjectURL(url);
|
| 422 |
+
showToast('Bookings exported');
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
// Load Risk Stats
|
| 426 |
+
async function loadRiskStats() {
|
| 427 |
+
try {
|
| 428 |
+
const response = await api('/monitor/risk-stats');
|
| 429 |
+
const stats = response.risk_stats || {};
|
| 430 |
+
|
| 431 |
+
document.getElementById('criticalCount').textContent = stats.critical || 0;
|
| 432 |
+
document.getElementById('highCount').textContent = stats.high || 0;
|
| 433 |
+
document.getElementById('mediumCount').textContent = stats.medium || 0;
|
| 434 |
+
document.getElementById('lowCount').textContent = stats.low || 0;
|
| 435 |
+
} catch (error) {
|
| 436 |
+
console.error('Failed to load risk stats:', error);
|
| 437 |
+
}
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
// Load Recent Assessments
|
| 441 |
+
async function loadRecentAssessments() {
|
| 442 |
+
try {
|
| 443 |
+
const response = await api('/monitor/recent-assessments?limit=10');
|
| 444 |
+
assessments = response.recent_assessments || [];
|
| 445 |
+
renderRecentAssessments();
|
| 446 |
+
} catch (error) {
|
| 447 |
+
console.error('Failed to load recent assessments:', error);
|
| 448 |
+
}
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
function renderRecentAssessments() {
|
| 452 |
+
recentAssessments.innerHTML = '';
|
| 453 |
+
|
| 454 |
+
if (assessments.length === 0) {
|
| 455 |
+
recentAssessments.innerHTML = '<p class="no-data">No recent assessments</p>';
|
| 456 |
+
return;
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
assessments.forEach(assessment => {
|
| 460 |
+
const item = document.createElement('div');
|
| 461 |
+
item.className = 'assessment-item';
|
| 462 |
+
|
| 463 |
+
const time = new Date(assessment.assessment_timestamp * 1000).toLocaleString();
|
| 464 |
+
const query = assessment.user_query.length > 60 ?
|
| 465 |
+
assessment.user_query.substring(0, 60) + '...' :
|
| 466 |
+
assessment.user_query;
|
| 467 |
+
|
| 468 |
+
item.innerHTML = `
|
| 469 |
+
<div class="assessment-info">
|
| 470 |
+
<div class="assessment-query">${query}</div>
|
| 471 |
+
<div class="assessment-time">${time}</div>
|
| 472 |
+
</div>
|
| 473 |
+
<div>
|
| 474 |
+
<span class="risk-badge risk-${assessment.risk_level}">${assessment.risk_level.toUpperCase()}</span>
|
| 475 |
+
</div>
|
| 476 |
+
`;
|
| 477 |
+
|
| 478 |
+
recentAssessments.appendChild(item);
|
| 479 |
+
});
|
| 480 |
+
}
|
| 481 |
+
|
| 482 |
+
// Load Analytics
|
| 483 |
+
async function loadAnalytics() {
|
| 484 |
+
try {
|
| 485 |
+
// Load professionals count
|
| 486 |
+
const profResponse = await api('/admin/professionals');
|
| 487 |
+
document.getElementById('totalProfessionals').textContent = profResponse.professionals?.length || 0;
|
| 488 |
+
|
| 489 |
+
// Load active bookings count
|
| 490 |
+
const bookingsResponse = await api('/admin/bookings');
|
| 491 |
+
const activeBookings = bookingsResponse.bookings?.filter(b =>
|
| 492 |
+
['pending', 'confirmed'].includes(b.booking_status)
|
| 493 |
+
).length || 0;
|
| 494 |
+
document.getElementById('activeBookings').textContent = activeBookings;
|
| 495 |
+
|
| 496 |
+
// Load completed sessions count
|
| 497 |
+
const completedSessions = bookingsResponse.bookings?.filter(b =>
|
| 498 |
+
b.booking_status === 'completed'
|
| 499 |
+
).length || 0;
|
| 500 |
+
document.getElementById('completedSessions').textContent = completedSessions;
|
| 501 |
+
|
| 502 |
+
// Load assessments today count
|
| 503 |
+
const assessmentsResponse = await api('/admin/risk-assessments?limit=1000');
|
| 504 |
+
const today = new Date().toDateString();
|
| 505 |
+
const assessmentsToday = assessmentsResponse.assessments?.filter(a =>
|
| 506 |
+
new Date(a.assessment_timestamp * 1000).toDateString() === today
|
| 507 |
+
).length || 0;
|
| 508 |
+
document.getElementById('assessmentsToday').textContent = assessmentsToday;
|
| 509 |
+
|
| 510 |
+
// Update KPI header cards
|
| 511 |
+
const activeBookingsKpi = activeBookings;
|
| 512 |
+
const riskStats = await api('/monitor/risk-stats');
|
| 513 |
+
document.getElementById('kpiActiveBookings')?.replaceChildren(document.createTextNode(activeBookingsKpi));
|
| 514 |
+
document.getElementById('kpiCritical')?.replaceChildren(document.createTextNode(riskStats.risk_stats?.critical || 0));
|
| 515 |
+
document.getElementById('kpiProfessionals')?.replaceChildren(document.createTextNode(profResponse.professionals?.length || 0));
|
| 516 |
+
document.getElementById('kpiAssessments')?.replaceChildren(document.createTextNode(assessmentsToday));
|
| 517 |
+
|
| 518 |
+
} catch (error) {
|
| 519 |
+
console.error('Failed to load analytics:', error);
|
| 520 |
+
}
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
// Professional Management
|
| 524 |
+
addProfessionalBtn.addEventListener('click', () => {
|
| 525 |
+
openProfessionalModal();
|
| 526 |
+
});
|
| 527 |
+
|
| 528 |
+
function openProfessionalModal(professional = null) {
|
| 529 |
+
const modal = document.getElementById('professionalModal');
|
| 530 |
+
const form = document.getElementById('professionalForm');
|
| 531 |
+
const title = document.getElementById('modalTitle');
|
| 532 |
+
const passwordField = document.getElementById('password');
|
| 533 |
+
const passwordRequired = document.getElementById('passwordRequired');
|
| 534 |
+
const passwordHelp = document.getElementById('passwordHelp');
|
| 535 |
+
|
| 536 |
+
// Clear any previous validation states
|
| 537 |
+
clearFormValidation();
|
| 538 |
+
|
| 539 |
+
if (professional) {
|
| 540 |
+
title.textContent = 'Edit Professional';
|
| 541 |
+
form.dataset.professionalId = professional.id;
|
| 542 |
+
|
| 543 |
+
// Make password optional for edit mode
|
| 544 |
+
passwordField.required = false;
|
| 545 |
+
passwordRequired.textContent = '';
|
| 546 |
+
passwordHelp.style.display = 'block';
|
| 547 |
+
} else {
|
| 548 |
+
title.textContent = 'Add New Professional';
|
| 549 |
+
delete form.dataset.professionalId;
|
| 550 |
+
|
| 551 |
+
// Make password required for create mode
|
| 552 |
+
passwordField.required = true;
|
| 553 |
+
passwordRequired.textContent = '*';
|
| 554 |
+
passwordHelp.style.display = 'none';
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
// Use Bootstrap modal show method
|
| 558 |
+
$(modal).modal('show');
|
| 559 |
+
|
| 560 |
+
// Handle modal events properly
|
| 561 |
+
$(modal).off('shown.bs.modal').on('shown.bs.modal', function() {
|
| 562 |
+
// Reset form first
|
| 563 |
+
form.reset();
|
| 564 |
+
|
| 565 |
+
// Ensure all inputs are working
|
| 566 |
+
ensureInputsWorking();
|
| 567 |
+
|
| 568 |
+
if (professional) {
|
| 569 |
+
// Populate form for edit mode
|
| 570 |
+
populateForm(professional);
|
| 571 |
+
} else {
|
| 572 |
+
// Set default values for add mode
|
| 573 |
+
document.getElementById('experience_years').value = '0';
|
| 574 |
+
document.getElementById('consultation_fee').value = '';
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
// Re-setup form event listeners
|
| 578 |
+
setupFormEventListeners();
|
| 579 |
+
|
| 580 |
+
// Focus on first input
|
| 581 |
+
setTimeout(() => {
|
| 582 |
+
const firstInput = modal.querySelector('input[required]');
|
| 583 |
+
if (firstInput) {
|
| 584 |
+
firstInput.focus();
|
| 585 |
+
firstInput.select();
|
| 586 |
+
}
|
| 587 |
+
}, 300);
|
| 588 |
+
});
|
| 589 |
+
}
|
| 590 |
+
|
| 591 |
+
function populateForm(professional) {
|
| 592 |
+
// Clear all fields first
|
| 593 |
+
const form = document.getElementById('professionalForm');
|
| 594 |
+
form.reset();
|
| 595 |
+
|
| 596 |
+
// Populate text fields
|
| 597 |
+
document.getElementById('username').value = professional.username || '';
|
| 598 |
+
document.getElementById('first_name').value = professional.first_name || '';
|
| 599 |
+
document.getElementById('last_name').value = professional.last_name || '';
|
| 600 |
+
document.getElementById('email').value = professional.email || '';
|
| 601 |
+
document.getElementById('phone').value = professional.phone || '';
|
| 602 |
+
document.getElementById('specialization').value = professional.specialization || '';
|
| 603 |
+
document.getElementById('experience_years').value = professional.experience_years || 0;
|
| 604 |
+
document.getElementById('district').value = professional.district || '';
|
| 605 |
+
document.getElementById('consultation_fee').value = professional.consultation_fee || '';
|
| 606 |
+
document.getElementById('bio').value = professional.bio || '';
|
| 607 |
+
|
| 608 |
+
// Clear and check expertise areas
|
| 609 |
+
const expertiseCheckboxes = document.querySelectorAll('input[name="expertise"]');
|
| 610 |
+
expertiseCheckboxes.forEach(checkbox => {
|
| 611 |
+
checkbox.checked = false;
|
| 612 |
+
});
|
| 613 |
+
|
| 614 |
+
if (professional.expertise_areas && Array.isArray(professional.expertise_areas)) {
|
| 615 |
+
professional.expertise_areas.forEach(area => {
|
| 616 |
+
const checkbox = document.querySelector(`input[name="expertise"][value="${area}"]`);
|
| 617 |
+
if (checkbox) {
|
| 618 |
+
checkbox.checked = true;
|
| 619 |
+
}
|
| 620 |
+
});
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
// Trigger validation
|
| 624 |
+
validateForm();
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
function clearFormValidation() {
|
| 628 |
+
const form = document.getElementById('professionalForm');
|
| 629 |
+
const inputs = form.querySelectorAll('.form-control');
|
| 630 |
+
inputs.forEach(input => {
|
| 631 |
+
input.classList.remove('is-valid', 'is-invalid');
|
| 632 |
+
// Ensure input is enabled and working
|
| 633 |
+
input.disabled = false;
|
| 634 |
+
input.readOnly = false;
|
| 635 |
+
});
|
| 636 |
+
}
|
| 637 |
+
|
| 638 |
+
function ensureInputsWorking() {
|
| 639 |
+
const form = document.getElementById('professionalForm');
|
| 640 |
+
const inputs = form.querySelectorAll('input, select, textarea');
|
| 641 |
+
|
| 642 |
+
inputs.forEach(input => {
|
| 643 |
+
// Ensure all inputs are enabled
|
| 644 |
+
input.disabled = false;
|
| 645 |
+
input.readOnly = false;
|
| 646 |
+
|
| 647 |
+
// Add click handler to ensure focus
|
| 648 |
+
input.addEventListener('click', function() {
|
| 649 |
+
this.focus();
|
| 650 |
+
});
|
| 651 |
+
|
| 652 |
+
// Add keydown handler to ensure typing works
|
| 653 |
+
input.addEventListener('keydown', function(e) {
|
| 654 |
+
// Allow all normal typing
|
| 655 |
+
if (e.key.length === 1 || e.key === 'Backspace' || e.key === 'Delete' || e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
|
| 656 |
+
return true;
|
| 657 |
+
}
|
| 658 |
+
});
|
| 659 |
+
});
|
| 660 |
+
}
|
| 661 |
+
|
| 662 |
+
function validateForm() {
|
| 663 |
+
const form = document.getElementById('professionalForm');
|
| 664 |
+
const requiredFields = form.querySelectorAll('[required]');
|
| 665 |
+
let isValid = true;
|
| 666 |
+
|
| 667 |
+
requiredFields.forEach(field => {
|
| 668 |
+
const value = field.value.trim();
|
| 669 |
+
if (!value) {
|
| 670 |
+
field.classList.add('is-invalid');
|
| 671 |
+
field.classList.remove('is-valid');
|
| 672 |
+
isValid = false;
|
| 673 |
+
} else {
|
| 674 |
+
field.classList.remove('is-invalid');
|
| 675 |
+
field.classList.add('is-valid');
|
| 676 |
+
}
|
| 677 |
+
});
|
| 678 |
+
|
| 679 |
+
// Check if at least one expertise area is selected
|
| 680 |
+
const expertiseCheckboxes = form.querySelectorAll('input[name="expertise"]');
|
| 681 |
+
const expertiseSelected = Array.from(expertiseCheckboxes).some(cb => cb.checked);
|
| 682 |
+
|
| 683 |
+
if (!expertiseSelected) {
|
| 684 |
+
// Add visual indicator for expertise requirement
|
| 685 |
+
const expertiseContainer = form.querySelector('label[for="expertise_areas"]');
|
| 686 |
+
if (expertiseContainer) {
|
| 687 |
+
expertiseContainer.style.color = '#dc3545';
|
| 688 |
+
}
|
| 689 |
+
isValid = false;
|
| 690 |
+
} else {
|
| 691 |
+
const expertiseContainer = form.querySelector('label[for="expertise_areas"]');
|
| 692 |
+
if (expertiseContainer) {
|
| 693 |
+
expertiseContainer.style.color = '';
|
| 694 |
+
}
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
// Enable/disable submit button
|
| 698 |
+
const submitBtn = form.querySelector('button[type="submit"]');
|
| 699 |
+
if (submitBtn) {
|
| 700 |
+
submitBtn.disabled = !isValid;
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
return isValid;
|
| 704 |
+
}
|
| 705 |
+
|
| 706 |
+
professionalForm.addEventListener('submit', async (e) => {
|
| 707 |
+
e.preventDefault();
|
| 708 |
+
|
| 709 |
+
// Validate form before submission
|
| 710 |
+
if (!validateForm()) {
|
| 711 |
+
showMessage('Please fill in all required fields', 'error');
|
| 712 |
+
return;
|
| 713 |
+
}
|
| 714 |
+
|
| 715 |
+
// Show loading state
|
| 716 |
+
const submitBtn = professionalForm.querySelector('button[type="submit"]');
|
| 717 |
+
const originalText = submitBtn.textContent;
|
| 718 |
+
submitBtn.disabled = true;
|
| 719 |
+
submitBtn.textContent = 'Saving...';
|
| 720 |
+
|
| 721 |
+
try {
|
| 722 |
+
const formData = new FormData(professionalForm);
|
| 723 |
+
const data = Object.fromEntries(formData.entries());
|
| 724 |
+
|
| 725 |
+
// Get expertise areas
|
| 726 |
+
const expertiseAreas = Array.from(document.querySelectorAll('input[name="expertise"]:checked'))
|
| 727 |
+
.map(cb => cb.value);
|
| 728 |
+
|
| 729 |
+
const professionalData = {
|
| 730 |
+
...data,
|
| 731 |
+
expertise_areas: expertiseAreas,
|
| 732 |
+
languages: ['english'], // Default for now
|
| 733 |
+
qualifications: [], // Default for now
|
| 734 |
+
availability_schedule: {} // Default for now
|
| 735 |
+
};
|
| 736 |
+
|
| 737 |
+
// Remove empty password for updates
|
| 738 |
+
if (professionalForm.dataset.professionalId && !professionalData.password) {
|
| 739 |
+
delete professionalData.password;
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
+
const professionalId = professionalForm.dataset.professionalId;
|
| 743 |
+
let response;
|
| 744 |
+
|
| 745 |
+
if (professionalId) {
|
| 746 |
+
// Update existing professional
|
| 747 |
+
response = await api(`/admin/professionals/${professionalId}`, {
|
| 748 |
+
method: 'PUT',
|
| 749 |
+
body: JSON.stringify(professionalData)
|
| 750 |
+
});
|
| 751 |
+
|
| 752 |
+
if (response && response.success) {
|
| 753 |
+
showMessage('Professional updated successfully', 'success');
|
| 754 |
+
showToast('Professional updated', 'success');
|
| 755 |
+
closeModal();
|
| 756 |
+
loadProfessionals();
|
| 757 |
+
} else {
|
| 758 |
+
showMessage(response?.error || 'Failed to update professional', 'error');
|
| 759 |
+
}
|
| 760 |
+
} else {
|
| 761 |
+
// Create new professional
|
| 762 |
+
response = await api('/admin/professionals', {
|
| 763 |
+
method: 'POST',
|
| 764 |
+
body: JSON.stringify(professionalData)
|
| 765 |
+
});
|
| 766 |
+
|
| 767 |
+
if (response && response.success) {
|
| 768 |
+
showMessage('Professional created successfully', 'success');
|
| 769 |
+
showToast('Professional created', 'success');
|
| 770 |
+
closeModal();
|
| 771 |
+
loadProfessionals();
|
| 772 |
+
} else {
|
| 773 |
+
showMessage(response?.error || 'Failed to create professional', 'error');
|
| 774 |
+
}
|
| 775 |
+
}
|
| 776 |
+
} catch (error) {
|
| 777 |
+
console.error('Failed to save professional:', error);
|
| 778 |
+
const errorMessage = professionalForm.dataset.professionalId ?
|
| 779 |
+
'Failed to update professional' : 'Failed to create professional';
|
| 780 |
+
showMessage(errorMessage, 'error');
|
| 781 |
+
showToast(errorMessage, 'error');
|
| 782 |
+
} finally {
|
| 783 |
+
// Reset button state
|
| 784 |
+
submitBtn.disabled = false;
|
| 785 |
+
submitBtn.textContent = originalText;
|
| 786 |
+
}
|
| 787 |
+
});
|
| 788 |
+
|
| 789 |
+
// Modal Management
|
| 790 |
+
function closeModal() {
|
| 791 |
+
// Close all Bootstrap modals properly
|
| 792 |
+
$('.modal').modal('hide');
|
| 793 |
+
|
| 794 |
+
// Clear form validation states
|
| 795 |
+
setTimeout(() => {
|
| 796 |
+
clearFormValidation();
|
| 797 |
+
}, 300);
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
+
// Enhanced modal event handlers
|
| 801 |
+
document.querySelectorAll('.close').forEach(closeBtn => {
|
| 802 |
+
closeBtn.addEventListener('click', (e) => {
|
| 803 |
+
e.preventDefault();
|
| 804 |
+
closeModal();
|
| 805 |
+
});
|
| 806 |
+
});
|
| 807 |
+
|
| 808 |
+
// Handle cancel button
|
| 809 |
+
document.querySelectorAll('[data-dismiss="modal"]').forEach(cancelBtn => {
|
| 810 |
+
cancelBtn.addEventListener('click', (e) => {
|
| 811 |
+
e.preventDefault();
|
| 812 |
+
closeModal();
|
| 813 |
+
});
|
| 814 |
+
});
|
| 815 |
+
|
| 816 |
+
// Handle modal backdrop clicks
|
| 817 |
+
document.querySelectorAll('.modal').forEach(modal => {
|
| 818 |
+
modal.addEventListener('click', (e) => {
|
| 819 |
+
if (e.target === modal) {
|
| 820 |
+
closeModal();
|
| 821 |
+
}
|
| 822 |
+
});
|
| 823 |
+
});
|
| 824 |
+
|
| 825 |
+
// Handle modal close on escape key
|
| 826 |
+
document.addEventListener('keydown', (e) => {
|
| 827 |
+
if (e.key === 'Escape') {
|
| 828 |
+
const openModal = document.querySelector('.modal.show');
|
| 829 |
+
if (openModal) {
|
| 830 |
+
closeModal();
|
| 831 |
+
}
|
| 832 |
+
}
|
| 833 |
+
});
|
| 834 |
+
|
| 835 |
+
// Handle modal hidden event to reset form
|
| 836 |
+
document.getElementById('professionalModal').addEventListener('hidden.bs.modal', function() {
|
| 837 |
+
const form = document.getElementById('professionalForm');
|
| 838 |
+
form.reset();
|
| 839 |
+
clearFormValidation();
|
| 840 |
+
delete form.dataset.professionalId;
|
| 841 |
+
});
|
| 842 |
+
|
| 843 |
+
// Add comprehensive form event listeners
|
| 844 |
+
function setupFormEventListeners() {
|
| 845 |
+
const form = document.getElementById('professionalForm');
|
| 846 |
+
|
| 847 |
+
// Remove existing listeners to prevent duplicates
|
| 848 |
+
form.removeEventListener('input', handleFormInput);
|
| 849 |
+
form.removeEventListener('blur', handleFormBlur);
|
| 850 |
+
form.removeEventListener('change', handleFormChange);
|
| 851 |
+
|
| 852 |
+
// Add new listeners
|
| 853 |
+
form.addEventListener('input', handleFormInput);
|
| 854 |
+
form.addEventListener('blur', handleFormBlur, true);
|
| 855 |
+
form.addEventListener('change', handleFormChange);
|
| 856 |
+
}
|
| 857 |
+
|
| 858 |
+
function handleFormInput(e) {
|
| 859 |
+
// Real-time validation on input
|
| 860 |
+
validateForm();
|
| 861 |
+
|
| 862 |
+
// Ensure input is working
|
| 863 |
+
if (e.target.type === 'text' || e.target.type === 'email' || e.target.type === 'tel' || e.target.type === 'password') {
|
| 864 |
+
e.target.classList.remove('is-invalid');
|
| 865 |
+
if (e.target.value.trim()) {
|
| 866 |
+
e.target.classList.add('is-valid');
|
| 867 |
+
}
|
| 868 |
+
}
|
| 869 |
+
}
|
| 870 |
+
|
| 871 |
+
function handleFormBlur(e) {
|
| 872 |
+
// Validation on blur for better UX
|
| 873 |
+
if (e.target.classList.contains('form-control')) {
|
| 874 |
+
validateForm();
|
| 875 |
+
}
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
+
function handleFormChange(e) {
|
| 879 |
+
// Handle expertise area changes
|
| 880 |
+
if (e.target.name === 'expertise') {
|
| 881 |
+
validateForm();
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
// Handle specialization changes
|
| 885 |
+
if (e.target.name === 'specialization') {
|
| 886 |
+
validateForm();
|
| 887 |
+
}
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
// Initialize form event listeners
|
| 891 |
+
setupFormEventListeners();
|
| 892 |
+
|
| 893 |
+
// Debug function to check input functionality
|
| 894 |
+
function debugInputs() {
|
| 895 |
+
const form = document.getElementById('professionalForm');
|
| 896 |
+
const inputs = form.querySelectorAll('input, select, textarea');
|
| 897 |
+
|
| 898 |
+
console.log('Debugging form inputs:');
|
| 899 |
+
inputs.forEach((input, index) => {
|
| 900 |
+
console.log(`Input ${index}:`, {
|
| 901 |
+
type: input.type,
|
| 902 |
+
name: input.name,
|
| 903 |
+
id: input.id,
|
| 904 |
+
disabled: input.disabled,
|
| 905 |
+
readOnly: input.readOnly,
|
| 906 |
+
value: input.value,
|
| 907 |
+
style: input.style.cssText
|
| 908 |
+
});
|
| 909 |
+
});
|
| 910 |
+
}
|
| 911 |
+
|
| 912 |
+
// Add global debug function
|
| 913 |
+
window.debugFormInputs = debugInputs;
|
| 914 |
+
|
| 915 |
+
// Event Listeners
|
| 916 |
+
statusFilter.addEventListener('change', loadBookings);
|
| 917 |
+
riskLevelFilter.addEventListener('change', loadBookings);
|
| 918 |
+
// Professional search with debounce
|
| 919 |
+
const professionalSearch = document.getElementById('professionalSearch');
|
| 920 |
+
if (professionalSearch) {
|
| 921 |
+
professionalSearch.addEventListener('input', debounce(() => {
|
| 922 |
+
const q = professionalSearch.value.toLowerCase().trim();
|
| 923 |
+
filteredProfessionals = professionals.filter(p => (
|
| 924 |
+
`${p.first_name} ${p.last_name}`.toLowerCase().includes(q) ||
|
| 925 |
+
(p.specialization || '').toLowerCase().includes(q) ||
|
| 926 |
+
(p.email || '').toLowerCase().includes(q)
|
| 927 |
+
));
|
| 928 |
+
professionalsPage = 1;
|
| 929 |
+
renderProfessionals();
|
| 930 |
+
}, 250));
|
| 931 |
+
}
|
| 932 |
+
refreshStatsBtn.addEventListener('click', () => {
|
| 933 |
+
loadRiskStats();
|
| 934 |
+
loadRecentAssessments();
|
| 935 |
+
});
|
| 936 |
+
|
| 937 |
+
logoutBtn.addEventListener('click', () => {
|
| 938 |
+
if (confirm('Are you sure you want to logout?')) {
|
| 939 |
+
localStorage.removeItem('aimhsa_admin');
|
| 940 |
+
localStorage.removeItem('aimhsa_account');
|
| 941 |
+
localStorage.removeItem('aimhsa_professional');
|
| 942 |
+
window.location.href = '/login';
|
| 943 |
+
}
|
| 944 |
+
});
|
| 945 |
+
|
| 946 |
+
refreshStatsBtn.addEventListener('click', () => {
|
| 947 |
+
loadRiskStats();
|
| 948 |
+
loadRecentAssessments();
|
| 949 |
+
});
|
| 950 |
+
|
| 951 |
+
// Global Functions (for onclick handlers)
|
| 952 |
+
window.editProfessional = (id) => {
|
| 953 |
+
const professional = professionals.find(p => p.id === id);
|
| 954 |
+
if (professional) {
|
| 955 |
+
openProfessionalModal(professional);
|
| 956 |
+
}
|
| 957 |
+
};
|
| 958 |
+
|
| 959 |
+
window.toggleProfessionalStatus = async (id, currentStatus) => {
|
| 960 |
+
try {
|
| 961 |
+
const res = await api(`/admin/professionals/${id}/status`, {
|
| 962 |
+
method: 'POST',
|
| 963 |
+
headers: { 'Content-Type': 'application/json' },
|
| 964 |
+
body: JSON.stringify({ is_active: !currentStatus })
|
| 965 |
+
});
|
| 966 |
+
|
| 967 |
+
if (res && res.success) {
|
| 968 |
+
showToast(`Professional ${!currentStatus ? 'activated' : 'deactivated'}`, 'success');
|
| 969 |
+
await loadProfessionals();
|
| 970 |
+
} else {
|
| 971 |
+
showMessage(res?.error || 'Could not update status', 'error');
|
| 972 |
+
}
|
| 973 |
+
} catch (error) {
|
| 974 |
+
console.error('Failed to toggle status:', error);
|
| 975 |
+
showMessage('Failed to toggle status', 'error');
|
| 976 |
+
}
|
| 977 |
+
};
|
| 978 |
+
|
| 979 |
+
window.deleteProfessional = async (id, name) => {
|
| 980 |
+
if (!confirm(`Are you sure you want to delete "${name}"? This action cannot be undone.`)) {
|
| 981 |
+
return;
|
| 982 |
+
}
|
| 983 |
+
|
| 984 |
+
try {
|
| 985 |
+
const res = await api(`/admin/professionals/${id}`, {
|
| 986 |
+
method: 'DELETE'
|
| 987 |
+
});
|
| 988 |
+
|
| 989 |
+
if (res && res.success) {
|
| 990 |
+
showToast(`Professional "${name}" deleted successfully`, 'success');
|
| 991 |
+
await loadProfessionals();
|
| 992 |
+
} else {
|
| 993 |
+
showMessage(res?.error || 'Failed to delete professional', 'error');
|
| 994 |
+
}
|
| 995 |
+
} catch (error) {
|
| 996 |
+
console.error('Failed to delete professional:', error);
|
| 997 |
+
const errorText = await error.text?.() || error.message || 'Failed to delete professional';
|
| 998 |
+
showMessage(errorText, 'error');
|
| 999 |
+
}
|
| 1000 |
+
};
|
| 1001 |
+
|
| 1002 |
+
window.viewBookingDetails = (bookingId) => {
|
| 1003 |
+
const booking = bookings.find(b => b.booking_id === bookingId);
|
| 1004 |
+
if (booking) {
|
| 1005 |
+
const modal = document.getElementById('bookingModal');
|
| 1006 |
+
const details = document.getElementById('bookingDetails');
|
| 1007 |
+
|
| 1008 |
+
const scheduledTime = new Date(booking.scheduled_datetime * 1000).toLocaleString();
|
| 1009 |
+
const userInfo = booking.user_account || `IP: ${booking.user_ip}`;
|
| 1010 |
+
const indicators = booking.detected_indicators.join(', ');
|
| 1011 |
+
|
| 1012 |
+
details.innerHTML = `
|
| 1013 |
+
<div class="booking-detail">
|
| 1014 |
+
<h3>Booking Information</h3>
|
| 1015 |
+
<p><strong>Booking ID:</strong> ${booking.booking_id}</p>
|
| 1016 |
+
<p><strong>User:</strong> ${userInfo}</p>
|
| 1017 |
+
<p><strong>Professional:</strong> ${booking.first_name} ${booking.last_name}</p>
|
| 1018 |
+
<p><strong>Specialization:</strong> ${booking.specialization}</p>
|
| 1019 |
+
<p><strong>Risk Level:</strong> <span class="risk-badge risk-${booking.risk_level}">${booking.risk_level.toUpperCase()}</span></p>
|
| 1020 |
+
<p><strong>Risk Score:</strong> ${(booking.risk_score * 100).toFixed(1)}%</p>
|
| 1021 |
+
<p><strong>Scheduled Time:</strong> ${scheduledTime}</p>
|
| 1022 |
+
<p><strong>Session Type:</strong> ${booking.session_type}</p>
|
| 1023 |
+
<p><strong>Status:</strong> <span class="status-badge status-${booking.booking_status}">${booking.booking_status.toUpperCase()}</span></p>
|
| 1024 |
+
</div>
|
| 1025 |
+
|
| 1026 |
+
<div class="booking-detail">
|
| 1027 |
+
<h3>Risk Indicators</h3>
|
| 1028 |
+
<p>${indicators}</p>
|
| 1029 |
+
</div>
|
| 1030 |
+
|
| 1031 |
+
<div class="booking-detail">
|
| 1032 |
+
<h3>Conversation Summary</h3>
|
| 1033 |
+
<p>${booking.conversation_summary || 'No summary available'}</p>
|
| 1034 |
+
</div>
|
| 1035 |
+
`;
|
| 1036 |
+
|
| 1037 |
+
modal.style.display = 'block';
|
| 1038 |
+
}
|
| 1039 |
+
};
|
| 1040 |
+
|
| 1041 |
+
// Initialize
|
| 1042 |
+
loadProfessionals();
|
| 1043 |
+
|
| 1044 |
+
// Auto-refresh risk stats every 30 seconds
|
| 1045 |
+
setInterval(() => {
|
| 1046 |
+
if (document.querySelector('#risk-monitor').classList.contains('active')) {
|
| 1047 |
+
loadRiskStats();
|
| 1048 |
+
loadRecentAssessments();
|
| 1049 |
+
}
|
| 1050 |
+
}, 30000);
|
| 1051 |
+
|
| 1052 |
+
// AdminLTE 4 Enhancements
|
| 1053 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 1054 |
+
// Initialize AdminLTE components
|
| 1055 |
+
if (typeof $ !== 'undefined' && $.fn.DataTable) {
|
| 1056 |
+
// Initialize DataTables if available
|
| 1057 |
+
initializeDataTables();
|
| 1058 |
+
}
|
| 1059 |
+
|
| 1060 |
+
// Initialize mobile menu toggle
|
| 1061 |
+
initializeMobileMenu();
|
| 1062 |
+
|
| 1063 |
+
// Initialize tooltips
|
| 1064 |
+
initializeTooltips();
|
| 1065 |
+
|
| 1066 |
+
// Initialize loading states
|
| 1067 |
+
initializeLoadingStates();
|
| 1068 |
+
|
| 1069 |
+
// Initialize animations
|
| 1070 |
+
initializeAnimations();
|
| 1071 |
+
});
|
| 1072 |
+
|
| 1073 |
+
function initializeDataTables() {
|
| 1074 |
+
// Enhanced table functionality with AdminLTE styling
|
| 1075 |
+
const tables = document.querySelectorAll('table');
|
| 1076 |
+
tables.forEach(table => {
|
| 1077 |
+
if (table.id === 'bookingsTable') {
|
| 1078 |
+
// Add DataTables to bookings table if jQuery and DataTables are available
|
| 1079 |
+
if (typeof $ !== 'undefined' && $.fn.DataTable) {
|
| 1080 |
+
$(table).DataTable({
|
| 1081 |
+
responsive: true,
|
| 1082 |
+
pageLength: 25,
|
| 1083 |
+
order: [[4, 'desc']], // Sort by scheduled time
|
| 1084 |
+
columnDefs: [
|
| 1085 |
+
{ targets: [6], orderable: false } // Actions column
|
| 1086 |
+
],
|
| 1087 |
+
language: {
|
| 1088 |
+
search: "Search bookings:",
|
| 1089 |
+
lengthMenu: "Show _MENU_ bookings per page",
|
| 1090 |
+
info: "Showing _START_ to _END_ of _TOTAL_ bookings",
|
| 1091 |
+
paginate: {
|
| 1092 |
+
first: "First",
|
| 1093 |
+
last: "Last",
|
| 1094 |
+
next: "Next",
|
| 1095 |
+
previous: "Previous"
|
| 1096 |
+
}
|
| 1097 |
+
}
|
| 1098 |
+
});
|
| 1099 |
+
}
|
| 1100 |
+
}
|
| 1101 |
+
});
|
| 1102 |
+
}
|
| 1103 |
+
|
| 1104 |
+
function initializeMobileMenu() {
|
| 1105 |
+
const mobileToggle = document.getElementById('mobileMenuToggle');
|
| 1106 |
+
const adminNav = document.querySelector('.admin-nav');
|
| 1107 |
+
|
| 1108 |
+
if (mobileToggle && adminNav) {
|
| 1109 |
+
mobileToggle.addEventListener('click', function() {
|
| 1110 |
+
adminNav.classList.toggle('mobile-open');
|
| 1111 |
+
});
|
| 1112 |
+
|
| 1113 |
+
// Close mobile menu when clicking outside
|
| 1114 |
+
document.addEventListener('click', function(e) {
|
| 1115 |
+
if (!adminNav.contains(e.target) && !mobileToggle.contains(e.target)) {
|
| 1116 |
+
adminNav.classList.remove('mobile-open');
|
| 1117 |
+
}
|
| 1118 |
+
});
|
| 1119 |
+
}
|
| 1120 |
+
}
|
| 1121 |
+
|
| 1122 |
+
function initializeTooltips() {
|
| 1123 |
+
// Initialize Bootstrap tooltips if available
|
| 1124 |
+
if (typeof $ !== 'undefined' && $.fn.tooltip) {
|
| 1125 |
+
$('[data-toggle="tooltip"]').tooltip();
|
| 1126 |
+
}
|
| 1127 |
+
}
|
| 1128 |
+
|
| 1129 |
+
function initializeLoadingStates() {
|
| 1130 |
+
// Add loading states to buttons and forms
|
| 1131 |
+
const forms = document.querySelectorAll('form');
|
| 1132 |
+
forms.forEach(form => {
|
| 1133 |
+
form.addEventListener('submit', function() {
|
| 1134 |
+
const submitBtn = form.querySelector('button[type="submit"]');
|
| 1135 |
+
if (submitBtn) {
|
| 1136 |
+
submitBtn.classList.add('loading');
|
| 1137 |
+
submitBtn.disabled = true;
|
| 1138 |
+
|
| 1139 |
+
// Remove loading state after 3 seconds (adjust as needed)
|
| 1140 |
+
setTimeout(() => {
|
| 1141 |
+
submitBtn.classList.remove('loading');
|
| 1142 |
+
submitBtn.disabled = false;
|
| 1143 |
+
}, 3000);
|
| 1144 |
+
}
|
| 1145 |
+
});
|
| 1146 |
+
});
|
| 1147 |
+
}
|
| 1148 |
+
|
| 1149 |
+
function initializeAnimations() {
|
| 1150 |
+
// Add fade-in animation to cards
|
| 1151 |
+
const cards = document.querySelectorAll('.kpi-card, .stat-card, .analytics-card, .rag-card');
|
| 1152 |
+
cards.forEach((card, index) => {
|
| 1153 |
+
card.classList.add('fade-in');
|
| 1154 |
+
card.style.animationDelay = `${index * 0.1}s`;
|
| 1155 |
+
});
|
| 1156 |
+
|
| 1157 |
+
// Add slide-in animation to sections
|
| 1158 |
+
const sections = document.querySelectorAll('.admin-section');
|
| 1159 |
+
sections.forEach((section, index) => {
|
| 1160 |
+
section.classList.add('slide-in');
|
| 1161 |
+
section.style.animationDelay = `${index * 0.2}s`;
|
| 1162 |
+
});
|
| 1163 |
+
}
|
| 1164 |
+
|
| 1165 |
+
// Enhanced notification system using AdminLTE toast
|
| 1166 |
+
function showAdminLTEMessage(text, type = 'error') {
|
| 1167 |
+
if (typeof $ !== 'undefined' && $.fn.toast) {
|
| 1168 |
+
// Use AdminLTE toast if available
|
| 1169 |
+
const toastHtml = `
|
| 1170 |
+
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
| 1171 |
+
<div class="toast-header">
|
| 1172 |
+
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-triangle'} mr-2"></i>
|
| 1173 |
+
<strong class="mr-auto">AIMHSA Admin</strong>
|
| 1174 |
+
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast">
|
| 1175 |
+
<span aria-hidden="true">×</span>
|
| 1176 |
+
</button>
|
| 1177 |
+
</div>
|
| 1178 |
+
<div class="toast-body">
|
| 1179 |
+
${text}
|
| 1180 |
+
</div>
|
| 1181 |
+
</div>
|
| 1182 |
+
`;
|
| 1183 |
+
|
| 1184 |
+
// Create toast container if it doesn't exist
|
| 1185 |
+
let toastContainer = document.querySelector('.toast-container');
|
| 1186 |
+
if (!toastContainer) {
|
| 1187 |
+
toastContainer = document.createElement('div');
|
| 1188 |
+
toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3';
|
| 1189 |
+
toastContainer.style.zIndex = '9999';
|
| 1190 |
+
document.body.appendChild(toastContainer);
|
| 1191 |
+
}
|
| 1192 |
+
|
| 1193 |
+
// Add toast to container
|
| 1194 |
+
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
|
| 1195 |
+
|
| 1196 |
+
// Initialize and show toast
|
| 1197 |
+
const toastElement = toastContainer.lastElementChild;
|
| 1198 |
+
$(toastElement).toast({
|
| 1199 |
+
autohide: true,
|
| 1200 |
+
delay: 5000
|
| 1201 |
+
});
|
| 1202 |
+
$(toastElement).toast('show');
|
| 1203 |
+
|
| 1204 |
+
// Remove toast after it's hidden
|
| 1205 |
+
$(toastElement).on('hidden.bs.toast', function() {
|
| 1206 |
+
$(this).remove();
|
| 1207 |
+
});
|
| 1208 |
+
} else {
|
| 1209 |
+
// Fallback to original message system
|
| 1210 |
+
showMessage(text, type);
|
| 1211 |
+
}
|
| 1212 |
+
}
|
| 1213 |
+
|
| 1214 |
+
// Override the original showMessage function with AdminLTE version
|
| 1215 |
+
window.showMessage = showAdminLTEMessage;
|
| 1216 |
+
|
| 1217 |
+
// Enhanced refresh functionality
|
| 1218 |
+
function refreshAllData() {
|
| 1219 |
+
const refreshBtn = document.getElementById('refreshAllBtn');
|
| 1220 |
+
if (refreshBtn) {
|
| 1221 |
+
refreshBtn.classList.add('loading');
|
| 1222 |
+
refreshBtn.disabled = true;
|
| 1223 |
+
|
| 1224 |
+
// Refresh all data
|
| 1225 |
+
Promise.all([
|
| 1226 |
+
loadProfessionals(),
|
| 1227 |
+
loadBookings(),
|
| 1228 |
+
loadRiskStats(),
|
| 1229 |
+
loadRecentAssessments()
|
| 1230 |
+
]).finally(() => {
|
| 1231 |
+
refreshBtn.classList.remove('loading');
|
| 1232 |
+
refreshBtn.disabled = false;
|
| 1233 |
+
showAdminLTEMessage('All data refreshed successfully', 'success');
|
| 1234 |
+
});
|
| 1235 |
+
}
|
| 1236 |
+
}
|
| 1237 |
+
|
| 1238 |
+
// Add refresh button event listener
|
| 1239 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 1240 |
+
const refreshBtn = document.getElementById('refreshAllBtn');
|
| 1241 |
+
if (refreshBtn) {
|
| 1242 |
+
refreshBtn.addEventListener('click', refreshAllData);
|
| 1243 |
+
}
|
| 1244 |
+
});
|
| 1245 |
+
|
| 1246 |
+
// Update all admin API calls
|
| 1247 |
+
async function getAdminStats() {
|
| 1248 |
+
const response = await fetch(`${API_BASE_URL}/admin/dashboard-stats`);
|
| 1249 |
+
return await response.json();
|
| 1250 |
+
}
|
| 1251 |
+
|
| 1252 |
+
async function listProfessionals() {
|
| 1253 |
+
const response = await fetch(`${API_BASE_URL}/admin/professionals`);
|
| 1254 |
+
return await response.json();
|
| 1255 |
+
}
|
| 1256 |
+
|
| 1257 |
+
async function listBookings() {
|
| 1258 |
+
const response = await fetch(`${API_BASE_URL}/admin/bookings`);
|
| 1259 |
+
return await response.json();
|
| 1260 |
+
}
|
| 1261 |
+
|
| 1262 |
+
async function listUsers() {
|
| 1263 |
+
const response = await fetch(`${API_BASE_URL}/admin/users`);
|
| 1264 |
+
return await response.json();
|
| 1265 |
+
}
|
| 1266 |
+
|
| 1267 |
+
async function testSMSService(phone, message) {
|
| 1268 |
+
const response = await fetch(`${API_BASE_URL}/admin/sms/test`, {
|
| 1269 |
+
method: 'POST',
|
| 1270 |
+
headers: { 'Content-Type': 'application/json' },
|
| 1271 |
+
body: JSON.stringify({ phone, message })
|
| 1272 |
+
});
|
| 1273 |
+
return await response.json();
|
| 1274 |
+
}
|
| 1275 |
+
|
| 1276 |
+
async function getSMSStatus() {
|
| 1277 |
+
const response = await fetch(`${API_BASE_URL}/admin/sms/status`);
|
| 1278 |
+
return await response.json();
|
| 1279 |
+
}
|
| 1280 |
+
|
| 1281 |
+
})();
|
chatbot/admin_advanced.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
chatbot/admin_dashboard.html
ADDED
|
@@ -0,0 +1,1138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>AIMHSA Advanced Admin Dashboard</title>
|
| 7 |
+
|
| 8 |
+
<!-- AdminLTE 4 CSS -->
|
| 9 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
| 10 |
+
<!-- Font Awesome -->
|
| 11 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 12 |
+
<!-- Google Font: Source Sans Pro -->
|
| 13 |
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
|
| 14 |
+
<!-- Chart.js -->
|
| 15 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 16 |
+
<!-- DataTables CSS -->
|
| 17 |
+
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap4.min.css">
|
| 18 |
+
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.5.0/css/responsive.bootstrap4.min.css">
|
| 19 |
+
<!-- Select2 CSS -->
|
| 20 |
+
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
| 21 |
+
<!-- SweetAlert2 CSS -->
|
| 22 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
|
| 23 |
+
<!-- FullCalendar CSS removed - not needed for admin dashboard -->
|
| 24 |
+
|
| 25 |
+
<!-- Custom CSS (preserves existing styles) -->
|
| 26 |
+
<link rel="stylesheet" href="admin.css">
|
| 27 |
+
|
| 28 |
+
<!-- Additional CSS for fixes -->
|
| 29 |
+
<style>
|
| 30 |
+
.brand-image, .user-panel .image > div {
|
| 31 |
+
border-radius: 50%;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.collapsed-card .card-body {
|
| 35 |
+
display: none;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.card.collapsed-card .card-header::after {
|
| 39 |
+
content: " (Collapsed)";
|
| 40 |
+
font-size: 0.8em;
|
| 41 |
+
color: #6c757d;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.loading-overlay {
|
| 45 |
+
position: fixed;
|
| 46 |
+
top: 0;
|
| 47 |
+
left: 0;
|
| 48 |
+
width: 100%;
|
| 49 |
+
height: 100%;
|
| 50 |
+
background: rgba(0, 0, 0, 0.5);
|
| 51 |
+
z-index: 9999;
|
| 52 |
+
display: flex;
|
| 53 |
+
align-items: center;
|
| 54 |
+
justify-content: center;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.loading-spinner {
|
| 58 |
+
text-align: center;
|
| 59 |
+
color: white;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.loading-spinner i {
|
| 63 |
+
margin-bottom: 10px;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
/* Form input fixes */
|
| 67 |
+
.form-control {
|
| 68 |
+
background-color: #fff !important;
|
| 69 |
+
border: 1px solid #ced4da !important;
|
| 70 |
+
color: #495057 !important;
|
| 71 |
+
}
|
| 72 |
+
.form-control:focus {
|
| 73 |
+
border-color: #80bdff !important;
|
| 74 |
+
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
|
| 75 |
+
}
|
| 76 |
+
.form-control.is-invalid {
|
| 77 |
+
border-color: #dc3545 !important;
|
| 78 |
+
}
|
| 79 |
+
.form-check-input {
|
| 80 |
+
margin-top: 0.25rem !important;
|
| 81 |
+
}
|
| 82 |
+
.modal-body .form-control {
|
| 83 |
+
background-color: #fff !important;
|
| 84 |
+
color: #495057 !important;
|
| 85 |
+
}
|
| 86 |
+
.modal-body .form-control:disabled {
|
| 87 |
+
background-color: #e9ecef !important;
|
| 88 |
+
color: #6c757d !important;
|
| 89 |
+
}
|
| 90 |
+
</style>
|
| 91 |
+
|
| 92 |
+
<!-- Configuration Management -->
|
| 93 |
+
<script src="config.js"></script>
|
| 94 |
+
<script src="config-ui.js"></script>
|
| 95 |
+
</head>
|
| 96 |
+
<body class="hold-transition sidebar-mini layout-fixed">
|
| 97 |
+
<div class="wrapper">
|
| 98 |
+
<!-- Navbar -->
|
| 99 |
+
<nav class="main-header navbar navbar-expand navbar-dark navbar-primary">
|
| 100 |
+
<!-- Left navbar links -->
|
| 101 |
+
<ul class="navbar-nav">
|
| 102 |
+
<li class="nav-item">
|
| 103 |
+
<a class="nav-link" data-widget="pushmenu" href="#" role="button">
|
| 104 |
+
<i class="fas fa-bars"></i>
|
| 105 |
+
</a>
|
| 106 |
+
</li>
|
| 107 |
+
<li class="nav-item d-none d-sm-inline-block">
|
| 108 |
+
<a href="#" class="nav-link">Dashboard</a>
|
| 109 |
+
</li>
|
| 110 |
+
<li class="nav-item d-none d-sm-inline-block">
|
| 111 |
+
<a href="#" class="nav-link">Reports</a>
|
| 112 |
+
</li>
|
| 113 |
+
</ul>
|
| 114 |
+
|
| 115 |
+
<!-- Right navbar links -->
|
| 116 |
+
<ul class="navbar-nav ml-auto">
|
| 117 |
+
<!-- Refresh Button -->
|
| 118 |
+
<li class="nav-item">
|
| 119 |
+
<a class="nav-link" href="#" id="refreshAllBtn" title="Refresh All Data">
|
| 120 |
+
<i class="fas fa-sync-alt"></i>
|
| 121 |
+
</a>
|
| 122 |
+
</li>
|
| 123 |
+
<!-- Notifications Dropdown Menu -->
|
| 124 |
+
<li class="nav-item dropdown">
|
| 125 |
+
<a class="nav-link" data-toggle="dropdown" href="#">
|
| 126 |
+
<i class="far fa-bell"></i>
|
| 127 |
+
<span class="badge badge-warning navbar-badge" id="notificationBadge">15</span>
|
| 128 |
+
</a>
|
| 129 |
+
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right notifications-menu">
|
| 130 |
+
<span class="dropdown-item dropdown-header">15 Notifications</span>
|
| 131 |
+
<div class="dropdown-divider"></div>
|
| 132 |
+
<a href="#" class="dropdown-item">
|
| 133 |
+
<i class="fas fa-envelope mr-2"></i> 4 new messages
|
| 134 |
+
<span class="float-right text-muted text-sm">3 mins</span>
|
| 135 |
+
</a>
|
| 136 |
+
<div class="dropdown-divider"></div>
|
| 137 |
+
<a href="#" class="dropdown-item">
|
| 138 |
+
<i class="fas fa-users mr-2"></i> 8 friend requests
|
| 139 |
+
<span class="float-right text-muted text-sm">12 hours</span>
|
| 140 |
+
</a>
|
| 141 |
+
<div class="dropdown-divider"></div>
|
| 142 |
+
<a href="#" class="dropdown-item">
|
| 143 |
+
<i class="fas fa-file mr-2"></i> 3 new reports
|
| 144 |
+
<span class="float-right text-muted text-sm">2 days</span>
|
| 145 |
+
</a>
|
| 146 |
+
<div class="dropdown-divider"></div>
|
| 147 |
+
<a href="#" class="dropdown-item dropdown-footer">See All Notifications</a>
|
| 148 |
+
</div>
|
| 149 |
+
</li>
|
| 150 |
+
|
| 151 |
+
<!-- User Account Dropdown -->
|
| 152 |
+
<li class="nav-item dropdown">
|
| 153 |
+
<a class="nav-link" data-toggle="dropdown" href="#">
|
| 154 |
+
<i class="far fa-user"></i>
|
| 155 |
+
<span class="ml-2">Admin User</span>
|
| 156 |
+
</a>
|
| 157 |
+
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
|
| 158 |
+
<a href="#" class="dropdown-item">
|
| 159 |
+
<i class="fas fa-user mr-2"></i> Profile
|
| 160 |
+
</a>
|
| 161 |
+
<a href="#" class="dropdown-item">
|
| 162 |
+
<i class="fas fa-cog mr-2"></i> Settings
|
| 163 |
+
</a>
|
| 164 |
+
<div class="dropdown-divider"></div>
|
| 165 |
+
<a href="#" class="dropdown-item" id="logoutBtn">
|
| 166 |
+
<i class="fas fa-sign-out-alt mr-2"></i> Logout
|
| 167 |
+
</a>
|
| 168 |
+
</div>
|
| 169 |
+
</li>
|
| 170 |
+
</ul>
|
| 171 |
+
</nav>
|
| 172 |
+
|
| 173 |
+
<!-- Main Sidebar Container -->
|
| 174 |
+
<aside class="main-sidebar sidebar-dark-primary elevation-4">
|
| 175 |
+
<!-- Brand Logo -->
|
| 176 |
+
<a href="#" class="brand-link">
|
| 177 |
+
<div class="brand-image img-circle elevation-3" style="opacity: .8; width: 33px; height: 33px; background: #007bff; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 14px;">AI</div>
|
| 178 |
+
<span class="brand-text font-weight-light">AIMHSA Admin</span>
|
| 179 |
+
</a>
|
| 180 |
+
|
| 181 |
+
<!-- Sidebar -->
|
| 182 |
+
<div class="sidebar">
|
| 183 |
+
<!-- Sidebar user panel -->
|
| 184 |
+
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
|
| 185 |
+
<div class="image">
|
| 186 |
+
<div class="img-circle elevation-2" style="width: 40px; height: 40px; background: #007bff; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 16px;">AD</div>
|
| 187 |
+
</div>
|
| 188 |
+
<div class="info">
|
| 189 |
+
<a href="#" class="d-block">Admin User</a>
|
| 190 |
+
<small class="text-muted">System Administrator</small>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
|
| 194 |
+
<!-- Sidebar Menu -->
|
| 195 |
+
<nav class="mt-2">
|
| 196 |
+
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
|
| 197 |
+
<li class="nav-item">
|
| 198 |
+
<a href="#dashboard" class="nav-link active" data-section="dashboard">
|
| 199 |
+
<i class="nav-icon fas fa-tachometer-alt"></i>
|
| 200 |
+
<p>Dashboard</p>
|
| 201 |
+
</a>
|
| 202 |
+
</li>
|
| 203 |
+
<li class="nav-item">
|
| 204 |
+
<a href="#professionals" class="nav-link" data-section="professionals">
|
| 205 |
+
<i class="nav-icon fas fa-user-md"></i>
|
| 206 |
+
<p>Professionals</p>
|
| 207 |
+
</a>
|
| 208 |
+
</li>
|
| 209 |
+
<li class="nav-item">
|
| 210 |
+
<a href="#bookings" class="nav-link" data-section="bookings">
|
| 211 |
+
<i class="nav-icon fas fa-calendar-check"></i>
|
| 212 |
+
<p>Bookings</p>
|
| 213 |
+
</a>
|
| 214 |
+
</li>
|
| 215 |
+
<li class="nav-item">
|
| 216 |
+
<a href="#risk-monitor" class="nav-link" data-section="risk-monitor">
|
| 217 |
+
<i class="nav-icon fas fa-exclamation-triangle"></i>
|
| 218 |
+
<p>Risk Monitor</p>
|
| 219 |
+
</a>
|
| 220 |
+
</li>
|
| 221 |
+
<li class="nav-item">
|
| 222 |
+
<a href="#analytics" class="nav-link" data-section="analytics">
|
| 223 |
+
<i class="nav-icon fas fa-chart-bar"></i>
|
| 224 |
+
<p>Analytics</p>
|
| 225 |
+
</a>
|
| 226 |
+
</li>
|
| 227 |
+
<li class="nav-item">
|
| 228 |
+
<a href="#rag-status" class="nav-link" data-section="rag-status">
|
| 229 |
+
<i class="nav-icon fas fa-brain"></i>
|
| 230 |
+
<p>RAG Status</p>
|
| 231 |
+
</a>
|
| 232 |
+
</li>
|
| 233 |
+
<li class="nav-item">
|
| 234 |
+
</li>
|
| 235 |
+
<li class="nav-item">
|
| 236 |
+
<a href="#reports" class="nav-link" data-section="reports">
|
| 237 |
+
<i class="nav-icon fas fa-file-alt"></i>
|
| 238 |
+
<p>Reports</p>
|
| 239 |
+
</a>
|
| 240 |
+
</li>
|
| 241 |
+
<li class="nav-item">
|
| 242 |
+
<a href="#settings" class="nav-link" data-section="settings">
|
| 243 |
+
<i class="nav-icon fas fa-cog"></i>
|
| 244 |
+
<p>Settings</p>
|
| 245 |
+
</a>
|
| 246 |
+
</li>
|
| 247 |
+
</ul>
|
| 248 |
+
</nav>
|
| 249 |
+
</div>
|
| 250 |
+
</aside>
|
| 251 |
+
|
| 252 |
+
<!-- Content Wrapper -->
|
| 253 |
+
<div class="content-wrapper">
|
| 254 |
+
<!-- Content Header -->
|
| 255 |
+
<div class="content-header">
|
| 256 |
+
<div class="container-fluid">
|
| 257 |
+
<div class="row mb-2">
|
| 258 |
+
<div class="col-sm-6">
|
| 259 |
+
<h1 class="m-0" id="pageTitle">Dashboard</h1>
|
| 260 |
+
</div>
|
| 261 |
+
<div class="col-sm-6">
|
| 262 |
+
<ol class="breadcrumb float-sm-right">
|
| 263 |
+
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
| 264 |
+
<li class="breadcrumb-item active" id="breadcrumbActive">Dashboard</li>
|
| 265 |
+
</ol>
|
| 266 |
+
</div>
|
| 267 |
+
</div>
|
| 268 |
+
</div>
|
| 269 |
+
</div>
|
| 270 |
+
|
| 271 |
+
<!-- Main content -->
|
| 272 |
+
<section class="content">
|
| 273 |
+
<div class="container-fluid">
|
| 274 |
+
|
| 275 |
+
<!-- Dashboard Section -->
|
| 276 |
+
<div id="dashboard-section" class="content-section">
|
| 277 |
+
<!-- Info boxes -->
|
| 278 |
+
<div class="row">
|
| 279 |
+
<div class="col-lg-3 col-6">
|
| 280 |
+
<div class="small-box bg-info">
|
| 281 |
+
<div class="inner">
|
| 282 |
+
<h3 id="kpiActiveBookings">0</h3>
|
| 283 |
+
<p>Active Bookings</p>
|
| 284 |
+
</div>
|
| 285 |
+
<div class="icon">
|
| 286 |
+
<i class="fas fa-calendar-check"></i>
|
| 287 |
+
</div>
|
| 288 |
+
<a href="#bookings" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 289 |
+
</div>
|
| 290 |
+
</div>
|
| 291 |
+
<div class="col-lg-3 col-6">
|
| 292 |
+
<div class="small-box bg-danger">
|
| 293 |
+
<div class="inner">
|
| 294 |
+
<h3 id="kpiCritical">0</h3>
|
| 295 |
+
<p>Critical Risks</p>
|
| 296 |
+
</div>
|
| 297 |
+
<div class="icon">
|
| 298 |
+
<i class="fas fa-exclamation-triangle"></i>
|
| 299 |
+
</div>
|
| 300 |
+
<a href="#risk-monitor" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 301 |
+
</div>
|
| 302 |
+
</div>
|
| 303 |
+
<div class="col-lg-3 col-6">
|
| 304 |
+
<div class="small-box bg-success">
|
| 305 |
+
<div class="inner">
|
| 306 |
+
<h3 id="kpiProfessionals">0</h3>
|
| 307 |
+
<p>Professionals</p>
|
| 308 |
+
</div>
|
| 309 |
+
<div class="icon">
|
| 310 |
+
<i class="fas fa-user-md"></i>
|
| 311 |
+
</div>
|
| 312 |
+
<a href="#professionals" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 313 |
+
</div>
|
| 314 |
+
</div>
|
| 315 |
+
<div class="col-lg-3 col-6">
|
| 316 |
+
<div class="small-box bg-warning">
|
| 317 |
+
<div class="inner">
|
| 318 |
+
<h3 id="kpiAssessments">0</h3>
|
| 319 |
+
<p>Assessments Today</p>
|
| 320 |
+
</div>
|
| 321 |
+
<div class="icon">
|
| 322 |
+
<i class="fas fa-chart-line"></i>
|
| 323 |
+
</div>
|
| 324 |
+
<a href="#analytics" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 325 |
+
</div>
|
| 326 |
+
</div>
|
| 327 |
+
</div>
|
| 328 |
+
|
| 329 |
+
<!-- Charts Row -->
|
| 330 |
+
<div class="row">
|
| 331 |
+
<div class="col-lg-8">
|
| 332 |
+
<div class="card">
|
| 333 |
+
<div class="card-header">
|
| 334 |
+
<h3 class="card-title">Risk Assessment Trends</h3>
|
| 335 |
+
<div class="card-tools">
|
| 336 |
+
<button type="button" class="btn btn-tool" data-card-widget="collapse">
|
| 337 |
+
<i class="fas fa-minus"></i>
|
| 338 |
+
</button>
|
| 339 |
+
<button type="button" class="btn btn-tool" data-card-widget="remove">
|
| 340 |
+
<i class="fas fa-times"></i>
|
| 341 |
+
</button>
|
| 342 |
+
</div>
|
| 343 |
+
</div>
|
| 344 |
+
<div class="card-body">
|
| 345 |
+
<canvas id="riskTrendChart" style="height: 300px;"></canvas>
|
| 346 |
+
</div>
|
| 347 |
+
</div>
|
| 348 |
+
</div>
|
| 349 |
+
<div class="col-lg-4">
|
| 350 |
+
<div class="card">
|
| 351 |
+
<div class="card-header">
|
| 352 |
+
<h3 class="card-title">Risk Distribution</h3>
|
| 353 |
+
</div>
|
| 354 |
+
<div class="card-body">
|
| 355 |
+
<canvas id="riskDistributionChart" style="height: 300px;"></canvas>
|
| 356 |
+
</div>
|
| 357 |
+
</div>
|
| 358 |
+
</div>
|
| 359 |
+
</div>
|
| 360 |
+
|
| 361 |
+
<!-- Recent Activity -->
|
| 362 |
+
<div class="row">
|
| 363 |
+
<div class="col-lg-6">
|
| 364 |
+
<div class="card">
|
| 365 |
+
<div class="card-header">
|
| 366 |
+
<h3 class="card-title">Recent Bookings</h3>
|
| 367 |
+
</div>
|
| 368 |
+
<div class="card-body">
|
| 369 |
+
<div class="table-responsive">
|
| 370 |
+
<table class="table table-striped">
|
| 371 |
+
<thead>
|
| 372 |
+
<tr>
|
| 373 |
+
<th>ID</th>
|
| 374 |
+
<th>User</th>
|
| 375 |
+
<th>Risk Level</th>
|
| 376 |
+
<th>Status</th>
|
| 377 |
+
</tr>
|
| 378 |
+
</thead>
|
| 379 |
+
<tbody id="recentBookingsTable">
|
| 380 |
+
<!-- Data will be loaded here -->
|
| 381 |
+
</tbody>
|
| 382 |
+
</table>
|
| 383 |
+
</div>
|
| 384 |
+
</div>
|
| 385 |
+
</div>
|
| 386 |
+
</div>
|
| 387 |
+
<div class="col-lg-6">
|
| 388 |
+
<div class="card">
|
| 389 |
+
<div class="card-header">
|
| 390 |
+
<h3 class="card-title">System Status</h3>
|
| 391 |
+
</div>
|
| 392 |
+
<div class="card-body">
|
| 393 |
+
<div class="row">
|
| 394 |
+
<div class="col-6">
|
| 395 |
+
<div class="info-box">
|
| 396 |
+
<span class="info-box-icon bg-success"><i class="fas fa-server"></i></span>
|
| 397 |
+
<div class="info-box-content">
|
| 398 |
+
<span class="info-box-text">API Status</span>
|
| 399 |
+
<span class="info-box-number">Online</span>
|
| 400 |
+
</div>
|
| 401 |
+
</div>
|
| 402 |
+
</div>
|
| 403 |
+
<div class="col-6">
|
| 404 |
+
<div class="info-box">
|
| 405 |
+
<span class="info-box-icon bg-info"><i class="fas fa-database"></i></span>
|
| 406 |
+
<div class="info-box-content">
|
| 407 |
+
<span class="info-box-text">Database</span>
|
| 408 |
+
<span class="info-box-number">Healthy</span>
|
| 409 |
+
</div>
|
| 410 |
+
</div>
|
| 411 |
+
</div>
|
| 412 |
+
</div>
|
| 413 |
+
<div class="row">
|
| 414 |
+
<div class="col-6">
|
| 415 |
+
<div class="info-box">
|
| 416 |
+
<span class="info-box-icon bg-warning"><i class="fas fa-brain"></i></span>
|
| 417 |
+
<div class="info-box-content">
|
| 418 |
+
<span class="info-box-text">AI Model</span>
|
| 419 |
+
<span class="info-box-number">Active</span>
|
| 420 |
+
</div>
|
| 421 |
+
</div>
|
| 422 |
+
</div>
|
| 423 |
+
<div class="col-6">
|
| 424 |
+
<div class="info-box">
|
| 425 |
+
<span class="info-box-icon bg-primary"><i class="fas fa-sms"></i></span>
|
| 426 |
+
<div class="info-box-content">
|
| 427 |
+
<span class="info-box-text">SMS Service</span>
|
| 428 |
+
<span class="info-box-number">Ready</span>
|
| 429 |
+
</div>
|
| 430 |
+
</div>
|
| 431 |
+
</div>
|
| 432 |
+
</div>
|
| 433 |
+
</div>
|
| 434 |
+
</div>
|
| 435 |
+
</div>
|
| 436 |
+
</div>
|
| 437 |
+
</div>
|
| 438 |
+
<!-- Professionals Section -->
|
| 439 |
+
<div id="professionals-section" class="content-section" style="display: none;">
|
| 440 |
+
<div class="row">
|
| 441 |
+
<div class="col-12">
|
| 442 |
+
<div class="card">
|
| 443 |
+
<div class="card-header">
|
| 444 |
+
<h3 class="card-title">Professional Management</h3>
|
| 445 |
+
<div class="card-tools">
|
| 446 |
+
<button type="button" class="btn btn-primary" id="addProfessionalBtn">
|
| 447 |
+
<i class="fas fa-plus"></i> Add Professional
|
| 448 |
+
</button>
|
| 449 |
+
</div>
|
| 450 |
+
</div>
|
| 451 |
+
<div class="card-body">
|
| 452 |
+
<div class="row mb-3">
|
| 453 |
+
<div class="col-md-6">
|
| 454 |
+
<div class="input-group">
|
| 455 |
+
<input type="text" class="form-control" id="professionalSearch" placeholder="Search professionals...">
|
| 456 |
+
<div class="input-group-append">
|
| 457 |
+
<button class="btn btn-outline-secondary" type="button">
|
| 458 |
+
<i class="fas fa-search"></i>
|
| 459 |
+
</button>
|
| 460 |
+
</div>
|
| 461 |
+
</div>
|
| 462 |
+
</div>
|
| 463 |
+
<div class="col-md-6">
|
| 464 |
+
<select class="form-control select2" id="professionalSpecializationFilter">
|
| 465 |
+
<option value="">All Specializations</option>
|
| 466 |
+
<option value="psychiatrist">Psychiatrist</option>
|
| 467 |
+
<option value="psychologist">Psychologist</option>
|
| 468 |
+
<option value="counselor">Counselor</option>
|
| 469 |
+
<option value="social_worker">Social Worker</option>
|
| 470 |
+
</select>
|
| 471 |
+
</div>
|
| 472 |
+
</div>
|
| 473 |
+
<div class="table-responsive">
|
| 474 |
+
<table id="professionalsTable" class="table table-bordered table-striped">
|
| 475 |
+
<thead>
|
| 476 |
+
<tr>
|
| 477 |
+
<th>ID</th>
|
| 478 |
+
<th>Name</th>
|
| 479 |
+
<th>Specialization</th>
|
| 480 |
+
<th>Email</th>
|
| 481 |
+
<th>Phone</th>
|
| 482 |
+
<th>Experience</th>
|
| 483 |
+
<th>Status</th>
|
| 484 |
+
<th>Actions</th>
|
| 485 |
+
</tr>
|
| 486 |
+
</thead>
|
| 487 |
+
<tbody id="professionalsTableBody">
|
| 488 |
+
<!-- Data will be loaded here -->
|
| 489 |
+
</tbody>
|
| 490 |
+
</table>
|
| 491 |
+
</div>
|
| 492 |
+
</div>
|
| 493 |
+
</div>
|
| 494 |
+
</div>
|
| 495 |
+
</div>
|
| 496 |
+
</div>
|
| 497 |
+
|
| 498 |
+
<!-- Bookings Section -->
|
| 499 |
+
<div id="bookings-section" class="content-section" style="display: none;">
|
| 500 |
+
<div class="row">
|
| 501 |
+
<div class="col-12">
|
| 502 |
+
<div class="card">
|
| 503 |
+
<div class="card-header">
|
| 504 |
+
<h3 class="card-title">Automated Bookings</h3>
|
| 505 |
+
<div class="card-tools">
|
| 506 |
+
<button type="button" class="btn btn-success" id="exportBookingsBtn">
|
| 507 |
+
<i class="fas fa-download"></i> Export
|
| 508 |
+
</button>
|
| 509 |
+
</div>
|
| 510 |
+
</div>
|
| 511 |
+
<div class="card-body">
|
| 512 |
+
<div class="row mb-3">
|
| 513 |
+
<div class="col-md-3">
|
| 514 |
+
<select class="form-control" id="statusFilter">
|
| 515 |
+
<option value="">All Status</option>
|
| 516 |
+
<option value="pending">Pending</option>
|
| 517 |
+
<option value="confirmed">Confirmed</option>
|
| 518 |
+
<option value="completed">Completed</option>
|
| 519 |
+
<option value="declined">Declined</option>
|
| 520 |
+
</select>
|
| 521 |
+
</div>
|
| 522 |
+
<div class="col-md-3">
|
| 523 |
+
<select class="form-control" id="riskLevelFilter">
|
| 524 |
+
<option value="">All Risk Levels</option>
|
| 525 |
+
<option value="critical">Critical</option>
|
| 526 |
+
<option value="high">High</option>
|
| 527 |
+
<option value="medium">Medium</option>
|
| 528 |
+
<option value="low">Low</option>
|
| 529 |
+
</select>
|
| 530 |
+
</div>
|
| 531 |
+
<div class="col-md-3">
|
| 532 |
+
<input type="date" class="form-control" id="dateFilter" placeholder="Filter by date">
|
| 533 |
+
</div>
|
| 534 |
+
<div class="col-md-3">
|
| 535 |
+
<button class="btn btn-primary" id="refreshBookingsBtn">
|
| 536 |
+
<i class="fas fa-sync"></i> Refresh
|
| 537 |
+
</button>
|
| 538 |
+
</div>
|
| 539 |
+
</div>
|
| 540 |
+
<div class="table-responsive">
|
| 541 |
+
<table id="bookingsTable" class="table table-bordered table-striped">
|
| 542 |
+
<thead>
|
| 543 |
+
<tr>
|
| 544 |
+
<th>Booking ID</th>
|
| 545 |
+
<th>User</th>
|
| 546 |
+
<th>Professional</th>
|
| 547 |
+
<th>Risk Level</th>
|
| 548 |
+
<th>Scheduled Time</th>
|
| 549 |
+
<th>Status</th>
|
| 550 |
+
<th>Actions</th>
|
| 551 |
+
</tr>
|
| 552 |
+
</thead>
|
| 553 |
+
<tbody id="bookingsTableBody">
|
| 554 |
+
<!-- Data will be loaded here -->
|
| 555 |
+
</tbody>
|
| 556 |
+
</table>
|
| 557 |
+
</div>
|
| 558 |
+
</div>
|
| 559 |
+
</div>
|
| 560 |
+
</div>
|
| 561 |
+
</div>
|
| 562 |
+
</div>
|
| 563 |
+
|
| 564 |
+
<!-- Risk Monitor Section -->
|
| 565 |
+
<div id="risk-monitor-section" class="content-section" style="display: none;">
|
| 566 |
+
<div class="row">
|
| 567 |
+
<div class="col-lg-3 col-6">
|
| 568 |
+
<div class="small-box bg-danger">
|
| 569 |
+
<div class="inner">
|
| 570 |
+
<h3 id="criticalCount">0</h3>
|
| 571 |
+
<p>Critical Risk</p>
|
| 572 |
+
</div>
|
| 573 |
+
<div class="icon">
|
| 574 |
+
<i class="fas fa-exclamation-triangle"></i>
|
| 575 |
+
</div>
|
| 576 |
+
</div>
|
| 577 |
+
</div>
|
| 578 |
+
<div class="col-lg-3 col-6">
|
| 579 |
+
<div class="small-box bg-warning">
|
| 580 |
+
<div class="inner">
|
| 581 |
+
<h3 id="highCount">0</h3>
|
| 582 |
+
<p>High Risk</p>
|
| 583 |
+
</div>
|
| 584 |
+
<div class="icon">
|
| 585 |
+
<i class="fas fa-exclamation-circle"></i>
|
| 586 |
+
</div>
|
| 587 |
+
</div>
|
| 588 |
+
</div>
|
| 589 |
+
<div class="col-lg-3 col-6">
|
| 590 |
+
<div class="small-box bg-info">
|
| 591 |
+
<div class="inner">
|
| 592 |
+
<h3 id="mediumCount">0</h3>
|
| 593 |
+
<p>Medium Risk</p>
|
| 594 |
+
</div>
|
| 595 |
+
<div class="icon">
|
| 596 |
+
<i class="fas fa-info-circle"></i>
|
| 597 |
+
</div>
|
| 598 |
+
</div>
|
| 599 |
+
</div>
|
| 600 |
+
<div class="col-lg-3 col-6">
|
| 601 |
+
<div class="small-box bg-success">
|
| 602 |
+
<div class="inner">
|
| 603 |
+
<h3 id="lowCount">0</h3>
|
| 604 |
+
<p>Low Risk</p>
|
| 605 |
+
</div>
|
| 606 |
+
<div class="icon">
|
| 607 |
+
<i class="fas fa-check-circle"></i>
|
| 608 |
+
</div>
|
| 609 |
+
</div>
|
| 610 |
+
</div>
|
| 611 |
+
</div>
|
| 612 |
+
|
| 613 |
+
<div class="row">
|
| 614 |
+
<div class="col-lg-8">
|
| 615 |
+
<div class="card">
|
| 616 |
+
<div class="card-header">
|
| 617 |
+
<h3 class="card-title">Risk Assessment Timeline</h3>
|
| 618 |
+
<div class="card-tools">
|
| 619 |
+
<button type="button" class="btn btn-tool" data-card-widget="collapse">
|
| 620 |
+
<i class="fas fa-minus"></i>
|
| 621 |
+
</button>
|
| 622 |
+
</div>
|
| 623 |
+
</div>
|
| 624 |
+
<div class="card-body">
|
| 625 |
+
<canvas id="riskTimelineChart" style="height: 300px;"></canvas>
|
| 626 |
+
</div>
|
| 627 |
+
</div>
|
| 628 |
+
</div>
|
| 629 |
+
<div class="col-lg-4">
|
| 630 |
+
<div class="card">
|
| 631 |
+
<div class="card-header">
|
| 632 |
+
<h3 class="card-title">Recent Assessments</h3>
|
| 633 |
+
<div class="card-tools">
|
| 634 |
+
<button type="button" class="btn btn-tool" id="refreshStatsBtn">
|
| 635 |
+
<i class="fas fa-sync"></i>
|
| 636 |
+
</button>
|
| 637 |
+
</div>
|
| 638 |
+
</div>
|
| 639 |
+
<div class="card-body">
|
| 640 |
+
<div id="recentAssessments">
|
| 641 |
+
<!-- Recent assessments will be loaded here -->
|
| 642 |
+
</div>
|
| 643 |
+
</div>
|
| 644 |
+
</div>
|
| 645 |
+
</div>
|
| 646 |
+
</div>
|
| 647 |
+
</div>
|
| 648 |
+
|
| 649 |
+
<!-- Analytics Section -->
|
| 650 |
+
<div id="analytics-section" class="content-section" style="display: none;">
|
| 651 |
+
<div class="row">
|
| 652 |
+
<div class="col-lg-3 col-6">
|
| 653 |
+
<div class="info-box">
|
| 654 |
+
<span class="info-box-icon bg-info"><i class="fas fa-user-md"></i></span>
|
| 655 |
+
<div class="info-box-content">
|
| 656 |
+
<span class="info-box-text">Total Professionals</span>
|
| 657 |
+
<span class="info-box-number" id="totalProfessionals">0</span>
|
| 658 |
+
</div>
|
| 659 |
+
</div>
|
| 660 |
+
</div>
|
| 661 |
+
<div class="col-lg-3 col-6">
|
| 662 |
+
<div class="info-box">
|
| 663 |
+
<span class="info-box-icon bg-success"><i class="fas fa-calendar-check"></i></span>
|
| 664 |
+
<div class="info-box-content">
|
| 665 |
+
<span class="info-box-text">Active Bookings</span>
|
| 666 |
+
<span class="info-box-number" id="activeBookings">0</span>
|
| 667 |
+
</div>
|
| 668 |
+
</div>
|
| 669 |
+
</div>
|
| 670 |
+
<div class="col-lg-3 col-6">
|
| 671 |
+
<div class="info-box">
|
| 672 |
+
<span class="info-box-icon bg-warning"><i class="fas fa-check-circle"></i></span>
|
| 673 |
+
<div class="info-box-content">
|
| 674 |
+
<span class="info-box-text">Completed Sessions</span>
|
| 675 |
+
<span class="info-box-number" id="completedSessions">0</span>
|
| 676 |
+
</div>
|
| 677 |
+
</div>
|
| 678 |
+
</div>
|
| 679 |
+
<div class="col-lg-3 col-6">
|
| 680 |
+
<div class="info-box">
|
| 681 |
+
<span class="info-box-icon bg-primary"><i class="fas fa-chart-line"></i></span>
|
| 682 |
+
<div class="info-box-content">
|
| 683 |
+
<span class="info-box-text">Assessments Today</span>
|
| 684 |
+
<span class="info-box-number" id="assessmentsToday">0</span>
|
| 685 |
+
</div>
|
| 686 |
+
</div>
|
| 687 |
+
</div>
|
| 688 |
+
</div>
|
| 689 |
+
|
| 690 |
+
<div class="row">
|
| 691 |
+
<div class="col-lg-6">
|
| 692 |
+
<div class="card">
|
| 693 |
+
<div class="card-header">
|
| 694 |
+
<h3 class="card-title">Monthly Trends</h3>
|
| 695 |
+
</div>
|
| 696 |
+
<div class="card-body">
|
| 697 |
+
<canvas id="monthlyTrendsChart" style="height: 300px;"></canvas>
|
| 698 |
+
</div>
|
| 699 |
+
</div>
|
| 700 |
+
</div>
|
| 701 |
+
<div class="col-lg-6">
|
| 702 |
+
<div class="card">
|
| 703 |
+
<div class="card-header">
|
| 704 |
+
<h3 class="card-title">Professional Performance</h3>
|
| 705 |
+
</div>
|
| 706 |
+
<div class="card-body">
|
| 707 |
+
<canvas id="professionalPerformanceChart" style="height: 300px;"></canvas>
|
| 708 |
+
</div>
|
| 709 |
+
</div>
|
| 710 |
+
</div>
|
| 711 |
+
</div>
|
| 712 |
+
</div>
|
| 713 |
+
|
| 714 |
+
<!-- RAG Status Section -->
|
| 715 |
+
<div id="rag-status-section" class="content-section" style="display: none;">
|
| 716 |
+
<div class="row">
|
| 717 |
+
<div class="col-lg-3 col-6">
|
| 718 |
+
<div class="info-box">
|
| 719 |
+
<span class="info-box-icon bg-info"><i class="fas fa-database"></i></span>
|
| 720 |
+
<div class="info-box-content">
|
| 721 |
+
<span class="info-box-text">Knowledge Chunks</span>
|
| 722 |
+
<span class="info-box-number" id="totalChunks">15</span>
|
| 723 |
+
</div>
|
| 724 |
+
</div>
|
| 725 |
+
</div>
|
| 726 |
+
<div class="col-lg-3 col-6">
|
| 727 |
+
<div class="info-box">
|
| 728 |
+
<span class="info-box-icon bg-success"><i class="fas fa-brain"></i></span>
|
| 729 |
+
<div class="info-box-content">
|
| 730 |
+
<span class="info-box-text">AI Model</span>
|
| 731 |
+
<span class="info-box-number">Online</span>
|
| 732 |
+
</div>
|
| 733 |
+
</div>
|
| 734 |
+
</div>
|
| 735 |
+
<div class="col-lg-3 col-6">
|
| 736 |
+
<div class="info-box">
|
| 737 |
+
<span class="info-box-icon bg-warning"><i class="fas fa-language"></i></span>
|
| 738 |
+
<div class="info-box-content">
|
| 739 |
+
<span class="info-box-text">Languages</span>
|
| 740 |
+
<span class="info-box-number">4</span>
|
| 741 |
+
</div>
|
| 742 |
+
</div>
|
| 743 |
+
</div>
|
| 744 |
+
<div class="col-lg-3 col-6">
|
| 745 |
+
<div class="info-box">
|
| 746 |
+
<span class="info-box-icon bg-primary"><i class="fas fa-tachometer-alt"></i></span>
|
| 747 |
+
<div class="info-box-content">
|
| 748 |
+
<span class="info-box-text">Response Time</span>
|
| 749 |
+
<span class="info-box-number" id="avgResponseTime">~2s</span>
|
| 750 |
+
</div>
|
| 751 |
+
</div>
|
| 752 |
+
</div>
|
| 753 |
+
</div>
|
| 754 |
+
|
| 755 |
+
<div class="row">
|
| 756 |
+
<div class="col-lg-8">
|
| 757 |
+
<div class="card">
|
| 758 |
+
<div class="card-header">
|
| 759 |
+
<h3 class="card-title">System Performance</h3>
|
| 760 |
+
</div>
|
| 761 |
+
<div class="card-body">
|
| 762 |
+
<div class="row">
|
| 763 |
+
<div class="col-md-6">
|
| 764 |
+
<div class="description-block border-right">
|
| 765 |
+
<span class="description-percentage text-success">
|
| 766 |
+
<i class="fas fa-caret-up"></i> 98%
|
| 767 |
+
</span>
|
| 768 |
+
<h5 class="description-header">Success Rate</h5>
|
| 769 |
+
<span class="description-text">AI Response Accuracy</span>
|
| 770 |
+
</div>
|
| 771 |
+
</div>
|
| 772 |
+
<div class="col-md-6">
|
| 773 |
+
<div class="description-block">
|
| 774 |
+
<span class="description-percentage text-info">
|
| 775 |
+
<i class="fas fa-clock"></i> 2.1s
|
| 776 |
+
</span>
|
| 777 |
+
<h5 class="description-header">Avg Response Time</h5>
|
| 778 |
+
<span class="description-text">Query Processing</span>
|
| 779 |
+
</div>
|
| 780 |
+
</div>
|
| 781 |
+
</div>
|
| 782 |
+
</div>
|
| 783 |
+
</div>
|
| 784 |
+
</div>
|
| 785 |
+
<div class="col-lg-4">
|
| 786 |
+
<div class="card">
|
| 787 |
+
<div class="card-header">
|
| 788 |
+
<h3 class="card-title">Knowledge Sources</h3>
|
| 789 |
+
</div>
|
| 790 |
+
<div class="card-body">
|
| 791 |
+
<div class="list-group list-group-flush">
|
| 792 |
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
| 793 |
+
Mental Health Overview
|
| 794 |
+
<span class="badge badge-success badge-pill">Loaded</span>
|
| 795 |
+
</div>
|
| 796 |
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
| 797 |
+
Youth Mental Health
|
| 798 |
+
<span class="badge badge-success badge-pill">Loaded</span>
|
| 799 |
+
</div>
|
| 800 |
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
| 801 |
+
Rwanda Helplines
|
| 802 |
+
<span class="badge badge-success badge-pill">Loaded</span>
|
| 803 |
+
</div>
|
| 804 |
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
| 805 |
+
Self-Help Coping
|
| 806 |
+
<span class="badge badge-success badge-pill">Loaded</span>
|
| 807 |
+
</div>
|
| 808 |
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
| 809 |
+
PTSD Trauma Guide
|
| 810 |
+
<span class="badge badge-success badge-pill">Loaded</span>
|
| 811 |
+
</div>
|
| 812 |
+
<div class="list-group-item d-flex justify-content-between align-items-center">
|
| 813 |
+
Mental Health Policy
|
| 814 |
+
<span class="badge badge-success badge-pill">Loaded</span>
|
| 815 |
+
</div>
|
| 816 |
+
</div>
|
| 817 |
+
</div>
|
| 818 |
+
</div>
|
| 819 |
+
</div>
|
| 820 |
+
</div>
|
| 821 |
+
</div>
|
| 822 |
+
|
| 823 |
+
|
| 824 |
+
<!-- Reports Section -->
|
| 825 |
+
<div id="reports-section" class="content-section" style="display: none;">
|
| 826 |
+
<div class="row">
|
| 827 |
+
<div class="col-12">
|
| 828 |
+
<div class="card">
|
| 829 |
+
<div class="card-header">
|
| 830 |
+
<h3 class="card-title">System Reports</h3>
|
| 831 |
+
<div class="card-tools">
|
| 832 |
+
<button type="button" class="btn btn-success">
|
| 833 |
+
<i class="fas fa-download"></i> Generate Report
|
| 834 |
+
</button>
|
| 835 |
+
</div>
|
| 836 |
+
</div>
|
| 837 |
+
<div class="card-body">
|
| 838 |
+
<div class="row">
|
| 839 |
+
<div class="col-md-4">
|
| 840 |
+
<div class="card card-primary">
|
| 841 |
+
<div class="card-header">
|
| 842 |
+
<h3 class="card-title">Monthly Summary</h3>
|
| 843 |
+
</div>
|
| 844 |
+
<div class="card-body">
|
| 845 |
+
<p>Generate comprehensive monthly reports</p>
|
| 846 |
+
<button class="btn btn-primary">Generate</button>
|
| 847 |
+
</div>
|
| 848 |
+
</div>
|
| 849 |
+
</div>
|
| 850 |
+
<div class="col-md-4">
|
| 851 |
+
<div class="card card-success">
|
| 852 |
+
<div class="card-header">
|
| 853 |
+
<h3 class="card-title">Risk Analysis</h3>
|
| 854 |
+
</div>
|
| 855 |
+
<div class="card-body">
|
| 856 |
+
<p>Detailed risk assessment reports</p>
|
| 857 |
+
<button class="btn btn-success">Generate</button>
|
| 858 |
+
</div>
|
| 859 |
+
</div>
|
| 860 |
+
</div>
|
| 861 |
+
<div class="col-md-4">
|
| 862 |
+
<div class="card card-warning">
|
| 863 |
+
<div class="card-header">
|
| 864 |
+
<h3 class="card-title">Professional Performance</h3>
|
| 865 |
+
</div>
|
| 866 |
+
<div class="card-body">
|
| 867 |
+
<p>Professional workload and performance</p>
|
| 868 |
+
<button class="btn btn-warning">Generate</button>
|
| 869 |
+
</div>
|
| 870 |
+
</div>
|
| 871 |
+
</div>
|
| 872 |
+
</div>
|
| 873 |
+
</div>
|
| 874 |
+
</div>
|
| 875 |
+
</div>
|
| 876 |
+
</div>
|
| 877 |
+
</div>
|
| 878 |
+
|
| 879 |
+
<!-- Settings Section -->
|
| 880 |
+
<div id="settings-section" class="content-section" style="display: none;">
|
| 881 |
+
<div class="row">
|
| 882 |
+
<div class="col-12">
|
| 883 |
+
<div class="card">
|
| 884 |
+
<div class="card-header">
|
| 885 |
+
<h3 class="card-title">System Settings</h3>
|
| 886 |
+
</div>
|
| 887 |
+
<div class="card-body">
|
| 888 |
+
<form>
|
| 889 |
+
<div class="row">
|
| 890 |
+
<div class="col-md-6">
|
| 891 |
+
<div class="form-group">
|
| 892 |
+
<label>System Name</label>
|
| 893 |
+
<input type="text" class="form-control" value="AIMHSA Admin">
|
| 894 |
+
</div>
|
| 895 |
+
</div>
|
| 896 |
+
<div class="col-md-6">
|
| 897 |
+
<div class="form-group">
|
| 898 |
+
<label>Admin Email</label>
|
| 899 |
+
<input type="email" class="form-control" value="admin@aimhsa.rw">
|
| 900 |
+
</div>
|
| 901 |
+
</div>
|
| 902 |
+
</div>
|
| 903 |
+
<div class="row">
|
| 904 |
+
<div class="col-md-6">
|
| 905 |
+
<div class="form-group">
|
| 906 |
+
<label>Risk Threshold</label>
|
| 907 |
+
<select class="form-control">
|
| 908 |
+
<option>Low</option>
|
| 909 |
+
<option selected>Medium</option>
|
| 910 |
+
<option>High</option>
|
| 911 |
+
</select>
|
| 912 |
+
</div>
|
| 913 |
+
</div>
|
| 914 |
+
<div class="col-md-6">
|
| 915 |
+
<div class="form-group">
|
| 916 |
+
<label>Auto-Refresh Interval</label>
|
| 917 |
+
<select class="form-control">
|
| 918 |
+
<option>30 seconds</option>
|
| 919 |
+
<option selected>1 minute</option>
|
| 920 |
+
<option>5 minutes</option>
|
| 921 |
+
</select>
|
| 922 |
+
</div>
|
| 923 |
+
</div>
|
| 924 |
+
</div>
|
| 925 |
+
<button type="submit" class="btn btn-primary">Save Settings</button>
|
| 926 |
+
</form>
|
| 927 |
+
</div>
|
| 928 |
+
</div>
|
| 929 |
+
</div>
|
| 930 |
+
</div>
|
| 931 |
+
</div>
|
| 932 |
+
</div>
|
| 933 |
+
</section>
|
| 934 |
+
</div>
|
| 935 |
+
</div>
|
| 936 |
+
|
| 937 |
+
<!-- Professional Modal -->
|
| 938 |
+
<div class="modal fade" id="professionalModal" tabindex="-1" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
|
| 939 |
+
<div class="modal-dialog modal-lg" role="document">
|
| 940 |
+
<div class="modal-content">
|
| 941 |
+
<div class="modal-header">
|
| 942 |
+
<h4 class="modal-title" id="modalTitle">Add New Professional</h4>
|
| 943 |
+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
| 944 |
+
<span aria-hidden="true">×</span>
|
| 945 |
+
</button>
|
| 946 |
+
</div>
|
| 947 |
+
<div class="modal-body">
|
| 948 |
+
<form id="professionalForm">
|
| 949 |
+
<div class="row">
|
| 950 |
+
<div class="col-md-6">
|
| 951 |
+
<div class="form-group">
|
| 952 |
+
<label for="username">Username *</label>
|
| 953 |
+
<input type="text" class="form-control" id="username" name="username" required>
|
| 954 |
+
</div>
|
| 955 |
+
</div>
|
| 956 |
+
<div class="col-md-6">
|
| 957 |
+
<div class="form-group">
|
| 958 |
+
<label for="password">Password <span id="passwordRequired">*</span></label>
|
| 959 |
+
<input type="password" class="form-control" id="password" name="password" required>
|
| 960 |
+
<small id="passwordHelp" class="form-text text-muted" style="display: none;">Leave blank to keep current password</small>
|
| 961 |
+
</div>
|
| 962 |
+
</div>
|
| 963 |
+
</div>
|
| 964 |
+
|
| 965 |
+
<div class="row">
|
| 966 |
+
<div class="col-md-6">
|
| 967 |
+
<div class="form-group">
|
| 968 |
+
<label for="first_name">First Name *</label>
|
| 969 |
+
<input type="text" class="form-control" id="first_name" name="first_name" required>
|
| 970 |
+
</div>
|
| 971 |
+
</div>
|
| 972 |
+
<div class="col-md-6">
|
| 973 |
+
<div class="form-group">
|
| 974 |
+
<label for="last_name">Last Name *</label>
|
| 975 |
+
<input type="text" class="form-control" id="last_name" name="last_name" required>
|
| 976 |
+
</div>
|
| 977 |
+
</div>
|
| 978 |
+
</div>
|
| 979 |
+
|
| 980 |
+
<div class="row">
|
| 981 |
+
<div class="col-md-6">
|
| 982 |
+
<div class="form-group">
|
| 983 |
+
<label for="email">Email *</label>
|
| 984 |
+
<input type="email" class="form-control" id="email" name="email" required>
|
| 985 |
+
</div>
|
| 986 |
+
</div>
|
| 987 |
+
<div class="col-md-6">
|
| 988 |
+
<div class="form-group">
|
| 989 |
+
<label for="phone">Phone</label>
|
| 990 |
+
<input type="tel" class="form-control" id="phone" name="phone">
|
| 991 |
+
</div>
|
| 992 |
+
</div>
|
| 993 |
+
</div>
|
| 994 |
+
|
| 995 |
+
<div class="row">
|
| 996 |
+
<div class="col-md-6">
|
| 997 |
+
<div class="form-group">
|
| 998 |
+
<label for="specialization">Specialization *</label>
|
| 999 |
+
<select class="form-control" id="specialization" name="specialization" required>
|
| 1000 |
+
<option value="">Select Specialization</option>
|
| 1001 |
+
<option value="psychiatrist">Psychiatrist</option>
|
| 1002 |
+
<option value="psychologist">Psychologist</option>
|
| 1003 |
+
<option value="counselor">Counselor</option>
|
| 1004 |
+
<option value="social_worker">Social Worker</option>
|
| 1005 |
+
</select>
|
| 1006 |
+
</div>
|
| 1007 |
+
</div>
|
| 1008 |
+
<div class="col-md-6">
|
| 1009 |
+
<div class="form-group">
|
| 1010 |
+
<label for="experience_years">Experience (Years)</label>
|
| 1011 |
+
<input type="number" class="form-control" id="experience_years" name="experience_years" min="0" value="0">
|
| 1012 |
+
</div>
|
| 1013 |
+
</div>
|
| 1014 |
+
</div>
|
| 1015 |
+
|
| 1016 |
+
<div class="form-group">
|
| 1017 |
+
<label for="expertise_areas">Expertise Areas *</label>
|
| 1018 |
+
<div class="row">
|
| 1019 |
+
<div class="col-md-6">
|
| 1020 |
+
<div class="form-check">
|
| 1021 |
+
<input class="form-check-input" type="checkbox" name="expertise" value="depression" id="expertise_depression">
|
| 1022 |
+
<label class="form-check-label" for="expertise_depression">Depression</label>
|
| 1023 |
+
</div>
|
| 1024 |
+
<div class="form-check">
|
| 1025 |
+
<input class="form-check-input" type="checkbox" name="expertise" value="anxiety" id="expertise_anxiety">
|
| 1026 |
+
<label class="form-check-label" for="expertise_anxiety">Anxiety</label>
|
| 1027 |
+
</div>
|
| 1028 |
+
<div class="form-check">
|
| 1029 |
+
<input class="form-check-input" type="checkbox" name="expertise" value="ptsd" id="expertise_ptsd">
|
| 1030 |
+
<label class="form-check-label" for="expertise_ptsd">PTSD</label>
|
| 1031 |
+
</div>
|
| 1032 |
+
<div class="form-check">
|
| 1033 |
+
<input class="form-check-input" type="checkbox" name="expertise" value="trauma" id="expertise_trauma">
|
| 1034 |
+
<label class="form-check-label" for="expertise_trauma">Trauma</label>
|
| 1035 |
+
</div>
|
| 1036 |
+
</div>
|
| 1037 |
+
<div class="col-md-6">
|
| 1038 |
+
<div class="form-check">
|
| 1039 |
+
<input class="form-check-input" type="checkbox" name="expertise" value="suicide" id="expertise_suicide">
|
| 1040 |
+
<label class="form-check-label" for="expertise_suicide">Suicide Prevention</label>
|
| 1041 |
+
</div>
|
| 1042 |
+
<div class="form-check">
|
| 1043 |
+
<input class="form-check-input" type="checkbox" name="expertise" value="crisis" id="expertise_crisis">
|
| 1044 |
+
<label class="form-check-label" for="expertise_crisis">Crisis Intervention</label>
|
| 1045 |
+
</div>
|
| 1046 |
+
<div class="form-check">
|
| 1047 |
+
<input class="form-check-input" type="checkbox" name="expertise" value="youth" id="expertise_youth">
|
| 1048 |
+
<label class="form-check-label" for="expertise_youth">Youth Mental Health</label>
|
| 1049 |
+
</div>
|
| 1050 |
+
<div class="form-check">
|
| 1051 |
+
<input class="form-check-input" type="checkbox" name="expertise" value="family" id="expertise_family">
|
| 1052 |
+
<label class="form-check-label" for="expertise_family">Family Therapy</label>
|
| 1053 |
+
</div>
|
| 1054 |
+
</div>
|
| 1055 |
+
</div>
|
| 1056 |
+
</div>
|
| 1057 |
+
|
| 1058 |
+
<div class="row">
|
| 1059 |
+
<div class="col-md-6">
|
| 1060 |
+
<div class="form-group">
|
| 1061 |
+
<label for="district">District</label>
|
| 1062 |
+
<input type="text" class="form-control" id="district" name="district" placeholder="e.g., Gasabo, Kigali">
|
| 1063 |
+
</div>
|
| 1064 |
+
</div>
|
| 1065 |
+
<div class="col-md-6">
|
| 1066 |
+
<div class="form-group">
|
| 1067 |
+
<label for="consultation_fee">Consultation Fee (RWF)</label>
|
| 1068 |
+
<input type="number" class="form-control" id="consultation_fee" name="consultation_fee" min="0">
|
| 1069 |
+
</div>
|
| 1070 |
+
</div>
|
| 1071 |
+
</div>
|
| 1072 |
+
|
| 1073 |
+
<div class="form-group">
|
| 1074 |
+
<label for="bio">Bio</label>
|
| 1075 |
+
<textarea class="form-control" id="bio" name="bio" rows="3" placeholder="Brief professional background..."></textarea>
|
| 1076 |
+
</div>
|
| 1077 |
+
</form>
|
| 1078 |
+
</div>
|
| 1079 |
+
<div class="modal-footer">
|
| 1080 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
| 1081 |
+
<button type="submit" form="professionalForm" class="btn btn-primary">Save Professional</button>
|
| 1082 |
+
</div>
|
| 1083 |
+
</div>
|
| 1084 |
+
</div>
|
| 1085 |
+
</div>
|
| 1086 |
+
|
| 1087 |
+
<!-- Booking Details Modal -->
|
| 1088 |
+
<div class="modal fade" id="bookingModal" tabindex="-1">
|
| 1089 |
+
<div class="modal-dialog modal-lg">
|
| 1090 |
+
<div class="modal-content">
|
| 1091 |
+
<div class="modal-header">
|
| 1092 |
+
<h4 class="modal-title">Booking Details</h4>
|
| 1093 |
+
<button type="button" class="close" data-dismiss="modal">
|
| 1094 |
+
<span>×</span>
|
| 1095 |
+
</button>
|
| 1096 |
+
</div>
|
| 1097 |
+
<div class="modal-body">
|
| 1098 |
+
<div id="bookingDetails">
|
| 1099 |
+
<!-- Booking details will be loaded here -->
|
| 1100 |
+
</div>
|
| 1101 |
+
</div>
|
| 1102 |
+
<div class="modal-footer">
|
| 1103 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
| 1104 |
+
</div>
|
| 1105 |
+
</div>
|
| 1106 |
+
</div>
|
| 1107 |
+
</div>
|
| 1108 |
+
|
| 1109 |
+
|
| 1110 |
+
<!-- jQuery -->
|
| 1111 |
+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
| 1112 |
+
<!-- Bootstrap 4 -->
|
| 1113 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
| 1114 |
+
<!-- AdminLTE 4 -->
|
| 1115 |
+
<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/js/adminlte.min.js"></script>
|
| 1116 |
+
<!-- DataTables -->
|
| 1117 |
+
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
| 1118 |
+
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap4.min.js"></script>
|
| 1119 |
+
<script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
|
| 1120 |
+
<script src="https://cdn.datatables.net/responsive/2.5.0/js/responsive.bootstrap4.min.js"></script>
|
| 1121 |
+
<!-- Select2 -->
|
| 1122 |
+
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
| 1123 |
+
<!-- SweetAlert2 -->
|
| 1124 |
+
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
| 1125 |
+
<!-- FullCalendar removed - not needed for admin dashboard -->
|
| 1126 |
+
|
| 1127 |
+
<!-- Loading Overlay -->
|
| 1128 |
+
<div id="loadingOverlay" class="loading-overlay" style="display: none;">
|
| 1129 |
+
<div class="loading-spinner">
|
| 1130 |
+
<i class="fas fa-spinner fa-spin fa-3x"></i>
|
| 1131 |
+
<p>Loading...</p>
|
| 1132 |
+
</div>
|
| 1133 |
+
</div>
|
| 1134 |
+
|
| 1135 |
+
<!-- Custom JavaScript (preserves existing functionality) -->
|
| 1136 |
+
<script src="admin_advanced.js"></script>
|
| 1137 |
+
</body>
|
| 1138 |
+
</html>
|
chatbot/app.js
ADDED
|
@@ -0,0 +1,958 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(() => {
|
| 2 |
+
// Get API URL from configuration
|
| 3 |
+
const getAPIBaseUrl = () => {
|
| 4 |
+
if (window.AIMHSA && window.AIMHSA.Config) {
|
| 5 |
+
return window.AIMHSA.Config.getApiBaseUrl();
|
| 6 |
+
}
|
| 7 |
+
// Fallback to auto-detection
|
| 8 |
+
return `http://${window.location.hostname}:${window.location.port || '5057'}`;
|
| 9 |
+
};
|
| 10 |
+
|
| 11 |
+
const API_BASE_URL = getAPIBaseUrl();
|
| 12 |
+
|
| 13 |
+
// Check authentication
|
| 14 |
+
const account = localStorage.getItem("aimhsa_account");
|
| 15 |
+
const professionalData = localStorage.getItem("aimhsa_professional");
|
| 16 |
+
const adminData = localStorage.getItem("aimhsa_admin");
|
| 17 |
+
|
| 18 |
+
if (professionalData) {
|
| 19 |
+
alert('You are logged in as a professional. Please logout and login as a regular user to use the chat.');
|
| 20 |
+
window.location.href = '/professional_dashboard.html';
|
| 21 |
+
return;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
if (adminData) {
|
| 25 |
+
alert('You are logged in as an admin. Please logout and login as a regular user to use the chat.');
|
| 26 |
+
window.location.href = '/admin_dashboard.html';
|
| 27 |
+
return;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
if (!account) {
|
| 31 |
+
window.location.href = '/login';
|
| 32 |
+
return;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
// Elements
|
| 36 |
+
const messagesEl = document.getElementById("messages");
|
| 37 |
+
const form = document.getElementById("form");
|
| 38 |
+
const queryInput = document.getElementById("query");
|
| 39 |
+
const sendBtn = document.getElementById("send");
|
| 40 |
+
const fileInput = document.getElementById("file");
|
| 41 |
+
const composer = form; // composer container (used for inserting preview)
|
| 42 |
+
const historyList = document.getElementById("historyList");
|
| 43 |
+
const newChatBtn = document.getElementById("newChatBtn");
|
| 44 |
+
const clearChatBtn = document.getElementById("clearChatBtn");
|
| 45 |
+
const clearHistoryBtn = document.getElementById("clearHistoryBtn");
|
| 46 |
+
const logoutBtn = document.getElementById("logoutBtn");
|
| 47 |
+
const usernameEl = document.getElementById("username");
|
| 48 |
+
const archivedList = document.getElementById("archivedList");
|
| 49 |
+
|
| 50 |
+
let convId = localStorage.getItem("aimhsa_conv") || null;
|
| 51 |
+
let typingEl = null;
|
| 52 |
+
let currentPreview = null;
|
| 53 |
+
const archivedPwById = new Map();
|
| 54 |
+
// Model selection: via URL (?model=xyz) or localStorage (aimhsa_model)
|
| 55 |
+
const urlParams = new URLSearchParams(window.location.search || "");
|
| 56 |
+
const urlModel = (urlParams.get('model') || '').trim();
|
| 57 |
+
if (urlModel) {
|
| 58 |
+
try { localStorage.setItem('aimhsa_model', urlModel); } catch (_) {}
|
| 59 |
+
}
|
| 60 |
+
function getSelectedModel() {
|
| 61 |
+
try { return (localStorage.getItem('aimhsa_model') || '').trim() || null; } catch (_) { return null; }
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// Set username
|
| 65 |
+
usernameEl.textContent = account === 'null' ? 'Guest' : account;
|
| 66 |
+
|
| 67 |
+
// Inject runtime CSS for animations & preview (keeps frontend simple)
|
| 68 |
+
(function injectStyles(){
|
| 69 |
+
const css = `
|
| 70 |
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(6px); } to { opacity:1; transform:none; } }
|
| 71 |
+
.fade-in { animation: fadeIn 280ms ease both; }
|
| 72 |
+
.typing { display:flex; align-items:center; gap:8px; padding:8px 12px; border-radius:10px; width:fit-content; background:rgba(255,255,255,0.02); border:1px solid rgba(255,255,255,0.03); }
|
| 73 |
+
.dots { display:inline-block; width:36px; text-align:center; }
|
| 74 |
+
.dot { display:inline-block; width:6px; height:6px; margin:0 2px; background:var(--muted); border-radius:50%; opacity:0.25; animation: blink 1s infinite; }
|
| 75 |
+
.dot:nth-child(2){ animation-delay: .2s; } .dot:nth-child(3){ animation-delay: .4s; }
|
| 76 |
+
@keyframes blink { 0%{opacity:.25} 50%{opacity:1} 100%{opacity:.25} }
|
| 77 |
+
.upload-preview { display:flex; align-items:center; gap:12px; padding:8px 10px; border-radius:8px; background:rgba(255,255,255,0.02); border:1px solid rgba(255,255,255,0.03); margin-right:auto; max-width:420px; }
|
| 78 |
+
.upload-meta { display:flex; flex-direction:column; gap:4px; font-size:13px; color:var(--muted); }
|
| 79 |
+
.upload-filename { font-weight:600; color:var(--text); }
|
| 80 |
+
.upload-actions { display:flex; gap:8px; align-items:center; }
|
| 81 |
+
.progress-bar { width:160px; height:8px; background:rgba(255,255,255,0.03); border-radius:6px; overflow:hidden; }
|
| 82 |
+
.progress-inner { height:100%; width:0%; background:linear-gradient(90deg,var(--accent), #5b21b6); transition:width .2s ease; }
|
| 83 |
+
.btn-small { padding:6px 8px; border-radius:8px; background:transparent; border:1px solid rgba(255,255,255,0.04); color:var(--muted); cursor:pointer; font-size:12px; }
|
| 84 |
+
.sending { opacity:0.7; transform:scale(.98); transition:transform .12s ease, opacity .12s ease; }
|
| 85 |
+
.msg.fade-in { transform-origin: left top; }
|
| 86 |
+
`;
|
| 87 |
+
const s = document.createElement("style");
|
| 88 |
+
s.textContent = css;
|
| 89 |
+
document.head.appendChild(s);
|
| 90 |
+
})();
|
| 91 |
+
|
| 92 |
+
// helper: ensure messages container scrolls to bottom after layout updates
|
| 93 |
+
function ensureScroll() {
|
| 94 |
+
const doScroll = () => {
|
| 95 |
+
try {
|
| 96 |
+
const last = messagesEl.lastElementChild;
|
| 97 |
+
if (last && typeof last.scrollIntoView === "function") {
|
| 98 |
+
last.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
|
| 99 |
+
} else {
|
| 100 |
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
| 101 |
+
}
|
| 102 |
+
} catch (e) {
|
| 103 |
+
try { messagesEl.scrollTop = messagesEl.scrollHeight; } catch (_) {}
|
| 104 |
+
}
|
| 105 |
+
};
|
| 106 |
+
requestAnimationFrame(() => {
|
| 107 |
+
requestAnimationFrame(() => {
|
| 108 |
+
setTimeout(doScroll, 40);
|
| 109 |
+
});
|
| 110 |
+
});
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
// Logout handler
|
| 114 |
+
logoutBtn.addEventListener("click", () => {
|
| 115 |
+
localStorage.removeItem("aimhsa_account");
|
| 116 |
+
localStorage.removeItem("aimhsa_conv");
|
| 117 |
+
localStorage.removeItem("aimhsa_professional");
|
| 118 |
+
localStorage.removeItem("aimhsa_admin");
|
| 119 |
+
window.location.href = '/login';
|
| 120 |
+
});
|
| 121 |
+
|
| 122 |
+
// Modern message display
|
| 123 |
+
function appendMessage(role, text) {
|
| 124 |
+
const msgDiv = document.createElement("div");
|
| 125 |
+
msgDiv.className = `msg ${role === "user" ? "user" : "bot"}`;
|
| 126 |
+
|
| 127 |
+
const contentDiv = document.createElement("div");
|
| 128 |
+
contentDiv.className = "msg-content";
|
| 129 |
+
|
| 130 |
+
const metaDiv = document.createElement("div");
|
| 131 |
+
metaDiv.className = "msg-meta";
|
| 132 |
+
metaDiv.textContent = role === "user" ? "You" : "AIMHSA";
|
| 133 |
+
|
| 134 |
+
const textDiv = document.createElement("div");
|
| 135 |
+
textDiv.className = "msg-text";
|
| 136 |
+
textDiv.textContent = text;
|
| 137 |
+
|
| 138 |
+
contentDiv.appendChild(metaDiv);
|
| 139 |
+
contentDiv.appendChild(textDiv);
|
| 140 |
+
msgDiv.appendChild(contentDiv);
|
| 141 |
+
|
| 142 |
+
messagesEl.appendChild(msgDiv);
|
| 143 |
+
ensureScroll();
|
| 144 |
+
return msgDiv;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
function createTypingIndicator() {
|
| 148 |
+
if (typingEl) return;
|
| 149 |
+
typingEl = document.createElement("div");
|
| 150 |
+
typingEl.className = "msg bot";
|
| 151 |
+
|
| 152 |
+
const contentDiv = document.createElement("div");
|
| 153 |
+
contentDiv.className = "typing";
|
| 154 |
+
|
| 155 |
+
const dotsDiv = document.createElement("div");
|
| 156 |
+
dotsDiv.className = "typing-dots";
|
| 157 |
+
dotsDiv.innerHTML = '<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>';
|
| 158 |
+
|
| 159 |
+
contentDiv.appendChild(dotsDiv);
|
| 160 |
+
typingEl.appendChild(contentDiv);
|
| 161 |
+
messagesEl.appendChild(typingEl);
|
| 162 |
+
ensureScroll();
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
function removeTypingIndicator() {
|
| 166 |
+
if (!typingEl) return;
|
| 167 |
+
typingEl.remove();
|
| 168 |
+
typingEl = null;
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
async function api(path, opts) {
|
| 172 |
+
const url = API_BASE_URL + path;
|
| 173 |
+
const res = await fetch(url, opts);
|
| 174 |
+
if (!res.ok) {
|
| 175 |
+
const txt = await res.text();
|
| 176 |
+
throw new Error(txt || res.statusText);
|
| 177 |
+
}
|
| 178 |
+
return res.json();
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
async function initSession(useAccount = false) {
|
| 182 |
+
const payload = {};
|
| 183 |
+
if (useAccount && account) payload.account = account;
|
| 184 |
+
try {
|
| 185 |
+
const resp = await api("/session", {
|
| 186 |
+
method: "POST",
|
| 187 |
+
headers: { "Content-Type": "application/json" },
|
| 188 |
+
body: JSON.stringify(payload),
|
| 189 |
+
});
|
| 190 |
+
convId = resp.id;
|
| 191 |
+
localStorage.setItem("aimhsa_conv", convId);
|
| 192 |
+
await loadHistory();
|
| 193 |
+
await updateHistoryList();
|
| 194 |
+
} catch (err) {
|
| 195 |
+
console.error("session error", err);
|
| 196 |
+
appendMessage("bot", "Could not start session. Try again.");
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
// helper to generate a client-side conv id when needed (fallback)
|
| 201 |
+
function newConvId() {
|
| 202 |
+
if (typeof crypto !== "undefined" && crypto.randomUUID) return crypto.randomUUID();
|
| 203 |
+
return "conv-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2,8);
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
async function loadHistory() {
|
| 207 |
+
if (!convId) return;
|
| 208 |
+
try {
|
| 209 |
+
const pw = archivedPwById.get(convId);
|
| 210 |
+
const url = "/history?id=" + encodeURIComponent(convId) + (pw ? ("&password=" + encodeURIComponent(pw)) : "");
|
| 211 |
+
const resp = await api(url);
|
| 212 |
+
messagesEl.innerHTML = "";
|
| 213 |
+
const hist = resp.history || [];
|
| 214 |
+
for (const m of hist) {
|
| 215 |
+
appendMessage(m.role, m.content);
|
| 216 |
+
}
|
| 217 |
+
if (resp.attachments && resp.attachments.length) {
|
| 218 |
+
resp.attachments.forEach(a => {
|
| 219 |
+
appendMessage("bot", `Attachment (${a.filename}):\n` + (a.text.slice(0,400) + (a.text.length>400?"...[truncated]":"")));
|
| 220 |
+
});
|
| 221 |
+
}
|
| 222 |
+
ensureScroll();
|
| 223 |
+
} catch (err) {
|
| 224 |
+
console.error("history load error", err);
|
| 225 |
+
}
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
// Auto-resize textarea
|
| 229 |
+
function autoResizeTextarea() {
|
| 230 |
+
queryInput.style.height = 'auto';
|
| 231 |
+
const scrollHeight = queryInput.scrollHeight;
|
| 232 |
+
const maxHeight = 120; // Match CSS max-height
|
| 233 |
+
queryInput.style.height = Math.min(scrollHeight, maxHeight) + 'px';
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
// Add textarea auto-resize listener
|
| 237 |
+
queryInput.addEventListener('input', autoResizeTextarea);
|
| 238 |
+
queryInput.addEventListener('keydown', (e) => {
|
| 239 |
+
if (e.key === 'Enter' && !e.shiftKey) {
|
| 240 |
+
e.preventDefault();
|
| 241 |
+
form.dispatchEvent(new Event('submit'));
|
| 242 |
+
}
|
| 243 |
+
});
|
| 244 |
+
|
| 245 |
+
async function sendMessage(query) {
|
| 246 |
+
if (!query) return;
|
| 247 |
+
disableComposer(true);
|
| 248 |
+
appendMessage("user", query);
|
| 249 |
+
createTypingIndicator();
|
| 250 |
+
queryInput.value = "";
|
| 251 |
+
autoResizeTextarea(); // Reset textarea height
|
| 252 |
+
|
| 253 |
+
try {
|
| 254 |
+
// include account so server can bind new convs to the logged-in user
|
| 255 |
+
const payload = { id: convId, query, history: [] };
|
| 256 |
+
if (account) payload.account = account;
|
| 257 |
+
const model = getSelectedModel();
|
| 258 |
+
if (model) payload.model = model;
|
| 259 |
+
const resp = await api("/ask", {
|
| 260 |
+
method: "POST",
|
| 261 |
+
headers: { "Content-Type": "application/json" },
|
| 262 |
+
body: JSON.stringify(payload),
|
| 263 |
+
});
|
| 264 |
+
removeTypingIndicator();
|
| 265 |
+
appendMessage("assistant", resp.answer || "(no answer)");
|
| 266 |
+
|
| 267 |
+
// Silent language detection: backend already replies in user's language
|
| 268 |
+
|
| 269 |
+
// Risk assessment is handled in backend only (no display)
|
| 270 |
+
// But show booking confirmation to user
|
| 271 |
+
if (resp.emergency_booking) {
|
| 272 |
+
displayEmergencyBooking(resp.emergency_booking);
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
// Handle booking question from backend
|
| 276 |
+
if (resp.ask_booking) {
|
| 277 |
+
displayBookingQuestion(resp.ask_booking);
|
| 278 |
+
}
|
| 279 |
+
|
| 280 |
+
if (resp.id && resp.id !== convId) {
|
| 281 |
+
convId = resp.id;
|
| 282 |
+
localStorage.setItem("aimhsa_conv", convId);
|
| 283 |
+
}
|
| 284 |
+
// refresh server-side conversation list for signed-in users
|
| 285 |
+
if (account) await updateHistoryList();
|
| 286 |
+
} catch (err) {
|
| 287 |
+
console.error("ask error", err);
|
| 288 |
+
removeTypingIndicator();
|
| 289 |
+
appendMessage("bot", "Error contacting server. Try again.");
|
| 290 |
+
} finally {
|
| 291 |
+
disableComposer(false);
|
| 292 |
+
}
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
// show upload preview block when a file is selected
|
| 296 |
+
function showUploadPreview(file) {
|
| 297 |
+
clearUploadPreview();
|
| 298 |
+
const preview = document.createElement("div");
|
| 299 |
+
preview.className = "upload-preview fade-in";
|
| 300 |
+
preview.dataset.name = file.name;
|
| 301 |
+
|
| 302 |
+
const icon = document.createElement("div");
|
| 303 |
+
icon.style.fontSize = "20px";
|
| 304 |
+
icon.textContent = "📄";
|
| 305 |
+
|
| 306 |
+
const meta = document.createElement("div");
|
| 307 |
+
meta.className = "upload-meta";
|
| 308 |
+
const fname = document.createElement("div");
|
| 309 |
+
fname.className = "upload-filename";
|
| 310 |
+
fname.textContent = file.name;
|
| 311 |
+
const fsize = document.createElement("div");
|
| 312 |
+
fsize.className = "small";
|
| 313 |
+
fsize.textContent = `${(file.size/1024).toFixed(1)} KB`;
|
| 314 |
+
|
| 315 |
+
meta.appendChild(fname);
|
| 316 |
+
meta.appendChild(fsize);
|
| 317 |
+
|
| 318 |
+
const actions = document.createElement("div");
|
| 319 |
+
actions.className = "upload-actions";
|
| 320 |
+
const progress = document.createElement("div");
|
| 321 |
+
progress.className = "progress-bar";
|
| 322 |
+
const inner = document.createElement("div");
|
| 323 |
+
inner.className = "progress-inner";
|
| 324 |
+
progress.appendChild(inner);
|
| 325 |
+
|
| 326 |
+
const removeBtn = document.createElement("button");
|
| 327 |
+
removeBtn.className = "btn-small";
|
| 328 |
+
removeBtn.type = "button";
|
| 329 |
+
removeBtn.textContent = "Remove";
|
| 330 |
+
removeBtn.addEventListener("click", () => {
|
| 331 |
+
fileInput.value = "";
|
| 332 |
+
clearUploadPreview();
|
| 333 |
+
});
|
| 334 |
+
|
| 335 |
+
actions.appendChild(progress);
|
| 336 |
+
actions.appendChild(removeBtn);
|
| 337 |
+
|
| 338 |
+
preview.appendChild(icon);
|
| 339 |
+
preview.appendChild(meta);
|
| 340 |
+
preview.appendChild(actions);
|
| 341 |
+
|
| 342 |
+
// insert preview at left of composer (before send button)
|
| 343 |
+
composer.insertBefore(preview, composer.firstChild);
|
| 344 |
+
currentPreview = { el: preview, inner };
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
function updateUploadProgress(pct) {
|
| 348 |
+
if (!currentPreview) return;
|
| 349 |
+
currentPreview.inner.style.width = Math.max(0, Math.min(100, pct)) + "%";
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
function clearUploadPreview() {
|
| 353 |
+
if (currentPreview && currentPreview.el) currentPreview.el.remove();
|
| 354 |
+
currentPreview = null;
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
// Use XHR for upload to track progress
|
| 358 |
+
function uploadPdf(file) {
|
| 359 |
+
if (!file) return;
|
| 360 |
+
disableComposer(true);
|
| 361 |
+
showUploadPreview(file);
|
| 362 |
+
|
| 363 |
+
const url = API_BASE_URL + "/upload_pdf";
|
| 364 |
+
const xhr = new XMLHttpRequest();
|
| 365 |
+
xhr.open("POST", url, true);
|
| 366 |
+
|
| 367 |
+
xhr.upload.onprogress = function(e) {
|
| 368 |
+
if (e.lengthComputable) {
|
| 369 |
+
const pct = Math.round((e.loaded / e.total) * 100);
|
| 370 |
+
updateUploadProgress(pct);
|
| 371 |
+
}
|
| 372 |
+
};
|
| 373 |
+
|
| 374 |
+
xhr.onload = function() {
|
| 375 |
+
disableComposer(false);
|
| 376 |
+
try {
|
| 377 |
+
const resText = xhr.responseText || "{}";
|
| 378 |
+
const data = JSON.parse(resText);
|
| 379 |
+
if (xhr.status >= 200 && xhr.status < 300) {
|
| 380 |
+
convId = data.id;
|
| 381 |
+
localStorage.setItem("aimhsa_conv", convId);
|
| 382 |
+
appendMessage("bot", `Uploaded ${data.filename}. What would you like to know about this document?`);
|
| 383 |
+
clearUploadPreview();
|
| 384 |
+
if (account) updateHistoryList();
|
| 385 |
+
} else {
|
| 386 |
+
appendMessage("bot", "PDF upload failed: " + (data.error || xhr.statusText));
|
| 387 |
+
}
|
| 388 |
+
} catch (err) {
|
| 389 |
+
appendMessage("bot", "Upload parsing error");
|
| 390 |
+
}
|
| 391 |
+
};
|
| 392 |
+
|
| 393 |
+
xhr.onerror = function() {
|
| 394 |
+
disableComposer(false);
|
| 395 |
+
appendMessage("bot", "PDF upload error");
|
| 396 |
+
};
|
| 397 |
+
|
| 398 |
+
const fd = new FormData();
|
| 399 |
+
fd.append("file", file, file.name);
|
| 400 |
+
if (convId) fd.append("id", convId);
|
| 401 |
+
if (account) fd.append("account", account);
|
| 402 |
+
const model = getSelectedModel();
|
| 403 |
+
if (model) fd.append("model", model);
|
| 404 |
+
xhr.send(fd);
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
function disableComposer(disabled) {
|
| 408 |
+
if (disabled) {
|
| 409 |
+
sendBtn.disabled = true;
|
| 410 |
+
sendBtn.classList.add("sending");
|
| 411 |
+
fileInput.disabled = true;
|
| 412 |
+
queryInput.disabled = true;
|
| 413 |
+
} else {
|
| 414 |
+
sendBtn.disabled = false;
|
| 415 |
+
sendBtn.classList.remove("sending");
|
| 416 |
+
fileInput.disabled = false;
|
| 417 |
+
queryInput.disabled = false;
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
// New chat: require account (server enforces too)
|
| 422 |
+
newChatBtn.addEventListener('click', async () => {
|
| 423 |
+
if (!account) {
|
| 424 |
+
appendMessage("bot", "Please sign in to create and view saved conversations.");
|
| 425 |
+
return;
|
| 426 |
+
}
|
| 427 |
+
try {
|
| 428 |
+
const payload = { account };
|
| 429 |
+
const resp = await api("/conversations", {
|
| 430 |
+
method: "POST",
|
| 431 |
+
headers: { "Content-Type": "application/json" },
|
| 432 |
+
body: JSON.stringify(payload)
|
| 433 |
+
});
|
| 434 |
+
if (resp && resp.id) {
|
| 435 |
+
convId = resp.id;
|
| 436 |
+
localStorage.setItem("aimhsa_conv", convId);
|
| 437 |
+
messagesEl.innerHTML = '';
|
| 438 |
+
await updateHistoryList();
|
| 439 |
+
}
|
| 440 |
+
} catch (e) {
|
| 441 |
+
console.error("failed to create conversation", e);
|
| 442 |
+
appendMessage("bot", "Could not start new conversation. Try again.");
|
| 443 |
+
}
|
| 444 |
+
});
|
| 445 |
+
|
| 446 |
+
// Clear only visual messages
|
| 447 |
+
clearChatBtn.addEventListener("click", () => {
|
| 448 |
+
if (!convId) return;
|
| 449 |
+
|
| 450 |
+
if (confirm("Clear current messages? This will only clear the visible chat.")) {
|
| 451 |
+
messagesEl.innerHTML = "";
|
| 452 |
+
appendMessage("bot", "Messages cleared. How can I help you?");
|
| 453 |
+
}
|
| 454 |
+
});
|
| 455 |
+
|
| 456 |
+
// Clear server-side history
|
| 457 |
+
clearHistoryBtn.addEventListener("click", async () => {
|
| 458 |
+
if (!convId) return;
|
| 459 |
+
|
| 460 |
+
if (confirm("Are you sure? This will permanently clear all saved messages and attachments.")) {
|
| 461 |
+
try {
|
| 462 |
+
await api("/clear_chat", {
|
| 463 |
+
method: "POST",
|
| 464 |
+
headers: { "Content-Type": "application/json" },
|
| 465 |
+
body: JSON.stringify({ id: convId })
|
| 466 |
+
});
|
| 467 |
+
|
| 468 |
+
// Clear both messages and conversation history
|
| 469 |
+
messagesEl.innerHTML = "";
|
| 470 |
+
historyList.innerHTML = "";
|
| 471 |
+
|
| 472 |
+
// Add default "no conversations" message
|
| 473 |
+
const note = document.createElement('div');
|
| 474 |
+
note.className = 'small';
|
| 475 |
+
note.style.padding = '12px';
|
| 476 |
+
note.style.color = 'var(--muted)';
|
| 477 |
+
note.textContent = 'No conversations yet. Start a new chat!';
|
| 478 |
+
historyList.appendChild(note);
|
| 479 |
+
|
| 480 |
+
appendMessage("bot", "Chat history cleared. How can I help you?");
|
| 481 |
+
|
| 482 |
+
// Start a new conversation
|
| 483 |
+
const payload = { account };
|
| 484 |
+
const resp = await api("/conversations", {
|
| 485 |
+
method: "POST",
|
| 486 |
+
headers: { "Content-Type": "application/json" },
|
| 487 |
+
body: JSON.stringify(payload)
|
| 488 |
+
});
|
| 489 |
+
|
| 490 |
+
if (resp && resp.id) {
|
| 491 |
+
convId = resp.id;
|
| 492 |
+
localStorage.setItem("aimhsa_conv", convId);
|
| 493 |
+
await updateHistoryList();
|
| 494 |
+
}
|
| 495 |
+
} catch (err) {
|
| 496 |
+
console.error("Failed to clear chat history", err);
|
| 497 |
+
appendMessage("bot", "Failed to clear chat history on server. Try again.");
|
| 498 |
+
}
|
| 499 |
+
}
|
| 500 |
+
});
|
| 501 |
+
|
| 502 |
+
// show preview when file selected
|
| 503 |
+
fileInput.addEventListener("change", (e) => {
|
| 504 |
+
const f = fileInput.files[0];
|
| 505 |
+
if (f) showUploadPreview(f);
|
| 506 |
+
else clearUploadPreview();
|
| 507 |
+
});
|
| 508 |
+
|
| 509 |
+
const app = document.querySelector('.app');
|
| 510 |
+
|
| 511 |
+
// Replace existing drag/drop handlers with:
|
| 512 |
+
document.addEventListener('dragenter', (e) => {
|
| 513 |
+
e.preventDefault();
|
| 514 |
+
if (!e.dataTransfer.types.includes('Files')) return;
|
| 515 |
+
app.classList.add('dragging');
|
| 516 |
+
});
|
| 517 |
+
|
| 518 |
+
document.addEventListener('dragleave', (e) => {
|
| 519 |
+
e.preventDefault();
|
| 520 |
+
// Only remove if actually leaving the app
|
| 521 |
+
if (e.target === document || e.target === app) {
|
| 522 |
+
app.classList.remove('dragging');
|
| 523 |
+
}
|
| 524 |
+
});
|
| 525 |
+
|
| 526 |
+
document.addEventListener('dragover', (e) => {
|
| 527 |
+
e.preventDefault();
|
| 528 |
+
});
|
| 529 |
+
|
| 530 |
+
document.addEventListener('drop', (e) => {
|
| 531 |
+
e.preventDefault();
|
| 532 |
+
app.classList.remove('dragging');
|
| 533 |
+
|
| 534 |
+
const files = Array.from(e.dataTransfer.files);
|
| 535 |
+
const pdfFile = files.find(f => f.type === 'application/pdf');
|
| 536 |
+
|
| 537 |
+
if (pdfFile) {
|
| 538 |
+
fileInput.files = e.dataTransfer.files;
|
| 539 |
+
const event = new Event('change');
|
| 540 |
+
fileInput.dispatchEvent(event);
|
| 541 |
+
uploadPdf(pdfFile);
|
| 542 |
+
} else {
|
| 543 |
+
appendMessage('bot', 'Please drop a PDF file.');
|
| 544 |
+
}
|
| 545 |
+
});
|
| 546 |
+
|
| 547 |
+
form.addEventListener("submit", (e) => {
|
| 548 |
+
e.preventDefault();
|
| 549 |
+
const q = queryInput.value.trim();
|
| 550 |
+
if (!q && !fileInput.files[0]) return;
|
| 551 |
+
|
| 552 |
+
const file = fileInput.files[0];
|
| 553 |
+
if (file) {
|
| 554 |
+
uploadPdf(file);
|
| 555 |
+
fileInput.value = "";
|
| 556 |
+
} else {
|
| 557 |
+
// ensure a convId exists for anonymous users too
|
| 558 |
+
if (!convId) {
|
| 559 |
+
convId = newConvId();
|
| 560 |
+
localStorage.setItem("aimhsa_conv", convId);
|
| 561 |
+
}
|
| 562 |
+
sendMessage(q);
|
| 563 |
+
}
|
| 564 |
+
});
|
| 565 |
+
|
| 566 |
+
// require signed-in account for server-backed conversations; otherwise show prompt
|
| 567 |
+
async function updateHistoryList() {
|
| 568 |
+
historyList.innerHTML = '';
|
| 569 |
+
if (archivedList) archivedList.innerHTML = '';
|
| 570 |
+
if (!account || account === 'null') {
|
| 571 |
+
const note = document.createElement('div');
|
| 572 |
+
note.className = 'small';
|
| 573 |
+
note.style.padding = '12px';
|
| 574 |
+
note.style.color = 'var(--text-muted)';
|
| 575 |
+
note.textContent = 'Sign in to view and manage your conversation history.';
|
| 576 |
+
historyList.appendChild(note);
|
| 577 |
+
newChatBtn.disabled = true;
|
| 578 |
+
newChatBtn.title = "Sign in to create server-backed conversations";
|
| 579 |
+
return;
|
| 580 |
+
}
|
| 581 |
+
newChatBtn.disabled = false;
|
| 582 |
+
newChatBtn.title = "";
|
| 583 |
+
|
| 584 |
+
try {
|
| 585 |
+
const q = "?account=" + encodeURIComponent(account);
|
| 586 |
+
const resp = await api("/conversations" + q, { method: "GET" });
|
| 587 |
+
const entries = resp.conversations || [];
|
| 588 |
+
for (const historyData of entries) {
|
| 589 |
+
const item = document.createElement('div');
|
| 590 |
+
item.className = 'history-item' + (historyData.id === convId ? ' active' : '');
|
| 591 |
+
|
| 592 |
+
const preview = document.createElement('div');
|
| 593 |
+
preview.className = 'history-preview';
|
| 594 |
+
preview.textContent = historyData.preview || 'New chat';
|
| 595 |
+
preview.title = historyData.preview || 'New chat';
|
| 596 |
+
|
| 597 |
+
// three-dot menu button
|
| 598 |
+
const menuBtn = document.createElement('button');
|
| 599 |
+
menuBtn.className = 'history-menu-btn';
|
| 600 |
+
menuBtn.setAttribute('aria-label', 'Conversation actions');
|
| 601 |
+
menuBtn.title = 'More';
|
| 602 |
+
menuBtn.textContent = '...';
|
| 603 |
+
|
| 604 |
+
// dropdown menu
|
| 605 |
+
const menu = document.createElement('div');
|
| 606 |
+
menu.className = 'history-menu';
|
| 607 |
+
const renameBtn = document.createElement('button');
|
| 608 |
+
renameBtn.textContent = 'Rename';
|
| 609 |
+
const archiveBtn = document.createElement('button');
|
| 610 |
+
archiveBtn.textContent = 'Archive';
|
| 611 |
+
const deleteBtn = document.createElement('button');
|
| 612 |
+
deleteBtn.textContent = 'Delete';
|
| 613 |
+
deleteBtn.className = 'danger';
|
| 614 |
+
menu.appendChild(renameBtn);
|
| 615 |
+
menu.appendChild(archiveBtn);
|
| 616 |
+
menu.appendChild(deleteBtn);
|
| 617 |
+
// rename
|
| 618 |
+
renameBtn.addEventListener('click', async (e) => {
|
| 619 |
+
e.stopPropagation();
|
| 620 |
+
const title = prompt('Rename conversation to:');
|
| 621 |
+
if (title == null || title.trim() === '') return;
|
| 622 |
+
try {
|
| 623 |
+
await api('/conversations/rename', {
|
| 624 |
+
method: 'POST',
|
| 625 |
+
headers: { 'Content-Type': 'application/json' },
|
| 626 |
+
body: JSON.stringify({ account, id: historyData.id, preview: title })
|
| 627 |
+
});
|
| 628 |
+
await updateHistoryList();
|
| 629 |
+
} catch (err) {
|
| 630 |
+
appendMessage('bot', 'Failed to rename conversation.');
|
| 631 |
+
}
|
| 632 |
+
});
|
| 633 |
+
|
| 634 |
+
// selection
|
| 635 |
+
item.addEventListener('click', () => switchConversation(historyData.id));
|
| 636 |
+
|
| 637 |
+
// open/close menu
|
| 638 |
+
menuBtn.addEventListener('click', (e) => {
|
| 639 |
+
e.stopPropagation();
|
| 640 |
+
const isOpen = menu.classList.contains('open');
|
| 641 |
+
document.querySelectorAll('.history-menu.open').forEach(m => m.classList.remove('open'));
|
| 642 |
+
if (!isOpen) menu.classList.add('open');
|
| 643 |
+
});
|
| 644 |
+
|
| 645 |
+
document.addEventListener('click', () => {
|
| 646 |
+
menu.classList.remove('open');
|
| 647 |
+
});
|
| 648 |
+
|
| 649 |
+
// archive (password required)
|
| 650 |
+
archiveBtn.addEventListener('click', async (e) => {
|
| 651 |
+
e.stopPropagation();
|
| 652 |
+
let pw = prompt('Set a password to archive this conversation (required).');
|
| 653 |
+
if (pw == null || pw.trim() === '') { appendMessage('bot', 'Archive cancelled: password required.'); return; }
|
| 654 |
+
try {
|
| 655 |
+
await api('/conversations/archive', {
|
| 656 |
+
method: 'POST',
|
| 657 |
+
headers: { 'Content-Type': 'application/json' },
|
| 658 |
+
body: JSON.stringify({ account, id: historyData.id, archived: true, password: pw || '' })
|
| 659 |
+
});
|
| 660 |
+
if (historyData.id === convId) {
|
| 661 |
+
messagesEl.innerHTML = '';
|
| 662 |
+
convId = null;
|
| 663 |
+
localStorage.removeItem('aimhsa_conv');
|
| 664 |
+
}
|
| 665 |
+
await updateHistoryList();
|
| 666 |
+
} catch (err) {
|
| 667 |
+
console.error('archive conversation failed', err);
|
| 668 |
+
appendMessage('bot', 'Failed to archive conversation.');
|
| 669 |
+
}
|
| 670 |
+
});
|
| 671 |
+
|
| 672 |
+
// delete
|
| 673 |
+
deleteBtn.addEventListener('click', async (e) => {
|
| 674 |
+
e.stopPropagation();
|
| 675 |
+
if (!confirm('Delete this conversation? This cannot be undone.')) return;
|
| 676 |
+
try {
|
| 677 |
+
await api('/conversations/delete', {
|
| 678 |
+
method: 'POST',
|
| 679 |
+
headers: { 'Content-Type': 'application/json' },
|
| 680 |
+
body: JSON.stringify({ account, id: historyData.id })
|
| 681 |
+
});
|
| 682 |
+
if (historyData.id === convId) {
|
| 683 |
+
messagesEl.innerHTML = '';
|
| 684 |
+
convId = null;
|
| 685 |
+
localStorage.removeItem('aimhsa_conv');
|
| 686 |
+
}
|
| 687 |
+
await updateHistoryList();
|
| 688 |
+
} catch (err) {
|
| 689 |
+
console.error('delete conversation failed', err);
|
| 690 |
+
appendMessage('bot', 'Failed to delete conversation.');
|
| 691 |
+
}
|
| 692 |
+
});
|
| 693 |
+
|
| 694 |
+
item.appendChild(preview);
|
| 695 |
+
item.appendChild(menuBtn);
|
| 696 |
+
item.appendChild(menu);
|
| 697 |
+
historyList.appendChild(item);
|
| 698 |
+
}
|
| 699 |
+
// load archived
|
| 700 |
+
try {
|
| 701 |
+
const ar = await api('/conversations/archived' + q, { method: 'GET' });
|
| 702 |
+
const archivedEntries = ar.conversations || [];
|
| 703 |
+
for (const h of archivedEntries) {
|
| 704 |
+
const item = document.createElement('div');
|
| 705 |
+
item.className = 'history-item';
|
| 706 |
+
const preview = document.createElement('div');
|
| 707 |
+
preview.className = 'history-preview';
|
| 708 |
+
preview.textContent = h.preview || 'New chat';
|
| 709 |
+
preview.title = h.preview || 'New chat';
|
| 710 |
+
|
| 711 |
+
const menuBtn = document.createElement('button');
|
| 712 |
+
menuBtn.className = 'history-menu-btn';
|
| 713 |
+
menuBtn.textContent = '...';
|
| 714 |
+
const menu = document.createElement('div');
|
| 715 |
+
menu.className = 'history-menu';
|
| 716 |
+
const unarchiveBtn = document.createElement('button');
|
| 717 |
+
unarchiveBtn.textContent = 'Unarchive';
|
| 718 |
+
const deleteBtn = document.createElement('button');
|
| 719 |
+
deleteBtn.textContent = 'Delete';
|
| 720 |
+
deleteBtn.className = 'danger';
|
| 721 |
+
// do not allow rename for archived
|
| 722 |
+
menu.appendChild(unarchiveBtn);
|
| 723 |
+
menu.appendChild(deleteBtn);
|
| 724 |
+
|
| 725 |
+
item.addEventListener('click', async () => {
|
| 726 |
+
try {
|
| 727 |
+
await api('/history?id=' + encodeURIComponent(h.id));
|
| 728 |
+
archivedPwById.delete(h.id);
|
| 729 |
+
await switchConversation(h.id);
|
| 730 |
+
} catch (e) {
|
| 731 |
+
try {
|
| 732 |
+
const pw = prompt('Enter password to open this archived conversation:');
|
| 733 |
+
if (pw == null) return;
|
| 734 |
+
await api('/history?id=' + encodeURIComponent(h.id) + '&password=' + encodeURIComponent(pw));
|
| 735 |
+
archivedPwById.set(h.id, pw);
|
| 736 |
+
await switchConversation(h.id);
|
| 737 |
+
} catch (e2) {
|
| 738 |
+
appendMessage('bot', 'Incorrect or missing password.');
|
| 739 |
+
}
|
| 740 |
+
}
|
| 741 |
+
});
|
| 742 |
+
menuBtn.addEventListener('click', (e) => {
|
| 743 |
+
e.stopPropagation();
|
| 744 |
+
const isOpen = menu.classList.contains('open');
|
| 745 |
+
document.querySelectorAll('.history-menu.open').forEach(m => m.classList.remove('open'));
|
| 746 |
+
if (!isOpen) menu.classList.add('open');
|
| 747 |
+
});
|
| 748 |
+
document.addEventListener('click', () => { menu.classList.remove('open'); });
|
| 749 |
+
|
| 750 |
+
unarchiveBtn.addEventListener('click', async (e) => {
|
| 751 |
+
e.stopPropagation();
|
| 752 |
+
const pw = prompt('Enter archive password to unarchive:');
|
| 753 |
+
if (pw == null || pw.trim() === '') { appendMessage('bot', 'Unarchive cancelled: password required.'); return; }
|
| 754 |
+
try {
|
| 755 |
+
await api('/conversations/archive', {
|
| 756 |
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
| 757 |
+
body: JSON.stringify({ account, id: h.id, archived: false, password: pw })
|
| 758 |
+
});
|
| 759 |
+
await updateHistoryList();
|
| 760 |
+
} catch (err) {
|
| 761 |
+
appendMessage('bot', 'Failed to unarchive conversation.');
|
| 762 |
+
}
|
| 763 |
+
});
|
| 764 |
+
deleteBtn.addEventListener('click', async (e) => {
|
| 765 |
+
e.stopPropagation();
|
| 766 |
+
if (!confirm('Delete this conversation? This cannot be undone.')) return;
|
| 767 |
+
const pw = prompt('Enter archive password to delete:');
|
| 768 |
+
if (pw == null || pw.trim() === '') { appendMessage('bot', 'Delete cancelled: password required.'); return; }
|
| 769 |
+
try {
|
| 770 |
+
await api('/conversations/delete', {
|
| 771 |
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
| 772 |
+
body: JSON.stringify({ account, id: h.id, password: pw })
|
| 773 |
+
});
|
| 774 |
+
await updateHistoryList();
|
| 775 |
+
} catch (err) {
|
| 776 |
+
appendMessage('bot', 'Failed to delete conversation.');
|
| 777 |
+
}
|
| 778 |
+
});
|
| 779 |
+
|
| 780 |
+
item.appendChild(preview);
|
| 781 |
+
item.appendChild(menuBtn);
|
| 782 |
+
item.appendChild(menu);
|
| 783 |
+
if (archivedList) archivedList.appendChild(item);
|
| 784 |
+
}
|
| 785 |
+
} catch (e2) {
|
| 786 |
+
// ignore archived load errors, show main list anyway
|
| 787 |
+
}
|
| 788 |
+
} catch (e) {
|
| 789 |
+
console.warn("failed to load conversations", e);
|
| 790 |
+
const errNote = document.createElement('div');
|
| 791 |
+
errNote.className = 'small';
|
| 792 |
+
errNote.style.padding = '12px';
|
| 793 |
+
errNote.style.color = 'var(--muted)';
|
| 794 |
+
errNote.textContent = 'Unable to load conversations.';
|
| 795 |
+
historyList.appendChild(errNote);
|
| 796 |
+
}
|
| 797 |
+
}
|
| 798 |
+
|
| 799 |
+
// switch conversation -> set convId, persist selection and load history
|
| 800 |
+
async function switchConversation(newConvId) {
|
| 801 |
+
if (!newConvId || newConvId === convId) return;
|
| 802 |
+
convId = newConvId;
|
| 803 |
+
localStorage.setItem("aimhsa_conv", convId);
|
| 804 |
+
await loadHistory();
|
| 805 |
+
await updateHistoryList();
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
// Risk assessment is handled in backend only (no display)
|
| 809 |
+
// But show booking confirmation to user
|
| 810 |
+
function displayBookingQuestion(bookingQuestion) {
|
| 811 |
+
// Create booking question card
|
| 812 |
+
const questionCard = document.createElement('div');
|
| 813 |
+
questionCard.className = 'booking-question-card';
|
| 814 |
+
questionCard.style.cssText = `
|
| 815 |
+
margin: 12px 0;
|
| 816 |
+
padding: 20px;
|
| 817 |
+
border-radius: 8px;
|
| 818 |
+
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
| 819 |
+
color: white;
|
| 820 |
+
border: 2px solid #2563eb;
|
| 821 |
+
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
| 822 |
+
`;
|
| 823 |
+
|
| 824 |
+
questionCard.innerHTML = `
|
| 825 |
+
<div style="display: flex; align-items: center; margin-bottom: 12px;">
|
| 826 |
+
<div style="font-size: 24px; margin-right: 12px;">💬</div>
|
| 827 |
+
<h3 style="margin: 0; font-size: 18px; font-weight: 700;">Professional Support Available</h3>
|
| 828 |
+
</div>
|
| 829 |
+
<p style="margin: 0 0 16px 0; font-size: 16px; opacity: 0.9;">
|
| 830 |
+
${bookingQuestion.message}
|
| 831 |
+
</p>
|
| 832 |
+
<div style="display: flex; gap: 12px;">
|
| 833 |
+
<button id="booking-yes" style="
|
| 834 |
+
padding: 12px 24px;
|
| 835 |
+
border: none;
|
| 836 |
+
border-radius: 6px;
|
| 837 |
+
background: white;
|
| 838 |
+
color: #3b82f6;
|
| 839 |
+
font-weight: 600;
|
| 840 |
+
cursor: pointer;
|
| 841 |
+
transition: all 0.2s;
|
| 842 |
+
">${bookingQuestion.options[0]}</button>
|
| 843 |
+
<button id="booking-no" style="
|
| 844 |
+
padding: 12px 24px;
|
| 845 |
+
border: 2px solid white;
|
| 846 |
+
border-radius: 6px;
|
| 847 |
+
background: transparent;
|
| 848 |
+
color: white;
|
| 849 |
+
font-weight: 600;
|
| 850 |
+
cursor: pointer;
|
| 851 |
+
transition: all 0.2s;
|
| 852 |
+
">${bookingQuestion.options[1]}</button>
|
| 853 |
+
</div>
|
| 854 |
+
`;
|
| 855 |
+
|
| 856 |
+
// Insert after the last message
|
| 857 |
+
const lastMessage = messagesEl.lastElementChild;
|
| 858 |
+
if (lastMessage) {
|
| 859 |
+
lastMessage.parentNode.insertBefore(questionCard, lastMessage.nextSibling);
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
// Add event listeners
|
| 863 |
+
document.getElementById('booking-yes').addEventListener('click', () => {
|
| 864 |
+
handleBookingResponse('yes');
|
| 865 |
+
questionCard.remove();
|
| 866 |
+
});
|
| 867 |
+
|
| 868 |
+
document.getElementById('booking-no').addEventListener('click', () => {
|
| 869 |
+
handleBookingResponse('no');
|
| 870 |
+
questionCard.remove();
|
| 871 |
+
});
|
| 872 |
+
|
| 873 |
+
// Scroll to show the question
|
| 874 |
+
questionCard.scrollIntoView({ behavior: 'smooth' });
|
| 875 |
+
}
|
| 876 |
+
|
| 877 |
+
async function handleBookingResponse(response) {
|
| 878 |
+
try {
|
| 879 |
+
const res = await fetch(API_ROOT + '/booking_response', {
|
| 880 |
+
method: 'POST',
|
| 881 |
+
headers: { 'Content-Type': 'application/json' },
|
| 882 |
+
body: JSON.stringify({
|
| 883 |
+
conversation_id: convId,
|
| 884 |
+
response: response,
|
| 885 |
+
account: localStorage.getItem('aimhsa_account')
|
| 886 |
+
})
|
| 887 |
+
});
|
| 888 |
+
|
| 889 |
+
const data = await res.json();
|
| 890 |
+
|
| 891 |
+
if (response === 'yes' && data.booking) {
|
| 892 |
+
// Show booking confirmation
|
| 893 |
+
displayEmergencyBooking(data.booking);
|
| 894 |
+
} else {
|
| 895 |
+
// Show acknowledgment message
|
| 896 |
+
appendMessage("assistant", data.message || "No problem! I'm here whenever you need support.");
|
| 897 |
+
}
|
| 898 |
+
} catch (error) {
|
| 899 |
+
console.error('Booking response error:', error);
|
| 900 |
+
appendMessage("assistant", "Sorry, there was an error processing your response. Please try again.");
|
| 901 |
+
}
|
| 902 |
+
}
|
| 903 |
+
|
| 904 |
+
// Removed language indicator UI for a cleaner experience
|
| 905 |
+
|
| 906 |
+
function displayEmergencyBooking(booking) {
|
| 907 |
+
const scheduledTime = new Date(booking.scheduled_time * 1000).toLocaleString();
|
| 908 |
+
|
| 909 |
+
// Create emergency booking notification
|
| 910 |
+
const bookingCard = document.createElement('div');
|
| 911 |
+
bookingCard.className = 'emergency-booking-card';
|
| 912 |
+
bookingCard.style.cssText = `
|
| 913 |
+
margin: 12px 0;
|
| 914 |
+
padding: 20px;
|
| 915 |
+
border-radius: 8px;
|
| 916 |
+
background: linear-gradient(135deg, #dc2626, #b91c1c);
|
| 917 |
+
color: white;
|
| 918 |
+
border: 2px solid #ef4444;
|
| 919 |
+
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);
|
| 920 |
+
`;
|
| 921 |
+
|
| 922 |
+
bookingCard.innerHTML = `
|
| 923 |
+
<div style="display: flex; align-items: center; margin-bottom: 12px;">
|
| 924 |
+
<div style="font-size: 24px; margin-right: 12px;">🚨</div>
|
| 925 |
+
<h3 style="margin: 0; font-size: 18px; font-weight: 700;">Emergency Session Scheduled</h3>
|
| 926 |
+
</div>
|
| 927 |
+
<div style="background: rgba(255, 255, 255, 0.1); padding: 12px; border-radius: 6px; margin-bottom: 12px;">
|
| 928 |
+
<p style="margin: 0 0 8px 0; font-size: 14px;"><strong>Professional:</strong> ${booking.professional_name}</p>
|
| 929 |
+
<p style="margin: 0 0 8px 0; font-size: 14px;"><strong>Specialization:</strong> ${booking.specialization}</p>
|
| 930 |
+
<p style="margin: 0 0 8px 0; font-size: 14px;"><strong>Scheduled:</strong> ${scheduledTime}</p>
|
| 931 |
+
<p style="margin: 0; font-size: 14px;"><strong>Session Type:</strong> ${booking.session_type}</p>
|
| 932 |
+
</div>
|
| 933 |
+
<p style="margin: 0; font-size: 14px; opacity: 0.9;">
|
| 934 |
+
A mental health professional has been automatically assigned to provide immediate support.
|
| 935 |
+
They will contact you shortly to confirm the session details.
|
| 936 |
+
</p>
|
| 937 |
+
`;
|
| 938 |
+
|
| 939 |
+
// Insert after the last message
|
| 940 |
+
const lastMessage = messagesEl.lastElementChild;
|
| 941 |
+
if (lastMessage) {
|
| 942 |
+
lastMessage.parentNode.insertBefore(bookingCard, lastMessage.nextSibling);
|
| 943 |
+
}
|
| 944 |
+
|
| 945 |
+
// Scroll to show the notification
|
| 946 |
+
bookingCard.scrollIntoView({ behavior: 'smooth' });
|
| 947 |
+
}
|
| 948 |
+
|
| 949 |
+
// initial load: start session (account-bound when available) and refresh history list
|
| 950 |
+
(async () => {
|
| 951 |
+
if (account) {
|
| 952 |
+
await initSession(true);
|
| 953 |
+
} else {
|
| 954 |
+
await initSession(false);
|
| 955 |
+
}
|
| 956 |
+
await updateHistoryList();
|
| 957 |
+
})();
|
| 958 |
+
})();
|
chatbot/auth.css
ADDED
|
@@ -0,0 +1,585 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--primary: #7c3aed;
|
| 3 |
+
--primary-dark: #5b21b6;
|
| 4 |
+
--primary-light: #a855f7;
|
| 5 |
+
--background: #0f172a;
|
| 6 |
+
--surface: #1e293b;
|
| 7 |
+
--card: #334155;
|
| 8 |
+
--text: #f8fafc;
|
| 9 |
+
--text-light: #cbd5e1;
|
| 10 |
+
--text-muted: #94a3b8;
|
| 11 |
+
--border: #334155;
|
| 12 |
+
--success: #10b981;
|
| 13 |
+
--error: #ef4444;
|
| 14 |
+
--gradient: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
* {
|
| 18 |
+
box-sizing: border-box;
|
| 19 |
+
margin: 0;
|
| 20 |
+
padding: 0;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
body {
|
| 24 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 25 |
+
background: var(--gradient);
|
| 26 |
+
min-height: 100vh;
|
| 27 |
+
display: flex;
|
| 28 |
+
align-items: center;
|
| 29 |
+
justify-content: center;
|
| 30 |
+
padding: 20px;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.auth-container {
|
| 34 |
+
width: 100%;
|
| 35 |
+
max-width: 400px;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.auth-card {
|
| 39 |
+
background: var(--surface);
|
| 40 |
+
border-radius: 20px;
|
| 41 |
+
padding: 40px;
|
| 42 |
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
| 43 |
+
backdrop-filter: blur(10px);
|
| 44 |
+
border: 1px solid var(--border);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.auth-header {
|
| 48 |
+
text-align: center;
|
| 49 |
+
margin-bottom: 30px;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
.brand {
|
| 53 |
+
font-size: 32px;
|
| 54 |
+
font-weight: 700;
|
| 55 |
+
color: var(--primary);
|
| 56 |
+
margin-bottom: 8px;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.subtitle {
|
| 60 |
+
color: var(--text-light);
|
| 61 |
+
font-size: 16px;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.auth-tabs {
|
| 65 |
+
display: flex;
|
| 66 |
+
background: var(--card);
|
| 67 |
+
border-radius: 12px;
|
| 68 |
+
padding: 4px;
|
| 69 |
+
margin-bottom: 30px;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.tab-btn {
|
| 73 |
+
flex: 1;
|
| 74 |
+
padding: 12px;
|
| 75 |
+
border: none;
|
| 76 |
+
background: transparent;
|
| 77 |
+
border-radius: 8px;
|
| 78 |
+
font-weight: 500;
|
| 79 |
+
color: var(--text-muted);
|
| 80 |
+
cursor: pointer;
|
| 81 |
+
transition: all 0.2s ease;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.tab-btn.active {
|
| 85 |
+
background: var(--surface);
|
| 86 |
+
color: var(--text);
|
| 87 |
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.tab-content {
|
| 91 |
+
display: block;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.tab-content.hidden {
|
| 95 |
+
display: none;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.form-group {
|
| 99 |
+
margin-bottom: 20px;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
.form-group label {
|
| 103 |
+
display: block;
|
| 104 |
+
margin-bottom: 8px;
|
| 105 |
+
font-weight: 500;
|
| 106 |
+
color: var(--text);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.form-group input {
|
| 110 |
+
width: 100%;
|
| 111 |
+
padding: 12px 16px;
|
| 112 |
+
border: 2px solid var(--border);
|
| 113 |
+
border-radius: 10px;
|
| 114 |
+
font-size: 16px;
|
| 115 |
+
transition: all 0.2s ease;
|
| 116 |
+
background: var(--background);
|
| 117 |
+
color: var(--text);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
.form-group input:focus {
|
| 121 |
+
outline: none;
|
| 122 |
+
border-color: var(--primary);
|
| 123 |
+
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.2);
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
/* Inline hints and rows */
|
| 127 |
+
.input-hint {
|
| 128 |
+
margin-top: 8px;
|
| 129 |
+
font-size: 12px;
|
| 130 |
+
color: var(--text-muted);
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.input-row {
|
| 134 |
+
display: flex;
|
| 135 |
+
align-items: center;
|
| 136 |
+
justify-content: space-between;
|
| 137 |
+
gap: 12px;
|
| 138 |
+
margin-top: 8px;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
/* Password field with toggle */
|
| 142 |
+
.password-field {
|
| 143 |
+
position: relative;
|
| 144 |
+
display: flex;
|
| 145 |
+
align-items: center;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.password-field input {
|
| 149 |
+
padding-right: 44px;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.toggle-password {
|
| 153 |
+
position: absolute;
|
| 154 |
+
right: 8px;
|
| 155 |
+
top: 50%;
|
| 156 |
+
transform: translateY(-50%);
|
| 157 |
+
background: transparent;
|
| 158 |
+
border: none;
|
| 159 |
+
width: 32px;
|
| 160 |
+
height: 32px;
|
| 161 |
+
border-radius: 8px;
|
| 162 |
+
cursor: pointer;
|
| 163 |
+
color: var(--text-muted);
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
.toggle-password:hover {
|
| 167 |
+
color: var(--text);
|
| 168 |
+
background: rgba(255,255,255,0.05);
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
.caps-lock {
|
| 172 |
+
color: var(--error);
|
| 173 |
+
font-size: 12px;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
/* Password strength meter */
|
| 177 |
+
.password-meter {
|
| 178 |
+
flex: 1;
|
| 179 |
+
height: 8px;
|
| 180 |
+
background: var(--card);
|
| 181 |
+
border: 1px solid var(--border);
|
| 182 |
+
border-radius: 9999px;
|
| 183 |
+
overflow: hidden;
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
.password-meter-bar {
|
| 187 |
+
height: 100%;
|
| 188 |
+
width: 0%;
|
| 189 |
+
background: var(--error);
|
| 190 |
+
transition: width 0.2s ease, background 0.2s ease;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
/* Form row */
|
| 194 |
+
.form-row {
|
| 195 |
+
display: flex;
|
| 196 |
+
align-items: center;
|
| 197 |
+
justify-content: space-between;
|
| 198 |
+
margin: 10px 0 20px;
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
.remember-me {
|
| 202 |
+
display: inline-flex;
|
| 203 |
+
align-items: center;
|
| 204 |
+
gap: 8px;
|
| 205 |
+
cursor: pointer;
|
| 206 |
+
color: var(--text-light);
|
| 207 |
+
user-select: none;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
.forgot-link {
|
| 211 |
+
color: var(--primary);
|
| 212 |
+
text-decoration: none;
|
| 213 |
+
font-size: 14px;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.forgot-link:hover {
|
| 217 |
+
color: var(--primary-light);
|
| 218 |
+
text-decoration: underline;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
/* Secondary button */
|
| 222 |
+
.secondary-btn {
|
| 223 |
+
padding: 12px 16px;
|
| 224 |
+
background: transparent;
|
| 225 |
+
border: 2px solid var(--border);
|
| 226 |
+
color: var(--text);
|
| 227 |
+
border-radius: 10px;
|
| 228 |
+
font-weight: 600;
|
| 229 |
+
cursor: pointer;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
.secondary-btn:hover {
|
| 233 |
+
border-color: var(--primary);
|
| 234 |
+
color: var(--primary);
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
/* Modal */
|
| 238 |
+
.modal {
|
| 239 |
+
position: fixed;
|
| 240 |
+
inset: 0;
|
| 241 |
+
display: none;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
.modal.open { display: block; }
|
| 245 |
+
|
| 246 |
+
.modal-backdrop {
|
| 247 |
+
position: absolute;
|
| 248 |
+
inset: 0;
|
| 249 |
+
background: rgba(0,0,0,0.6);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.modal-content {
|
| 253 |
+
position: relative;
|
| 254 |
+
width: 100%;
|
| 255 |
+
max-width: 420px;
|
| 256 |
+
margin: 10vh auto;
|
| 257 |
+
background: var(--surface);
|
| 258 |
+
border: 1px solid var(--border);
|
| 259 |
+
border-radius: 16px;
|
| 260 |
+
padding: 20px;
|
| 261 |
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.4);
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
.modal-header {
|
| 265 |
+
display: flex;
|
| 266 |
+
align-items: center;
|
| 267 |
+
justify-content: space-between;
|
| 268 |
+
margin-bottom: 10px;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.modal-close {
|
| 272 |
+
background: transparent;
|
| 273 |
+
border: none;
|
| 274 |
+
color: var(--text-muted);
|
| 275 |
+
cursor: pointer;
|
| 276 |
+
font-size: 18px;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
.modal-body p {
|
| 280 |
+
color: var(--text-light);
|
| 281 |
+
margin-bottom: 10px;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
.modal-actions {
|
| 285 |
+
display: flex;
|
| 286 |
+
gap: 10px;
|
| 287 |
+
margin-top: 10px;
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.modal-message {
|
| 291 |
+
margin-top: 10px;
|
| 292 |
+
font-size: 14px;
|
| 293 |
+
padding: 10px;
|
| 294 |
+
border-radius: 6px;
|
| 295 |
+
text-align: center;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
.modal-message.success {
|
| 299 |
+
background: rgba(16, 185, 129, 0.1);
|
| 300 |
+
color: #10b981;
|
| 301 |
+
border: 1px solid rgba(16, 185, 129, 0.2);
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.modal-message.error {
|
| 305 |
+
background: rgba(239, 68, 68, 0.1);
|
| 306 |
+
color: #ef4444;
|
| 307 |
+
border: 1px solid rgba(239, 68, 68, 0.2);
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
/* Forgot password steps */
|
| 311 |
+
.fp-step.hidden { display: none; }
|
| 312 |
+
|
| 313 |
+
.auth-btn {
|
| 314 |
+
width: 100%;
|
| 315 |
+
padding: 14px;
|
| 316 |
+
background: var(--primary);
|
| 317 |
+
color: white;
|
| 318 |
+
border: none;
|
| 319 |
+
border-radius: 10px;
|
| 320 |
+
font-size: 16px;
|
| 321 |
+
font-weight: 600;
|
| 322 |
+
cursor: pointer;
|
| 323 |
+
transition: all 0.2s ease;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
.auth-btn:hover {
|
| 327 |
+
background: var(--primary-dark);
|
| 328 |
+
transform: translateY(-1px);
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
.auth-btn:disabled {
|
| 332 |
+
opacity: 0.6;
|
| 333 |
+
cursor: not-allowed;
|
| 334 |
+
transform: none;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
.auth-divider {
|
| 338 |
+
text-align: center;
|
| 339 |
+
margin: 30px 0;
|
| 340 |
+
position: relative;
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
.auth-divider::before {
|
| 344 |
+
content: '';
|
| 345 |
+
position: absolute;
|
| 346 |
+
top: 50%;
|
| 347 |
+
left: 0;
|
| 348 |
+
right: 0;
|
| 349 |
+
height: 1px;
|
| 350 |
+
background: var(--border);
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
.auth-divider span {
|
| 354 |
+
background: var(--surface);
|
| 355 |
+
padding: 0 20px;
|
| 356 |
+
color: var(--text-muted);
|
| 357 |
+
font-size: 14px;
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
.anonymous-btn {
|
| 361 |
+
width: 100%;
|
| 362 |
+
padding: 14px;
|
| 363 |
+
background: transparent;
|
| 364 |
+
color: var(--text);
|
| 365 |
+
border: 2px solid var(--border);
|
| 366 |
+
border-radius: 10px;
|
| 367 |
+
font-size: 16px;
|
| 368 |
+
font-weight: 500;
|
| 369 |
+
cursor: pointer;
|
| 370 |
+
transition: all 0.2s ease;
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
.anonymous-btn:hover {
|
| 374 |
+
border-color: var(--primary);
|
| 375 |
+
color: var(--primary);
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
.auth-footer {
|
| 379 |
+
text-align: center;
|
| 380 |
+
margin-top: 30px;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
.auth-footer p {
|
| 384 |
+
font-size: 12px;
|
| 385 |
+
color: var(--text-muted);
|
| 386 |
+
line-height: 1.5;
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
.error-message {
|
| 390 |
+
background: #fef2f2;
|
| 391 |
+
color: var(--error);
|
| 392 |
+
padding: 12px;
|
| 393 |
+
border-radius: 8px;
|
| 394 |
+
margin-bottom: 20px;
|
| 395 |
+
font-size: 14px;
|
| 396 |
+
text-align: center;
|
| 397 |
+
border: 1px solid var(--error);
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
.success-message {
|
| 401 |
+
background: #f0fdf4;
|
| 402 |
+
color: var(--success);
|
| 403 |
+
padding: 12px;
|
| 404 |
+
border-radius: 8px;
|
| 405 |
+
margin-bottom: 20px;
|
| 406 |
+
font-size: 14px;
|
| 407 |
+
text-align: center;
|
| 408 |
+
border: 1px solid var(--success);
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
/* Validation Styles */
|
| 412 |
+
.field-error {
|
| 413 |
+
font-size: 12px;
|
| 414 |
+
color: var(--error);
|
| 415 |
+
margin-top: 4px;
|
| 416 |
+
display: none;
|
| 417 |
+
line-height: 1.4;
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
.field-error.show {
|
| 421 |
+
display: block;
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
.field-help {
|
| 425 |
+
font-size: 12px;
|
| 426 |
+
color: var(--text-muted);
|
| 427 |
+
margin-top: 4px;
|
| 428 |
+
line-height: 1.4;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
.form-group.error input,
|
| 432 |
+
.form-group.error select {
|
| 433 |
+
border-color: var(--error);
|
| 434 |
+
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
.form-group.success input,
|
| 438 |
+
.form-group.success select {
|
| 439 |
+
border-color: var(--success);
|
| 440 |
+
box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
|
| 441 |
+
}
|
| 442 |
+
|
| 443 |
+
.checkbox-label {
|
| 444 |
+
display: flex;
|
| 445 |
+
align-items: flex-start;
|
| 446 |
+
gap: 8px;
|
| 447 |
+
cursor: pointer;
|
| 448 |
+
font-size: 14px;
|
| 449 |
+
line-height: 1.5;
|
| 450 |
+
color: var(--text-light);
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
.checkbox-label input[type="checkbox"] {
|
| 454 |
+
margin: 0;
|
| 455 |
+
width: 16px;
|
| 456 |
+
height: 16px;
|
| 457 |
+
flex-shrink: 0;
|
| 458 |
+
margin-top: 2px;
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
.checkbox-label a {
|
| 462 |
+
color: var(--primary);
|
| 463 |
+
text-decoration: none;
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
.checkbox-label a:hover {
|
| 467 |
+
text-decoration: underline;
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
.validation-summary {
|
| 471 |
+
background: rgba(239, 68, 68, 0.1);
|
| 472 |
+
border: 1px solid rgba(239, 68, 68, 0.3);
|
| 473 |
+
border-radius: 8px;
|
| 474 |
+
padding: 12px;
|
| 475 |
+
margin-bottom: 20px;
|
| 476 |
+
display: none;
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
.validation-summary.show {
|
| 480 |
+
display: block;
|
| 481 |
+
}
|
| 482 |
+
|
| 483 |
+
.validation-summary h4 {
|
| 484 |
+
color: var(--error);
|
| 485 |
+
font-size: 14px;
|
| 486 |
+
font-weight: 600;
|
| 487 |
+
margin-bottom: 8px;
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
.validation-summary ul {
|
| 491 |
+
list-style: none;
|
| 492 |
+
margin: 0;
|
| 493 |
+
padding: 0;
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
.validation-summary li {
|
| 497 |
+
color: var(--error);
|
| 498 |
+
font-size: 13px;
|
| 499 |
+
margin-bottom: 4px;
|
| 500 |
+
padding-left: 16px;
|
| 501 |
+
position: relative;
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
.validation-summary li:before {
|
| 505 |
+
content: "•";
|
| 506 |
+
position: absolute;
|
| 507 |
+
left: 0;
|
| 508 |
+
color: var(--error);
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
.password-strength {
|
| 512 |
+
margin-top: 4px;
|
| 513 |
+
font-size: 12px;
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
.password-strength.weak {
|
| 517 |
+
color: var(--error);
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
.password-strength.medium {
|
| 521 |
+
color: #f59e0b;
|
| 522 |
+
}
|
| 523 |
+
|
| 524 |
+
.password-strength.strong {
|
| 525 |
+
color: var(--success);
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
.password-strength-indicator {
|
| 529 |
+
height: 4px;
|
| 530 |
+
background: var(--card);
|
| 531 |
+
border-radius: 2px;
|
| 532 |
+
margin-top: 4px;
|
| 533 |
+
overflow: hidden;
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
.password-strength-bar {
|
| 537 |
+
height: 100%;
|
| 538 |
+
width: 0%;
|
| 539 |
+
transition: width 0.3s ease, background-color 0.3s ease;
|
| 540 |
+
border-radius: 2px;
|
| 541 |
+
}
|
| 542 |
+
|
| 543 |
+
.password-strength-bar.weak {
|
| 544 |
+
background: var(--error);
|
| 545 |
+
width: 33%;
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
.password-strength-bar.medium {
|
| 549 |
+
background: #f59e0b;
|
| 550 |
+
width: 66%;
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
.password-strength-bar.strong {
|
| 554 |
+
background: var(--success);
|
| 555 |
+
width: 100%;
|
| 556 |
+
}
|
| 557 |
+
|
| 558 |
+
.auth-links {
|
| 559 |
+
text-align: center;
|
| 560 |
+
margin-top: 20px;
|
| 561 |
+
color: var(--text-light);
|
| 562 |
+
font-size: 14px;
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
.auth-links a {
|
| 566 |
+
color: var(--primary);
|
| 567 |
+
text-decoration: none;
|
| 568 |
+
font-weight: 500;
|
| 569 |
+
transition: color 0.2s;
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
.auth-links a:hover {
|
| 573 |
+
color: var(--primary-light);
|
| 574 |
+
text-decoration: underline;
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
@media (max-width: 480px) {
|
| 578 |
+
.auth-card {
|
| 579 |
+
padding: 30px 20px;
|
| 580 |
+
}
|
| 581 |
+
|
| 582 |
+
.brand {
|
| 583 |
+
font-size: 28px;
|
| 584 |
+
}
|
| 585 |
+
}
|
chatbot/auth.html
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
| 6 |
+
<title>AIMHSA - Sign In</title>
|
| 7 |
+
<link rel="stylesheet" href="auth.css" />
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="auth-container">
|
| 11 |
+
<div class="auth-card">
|
| 12 |
+
<div class="auth-header">
|
| 13 |
+
<h1 class="brand">AIMHSA</h1>
|
| 14 |
+
<p class="subtitle">Mental Health Companion for Rwanda</p>
|
| 15 |
+
</div>
|
| 16 |
+
|
| 17 |
+
<div class="auth-tabs">
|
| 18 |
+
<button class="tab-btn active" data-tab="signin">Sign In</button>
|
| 19 |
+
<button class="tab-btn" data-tab="register">Register</button>
|
| 20 |
+
</div>
|
| 21 |
+
|
| 22 |
+
<form class="auth-form" id="authForm">
|
| 23 |
+
<div class="tab-content" id="signin">
|
| 24 |
+
<div class="form-group">
|
| 25 |
+
<label for="loginUsername">Username</label>
|
| 26 |
+
<input type="text" id="loginUsername" name="loginUsername" required />
|
| 27 |
+
</div>
|
| 28 |
+
<div class="form-group">
|
| 29 |
+
<label for="loginPassword">Password</label>
|
| 30 |
+
<input type="password" id="loginPassword" name="loginPassword" required />
|
| 31 |
+
</div>
|
| 32 |
+
<button type="submit" class="auth-btn" id="signInBtn">Sign In</button>
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<div class="tab-content hidden" id="register">
|
| 36 |
+
<div class="form-group">
|
| 37 |
+
<label for="regUsername">Username</label>
|
| 38 |
+
<input type="text" id="regUsername" name="regUsername" required />
|
| 39 |
+
</div>
|
| 40 |
+
<div class="form-group">
|
| 41 |
+
<label for="regPassword">Password</label>
|
| 42 |
+
<input type="password" id="regPassword" name="regPassword" required />
|
| 43 |
+
</div>
|
| 44 |
+
<div class="form-group">
|
| 45 |
+
<label for="regConfirmPassword">Confirm Password</label>
|
| 46 |
+
<input type="password" id="regConfirmPassword" name="regConfirmPassword" required />
|
| 47 |
+
</div>
|
| 48 |
+
<button type="submit" class="auth-btn" id="registerBtn">Create Account</button>
|
| 49 |
+
</div>
|
| 50 |
+
</form>
|
| 51 |
+
|
| 52 |
+
<div class="auth-divider">
|
| 53 |
+
<span>or</span>
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<button class="anonymous-btn" id="anonBtn">Continue as Guest</button>
|
| 57 |
+
|
| 58 |
+
<div class="auth-footer">
|
| 59 |
+
<p>By continuing, you agree to our Terms of Service and Privacy Policy</p>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<script src="auth.js"></script>
|
| 65 |
+
</body>
|
| 66 |
+
</html>
|
chatbot/auth.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(() => {
|
| 2 |
+
'use strict';
|
| 3 |
+
|
| 4 |
+
// Get API URL from configuration
|
| 5 |
+
const getAPIBaseUrl = () => {
|
| 6 |
+
if (window.AIMHSA && window.AIMHSA.Config) {
|
| 7 |
+
return window.AIMHSA.Config.getApiBaseUrl();
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
// Fallback to auto-detection
|
| 11 |
+
return `http://${window.location.hostname}:${window.location.port || '5057'}`;
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
const API_BASE_URL = getAPIBaseUrl();
|
| 15 |
+
|
| 16 |
+
// Elements
|
| 17 |
+
const tabBtns = document.querySelectorAll('.tab-btn');
|
| 18 |
+
const tabContents = document.querySelectorAll('.tab-content');
|
| 19 |
+
const authForm = document.getElementById('authForm');
|
| 20 |
+
const signInBtn = document.getElementById('signInBtn');
|
| 21 |
+
const registerBtn = document.getElementById('registerBtn');
|
| 22 |
+
const anonBtn = document.getElementById('anonBtn');
|
| 23 |
+
|
| 24 |
+
// Tab switching
|
| 25 |
+
tabBtns.forEach(btn => {
|
| 26 |
+
btn.addEventListener('click', () => {
|
| 27 |
+
const targetTab = btn.dataset.tab;
|
| 28 |
+
|
| 29 |
+
// Update active tab
|
| 30 |
+
tabBtns.forEach(b => b.classList.remove('active'));
|
| 31 |
+
btn.classList.add('active');
|
| 32 |
+
|
| 33 |
+
// Show target content
|
| 34 |
+
tabContents.forEach(content => {
|
| 35 |
+
if (content.id === targetTab) {
|
| 36 |
+
content.classList.remove('hidden');
|
| 37 |
+
} else {
|
| 38 |
+
content.classList.add('hidden');
|
| 39 |
+
}
|
| 40 |
+
});
|
| 41 |
+
});
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
// API helper
|
| 45 |
+
async function api(path, opts) {
|
| 46 |
+
const url = API_BASE_URL + path;
|
| 47 |
+
const res = await fetch(url, opts);
|
| 48 |
+
if (!res.ok) {
|
| 49 |
+
const txt = await res.text();
|
| 50 |
+
throw new Error(txt || res.statusText);
|
| 51 |
+
}
|
| 52 |
+
return res.json();
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// Show message
|
| 56 |
+
function showMessage(text, type = 'error') {
|
| 57 |
+
const existing = document.querySelector('.error-message, .success-message');
|
| 58 |
+
if (existing) existing.remove();
|
| 59 |
+
|
| 60 |
+
const message = document.createElement('div');
|
| 61 |
+
message.className = type === 'error' ? 'error-message' : 'success-message';
|
| 62 |
+
message.textContent = text;
|
| 63 |
+
|
| 64 |
+
authForm.insertBefore(message, authForm.firstChild);
|
| 65 |
+
|
| 66 |
+
setTimeout(() => message.remove(), 5000);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// Redirect to main app
|
| 70 |
+
function redirectToApp(account = null) {
|
| 71 |
+
if (account) {
|
| 72 |
+
localStorage.setItem('aimhsa_account', account);
|
| 73 |
+
}
|
| 74 |
+
window.location.href = 'index.html';
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
// Form submission
|
| 78 |
+
authForm.addEventListener('submit', async (e) => {
|
| 79 |
+
e.preventDefault();
|
| 80 |
+
|
| 81 |
+
const activeTab = document.querySelector('.tab-content:not(.hidden)').id;
|
| 82 |
+
|
| 83 |
+
if (activeTab === 'signin') {
|
| 84 |
+
const username = document.getElementById('loginUsername').value.trim();
|
| 85 |
+
const password = document.getElementById('loginPassword').value;
|
| 86 |
+
|
| 87 |
+
if (!username || !password) {
|
| 88 |
+
showMessage('Please enter both username and password');
|
| 89 |
+
return;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
signInBtn.disabled = true;
|
| 93 |
+
signInBtn.textContent = 'Signing in...';
|
| 94 |
+
|
| 95 |
+
try {
|
| 96 |
+
const res = await api('/api/login', {
|
| 97 |
+
method: 'POST',
|
| 98 |
+
headers: { 'Content-Type': 'application/json' },
|
| 99 |
+
body: JSON.stringify({ username, password })
|
| 100 |
+
});
|
| 101 |
+
|
| 102 |
+
showMessage('Successfully signed in!', 'success');
|
| 103 |
+
setTimeout(() => redirectToApp(res.account || username), 1000);
|
| 104 |
+
} catch (err) {
|
| 105 |
+
showMessage('Invalid username or password');
|
| 106 |
+
} finally {
|
| 107 |
+
signInBtn.disabled = false;
|
| 108 |
+
signInBtn.textContent = 'Sign In';
|
| 109 |
+
}
|
| 110 |
+
} else {
|
| 111 |
+
const username = document.getElementById('regUsername').value.trim();
|
| 112 |
+
const password = document.getElementById('regPassword').value;
|
| 113 |
+
const confirmPassword = document.getElementById('regConfirmPassword').value;
|
| 114 |
+
|
| 115 |
+
if (!username || !password || !confirmPassword) {
|
| 116 |
+
showMessage('Please fill in all fields');
|
| 117 |
+
return;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
if (password !== confirmPassword) {
|
| 121 |
+
showMessage('Passwords do not match');
|
| 122 |
+
return;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
if (password.length < 6) {
|
| 126 |
+
showMessage('Password must be at least 6 characters');
|
| 127 |
+
return;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
registerBtn.disabled = true;
|
| 131 |
+
registerBtn.textContent = 'Creating account...';
|
| 132 |
+
|
| 133 |
+
try {
|
| 134 |
+
await api('/api/register', {
|
| 135 |
+
method: 'POST',
|
| 136 |
+
headers: { 'Content-Type': 'application/json' },
|
| 137 |
+
body: JSON.stringify({ username, password })
|
| 138 |
+
});
|
| 139 |
+
|
| 140 |
+
showMessage('Account created successfully!', 'success');
|
| 141 |
+
setTimeout(() => redirectToApp(username), 1000);
|
| 142 |
+
} catch (err) {
|
| 143 |
+
showMessage('Username already exists or registration failed');
|
| 144 |
+
} finally {
|
| 145 |
+
registerBtn.disabled = false;
|
| 146 |
+
registerBtn.textContent = 'Create Account';
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
});
|
| 150 |
+
|
| 151 |
+
// Anonymous access
|
| 152 |
+
anonBtn.addEventListener('click', () => {
|
| 153 |
+
localStorage.removeItem('aimhsa_account');
|
| 154 |
+
redirectToApp();
|
| 155 |
+
});
|
| 156 |
+
|
| 157 |
+
// Check if already logged in
|
| 158 |
+
const account = localStorage.getItem('aimhsa_account');
|
| 159 |
+
if (account) {
|
| 160 |
+
redirectToApp(account);
|
| 161 |
+
}
|
| 162 |
+
})();
|
chatbot/config-ui.js
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Configuration UI Management
|
| 3 |
+
* Provides UI components for managing frontend configuration
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
(() => {
|
| 7 |
+
'use strict';
|
| 8 |
+
|
| 9 |
+
class ConfigUI {
|
| 10 |
+
constructor() {
|
| 11 |
+
this.config = window.AIMHSA?.Config;
|
| 12 |
+
this.init();
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
init() {
|
| 16 |
+
this.createConfigPanel();
|
| 17 |
+
this.bindEvents();
|
| 18 |
+
console.log('⚙️ Configuration UI initialized');
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
/**
|
| 22 |
+
* Create configuration panel
|
| 23 |
+
*/
|
| 24 |
+
createConfigPanel() {
|
| 25 |
+
const configPanel = document.createElement('div');
|
| 26 |
+
configPanel.id = 'configPanel';
|
| 27 |
+
configPanel.className = 'config-panel';
|
| 28 |
+
configPanel.innerHTML = `
|
| 29 |
+
<div class="config-header">
|
| 30 |
+
<h3><i class="fas fa-cog"></i> Configuration</h3>
|
| 31 |
+
<button class="btn btn-sm btn-outline-secondary" id="toggleConfigPanel">
|
| 32 |
+
<i class="fas fa-times"></i>
|
| 33 |
+
</button>
|
| 34 |
+
</div>
|
| 35 |
+
<div class="config-content">
|
| 36 |
+
<div class="config-section">
|
| 37 |
+
<h4>API Configuration</h4>
|
| 38 |
+
<div class="form-group">
|
| 39 |
+
<label>API Base URL</label>
|
| 40 |
+
<input type="url" id="apiBaseUrl" class="form-control"
|
| 41 |
+
value="${this.config?.getApiBaseUrl() || ''}"
|
| 42 |
+
placeholder="http://localhost:5057">
|
| 43 |
+
<small class="form-text text-muted">
|
| 44 |
+
Base URL for the backend API server
|
| 45 |
+
</small>
|
| 46 |
+
</div>
|
| 47 |
+
<div class="form-group">
|
| 48 |
+
<label>Environment</label>
|
| 49 |
+
<select id="environment" class="form-control">
|
| 50 |
+
<option value="development">Development</option>
|
| 51 |
+
<option value="staging">Staging</option>
|
| 52 |
+
<option value="production">Production</option>
|
| 53 |
+
</select>
|
| 54 |
+
</div>
|
| 55 |
+
<button class="btn btn-primary btn-sm" id="testConnection">
|
| 56 |
+
<i class="fas fa-plug"></i> Test Connection
|
| 57 |
+
</button>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<div class="config-section">
|
| 61 |
+
<h4>UI Configuration</h4>
|
| 62 |
+
<div class="form-group">
|
| 63 |
+
<label>Theme</label>
|
| 64 |
+
<select id="theme" class="form-control">
|
| 65 |
+
<option value="dark">Dark</option>
|
| 66 |
+
<option value="light">Light</option>
|
| 67 |
+
</select>
|
| 68 |
+
</div>
|
| 69 |
+
<div class="form-group">
|
| 70 |
+
<label>Auto-refresh Interval (seconds)</label>
|
| 71 |
+
<input type="number" id="autoRefresh" class="form-control"
|
| 72 |
+
value="${this.config?.get('ui.autoRefreshInterval', 30000) / 1000}"
|
| 73 |
+
min="5" max="300">
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<div class="config-actions">
|
| 78 |
+
<button class="btn btn-success btn-sm" id="saveConfig">
|
| 79 |
+
<i class="fas fa-save"></i> Save Configuration
|
| 80 |
+
</button>
|
| 81 |
+
<button class="btn btn-warning btn-sm" id="resetConfig">
|
| 82 |
+
<i class="fas fa-undo"></i> Reset to Defaults
|
| 83 |
+
</button>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
<div class="config-info">
|
| 87 |
+
<h4>Environment Information</h4>
|
| 88 |
+
<pre id="envInfo"></pre>
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
`;
|
| 92 |
+
|
| 93 |
+
// Add styles
|
| 94 |
+
const style = document.createElement('style');
|
| 95 |
+
style.textContent = `
|
| 96 |
+
.config-panel {
|
| 97 |
+
position: fixed;
|
| 98 |
+
top: 20px;
|
| 99 |
+
right: -400px;
|
| 100 |
+
width: 380px;
|
| 101 |
+
height: calc(100vh - 40px);
|
| 102 |
+
background: var(--surface, #1e293b);
|
| 103 |
+
border: 1px solid var(--border, #334155);
|
| 104 |
+
border-radius: 12px;
|
| 105 |
+
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
|
| 106 |
+
z-index: 10000;
|
| 107 |
+
transition: right 0.3s ease;
|
| 108 |
+
overflow-y: auto;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.config-panel.open {
|
| 112 |
+
right: 20px;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.config-header {
|
| 116 |
+
display: flex;
|
| 117 |
+
justify-content: space-between;
|
| 118 |
+
align-items: center;
|
| 119 |
+
padding: 1rem;
|
| 120 |
+
border-bottom: 1px solid var(--border, #334155);
|
| 121 |
+
background: var(--primary, #7c3aed);
|
| 122 |
+
color: white;
|
| 123 |
+
border-radius: 12px 12px 0 0;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.config-header h3 {
|
| 127 |
+
margin: 0;
|
| 128 |
+
font-size: 1.1rem;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.config-content {
|
| 132 |
+
padding: 1rem;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
.config-section {
|
| 136 |
+
margin-bottom: 1.5rem;
|
| 137 |
+
padding-bottom: 1rem;
|
| 138 |
+
border-bottom: 1px solid var(--border-light, #475569);
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
.config-section:last-child {
|
| 142 |
+
border-bottom: none;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.config-section h4 {
|
| 146 |
+
color: var(--text, #f8fafc);
|
| 147 |
+
font-size: 1rem;
|
| 148 |
+
margin-bottom: 1rem;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.config-actions {
|
| 152 |
+
display: flex;
|
| 153 |
+
gap: 0.5rem;
|
| 154 |
+
margin-bottom: 1rem;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
.config-info pre {
|
| 158 |
+
background: var(--background, #0f172a);
|
| 159 |
+
padding: 0.5rem;
|
| 160 |
+
border-radius: 4px;
|
| 161 |
+
font-size: 0.8rem;
|
| 162 |
+
color: var(--text-secondary, #cbd5e1);
|
| 163 |
+
border: 1px solid var(--border, #334155);
|
| 164 |
+
max-height: 200px;
|
| 165 |
+
overflow-y: auto;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.config-toggle {
|
| 169 |
+
position: fixed;
|
| 170 |
+
top: 20px;
|
| 171 |
+
right: 20px;
|
| 172 |
+
z-index: 9999;
|
| 173 |
+
background: var(--primary, #7c3aed);
|
| 174 |
+
color: white;
|
| 175 |
+
border: none;
|
| 176 |
+
border-radius: 50%;
|
| 177 |
+
width: 50px;
|
| 178 |
+
height: 50px;
|
| 179 |
+
display: flex;
|
| 180 |
+
align-items: center;
|
| 181 |
+
justify-content: center;
|
| 182 |
+
font-size: 1.2rem;
|
| 183 |
+
cursor: pointer;
|
| 184 |
+
transition: all 0.3s ease;
|
| 185 |
+
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.3);
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.config-toggle:hover {
|
| 189 |
+
background: var(--primary-dark, #5b21b6);
|
| 190 |
+
transform: scale(1.1);
|
| 191 |
+
}
|
| 192 |
+
`;
|
| 193 |
+
|
| 194 |
+
document.head.appendChild(style);
|
| 195 |
+
document.body.appendChild(configPanel);
|
| 196 |
+
|
| 197 |
+
// Create toggle button
|
| 198 |
+
const toggleBtn = document.createElement('button');
|
| 199 |
+
toggleBtn.id = 'configToggle';
|
| 200 |
+
toggleBtn.className = 'config-toggle';
|
| 201 |
+
toggleBtn.innerHTML = '<i class="fas fa-cog"></i>';
|
| 202 |
+
toggleBtn.title = 'Configuration Settings';
|
| 203 |
+
document.body.appendChild(toggleBtn);
|
| 204 |
+
|
| 205 |
+
this.updateEnvironmentInfo();
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
/**
|
| 209 |
+
* Bind event handlers
|
| 210 |
+
*/
|
| 211 |
+
bindEvents() {
|
| 212 |
+
// Toggle panel
|
| 213 |
+
document.getElementById('configToggle').addEventListener('click', () => {
|
| 214 |
+
this.togglePanel();
|
| 215 |
+
});
|
| 216 |
+
|
| 217 |
+
document.getElementById('toggleConfigPanel').addEventListener('click', () => {
|
| 218 |
+
this.togglePanel();
|
| 219 |
+
});
|
| 220 |
+
|
| 221 |
+
// Save configuration
|
| 222 |
+
document.getElementById('saveConfig').addEventListener('click', () => {
|
| 223 |
+
this.saveConfiguration();
|
| 224 |
+
});
|
| 225 |
+
|
| 226 |
+
// Reset configuration
|
| 227 |
+
document.getElementById('resetConfig').addEventListener('click', () => {
|
| 228 |
+
this.resetConfiguration();
|
| 229 |
+
});
|
| 230 |
+
|
| 231 |
+
// Test connection
|
| 232 |
+
document.getElementById('testConnection').addEventListener('click', () => {
|
| 233 |
+
this.testConnection();
|
| 234 |
+
});
|
| 235 |
+
|
| 236 |
+
// Real-time updates
|
| 237 |
+
document.getElementById('apiBaseUrl').addEventListener('input', (e) => {
|
| 238 |
+
if (this.config) {
|
| 239 |
+
this.config.setApiBaseUrl(e.target.value);
|
| 240 |
+
}
|
| 241 |
+
});
|
| 242 |
+
|
| 243 |
+
// Close panel on Escape key
|
| 244 |
+
document.addEventListener('keydown', (e) => {
|
| 245 |
+
if (e.key === 'Escape') {
|
| 246 |
+
this.closePanel();
|
| 247 |
+
}
|
| 248 |
+
});
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
/**
|
| 252 |
+
* Toggle configuration panel
|
| 253 |
+
*/
|
| 254 |
+
togglePanel() {
|
| 255 |
+
const panel = document.getElementById('configPanel');
|
| 256 |
+
panel.classList.toggle('open');
|
| 257 |
+
|
| 258 |
+
if (panel.classList.contains('open')) {
|
| 259 |
+
this.loadCurrentSettings();
|
| 260 |
+
this.updateEnvironmentInfo();
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
/**
|
| 265 |
+
* Close configuration panel
|
| 266 |
+
*/
|
| 267 |
+
closePanel() {
|
| 268 |
+
const panel = document.getElementById('configPanel');
|
| 269 |
+
panel.classList.remove('open');
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
/**
|
| 273 |
+
* Load current settings into UI
|
| 274 |
+
*/
|
| 275 |
+
loadCurrentSettings() {
|
| 276 |
+
if (!this.config) return;
|
| 277 |
+
|
| 278 |
+
document.getElementById('apiBaseUrl').value = this.config.getApiBaseUrl();
|
| 279 |
+
document.getElementById('environment').value = this.config.environment;
|
| 280 |
+
document.getElementById('theme').value = this.config.get('ui.theme', 'dark');
|
| 281 |
+
document.getElementById('autoRefresh').value = this.config.get('ui.autoRefreshInterval', 30000) / 1000;
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
/**
|
| 285 |
+
* Save configuration
|
| 286 |
+
*/
|
| 287 |
+
saveConfiguration() {
|
| 288 |
+
if (!this.config) return;
|
| 289 |
+
|
| 290 |
+
const apiBaseUrl = document.getElementById('apiBaseUrl').value;
|
| 291 |
+
const environment = document.getElementById('environment').value;
|
| 292 |
+
const theme = document.getElementById('theme').value;
|
| 293 |
+
const autoRefresh = parseInt(document.getElementById('autoRefresh').value) * 1000;
|
| 294 |
+
|
| 295 |
+
// Update configuration
|
| 296 |
+
this.config.setApiBaseUrl(apiBaseUrl);
|
| 297 |
+
this.config.set('ui.theme', theme);
|
| 298 |
+
this.config.set('ui.autoRefreshInterval', autoRefresh);
|
| 299 |
+
|
| 300 |
+
// Show success message
|
| 301 |
+
this.showToast('Configuration saved successfully!', 'success');
|
| 302 |
+
|
| 303 |
+
// Optionally reload the page to apply changes
|
| 304 |
+
if (confirm('Configuration saved. Reload the page to apply changes?')) {
|
| 305 |
+
window.location.reload();
|
| 306 |
+
}
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
/**
|
| 310 |
+
* Reset configuration to defaults
|
| 311 |
+
*/
|
| 312 |
+
resetConfiguration() {
|
| 313 |
+
if (!this.config) return;
|
| 314 |
+
|
| 315 |
+
if (confirm('Are you sure you want to reset all configuration to defaults?')) {
|
| 316 |
+
this.config.reset();
|
| 317 |
+
this.loadCurrentSettings();
|
| 318 |
+
this.updateEnvironmentInfo();
|
| 319 |
+
this.showToast('Configuration reset to defaults', 'info');
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
/**
|
| 324 |
+
* Test API connection
|
| 325 |
+
*/
|
| 326 |
+
async testConnection() {
|
| 327 |
+
if (!this.config) return;
|
| 328 |
+
|
| 329 |
+
const button = document.getElementById('testConnection');
|
| 330 |
+
const originalText = button.innerHTML;
|
| 331 |
+
|
| 332 |
+
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Testing...';
|
| 333 |
+
button.disabled = true;
|
| 334 |
+
|
| 335 |
+
try {
|
| 336 |
+
const isConnected = await this.config.validateApiConnection();
|
| 337 |
+
|
| 338 |
+
if (isConnected) {
|
| 339 |
+
this.showToast('API connection successful!', 'success');
|
| 340 |
+
} else {
|
| 341 |
+
this.showToast('API connection failed', 'error');
|
| 342 |
+
}
|
| 343 |
+
} catch (error) {
|
| 344 |
+
this.showToast('Connection test failed: ' + error.message, 'error');
|
| 345 |
+
} finally {
|
| 346 |
+
button.innerHTML = originalText;
|
| 347 |
+
button.disabled = false;
|
| 348 |
+
}
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
/**
|
| 352 |
+
* Update environment information display
|
| 353 |
+
*/
|
| 354 |
+
updateEnvironmentInfo() {
|
| 355 |
+
if (!this.config) return;
|
| 356 |
+
|
| 357 |
+
const envInfo = this.config.getEnvironmentInfo();
|
| 358 |
+
const formattedInfo = JSON.stringify(envInfo, null, 2);
|
| 359 |
+
document.getElementById('envInfo').textContent = formattedInfo;
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
/**
|
| 363 |
+
* Show toast notification
|
| 364 |
+
*/
|
| 365 |
+
showToast(message, type = 'info') {
|
| 366 |
+
// Use SweetAlert2 if available
|
| 367 |
+
if (typeof Swal !== 'undefined') {
|
| 368 |
+
Swal.fire({
|
| 369 |
+
toast: true,
|
| 370 |
+
position: 'top-end',
|
| 371 |
+
showConfirmButton: false,
|
| 372 |
+
timer: 3000,
|
| 373 |
+
icon: type === 'error' ? 'error' : type === 'success' ? 'success' : 'info',
|
| 374 |
+
title: message
|
| 375 |
+
});
|
| 376 |
+
return;
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
// Fallback to simple alert
|
| 380 |
+
const alertClass = type === 'error' ? 'alert-danger' :
|
| 381 |
+
type === 'success' ? 'alert-success' : 'alert-info';
|
| 382 |
+
|
| 383 |
+
const toast = document.createElement('div');
|
| 384 |
+
toast.className = `alert ${alertClass} alert-dismissible fade show`;
|
| 385 |
+
toast.style.cssText = `
|
| 386 |
+
position: fixed;
|
| 387 |
+
top: 20px;
|
| 388 |
+
left: 50%;
|
| 389 |
+
transform: translateX(-50%);
|
| 390 |
+
z-index: 10001;
|
| 391 |
+
min-width: 300px;
|
| 392 |
+
`;
|
| 393 |
+
toast.innerHTML = `
|
| 394 |
+
${message}
|
| 395 |
+
<button type="button" class="close" onclick="this.parentElement.remove()">
|
| 396 |
+
<span>×</span>
|
| 397 |
+
</button>
|
| 398 |
+
`;
|
| 399 |
+
|
| 400 |
+
document.body.appendChild(toast);
|
| 401 |
+
|
| 402 |
+
setTimeout(() => {
|
| 403 |
+
if (toast.parentElement) {
|
| 404 |
+
toast.remove();
|
| 405 |
+
}
|
| 406 |
+
}, 3000);
|
| 407 |
+
}
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
// Initialize when DOM is ready
|
| 411 |
+
if (document.readyState === 'loading') {
|
| 412 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 413 |
+
new ConfigUI();
|
| 414 |
+
});
|
| 415 |
+
} else {
|
| 416 |
+
new ConfigUI();
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
})();
|
chatbot/config.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Frontend Configuration Management
|
| 3 |
+
* Centralized configuration for the AIMHSA chatbot frontend
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
(() => {
|
| 7 |
+
'use strict';
|
| 8 |
+
|
| 9 |
+
// Default configuration
|
| 10 |
+
const DEFAULT_CONFIG = {
|
| 11 |
+
// API Configuration
|
| 12 |
+
api: {
|
| 13 |
+
baseUrl: 'http://localhost:5057',
|
| 14 |
+
timeout: 10000,
|
| 15 |
+
retryAttempts: 3,
|
| 16 |
+
retryDelay: 1000
|
| 17 |
+
},
|
| 18 |
+
|
| 19 |
+
// UI Configuration
|
| 20 |
+
ui: {
|
| 21 |
+
theme: 'dark',
|
| 22 |
+
language: 'en',
|
| 23 |
+
autoRefreshInterval: 30000,
|
| 24 |
+
animationDuration: 300
|
| 25 |
+
},
|
| 26 |
+
|
| 27 |
+
// Chat Configuration
|
| 28 |
+
chat: {
|
| 29 |
+
maxMessageLength: 5000,
|
| 30 |
+
typingIndicatorDelay: 1000,
|
| 31 |
+
autoScroll: true,
|
| 32 |
+
saveConversations: true
|
| 33 |
+
},
|
| 34 |
+
|
| 35 |
+
// Professional Dashboard Configuration
|
| 36 |
+
professional: {
|
| 37 |
+
defaultPageSize: 25,
|
| 38 |
+
autoRefreshBookings: true,
|
| 39 |
+
notificationSound: true
|
| 40 |
+
},
|
| 41 |
+
|
| 42 |
+
// Admin Dashboard Configuration
|
| 43 |
+
admin: {
|
| 44 |
+
defaultPageSize: 50,
|
| 45 |
+
enableDataExport: true,
|
| 46 |
+
showAdvancedStats: true
|
| 47 |
+
}
|
| 48 |
+
};
|
| 49 |
+
|
| 50 |
+
// Environment-specific configurations
|
| 51 |
+
const ENVIRONMENT_CONFIGS = {
|
| 52 |
+
development: {
|
| 53 |
+
api: {
|
| 54 |
+
baseUrl: 'http://localhost:5057'
|
| 55 |
+
}
|
| 56 |
+
},
|
| 57 |
+
production: {
|
| 58 |
+
api: {
|
| 59 |
+
baseUrl: 'https://api.aimhsa.rw'
|
| 60 |
+
}
|
| 61 |
+
},
|
| 62 |
+
staging: {
|
| 63 |
+
api: {
|
| 64 |
+
baseUrl: 'https://staging-api.aimhsa.rw'
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
};
|
| 68 |
+
|
| 69 |
+
class ConfigManager {
|
| 70 |
+
constructor() {
|
| 71 |
+
this.config = { ...DEFAULT_CONFIG };
|
| 72 |
+
this.environment = this.detectEnvironment();
|
| 73 |
+
this.loadConfiguration();
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/**
|
| 77 |
+
* Detect current environment
|
| 78 |
+
*/
|
| 79 |
+
detectEnvironment() {
|
| 80 |
+
const hostname = window.location.hostname;
|
| 81 |
+
const port = window.location.port;
|
| 82 |
+
|
| 83 |
+
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
| 84 |
+
return 'development';
|
| 85 |
+
} else if (hostname.includes('staging')) {
|
| 86 |
+
return 'staging';
|
| 87 |
+
} else {
|
| 88 |
+
return 'production';
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
/**
|
| 93 |
+
* Load configuration from multiple sources
|
| 94 |
+
*/
|
| 95 |
+
loadConfiguration() {
|
| 96 |
+
try {
|
| 97 |
+
// 1. Apply environment-specific config
|
| 98 |
+
this.applyEnvironmentConfig();
|
| 99 |
+
|
| 100 |
+
// 2. Load from localStorage (user preferences)
|
| 101 |
+
this.loadFromLocalStorage();
|
| 102 |
+
|
| 103 |
+
// 3. Load from URL parameters
|
| 104 |
+
this.loadFromUrlParams();
|
| 105 |
+
|
| 106 |
+
// 4. Auto-detect API URL based on current location
|
| 107 |
+
this.autoDetectApiUrl();
|
| 108 |
+
|
| 109 |
+
console.log('🔧 Configuration loaded:', this.config);
|
| 110 |
+
|
| 111 |
+
} catch (error) {
|
| 112 |
+
console.error('❌ Error loading configuration:', error);
|
| 113 |
+
// Fallback to default config
|
| 114 |
+
this.config = { ...DEFAULT_CONFIG };
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
/**
|
| 119 |
+
* Apply environment-specific configuration
|
| 120 |
+
*/
|
| 121 |
+
applyEnvironmentConfig() {
|
| 122 |
+
const envConfig = ENVIRONMENT_CONFIGS[this.environment];
|
| 123 |
+
if (envConfig) {
|
| 124 |
+
this.config = this.deepMerge(this.config, envConfig);
|
| 125 |
+
console.log(`🌍 Applied ${this.environment} environment config`);
|
| 126 |
+
}
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
/**
|
| 130 |
+
* Load configuration from localStorage
|
| 131 |
+
*/
|
| 132 |
+
loadFromLocalStorage() {
|
| 133 |
+
try {
|
| 134 |
+
const savedConfig = localStorage.getItem('aimhsa_config');
|
| 135 |
+
if (savedConfig) {
|
| 136 |
+
const parsedConfig = JSON.parse(savedConfig);
|
| 137 |
+
this.config = this.deepMerge(this.config, parsedConfig);
|
| 138 |
+
console.log('💾 Loaded config from localStorage');
|
| 139 |
+
}
|
| 140 |
+
} catch (error) {
|
| 141 |
+
console.warn('⚠️ Failed to load config from localStorage:', error);
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
/**
|
| 146 |
+
* Load configuration from URL parameters
|
| 147 |
+
*/
|
| 148 |
+
loadFromUrlParams() {
|
| 149 |
+
const urlParams = new URLSearchParams(window.location.search);
|
| 150 |
+
|
| 151 |
+
// API Base URL override
|
| 152 |
+
const apiUrl = urlParams.get('api_url') || urlParams.get('baseUrl');
|
| 153 |
+
if (apiUrl) {
|
| 154 |
+
this.config.api.baseUrl = apiUrl;
|
| 155 |
+
console.log('🔗 API URL overridden from URL params:', apiUrl);
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
// Environment override
|
| 159 |
+
const env = urlParams.get('env') || urlParams.get('environment');
|
| 160 |
+
if (env && ENVIRONMENT_CONFIGS[env]) {
|
| 161 |
+
this.environment = env;
|
| 162 |
+
this.applyEnvironmentConfig();
|
| 163 |
+
console.log('🌍 Environment overridden from URL params:', env);
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
// Theme override
|
| 167 |
+
const theme = urlParams.get('theme');
|
| 168 |
+
if (theme) {
|
| 169 |
+
this.config.ui.theme = theme;
|
| 170 |
+
console.log('🎨 Theme overridden from URL params:', theme);
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
/**
|
| 175 |
+
* Auto-detect API URL based on current page location
|
| 176 |
+
*/
|
| 177 |
+
autoDetectApiUrl() {
|
| 178 |
+
const currentLocation = window.location;
|
| 179 |
+
|
| 180 |
+
// Smart API URL detection
|
| 181 |
+
if (currentLocation.port === '8000') {
|
| 182 |
+
// Development server (likely Python/Django)
|
| 183 |
+
this.config.api.baseUrl = `${currentLocation.protocol}//${currentLocation.hostname}:5057`;
|
| 184 |
+
} else if (currentLocation.port === '3000') {
|
| 185 |
+
// React development server
|
| 186 |
+
this.config.api.baseUrl = `${currentLocation.protocol}//${currentLocation.hostname}:5057`;
|
| 187 |
+
} else if (currentLocation.port === '5057') {
|
| 188 |
+
// Running on API port
|
| 189 |
+
this.config.api.baseUrl = currentLocation.origin;
|
| 190 |
+
} else if (currentLocation.port === '80' || currentLocation.port === '443' || !currentLocation.port) {
|
| 191 |
+
// Production environment
|
| 192 |
+
this.config.api.baseUrl = currentLocation.origin;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
console.log('🔍 Auto-detected API URL:', this.config.api.baseUrl);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
/**
|
| 199 |
+
* Get configuration value
|
| 200 |
+
*/
|
| 201 |
+
get(path, defaultValue = null) {
|
| 202 |
+
return this.getNestedValue(this.config, path, defaultValue);
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
/**
|
| 206 |
+
* Set configuration value
|
| 207 |
+
*/
|
| 208 |
+
set(path, value) {
|
| 209 |
+
this.setNestedValue(this.config, path, value);
|
| 210 |
+
this.saveToLocalStorage();
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
/**
|
| 214 |
+
* Get API base URL
|
| 215 |
+
*/
|
| 216 |
+
getApiBaseUrl() {
|
| 217 |
+
return this.config.api.baseUrl;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
/**
|
| 221 |
+
* Set API base URL
|
| 222 |
+
*/
|
| 223 |
+
setApiBaseUrl(url) {
|
| 224 |
+
this.config.api.baseUrl = url;
|
| 225 |
+
this.saveToLocalStorage();
|
| 226 |
+
console.log('🔗 API Base URL updated:', url);
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
/**
|
| 230 |
+
* Get full API URL for an endpoint
|
| 231 |
+
*/
|
| 232 |
+
getApiUrl(endpoint = '') {
|
| 233 |
+
const baseUrl = this.config.api.baseUrl;
|
| 234 |
+
const cleanEndpoint = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
| 235 |
+
return `${baseUrl}${cleanEndpoint}`;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
/**
|
| 239 |
+
* Save current configuration to localStorage
|
| 240 |
+
*/
|
| 241 |
+
saveToLocalStorage() {
|
| 242 |
+
try {
|
| 243 |
+
localStorage.setItem('aimhsa_config', JSON.stringify(this.config));
|
| 244 |
+
console.log('💾 Configuration saved to localStorage');
|
| 245 |
+
} catch (error) {
|
| 246 |
+
console.warn('⚠️ Failed to save config to localStorage:', error);
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
/**
|
| 251 |
+
* Reset configuration to defaults
|
| 252 |
+
*/
|
| 253 |
+
reset() {
|
| 254 |
+
this.config = { ...DEFAULT_CONFIG };
|
| 255 |
+
localStorage.removeItem('aimhsa_config');
|
| 256 |
+
this.loadConfiguration();
|
| 257 |
+
console.log('🔄 Configuration reset to defaults');
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
/**
|
| 261 |
+
* Deep merge objects
|
| 262 |
+
*/
|
| 263 |
+
deepMerge(target, source) {
|
| 264 |
+
const result = { ...target };
|
| 265 |
+
|
| 266 |
+
for (const key in source) {
|
| 267 |
+
if (source.hasOwnProperty(key)) {
|
| 268 |
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
| 269 |
+
result[key] = this.deepMerge(result[key] || {}, source[key]);
|
| 270 |
+
} else {
|
| 271 |
+
result[key] = source[key];
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
return result;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
/**
|
| 280 |
+
* Get nested object value by path
|
| 281 |
+
*/
|
| 282 |
+
getNestedValue(obj, path, defaultValue = null) {
|
| 283 |
+
const keys = path.split('.');
|
| 284 |
+
let current = obj;
|
| 285 |
+
|
| 286 |
+
for (const key of keys) {
|
| 287 |
+
if (current && current.hasOwnProperty(key)) {
|
| 288 |
+
current = current[key];
|
| 289 |
+
} else {
|
| 290 |
+
return defaultValue;
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
return current;
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
/**
|
| 298 |
+
* Set nested object value by path
|
| 299 |
+
*/
|
| 300 |
+
setNestedValue(obj, path, value) {
|
| 301 |
+
const keys = path.split('.');
|
| 302 |
+
let current = obj;
|
| 303 |
+
|
| 304 |
+
for (let i = 0; i < keys.length - 1; i++) {
|
| 305 |
+
const key = keys[i];
|
| 306 |
+
if (!current[key] || typeof current[key] !== 'object') {
|
| 307 |
+
current[key] = {};
|
| 308 |
+
}
|
| 309 |
+
current = current[key];
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
current[keys[keys.length - 1]] = value;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
/**
|
| 316 |
+
* Validate API connection
|
| 317 |
+
*/
|
| 318 |
+
async validateApiConnection() {
|
| 319 |
+
try {
|
| 320 |
+
const response = await fetch(`${this.config.api.baseUrl}/health`, {
|
| 321 |
+
method: 'GET',
|
| 322 |
+
timeout: this.config.api.timeout
|
| 323 |
+
});
|
| 324 |
+
|
| 325 |
+
if (response.ok) {
|
| 326 |
+
console.log('✅ API connection validated');
|
| 327 |
+
return true;
|
| 328 |
+
} else {
|
| 329 |
+
console.warn('⚠️ API responded but not healthy:', response.status);
|
| 330 |
+
return false;
|
| 331 |
+
}
|
| 332 |
+
} catch (error) {
|
| 333 |
+
console.error('❌ API connection failed:', error);
|
| 334 |
+
return false;
|
| 335 |
+
}
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
/**
|
| 339 |
+
* Get environment info
|
| 340 |
+
*/
|
| 341 |
+
getEnvironmentInfo() {
|
| 342 |
+
return {
|
| 343 |
+
environment: this.environment,
|
| 344 |
+
hostname: window.location.hostname,
|
| 345 |
+
port: window.location.port,
|
| 346 |
+
protocol: window.location.protocol,
|
| 347 |
+
apiBaseUrl: this.config.api.baseUrl,
|
| 348 |
+
userAgent: navigator.userAgent,
|
| 349 |
+
timestamp: new Date().toISOString()
|
| 350 |
+
};
|
| 351 |
+
}
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
// Create global configuration manager instance
|
| 355 |
+
const configManager = new ConfigManager();
|
| 356 |
+
|
| 357 |
+
// Export to global scope
|
| 358 |
+
window.AIMHSA = window.AIMHSA || {};
|
| 359 |
+
window.AIMHSA.Config = configManager;
|
| 360 |
+
|
| 361 |
+
// Legacy support
|
| 362 |
+
window.getApiBaseUrl = () => configManager.getApiBaseUrl();
|
| 363 |
+
window.getApiUrl = (endpoint) => configManager.getApiUrl(endpoint);
|
| 364 |
+
|
| 365 |
+
console.log('⚙️ AIMHSA Configuration Manager initialized');
|
| 366 |
+
console.log('🌍 Environment:', configManager.environment);
|
| 367 |
+
console.log('🔗 API Base URL:', configManager.getApiBaseUrl());
|
| 368 |
+
|
| 369 |
+
})();
|
chatbot/index.html
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
| 6 |
+
<title>AIMHSA Chat</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css?hnb" />
|
| 8 |
+
|
| 9 |
+
<!-- Configuration Management -->
|
| 10 |
+
<script src="config.js"></script>
|
| 11 |
+
<script src="config-ui.js"></script>
|
| 12 |
+
</head>
|
| 13 |
+
<body>
|
| 14 |
+
<div class="app">
|
| 15 |
+
<aside class="sidebar">
|
| 16 |
+
<div class="sidebar-header">
|
| 17 |
+
<div class="user-info">
|
| 18 |
+
<div class="avatar">👤</div>
|
| 19 |
+
<div class="user-details">
|
| 20 |
+
<div class="username" id="username">Guest</div>
|
| 21 |
+
<div class="status">Online</div>
|
| 22 |
+
</div>
|
| 23 |
+
</div>
|
| 24 |
+
<button id="newChatBtn" class="new-chat-btn">
|
| 25 |
+
<span>+</span> New Chat
|
| 26 |
+
</button>
|
| 27 |
+
</div>
|
| 28 |
+
|
| 29 |
+
<div class="history-section">
|
| 30 |
+
<h3>Recent Conversations</h3>
|
| 31 |
+
<div class="history-list" id="historyList">
|
| 32 |
+
<!-- Chat history items will be dynamically added here -->
|
| 33 |
+
</div>
|
| 34 |
+
<h3 style="margin-top:16px">Archived</h3>
|
| 35 |
+
<div class="history-list" id="archivedList">
|
| 36 |
+
<!-- Archived items -->
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<div class="sidebar-footer">
|
| 41 |
+
<button id="logoutBtn" class="logout-btn">Sign Out</button>
|
| 42 |
+
</div>
|
| 43 |
+
</aside>
|
| 44 |
+
|
| 45 |
+
<main class="chat-area">
|
| 46 |
+
<header class="chat-header">
|
| 47 |
+
<div class="chat-title">
|
| 48 |
+
<h2>AIMHSA</h2>
|
| 49 |
+
<span class="chat-subtitle">Mental Health Companion</span>
|
| 50 |
+
</div>
|
| 51 |
+
<div class="chat-actions">
|
| 52 |
+
<button id="clearChatBtn" class="action-btn clear">Clear Messages</button>
|
| 53 |
+
<button id="clearHistoryBtn" class="action-btn danger">Clear History</button>
|
| 54 |
+
</div>
|
| 55 |
+
</header>
|
| 56 |
+
|
| 57 |
+
<div class="messages-container">
|
| 58 |
+
<div id="messages" class="messages"></div>
|
| 59 |
+
</div>
|
| 60 |
+
|
| 61 |
+
<form id="form" class="message-composer">
|
| 62 |
+
<div class="composer-input">
|
| 63 |
+
<textarea id="query" autocomplete="off" placeholder="Type your message..." rows="1"></textarea>
|
| 64 |
+
<label class="file-upload-btn" title="Upload PDF">
|
| 65 |
+
<input id="file" type="file" accept="application/pdf" />
|
| 66 |
+
📎
|
| 67 |
+
</label>
|
| 68 |
+
<button type="submit" id="send" class="send-btn">
|
| 69 |
+
<span>→</span>
|
| 70 |
+
</button>
|
| 71 |
+
</div>
|
| 72 |
+
</form>
|
| 73 |
+
</main>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<!-- Configuration Scripts (load first) -->
|
| 77 |
+
<script src="js/config.js"></script>
|
| 78 |
+
<script src="js/api.js"></script>
|
| 79 |
+
|
| 80 |
+
<!-- Main Application Script -->
|
| 81 |
+
<script src="app.js"></script>
|
| 82 |
+
</body>
|
| 83 |
+
</html>
|
chatbot/js/api.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* AIMHSA API Client
|
| 3 |
+
* Centralized API communication with automatic configuration
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
class ApiClient {
|
| 7 |
+
constructor() {
|
| 8 |
+
this.config = window.AppConfig;
|
| 9 |
+
this.defaultHeaders = {
|
| 10 |
+
'Content-Type': 'application/json'
|
| 11 |
+
};
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
async request(endpoint, options = {}) {
|
| 15 |
+
const url = this.config.getFullUrl(endpoint);
|
| 16 |
+
|
| 17 |
+
const defaultOptions = {
|
| 18 |
+
method: 'GET',
|
| 19 |
+
headers: { ...this.defaultHeaders },
|
| 20 |
+
timeout: this.config.settings.defaultTimeout
|
| 21 |
+
};
|
| 22 |
+
|
| 23 |
+
const mergedOptions = { ...defaultOptions, ...options };
|
| 24 |
+
|
| 25 |
+
// Add custom headers if provided
|
| 26 |
+
if (options.headers) {
|
| 27 |
+
mergedOptions.headers = { ...mergedOptions.headers, ...options.headers };
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
try {
|
| 31 |
+
const response = await fetch(url, mergedOptions);
|
| 32 |
+
|
| 33 |
+
if (!response.ok) {
|
| 34 |
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
const contentType = response.headers.get('content-type');
|
| 38 |
+
if (contentType && contentType.includes('application/json')) {
|
| 39 |
+
return await response.json();
|
| 40 |
+
} else {
|
| 41 |
+
return await response.text();
|
| 42 |
+
}
|
| 43 |
+
} catch (error) {
|
| 44 |
+
console.error('API Request failed:', error);
|
| 45 |
+
throw error;
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// GET request
|
| 50 |
+
async get(endpoint, params = {}) {
|
| 51 |
+
const url = new URL(this.config.getFullUrl(endpoint), window.location.origin);
|
| 52 |
+
Object.keys(params).forEach(key => {
|
| 53 |
+
if (params[key] !== null && params[key] !== undefined) {
|
| 54 |
+
url.searchParams.append(key, params[key]);
|
| 55 |
+
}
|
| 56 |
+
});
|
| 57 |
+
|
| 58 |
+
return await fetch(url.toString(), {
|
| 59 |
+
method: 'GET',
|
| 60 |
+
headers: this.defaultHeaders
|
| 61 |
+
}).then(res => res.json());
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// POST request
|
| 65 |
+
async post(endpoint, data = {}, options = {}) {
|
| 66 |
+
return await this.request(endpoint, {
|
| 67 |
+
method: 'POST',
|
| 68 |
+
body: JSON.stringify(data),
|
| 69 |
+
...options
|
| 70 |
+
});
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// PUT request
|
| 74 |
+
async put(endpoint, data = {}, options = {}) {
|
| 75 |
+
return await this.request(endpoint, {
|
| 76 |
+
method: 'PUT',
|
| 77 |
+
body: JSON.stringify(data),
|
| 78 |
+
...options
|
| 79 |
+
});
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
// DELETE request
|
| 83 |
+
async delete(endpoint, options = {}) {
|
| 84 |
+
return await this.request(endpoint, {
|
| 85 |
+
method: 'DELETE',
|
| 86 |
+
...options
|
| 87 |
+
});
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
// Chat-specific methods
|
| 91 |
+
async sendMessage(query, conversationId = null, account = null) {
|
| 92 |
+
return await this.post('ask', {
|
| 93 |
+
query,
|
| 94 |
+
id: conversationId,
|
| 95 |
+
account
|
| 96 |
+
});
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
async getSession(account = null) {
|
| 100 |
+
return await this.post('session', { account });
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
async getHistory(conversationId, password = null) {
|
| 104 |
+
const params = { id: conversationId };
|
| 105 |
+
if (password) params.password = password;
|
| 106 |
+
return await this.get('history', params);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
// Auth methods
|
| 110 |
+
async login(email, password) {
|
| 111 |
+
return await this.post('login', { email, password });
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
async register(userData) {
|
| 115 |
+
return await this.post('register', userData);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
async professionalLogin(credentials) {
|
| 119 |
+
return await this.post('professionalLogin', credentials);
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
async adminLogin(credentials) {
|
| 123 |
+
return await this.post('adminLogin', credentials);
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
// Professional methods
|
| 127 |
+
async getProfessionalProfile(professionalId) {
|
| 128 |
+
return await this.get('professionalProfile', {}, {
|
| 129 |
+
headers: { 'X-Professional-ID': professionalId }
|
| 130 |
+
});
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
async getProfessionalSessions(professionalId, limit = 50) {
|
| 134 |
+
return await this.get('professionalSessions', { limit }, {
|
| 135 |
+
headers: { 'X-Professional-ID': professionalId }
|
| 136 |
+
});
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
// Health check
|
| 140 |
+
async healthCheck() {
|
| 141 |
+
try {
|
| 142 |
+
const response = await this.get('healthz');
|
| 143 |
+
return response.ok === true;
|
| 144 |
+
} catch (error) {
|
| 145 |
+
return false;
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
// Global API client instance
|
| 151 |
+
window.ApiClient = new ApiClient();
|
| 152 |
+
|
| 153 |
+
// Auto-check API connectivity on load
|
| 154 |
+
document.addEventListener('DOMContentLoaded', async () => {
|
| 155 |
+
try {
|
| 156 |
+
const isHealthy = await window.ApiClient.healthCheck();
|
| 157 |
+
if (!isHealthy && window.AppConfig.isDevelopment()) {
|
| 158 |
+
console.warn('⚠️ API health check failed - backend may not be running');
|
| 159 |
+
}
|
| 160 |
+
} catch (error) {
|
| 161 |
+
if (window.AppConfig.isDevelopment()) {
|
| 162 |
+
console.error('❌ Failed to check API health:', error);
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
});
|
chatbot/js/config.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Frontend Configuration for AIMHSA
|
| 3 |
+
* Handles API endpoints and environment-specific settings
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
class AppConfig {
|
| 7 |
+
constructor() {
|
| 8 |
+
// Detect environment
|
| 9 |
+
this.environment = this.detectEnvironment();
|
| 10 |
+
|
| 11 |
+
// Set API base URL based on environment
|
| 12 |
+
this.apiBaseUrl = this.getApiBaseUrl();
|
| 13 |
+
|
| 14 |
+
// API endpoints
|
| 15 |
+
this.endpoints = {
|
| 16 |
+
// Chat endpoints
|
| 17 |
+
ask: '/ask',
|
| 18 |
+
session: '/session',
|
| 19 |
+
history: '/history',
|
| 20 |
+
conversations: '/conversations',
|
| 21 |
+
|
| 22 |
+
// User endpoints
|
| 23 |
+
register: '/register',
|
| 24 |
+
login: '/login',
|
| 25 |
+
logout: '/logout',
|
| 26 |
+
forgotPassword: '/forgot_password',
|
| 27 |
+
resetPassword: '/reset_password',
|
| 28 |
+
|
| 29 |
+
// Professional endpoints
|
| 30 |
+
professionalLogin: '/professional/login',
|
| 31 |
+
professionalProfile: '/professional/profile',
|
| 32 |
+
professionalSessions: '/professional/sessions',
|
| 33 |
+
professionalUsers: '/professional/users',
|
| 34 |
+
professionalNotifications: '/professional/notifications',
|
| 35 |
+
professionalDashboard: '/professional/dashboard-stats',
|
| 36 |
+
|
| 37 |
+
// Admin endpoints
|
| 38 |
+
adminLogin: '/admin/login',
|
| 39 |
+
adminProfessionals: '/admin/professionals',
|
| 40 |
+
adminBookings: '/admin/bookings',
|
| 41 |
+
adminUsers: '/admin/users',
|
| 42 |
+
|
| 43 |
+
// Utility endpoints
|
| 44 |
+
uploadPdf: '/upload_pdf',
|
| 45 |
+
clearChat: '/clear_chat',
|
| 46 |
+
reset: '/reset',
|
| 47 |
+
healthz: '/healthz'
|
| 48 |
+
};
|
| 49 |
+
|
| 50 |
+
// App settings
|
| 51 |
+
this.settings = {
|
| 52 |
+
defaultTimeout: 30000,
|
| 53 |
+
maxRetries: 3,
|
| 54 |
+
debounceDelay: 300,
|
| 55 |
+
autoSaveDelay: 1000
|
| 56 |
+
};
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
detectEnvironment() {
|
| 60 |
+
const hostname = window.location.hostname;
|
| 61 |
+
const protocol = window.location.protocol;
|
| 62 |
+
const port = window.location.port;
|
| 63 |
+
|
| 64 |
+
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
| 65 |
+
return 'development';
|
| 66 |
+
} else if (hostname.includes('test') || hostname.includes('staging')) {
|
| 67 |
+
return 'testing';
|
| 68 |
+
} else {
|
| 69 |
+
return 'production';
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
getApiBaseUrl() {
|
| 74 |
+
const hostname = window.location.hostname;
|
| 75 |
+
const protocol = window.location.protocol;
|
| 76 |
+
const port = window.location.port;
|
| 77 |
+
|
| 78 |
+
// Check if API_BASE_URL is set in environment
|
| 79 |
+
if (window.API_BASE_URL) {
|
| 80 |
+
return window.API_BASE_URL;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// Environment-specific API URLs
|
| 84 |
+
switch (this.environment) {
|
| 85 |
+
case 'development':
|
| 86 |
+
// In development, API might be on different port
|
| 87 |
+
if (port === '8000' || port === '3000') {
|
| 88 |
+
return `${protocol}//${hostname}:5057`;
|
| 89 |
+
}
|
| 90 |
+
return ''; // Same origin
|
| 91 |
+
|
| 92 |
+
case 'testing':
|
| 93 |
+
return ''; // Same origin for testing
|
| 94 |
+
|
| 95 |
+
case 'production':
|
| 96 |
+
// In production, use same origin (standard hosting setup)
|
| 97 |
+
return '';
|
| 98 |
+
|
| 99 |
+
default:
|
| 100 |
+
return '';
|
| 101 |
+
}
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
getFullUrl(endpoint) {
|
| 105 |
+
const baseUrl = this.apiBaseUrl;
|
| 106 |
+
const path = this.endpoints[endpoint] || endpoint;
|
| 107 |
+
|
| 108 |
+
if (baseUrl) {
|
| 109 |
+
return `${baseUrl}${path}`;
|
| 110 |
+
} else {
|
| 111 |
+
return path; // Relative URL
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
// Convenience methods for common endpoints
|
| 116 |
+
getChatUrl() { return this.getFullUrl('ask'); }
|
| 117 |
+
getLoginUrl() { return this.getFullUrl('login'); }
|
| 118 |
+
getRegisterUrl() { return this.getFullUrl('register'); }
|
| 119 |
+
getProfessionalLoginUrl() { return this.getFullUrl('professionalLogin'); }
|
| 120 |
+
getAdminLoginUrl() { return this.getFullUrl('adminLogin'); }
|
| 121 |
+
|
| 122 |
+
// Environment checks
|
| 123 |
+
isDevelopment() { return this.environment === 'development'; }
|
| 124 |
+
isProduction() { return this.environment === 'production'; }
|
| 125 |
+
isTesting() { return this.environment === 'testing'; }
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
// Global configuration instance
|
| 129 |
+
window.AppConfig = new AppConfig();
|
| 130 |
+
|
| 131 |
+
// Debug information in development
|
| 132 |
+
if (window.AppConfig.isDevelopment()) {
|
| 133 |
+
console.log('🔧 AIMHSA Development Mode');
|
| 134 |
+
console.log('API Base URL:', window.AppConfig.apiBaseUrl);
|
| 135 |
+
console.log('Environment:', window.AppConfig.environment);
|
| 136 |
+
}
|
chatbot/landing.css
ADDED
|
@@ -0,0 +1,1761 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* AIMHSA Landing Page Styles */
|
| 2 |
+
:root {
|
| 3 |
+
--primary: #7c3aed;
|
| 4 |
+
--primary-light: #a855f7;
|
| 5 |
+
--primary-dark: #5b21b6;
|
| 6 |
+
--secondary: #f59e0b;
|
| 7 |
+
--accent: #10b981;
|
| 8 |
+
--accent-light: #34d399;
|
| 9 |
+
--background: #0f172a;
|
| 10 |
+
--surface: #1e293b;
|
| 11 |
+
--card: #334155;
|
| 12 |
+
--card-hover: #3b4758;
|
| 13 |
+
--text: #f8fafc;
|
| 14 |
+
--text-secondary: #cbd5e1;
|
| 15 |
+
--text-muted: #94a3b8;
|
| 16 |
+
--border: #334155;
|
| 17 |
+
--border-light: #475569;
|
| 18 |
+
--border-focus: #6366f1;
|
| 19 |
+
--success: #10b981;
|
| 20 |
+
--warning: #f59e0b;
|
| 21 |
+
--danger: #ef4444;
|
| 22 |
+
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
|
| 23 |
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
|
| 24 |
+
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.2);
|
| 25 |
+
--gradient: linear-gradient(135deg, #7c3aed 0%, #a855f7 50%, #10b981 100%);
|
| 26 |
+
--gradient-radial: radial-gradient(circle at 30% 40%, rgba(124, 58, 237, 0.3) 0%, transparent 50%),
|
| 27 |
+
radial-gradient(circle at 70% 60%, rgba(16, 185, 129, 0.2) 0%, transparent 50%);
|
| 28 |
+
--blur: blur(10px);
|
| 29 |
+
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 30 |
+
--transition-fast: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
| 31 |
+
--transition-slow: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
/* Utility Classes */
|
| 35 |
+
.glass-effect {
|
| 36 |
+
backdrop-filter: var(--blur);
|
| 37 |
+
-webkit-backdrop-filter: var(--blur);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.gradient-border {
|
| 41 |
+
position: relative;
|
| 42 |
+
background: var(--gradient);
|
| 43 |
+
padding: 2px;
|
| 44 |
+
border-radius: inherit;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.gradient-border::before {
|
| 48 |
+
content: '';
|
| 49 |
+
position: absolute;
|
| 50 |
+
top: 0;
|
| 51 |
+
left: 0;
|
| 52 |
+
right: 0;
|
| 53 |
+
bottom: 0;
|
| 54 |
+
background: var(--surface);
|
| 55 |
+
border-radius: inherit;
|
| 56 |
+
margin: 1px;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.loading-spinner {
|
| 60 |
+
width: 20px;
|
| 61 |
+
height: 20px;
|
| 62 |
+
border: 2px solid var(--border);
|
| 63 |
+
border-top: 2px solid var(--primary);
|
| 64 |
+
border-radius: 50%;
|
| 65 |
+
animation: spin 1s linear infinite;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
@keyframes spin {
|
| 69 |
+
0% { transform: rotate(0deg); }
|
| 70 |
+
100% { transform: rotate(360deg); }
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.fade-in {
|
| 74 |
+
animation: fadeIn 0.6s ease-out;
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
.slide-up {
|
| 78 |
+
animation: slideInUp 0.6s ease-out;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.bounce-in {
|
| 82 |
+
animation: bounceIn 0.6s ease-out;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
@keyframes bounceIn {
|
| 86 |
+
0% {
|
| 87 |
+
opacity: 0;
|
| 88 |
+
transform: scale(0.3);
|
| 89 |
+
}
|
| 90 |
+
50% {
|
| 91 |
+
opacity: 1;
|
| 92 |
+
transform: scale(1.05);
|
| 93 |
+
}
|
| 94 |
+
70% {
|
| 95 |
+
transform: scale(0.9);
|
| 96 |
+
}
|
| 97 |
+
100% {
|
| 98 |
+
opacity: 1;
|
| 99 |
+
transform: scale(1);
|
| 100 |
+
}
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.pulse-glow {
|
| 104 |
+
animation: pulseGlow 2s ease-in-out infinite;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
@keyframes pulseGlow {
|
| 108 |
+
0%, 100% {
|
| 109 |
+
box-shadow: 0 0 20px rgba(124, 58, 237, 0.3);
|
| 110 |
+
}
|
| 111 |
+
50% {
|
| 112 |
+
box-shadow: 0 0 30px rgba(124, 58, 237, 0.6);
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
* {
|
| 117 |
+
box-sizing: border-box;
|
| 118 |
+
margin: 0;
|
| 119 |
+
padding: 0;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
body {
|
| 123 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 124 |
+
background: var(--background);
|
| 125 |
+
color: var(--text);
|
| 126 |
+
line-height: 1.6;
|
| 127 |
+
overflow-x: hidden;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.container {
|
| 131 |
+
max-width: 1200px;
|
| 132 |
+
margin: 0 auto;
|
| 133 |
+
padding: 0 20px;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
/* Navigation */
|
| 137 |
+
.navbar {
|
| 138 |
+
position: fixed;
|
| 139 |
+
top: 0;
|
| 140 |
+
left: 0;
|
| 141 |
+
right: 0;
|
| 142 |
+
background: rgba(15, 23, 42, 0.95);
|
| 143 |
+
backdrop-filter: var(--blur);
|
| 144 |
+
-webkit-backdrop-filter: var(--blur);
|
| 145 |
+
border-bottom: 1px solid var(--border);
|
| 146 |
+
z-index: 1000;
|
| 147 |
+
transition: var(--transition);
|
| 148 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.navbar.scrolled {
|
| 152 |
+
background: rgba(15, 23, 42, 0.98);
|
| 153 |
+
box-shadow: var(--shadow);
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.nav-container {
|
| 157 |
+
display: flex;
|
| 158 |
+
justify-content: space-between;
|
| 159 |
+
align-items: center;
|
| 160 |
+
padding: 1rem 2rem;
|
| 161 |
+
max-width: 1200px;
|
| 162 |
+
margin: 0 auto;
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.nav-logo {
|
| 166 |
+
display: flex;
|
| 167 |
+
align-items: center;
|
| 168 |
+
gap: 0.5rem;
|
| 169 |
+
font-size: 1.5rem;
|
| 170 |
+
font-weight: 700;
|
| 171 |
+
color: var(--primary);
|
| 172 |
+
transition: var(--transition-fast);
|
| 173 |
+
cursor: pointer;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.nav-logo:hover {
|
| 177 |
+
transform: scale(1.05);
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
.nav-logo i {
|
| 181 |
+
font-size: 1.8rem;
|
| 182 |
+
transition: var(--transition-fast);
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
.nav-logo:hover i {
|
| 186 |
+
transform: rotate(10deg);
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
.nav-links {
|
| 190 |
+
display: flex;
|
| 191 |
+
gap: 2rem;
|
| 192 |
+
align-items: center;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
.nav-links a {
|
| 196 |
+
color: var(--text-secondary);
|
| 197 |
+
text-decoration: none;
|
| 198 |
+
font-weight: 500;
|
| 199 |
+
transition: var(--transition-fast);
|
| 200 |
+
position: relative;
|
| 201 |
+
padding: 0.5rem 0;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.nav-links a::after {
|
| 205 |
+
content: '';
|
| 206 |
+
position: absolute;
|
| 207 |
+
bottom: 0;
|
| 208 |
+
left: 0;
|
| 209 |
+
width: 0;
|
| 210 |
+
height: 2px;
|
| 211 |
+
background: var(--gradient);
|
| 212 |
+
transition: var(--transition);
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
.nav-links a:hover::after,
|
| 216 |
+
.nav-links a:focus::after {
|
| 217 |
+
width: 100%;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
.nav-links a:hover,
|
| 221 |
+
.nav-links a:focus {
|
| 222 |
+
color: var(--text);
|
| 223 |
+
transform: translateY(-1px);
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
.nav-cta {
|
| 227 |
+
background: var(--gradient);
|
| 228 |
+
color: white !important;
|
| 229 |
+
padding: 0.75rem 1.5rem;
|
| 230 |
+
border-radius: 50px;
|
| 231 |
+
font-weight: 600;
|
| 232 |
+
transition: var(--transition);
|
| 233 |
+
box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3);
|
| 234 |
+
border: none;
|
| 235 |
+
cursor: pointer;
|
| 236 |
+
position: relative;
|
| 237 |
+
overflow: hidden;
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
.nav-cta::before {
|
| 241 |
+
content: '';
|
| 242 |
+
position: absolute;
|
| 243 |
+
top: 0;
|
| 244 |
+
left: -100%;
|
| 245 |
+
width: 100%;
|
| 246 |
+
height: 100%;
|
| 247 |
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
| 248 |
+
transition: var(--transition);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
.nav-cta:hover::before {
|
| 252 |
+
left: 100%;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
.nav-cta:hover {
|
| 256 |
+
transform: translateY(-2px);
|
| 257 |
+
box-shadow: 0 8px 25px rgba(124, 58, 237, 0.4);
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
.nav-cta:active {
|
| 261 |
+
transform: translateY(0);
|
| 262 |
+
}
|
| 263 |
+
|
| 264 |
+
/* Hero Section */
|
| 265 |
+
.hero {
|
| 266 |
+
min-height: 100vh;
|
| 267 |
+
display: flex;
|
| 268 |
+
align-items: center;
|
| 269 |
+
position: relative;
|
| 270 |
+
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%);
|
| 271 |
+
overflow: hidden;
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
.hero::before {
|
| 275 |
+
content: '';
|
| 276 |
+
position: absolute;
|
| 277 |
+
top: 0;
|
| 278 |
+
left: 0;
|
| 279 |
+
right: 0;
|
| 280 |
+
bottom: 0;
|
| 281 |
+
background: var(--gradient-radial);
|
| 282 |
+
pointer-events: none;
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
.hero-background {
|
| 286 |
+
position: absolute;
|
| 287 |
+
top: 0;
|
| 288 |
+
left: 0;
|
| 289 |
+
right: 0;
|
| 290 |
+
bottom: 0;
|
| 291 |
+
opacity: 0.1;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
.hero-pattern {
|
| 295 |
+
position: absolute;
|
| 296 |
+
top: 0;
|
| 297 |
+
left: 0;
|
| 298 |
+
right: 0;
|
| 299 |
+
bottom: 0;
|
| 300 |
+
background-image:
|
| 301 |
+
radial-gradient(circle at 25% 25%, var(--primary) 2px, transparent 2px),
|
| 302 |
+
radial-gradient(circle at 75% 75%, var(--accent) 2px, transparent 2px),
|
| 303 |
+
radial-gradient(circle at 50% 50%, var(--secondary) 1px, transparent 1px);
|
| 304 |
+
background-size: 50px 50px, 30px 30px, 80px 80px;
|
| 305 |
+
animation: float 20s ease-in-out infinite;
|
| 306 |
+
opacity: 0.6;
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
@keyframes float {
|
| 310 |
+
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
| 311 |
+
33% { transform: translateY(-20px) rotate(1deg); }
|
| 312 |
+
66% { transform: translateY(-10px) rotate(-1deg); }
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
/* Floating elements */
|
| 316 |
+
.hero-float-1,
|
| 317 |
+
.hero-float-2,
|
| 318 |
+
.hero-float-3 {
|
| 319 |
+
position: absolute;
|
| 320 |
+
border-radius: 50%;
|
| 321 |
+
background: var(--gradient);
|
| 322 |
+
opacity: 0.1;
|
| 323 |
+
animation: float-random 15s ease-in-out infinite;
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
.hero-float-1 {
|
| 327 |
+
width: 200px;
|
| 328 |
+
height: 200px;
|
| 329 |
+
top: 10%;
|
| 330 |
+
left: 10%;
|
| 331 |
+
animation-delay: 0s;
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
.hero-float-2 {
|
| 335 |
+
width: 150px;
|
| 336 |
+
height: 150px;
|
| 337 |
+
top: 60%;
|
| 338 |
+
right: 15%;
|
| 339 |
+
animation-delay: 5s;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
.hero-float-3 {
|
| 343 |
+
width: 100px;
|
| 344 |
+
height: 100px;
|
| 345 |
+
bottom: 20%;
|
| 346 |
+
left: 20%;
|
| 347 |
+
animation-delay: 10s;
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
@keyframes float-random {
|
| 351 |
+
0%, 100% {
|
| 352 |
+
transform: translateY(0px) translateX(0px) scale(1);
|
| 353 |
+
}
|
| 354 |
+
25% {
|
| 355 |
+
transform: translateY(-30px) translateX(20px) scale(1.1);
|
| 356 |
+
}
|
| 357 |
+
50% {
|
| 358 |
+
transform: translateY(-15px) translateX(-15px) scale(0.9);
|
| 359 |
+
}
|
| 360 |
+
75% {
|
| 361 |
+
transform: translateY(-40px) translateX(10px) scale(1.05);
|
| 362 |
+
}
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
.hero-content {
|
| 366 |
+
display: grid;
|
| 367 |
+
grid-template-columns: 1fr 1fr;
|
| 368 |
+
gap: 4rem;
|
| 369 |
+
align-items: center;
|
| 370 |
+
width: 100%;
|
| 371 |
+
max-width: 1200px;
|
| 372 |
+
margin: 0 auto;
|
| 373 |
+
padding: 0 2rem;
|
| 374 |
+
position: relative;
|
| 375 |
+
z-index: 2;
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
.hero-text {
|
| 379 |
+
animation: slideInLeft 1s ease-out;
|
| 380 |
+
animation-fill-mode: both;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
@keyframes slideInLeft {
|
| 384 |
+
from {
|
| 385 |
+
opacity: 0;
|
| 386 |
+
transform: translateX(-50px) scale(0.95);
|
| 387 |
+
}
|
| 388 |
+
to {
|
| 389 |
+
opacity: 1;
|
| 390 |
+
transform: translateX(0) scale(1);
|
| 391 |
+
}
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.hero-title {
|
| 395 |
+
font-size: clamp(2.5rem, 5vw, 3.5rem);
|
| 396 |
+
font-weight: 800;
|
| 397 |
+
line-height: 1.1;
|
| 398 |
+
margin-bottom: 1.5rem;
|
| 399 |
+
letter-spacing: -0.02em;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.hero-title span {
|
| 403 |
+
display: block;
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
.gradient-text {
|
| 407 |
+
background: var(--gradient);
|
| 408 |
+
-webkit-background-clip: text;
|
| 409 |
+
-webkit-text-fill-color: transparent;
|
| 410 |
+
background-clip: text;
|
| 411 |
+
animation: gradient-shift 3s ease-in-out infinite;
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
@keyframes gradient-shift {
|
| 415 |
+
0%, 100% { background-position: 0% 50%; }
|
| 416 |
+
50% { background-position: 100% 50%; }
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
.highlight {
|
| 420 |
+
color: var(--accent);
|
| 421 |
+
position: relative;
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
.highlight::after {
|
| 425 |
+
content: '';
|
| 426 |
+
position: absolute;
|
| 427 |
+
bottom: 2px;
|
| 428 |
+
left: 0;
|
| 429 |
+
right: 0;
|
| 430 |
+
height: 3px;
|
| 431 |
+
background: var(--accent);
|
| 432 |
+
border-radius: 2px;
|
| 433 |
+
opacity: 0.6;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
.hero-description {
|
| 437 |
+
font-size: clamp(1rem, 2vw, 1.25rem);
|
| 438 |
+
color: var(--text-secondary);
|
| 439 |
+
margin-bottom: 2rem;
|
| 440 |
+
max-width: 500px;
|
| 441 |
+
line-height: 1.6;
|
| 442 |
+
}
|
| 443 |
+
|
| 444 |
+
.hero-stats {
|
| 445 |
+
display: flex;
|
| 446 |
+
gap: 2rem;
|
| 447 |
+
margin-bottom: 2.5rem;
|
| 448 |
+
animation: fadeInUp 1s ease-out 0.3s both;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
.stat {
|
| 452 |
+
text-align: center;
|
| 453 |
+
padding: 1rem;
|
| 454 |
+
background: rgba(255, 255, 255, 0.02);
|
| 455 |
+
border-radius: 12px;
|
| 456 |
+
border: 1px solid var(--border);
|
| 457 |
+
transition: var(--transition);
|
| 458 |
+
backdrop-filter: blur(10px);
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
.stat:hover {
|
| 462 |
+
transform: translateY(-5px);
|
| 463 |
+
box-shadow: var(--shadow-lg);
|
| 464 |
+
border-color: var(--primary);
|
| 465 |
+
}
|
| 466 |
+
|
| 467 |
+
.stat-number {
|
| 468 |
+
display: block;
|
| 469 |
+
font-size: clamp(1.5rem, 3vw, 2rem);
|
| 470 |
+
font-weight: 700;
|
| 471 |
+
color: var(--primary);
|
| 472 |
+
margin-bottom: 0.25rem;
|
| 473 |
+
transition: var(--transition-fast);
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.stat:hover .stat-number {
|
| 477 |
+
color: var(--accent);
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
.stat-label {
|
| 481 |
+
font-size: 0.875rem;
|
| 482 |
+
color: var(--text-muted);
|
| 483 |
+
text-transform: uppercase;
|
| 484 |
+
letter-spacing: 0.5px;
|
| 485 |
+
font-weight: 600;
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
.hero-actions {
|
| 489 |
+
display: flex;
|
| 490 |
+
gap: 1rem;
|
| 491 |
+
flex-wrap: wrap;
|
| 492 |
+
animation: fadeInUp 1s ease-out 0.5s both;
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
@keyframes fadeInUp {
|
| 496 |
+
from {
|
| 497 |
+
opacity: 0;
|
| 498 |
+
transform: translateY(30px);
|
| 499 |
+
}
|
| 500 |
+
to {
|
| 501 |
+
opacity: 1;
|
| 502 |
+
transform: translateY(0);
|
| 503 |
+
}
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
@keyframes ripple {
|
| 507 |
+
to {
|
| 508 |
+
transform: scale(4);
|
| 509 |
+
opacity: 0;
|
| 510 |
+
}
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
.ripple-effect {
|
| 514 |
+
pointer-events: none;
|
| 515 |
+
z-index: 1;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
.btn-primary, .btn-secondary {
|
| 519 |
+
display: inline-flex;
|
| 520 |
+
align-items: center;
|
| 521 |
+
gap: 0.5rem;
|
| 522 |
+
padding: 1rem 2rem;
|
| 523 |
+
border-radius: 50px;
|
| 524 |
+
font-weight: 600;
|
| 525 |
+
text-decoration: none;
|
| 526 |
+
transition: all 0.3s ease;
|
| 527 |
+
font-size: 1rem;
|
| 528 |
+
}
|
| 529 |
+
|
| 530 |
+
.btn-primary {
|
| 531 |
+
background: var(--gradient);
|
| 532 |
+
color: white;
|
| 533 |
+
box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3);
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
.btn-primary:hover {
|
| 537 |
+
transform: translateY(-2px);
|
| 538 |
+
box-shadow: 0 8px 25px rgba(124, 58, 237, 0.4);
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
.btn-secondary {
|
| 542 |
+
background: transparent;
|
| 543 |
+
color: var(--text);
|
| 544 |
+
border: 2px solid var(--border);
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
.btn-secondary:hover {
|
| 548 |
+
background: var(--surface);
|
| 549 |
+
border-color: var(--primary);
|
| 550 |
+
color: var(--primary);
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
/* Chat Preview */
|
| 554 |
+
.hero-visual {
|
| 555 |
+
animation: slideInRight 1s ease-out;
|
| 556 |
+
animation-fill-mode: both;
|
| 557 |
+
}
|
| 558 |
+
|
| 559 |
+
@keyframes slideInRight {
|
| 560 |
+
from {
|
| 561 |
+
opacity: 0;
|
| 562 |
+
transform: translateX(50px) scale(0.95);
|
| 563 |
+
}
|
| 564 |
+
to {
|
| 565 |
+
opacity: 1;
|
| 566 |
+
transform: translateX(0) scale(1);
|
| 567 |
+
}
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
.chat-preview {
|
| 571 |
+
background: var(--surface);
|
| 572 |
+
border-radius: 20px;
|
| 573 |
+
padding: 1.5rem;
|
| 574 |
+
box-shadow: var(--shadow-lg);
|
| 575 |
+
border: 1px solid var(--border);
|
| 576 |
+
max-width: 400px;
|
| 577 |
+
margin: 0 auto;
|
| 578 |
+
position: relative;
|
| 579 |
+
overflow: hidden;
|
| 580 |
+
transition: var(--transition);
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
.chat-preview::before {
|
| 584 |
+
content: '';
|
| 585 |
+
position: absolute;
|
| 586 |
+
top: 0;
|
| 587 |
+
left: 0;
|
| 588 |
+
right: 0;
|
| 589 |
+
height: 4px;
|
| 590 |
+
background: var(--gradient);
|
| 591 |
+
border-radius: 20px 20px 0 0;
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
.chat-preview:hover {
|
| 595 |
+
transform: translateY(-5px);
|
| 596 |
+
box-shadow: var(--shadow-xl);
|
| 597 |
+
border-color: var(--primary);
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
.chat-header {
|
| 601 |
+
display: flex;
|
| 602 |
+
align-items: center;
|
| 603 |
+
gap: 1rem;
|
| 604 |
+
margin-bottom: 1.5rem;
|
| 605 |
+
padding-bottom: 1rem;
|
| 606 |
+
border-bottom: 1px solid var(--border);
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
.chat-avatar {
|
| 610 |
+
width: 50px;
|
| 611 |
+
height: 50px;
|
| 612 |
+
background: var(--gradient);
|
| 613 |
+
border-radius: 50%;
|
| 614 |
+
display: flex;
|
| 615 |
+
align-items: center;
|
| 616 |
+
justify-content: center;
|
| 617 |
+
font-size: 1.5rem;
|
| 618 |
+
color: white;
|
| 619 |
+
}
|
| 620 |
+
|
| 621 |
+
.chat-info {
|
| 622 |
+
flex: 1;
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
.chat-name {
|
| 626 |
+
display: block;
|
| 627 |
+
font-weight: 600;
|
| 628 |
+
color: var(--text);
|
| 629 |
+
margin-bottom: 0.25rem;
|
| 630 |
+
}
|
| 631 |
+
|
| 632 |
+
.chat-status {
|
| 633 |
+
font-size: 0.875rem;
|
| 634 |
+
color: var(--accent);
|
| 635 |
+
}
|
| 636 |
+
|
| 637 |
+
.chat-messages {
|
| 638 |
+
display: flex;
|
| 639 |
+
flex-direction: column;
|
| 640 |
+
gap: 1rem;
|
| 641 |
+
margin-bottom: 1rem;
|
| 642 |
+
}
|
| 643 |
+
|
| 644 |
+
.message {
|
| 645 |
+
max-width: 80%;
|
| 646 |
+
animation: messageSlide 0.5s ease-out;
|
| 647 |
+
}
|
| 648 |
+
|
| 649 |
+
@keyframes messageSlide {
|
| 650 |
+
from {
|
| 651 |
+
opacity: 0;
|
| 652 |
+
transform: translateY(10px);
|
| 653 |
+
}
|
| 654 |
+
to {
|
| 655 |
+
opacity: 1;
|
| 656 |
+
transform: translateY(0);
|
| 657 |
+
}
|
| 658 |
+
}
|
| 659 |
+
|
| 660 |
+
.bot-message {
|
| 661 |
+
align-self: flex-start;
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
.user-message {
|
| 665 |
+
align-self: flex-end;
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
.message-content {
|
| 669 |
+
padding: 1rem 1.25rem;
|
| 670 |
+
border-radius: 18px;
|
| 671 |
+
font-size: 0.875rem;
|
| 672 |
+
line-height: 1.5;
|
| 673 |
+
}
|
| 674 |
+
|
| 675 |
+
.bot-message .message-content {
|
| 676 |
+
background: var(--card);
|
| 677 |
+
color: var(--text);
|
| 678 |
+
border-bottom-left-radius: 6px;
|
| 679 |
+
}
|
| 680 |
+
|
| 681 |
+
.user-message .message-content {
|
| 682 |
+
background: var(--primary);
|
| 683 |
+
color: white;
|
| 684 |
+
border-bottom-right-radius: 6px;
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
.typing-indicator {
|
| 688 |
+
display: flex;
|
| 689 |
+
gap: 0.25rem;
|
| 690 |
+
padding: 1rem 1.25rem;
|
| 691 |
+
background: var(--card);
|
| 692 |
+
border-radius: 18px;
|
| 693 |
+
border-bottom-left-radius: 6px;
|
| 694 |
+
max-width: 60px;
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
.typing-indicator span {
|
| 698 |
+
width: 8px;
|
| 699 |
+
height: 8px;
|
| 700 |
+
background: var(--text-muted);
|
| 701 |
+
border-radius: 50%;
|
| 702 |
+
animation: typing 1.5s infinite;
|
| 703 |
+
}
|
| 704 |
+
|
| 705 |
+
.typing-indicator span:nth-child(2) {
|
| 706 |
+
animation-delay: 0.2s;
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
.typing-indicator span:nth-child(3) {
|
| 710 |
+
animation-delay: 0.4s;
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
@keyframes typing {
|
| 714 |
+
0%, 60%, 100% {
|
| 715 |
+
transform: translateY(0);
|
| 716 |
+
opacity: 0.5;
|
| 717 |
+
}
|
| 718 |
+
30% {
|
| 719 |
+
transform: translateY(-10px);
|
| 720 |
+
opacity: 1;
|
| 721 |
+
}
|
| 722 |
+
}
|
| 723 |
+
|
| 724 |
+
/* Features Section */
|
| 725 |
+
.features {
|
| 726 |
+
padding: 6rem 0;
|
| 727 |
+
background: var(--background);
|
| 728 |
+
position: relative;
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
.features::before {
|
| 732 |
+
content: '';
|
| 733 |
+
position: absolute;
|
| 734 |
+
top: 0;
|
| 735 |
+
left: 0;
|
| 736 |
+
right: 0;
|
| 737 |
+
bottom: 0;
|
| 738 |
+
background: var(--gradient-radial);
|
| 739 |
+
opacity: 0.3;
|
| 740 |
+
pointer-events: none;
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
.section-header {
|
| 744 |
+
text-align: center;
|
| 745 |
+
margin-bottom: 4rem;
|
| 746 |
+
position: relative;
|
| 747 |
+
z-index: 1;
|
| 748 |
+
}
|
| 749 |
+
|
| 750 |
+
.section-header h2 {
|
| 751 |
+
font-size: clamp(2rem, 4vw, 2.5rem);
|
| 752 |
+
font-weight: 700;
|
| 753 |
+
color: var(--text);
|
| 754 |
+
margin-bottom: 1rem;
|
| 755 |
+
background: var(--gradient);
|
| 756 |
+
-webkit-background-clip: text;
|
| 757 |
+
-webkit-text-fill-color: transparent;
|
| 758 |
+
background-clip: text;
|
| 759 |
+
}
|
| 760 |
+
|
| 761 |
+
.section-header p {
|
| 762 |
+
font-size: clamp(1rem, 2vw, 1.25rem);
|
| 763 |
+
color: var(--text-secondary);
|
| 764 |
+
max-width: 600px;
|
| 765 |
+
margin: 0 auto;
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
.features-grid {
|
| 769 |
+
display: grid;
|
| 770 |
+
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
| 771 |
+
gap: 2rem;
|
| 772 |
+
position: relative;
|
| 773 |
+
z-index: 1;
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
.feature-card {
|
| 777 |
+
background: var(--surface);
|
| 778 |
+
padding: 2.5rem;
|
| 779 |
+
border-radius: 20px;
|
| 780 |
+
border: 1px solid var(--border);
|
| 781 |
+
transition: var(--transition);
|
| 782 |
+
text-align: center;
|
| 783 |
+
position: relative;
|
| 784 |
+
overflow: hidden;
|
| 785 |
+
backdrop-filter: blur(10px);
|
| 786 |
+
-webkit-backdrop-filter: blur(10px);
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
.feature-card::before {
|
| 790 |
+
content: '';
|
| 791 |
+
position: absolute;
|
| 792 |
+
top: 0;
|
| 793 |
+
left: 0;
|
| 794 |
+
right: 0;
|
| 795 |
+
bottom: 0;
|
| 796 |
+
background: var(--gradient);
|
| 797 |
+
opacity: 0;
|
| 798 |
+
transition: var(--transition);
|
| 799 |
+
z-index: -1;
|
| 800 |
+
}
|
| 801 |
+
|
| 802 |
+
.feature-card:hover::before {
|
| 803 |
+
opacity: 0.05;
|
| 804 |
+
}
|
| 805 |
+
|
| 806 |
+
.feature-card:hover {
|
| 807 |
+
transform: translateY(-8px) scale(1.02);
|
| 808 |
+
box-shadow: var(--shadow-xl);
|
| 809 |
+
border-color: var(--primary);
|
| 810 |
+
}
|
| 811 |
+
|
| 812 |
+
.feature-icon {
|
| 813 |
+
width: 80px;
|
| 814 |
+
height: 80px;
|
| 815 |
+
background: var(--gradient);
|
| 816 |
+
border-radius: 50%;
|
| 817 |
+
display: flex;
|
| 818 |
+
align-items: center;
|
| 819 |
+
justify-content: center;
|
| 820 |
+
margin: 0 auto 1.5rem;
|
| 821 |
+
font-size: 2rem;
|
| 822 |
+
color: white;
|
| 823 |
+
transition: var(--transition);
|
| 824 |
+
position: relative;
|
| 825 |
+
z-index: 1;
|
| 826 |
+
}
|
| 827 |
+
|
| 828 |
+
.feature-card:hover .feature-icon {
|
| 829 |
+
transform: scale(1.1) rotate(5deg);
|
| 830 |
+
box-shadow: 0 8px 25px rgba(124, 58, 237, 0.3);
|
| 831 |
+
}
|
| 832 |
+
|
| 833 |
+
.feature-card h3 {
|
| 834 |
+
font-size: 1.5rem;
|
| 835 |
+
font-weight: 600;
|
| 836 |
+
color: var(--text);
|
| 837 |
+
margin-bottom: 1rem;
|
| 838 |
+
position: relative;
|
| 839 |
+
z-index: 1;
|
| 840 |
+
}
|
| 841 |
+
|
| 842 |
+
.feature-card p {
|
| 843 |
+
color: var(--text-secondary);
|
| 844 |
+
line-height: 1.6;
|
| 845 |
+
position: relative;
|
| 846 |
+
z-index: 1;
|
| 847 |
+
}
|
| 848 |
+
|
| 849 |
+
/* About Section */
|
| 850 |
+
.about {
|
| 851 |
+
padding: 6rem 0;
|
| 852 |
+
background: var(--surface);
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
.about-content {
|
| 856 |
+
display: grid;
|
| 857 |
+
grid-template-columns: 1fr 1fr;
|
| 858 |
+
gap: 4rem;
|
| 859 |
+
align-items: center;
|
| 860 |
+
}
|
| 861 |
+
|
| 862 |
+
.about-text h2 {
|
| 863 |
+
font-size: 2.5rem;
|
| 864 |
+
font-weight: 700;
|
| 865 |
+
color: var(--text);
|
| 866 |
+
margin-bottom: 1.5rem;
|
| 867 |
+
}
|
| 868 |
+
|
| 869 |
+
.about-intro {
|
| 870 |
+
font-size: 1.25rem;
|
| 871 |
+
color: var(--text-secondary);
|
| 872 |
+
margin-bottom: 2rem;
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
.about-features {
|
| 876 |
+
display: flex;
|
| 877 |
+
flex-direction: column;
|
| 878 |
+
gap: 1rem;
|
| 879 |
+
margin-bottom: 2rem;
|
| 880 |
+
}
|
| 881 |
+
|
| 882 |
+
.about-feature {
|
| 883 |
+
display: flex;
|
| 884 |
+
align-items: center;
|
| 885 |
+
gap: 1rem;
|
| 886 |
+
color: var(--text-secondary);
|
| 887 |
+
}
|
| 888 |
+
|
| 889 |
+
.about-feature i {
|
| 890 |
+
color: var(--accent);
|
| 891 |
+
font-size: 1.25rem;
|
| 892 |
+
}
|
| 893 |
+
|
| 894 |
+
.about-description {
|
| 895 |
+
color: var(--text-secondary);
|
| 896 |
+
line-height: 1.6;
|
| 897 |
+
}
|
| 898 |
+
|
| 899 |
+
.stats-container {
|
| 900 |
+
display: flex;
|
| 901 |
+
flex-direction: column;
|
| 902 |
+
gap: 2rem;
|
| 903 |
+
}
|
| 904 |
+
|
| 905 |
+
.stat-item {
|
| 906 |
+
display: flex;
|
| 907 |
+
align-items: center;
|
| 908 |
+
gap: 1.5rem;
|
| 909 |
+
padding: 1.5rem;
|
| 910 |
+
background: var(--card);
|
| 911 |
+
border-radius: 12px;
|
| 912 |
+
border: 1px solid var(--border);
|
| 913 |
+
}
|
| 914 |
+
|
| 915 |
+
.stat-icon {
|
| 916 |
+
width: 60px;
|
| 917 |
+
height: 60px;
|
| 918 |
+
background: var(--gradient);
|
| 919 |
+
border-radius: 12px;
|
| 920 |
+
display: flex;
|
| 921 |
+
align-items: center;
|
| 922 |
+
justify-content: center;
|
| 923 |
+
font-size: 1.5rem;
|
| 924 |
+
color: white;
|
| 925 |
+
}
|
| 926 |
+
|
| 927 |
+
.stat-info {
|
| 928 |
+
flex: 1;
|
| 929 |
+
}
|
| 930 |
+
|
| 931 |
+
.stat-number {
|
| 932 |
+
display: block;
|
| 933 |
+
font-size: 2rem;
|
| 934 |
+
font-weight: 700;
|
| 935 |
+
color: var(--primary);
|
| 936 |
+
margin-bottom: 0.25rem;
|
| 937 |
+
}
|
| 938 |
+
|
| 939 |
+
.stat-label {
|
| 940 |
+
font-size: 0.875rem;
|
| 941 |
+
color: var(--text-muted);
|
| 942 |
+
text-transform: uppercase;
|
| 943 |
+
letter-spacing: 0.5px;
|
| 944 |
+
}
|
| 945 |
+
|
| 946 |
+
/* Emergency Section */
|
| 947 |
+
.emergency {
|
| 948 |
+
padding: 4rem 0;
|
| 949 |
+
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
| 950 |
+
color: white;
|
| 951 |
+
position: relative;
|
| 952 |
+
overflow: hidden;
|
| 953 |
+
}
|
| 954 |
+
|
| 955 |
+
.emergency::before {
|
| 956 |
+
content: '';
|
| 957 |
+
position: absolute;
|
| 958 |
+
top: 0;
|
| 959 |
+
left: 0;
|
| 960 |
+
right: 0;
|
| 961 |
+
bottom: 0;
|
| 962 |
+
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="2" fill="rgba(255,255,255,0.1)"/></svg>');
|
| 963 |
+
opacity: 0.1;
|
| 964 |
+
}
|
| 965 |
+
|
| 966 |
+
.emergency-content {
|
| 967 |
+
display: flex;
|
| 968 |
+
align-items: center;
|
| 969 |
+
gap: 2rem;
|
| 970 |
+
max-width: 1000px;
|
| 971 |
+
margin: 0 auto;
|
| 972 |
+
text-align: center;
|
| 973 |
+
position: relative;
|
| 974 |
+
z-index: 1;
|
| 975 |
+
}
|
| 976 |
+
|
| 977 |
+
.emergency-icon {
|
| 978 |
+
font-size: 3rem;
|
| 979 |
+
margin-bottom: 1rem;
|
| 980 |
+
animation: pulse 2s infinite;
|
| 981 |
+
}
|
| 982 |
+
|
| 983 |
+
.emergency-text h3 {
|
| 984 |
+
font-size: 2rem;
|
| 985 |
+
font-weight: 700;
|
| 986 |
+
margin-bottom: 1rem;
|
| 987 |
+
}
|
| 988 |
+
|
| 989 |
+
.emergency-text p {
|
| 990 |
+
font-size: 1.125rem;
|
| 991 |
+
margin-bottom: 1.5rem;
|
| 992 |
+
opacity: 0.9;
|
| 993 |
+
}
|
| 994 |
+
|
| 995 |
+
.emergency-contacts {
|
| 996 |
+
display: flex;
|
| 997 |
+
flex-direction: column;
|
| 998 |
+
gap: 0.75rem;
|
| 999 |
+
margin-bottom: 2rem;
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
.contact-item {
|
| 1003 |
+
font-size: 1.125rem;
|
| 1004 |
+
font-weight: 600;
|
| 1005 |
+
padding: 0.5rem;
|
| 1006 |
+
background: rgba(255, 255, 255, 0.1);
|
| 1007 |
+
border-radius: 8px;
|
| 1008 |
+
backdrop-filter: blur(10px);
|
| 1009 |
+
}
|
| 1010 |
+
|
| 1011 |
+
.emergency-actions {
|
| 1012 |
+
display: flex;
|
| 1013 |
+
gap: 1rem;
|
| 1014 |
+
justify-content: center;
|
| 1015 |
+
flex-wrap: wrap;
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
.btn-emergency {
|
| 1019 |
+
display: inline-flex;
|
| 1020 |
+
align-items: center;
|
| 1021 |
+
gap: 0.5rem;
|
| 1022 |
+
padding: 1rem 1.5rem;
|
| 1023 |
+
border-radius: 50px;
|
| 1024 |
+
font-weight: 600;
|
| 1025 |
+
text-decoration: none;
|
| 1026 |
+
transition: var(--transition);
|
| 1027 |
+
cursor: pointer;
|
| 1028 |
+
border: none;
|
| 1029 |
+
font-size: 1rem;
|
| 1030 |
+
background: white;
|
| 1031 |
+
color: #dc2626;
|
| 1032 |
+
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
.btn-emergency:hover {
|
| 1036 |
+
transform: translateY(-2px);
|
| 1037 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
| 1038 |
+
}
|
| 1039 |
+
|
| 1040 |
+
.btn-emergency.secondary {
|
| 1041 |
+
background: rgba(255, 255, 255, 0.1);
|
| 1042 |
+
color: white;
|
| 1043 |
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
| 1044 |
+
}
|
| 1045 |
+
|
| 1046 |
+
.btn-emergency.secondary:hover {
|
| 1047 |
+
background: rgba(255, 255, 255, 0.2);
|
| 1048 |
+
border-color: rgba(255, 255, 255, 0.5);
|
| 1049 |
+
}
|
| 1050 |
+
|
| 1051 |
+
/* Community Section */
|
| 1052 |
+
.community {
|
| 1053 |
+
padding: 6rem 0;
|
| 1054 |
+
background: var(--surface);
|
| 1055 |
+
}
|
| 1056 |
+
|
| 1057 |
+
.community-grid {
|
| 1058 |
+
display: grid;
|
| 1059 |
+
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
| 1060 |
+
gap: 2rem;
|
| 1061 |
+
margin-bottom: 3rem;
|
| 1062 |
+
}
|
| 1063 |
+
|
| 1064 |
+
.community-card {
|
| 1065 |
+
background: var(--card);
|
| 1066 |
+
padding: 2.5rem;
|
| 1067 |
+
border-radius: 20px;
|
| 1068 |
+
border: 1px solid var(--border);
|
| 1069 |
+
text-align: center;
|
| 1070 |
+
transition: var(--transition);
|
| 1071 |
+
position: relative;
|
| 1072 |
+
overflow: hidden;
|
| 1073 |
+
}
|
| 1074 |
+
|
| 1075 |
+
.community-card::before {
|
| 1076 |
+
content: '';
|
| 1077 |
+
position: absolute;
|
| 1078 |
+
top: 0;
|
| 1079 |
+
left: 0;
|
| 1080 |
+
right: 0;
|
| 1081 |
+
bottom: 0;
|
| 1082 |
+
background: var(--gradient);
|
| 1083 |
+
opacity: 0;
|
| 1084 |
+
transition: var(--transition);
|
| 1085 |
+
z-index: -1;
|
| 1086 |
+
}
|
| 1087 |
+
|
| 1088 |
+
.community-card:hover::before {
|
| 1089 |
+
opacity: 0.05;
|
| 1090 |
+
}
|
| 1091 |
+
|
| 1092 |
+
.community-card:hover {
|
| 1093 |
+
transform: translateY(-8px) scale(1.02);
|
| 1094 |
+
box-shadow: var(--shadow-xl);
|
| 1095 |
+
border-color: var(--primary);
|
| 1096 |
+
}
|
| 1097 |
+
|
| 1098 |
+
.community-icon {
|
| 1099 |
+
width: 80px;
|
| 1100 |
+
height: 80px;
|
| 1101 |
+
background: var(--gradient);
|
| 1102 |
+
border-radius: 50%;
|
| 1103 |
+
display: flex;
|
| 1104 |
+
align-items: center;
|
| 1105 |
+
justify-content: center;
|
| 1106 |
+
margin: 0 auto 1.5rem;
|
| 1107 |
+
font-size: 2rem;
|
| 1108 |
+
color: white;
|
| 1109 |
+
transition: var(--transition);
|
| 1110 |
+
}
|
| 1111 |
+
|
| 1112 |
+
.community-card:hover .community-icon {
|
| 1113 |
+
transform: scale(1.1) rotate(5deg);
|
| 1114 |
+
box-shadow: 0 8px 25px rgba(124, 58, 237, 0.3);
|
| 1115 |
+
}
|
| 1116 |
+
|
| 1117 |
+
.community-card h3 {
|
| 1118 |
+
font-size: 1.5rem;
|
| 1119 |
+
font-weight: 600;
|
| 1120 |
+
color: var(--text);
|
| 1121 |
+
margin-bottom: 1rem;
|
| 1122 |
+
}
|
| 1123 |
+
|
| 1124 |
+
.community-card p {
|
| 1125 |
+
color: var(--text-secondary);
|
| 1126 |
+
line-height: 1.6;
|
| 1127 |
+
margin-bottom: 1.5rem;
|
| 1128 |
+
}
|
| 1129 |
+
|
| 1130 |
+
.community-stats {
|
| 1131 |
+
display: flex;
|
| 1132 |
+
justify-content: center;
|
| 1133 |
+
gap: 1rem;
|
| 1134 |
+
flex-wrap: wrap;
|
| 1135 |
+
}
|
| 1136 |
+
|
| 1137 |
+
.community-stats span {
|
| 1138 |
+
display: flex;
|
| 1139 |
+
align-items: center;
|
| 1140 |
+
gap: 0.25rem;
|
| 1141 |
+
font-size: 0.875rem;
|
| 1142 |
+
color: var(--text-muted);
|
| 1143 |
+
background: rgba(124, 58, 237, 0.1);
|
| 1144 |
+
padding: 0.25rem 0.75rem;
|
| 1145 |
+
border-radius: 20px;
|
| 1146 |
+
}
|
| 1147 |
+
|
| 1148 |
+
.community-cta {
|
| 1149 |
+
text-align: center;
|
| 1150 |
+
}
|
| 1151 |
+
|
| 1152 |
+
.community-note {
|
| 1153 |
+
display: flex;
|
| 1154 |
+
align-items: center;
|
| 1155 |
+
justify-content: center;
|
| 1156 |
+
gap: 0.5rem;
|
| 1157 |
+
color: var(--text-muted);
|
| 1158 |
+
font-size: 0.875rem;
|
| 1159 |
+
margin-top: 1rem;
|
| 1160 |
+
}
|
| 1161 |
+
|
| 1162 |
+
/* Resources Section */
|
| 1163 |
+
.resources {
|
| 1164 |
+
padding: 6rem 0;
|
| 1165 |
+
background: var(--background);
|
| 1166 |
+
}
|
| 1167 |
+
|
| 1168 |
+
.resources-grid {
|
| 1169 |
+
display: grid;
|
| 1170 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 1171 |
+
gap: 2rem;
|
| 1172 |
+
}
|
| 1173 |
+
|
| 1174 |
+
.resource-card {
|
| 1175 |
+
background: var(--surface);
|
| 1176 |
+
padding: 2rem;
|
| 1177 |
+
border-radius: 16px;
|
| 1178 |
+
border: 1px solid var(--border);
|
| 1179 |
+
transition: var(--transition);
|
| 1180 |
+
text-align: center;
|
| 1181 |
+
}
|
| 1182 |
+
|
| 1183 |
+
.resource-card:hover {
|
| 1184 |
+
transform: translateY(-4px);
|
| 1185 |
+
box-shadow: var(--shadow-lg);
|
| 1186 |
+
border-color: var(--primary);
|
| 1187 |
+
}
|
| 1188 |
+
|
| 1189 |
+
.resource-icon {
|
| 1190 |
+
width: 60px;
|
| 1191 |
+
height: 60px;
|
| 1192 |
+
background: var(--gradient);
|
| 1193 |
+
border-radius: 12px;
|
| 1194 |
+
display: flex;
|
| 1195 |
+
align-items: center;
|
| 1196 |
+
justify-content: center;
|
| 1197 |
+
margin: 0 auto 1.5rem;
|
| 1198 |
+
font-size: 1.5rem;
|
| 1199 |
+
color: white;
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
+
.resource-card h3 {
|
| 1203 |
+
font-size: 1.25rem;
|
| 1204 |
+
font-weight: 600;
|
| 1205 |
+
color: var(--text);
|
| 1206 |
+
margin-bottom: 1rem;
|
| 1207 |
+
}
|
| 1208 |
+
|
| 1209 |
+
.resource-card p {
|
| 1210 |
+
color: var(--text-secondary);
|
| 1211 |
+
line-height: 1.6;
|
| 1212 |
+
margin-bottom: 1.5rem;
|
| 1213 |
+
}
|
| 1214 |
+
|
| 1215 |
+
.resource-link {
|
| 1216 |
+
display: inline-flex;
|
| 1217 |
+
align-items: center;
|
| 1218 |
+
gap: 0.5rem;
|
| 1219 |
+
color: var(--primary);
|
| 1220 |
+
text-decoration: none;
|
| 1221 |
+
font-weight: 500;
|
| 1222 |
+
transition: var(--transition);
|
| 1223 |
+
}
|
| 1224 |
+
|
| 1225 |
+
.resource-link:hover {
|
| 1226 |
+
color: var(--primary-light);
|
| 1227 |
+
transform: translateX(4px);
|
| 1228 |
+
}
|
| 1229 |
+
|
| 1230 |
+
/* CTA Section */
|
| 1231 |
+
.cta {
|
| 1232 |
+
padding: 6rem 0;
|
| 1233 |
+
background: var(--background);
|
| 1234 |
+
text-align: center;
|
| 1235 |
+
}
|
| 1236 |
+
|
| 1237 |
+
.cta-content h2 {
|
| 1238 |
+
font-size: 2.5rem;
|
| 1239 |
+
font-weight: 700;
|
| 1240 |
+
color: var(--text);
|
| 1241 |
+
margin-bottom: 1rem;
|
| 1242 |
+
}
|
| 1243 |
+
|
| 1244 |
+
.cta-content p {
|
| 1245 |
+
font-size: 1.25rem;
|
| 1246 |
+
color: var(--text-secondary);
|
| 1247 |
+
margin-bottom: 2.5rem;
|
| 1248 |
+
}
|
| 1249 |
+
|
| 1250 |
+
.cta-actions {
|
| 1251 |
+
display: flex;
|
| 1252 |
+
gap: 1rem;
|
| 1253 |
+
justify-content: center;
|
| 1254 |
+
margin-bottom: 2rem;
|
| 1255 |
+
flex-wrap: wrap;
|
| 1256 |
+
}
|
| 1257 |
+
|
| 1258 |
+
.btn-primary.large, .btn-secondary.large {
|
| 1259 |
+
padding: 1.25rem 2.5rem;
|
| 1260 |
+
font-size: 1.125rem;
|
| 1261 |
+
}
|
| 1262 |
+
|
| 1263 |
+
.cta-note {
|
| 1264 |
+
display: flex;
|
| 1265 |
+
align-items: center;
|
| 1266 |
+
justify-content: center;
|
| 1267 |
+
gap: 0.5rem;
|
| 1268 |
+
color: var(--text-muted);
|
| 1269 |
+
font-size: 0.875rem;
|
| 1270 |
+
}
|
| 1271 |
+
|
| 1272 |
+
/* Footer */
|
| 1273 |
+
.footer {
|
| 1274 |
+
background: var(--surface);
|
| 1275 |
+
padding: 3rem 0 1rem;
|
| 1276 |
+
border-top: 1px solid var(--border);
|
| 1277 |
+
}
|
| 1278 |
+
|
| 1279 |
+
.footer-content {
|
| 1280 |
+
display: grid;
|
| 1281 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 1282 |
+
gap: 2rem;
|
| 1283 |
+
margin-bottom: 2rem;
|
| 1284 |
+
}
|
| 1285 |
+
|
| 1286 |
+
.footer-logo {
|
| 1287 |
+
display: flex;
|
| 1288 |
+
align-items: center;
|
| 1289 |
+
gap: 0.5rem;
|
| 1290 |
+
font-size: 1.5rem;
|
| 1291 |
+
font-weight: 700;
|
| 1292 |
+
color: var(--primary);
|
| 1293 |
+
margin-bottom: 1rem;
|
| 1294 |
+
}
|
| 1295 |
+
|
| 1296 |
+
.footer-section h4 {
|
| 1297 |
+
color: var(--text);
|
| 1298 |
+
margin-bottom: 1rem;
|
| 1299 |
+
font-weight: 600;
|
| 1300 |
+
}
|
| 1301 |
+
|
| 1302 |
+
.footer-section ul {
|
| 1303 |
+
list-style: none;
|
| 1304 |
+
}
|
| 1305 |
+
|
| 1306 |
+
.footer-section ul li {
|
| 1307 |
+
margin-bottom: 0.5rem;
|
| 1308 |
+
}
|
| 1309 |
+
|
| 1310 |
+
.footer-section ul li a {
|
| 1311 |
+
color: var(--text-secondary);
|
| 1312 |
+
text-decoration: none;
|
| 1313 |
+
transition: color 0.3s ease;
|
| 1314 |
+
}
|
| 1315 |
+
|
| 1316 |
+
.footer-section ul li a:hover {
|
| 1317 |
+
color: var(--primary);
|
| 1318 |
+
}
|
| 1319 |
+
|
| 1320 |
+
.emergency-info p {
|
| 1321 |
+
color: var(--text-secondary);
|
| 1322 |
+
margin-bottom: 0.5rem;
|
| 1323 |
+
}
|
| 1324 |
+
|
| 1325 |
+
.footer-bottom {
|
| 1326 |
+
text-align: center;
|
| 1327 |
+
padding-top: 2rem;
|
| 1328 |
+
border-top: 1px solid var(--border);
|
| 1329 |
+
color: var(--text-muted);
|
| 1330 |
+
}
|
| 1331 |
+
|
| 1332 |
+
/* Loading Screen */
|
| 1333 |
+
.loading-screen {
|
| 1334 |
+
position: fixed;
|
| 1335 |
+
top: 0;
|
| 1336 |
+
left: 0;
|
| 1337 |
+
width: 100%;
|
| 1338 |
+
height: 100%;
|
| 1339 |
+
background: var(--background);
|
| 1340 |
+
display: flex;
|
| 1341 |
+
align-items: center;
|
| 1342 |
+
justify-content: center;
|
| 1343 |
+
z-index: 9999;
|
| 1344 |
+
animation: fadeOut 0.5s ease-out 2s forwards;
|
| 1345 |
+
}
|
| 1346 |
+
|
| 1347 |
+
.loading-content {
|
| 1348 |
+
text-align: center;
|
| 1349 |
+
color: var(--text);
|
| 1350 |
+
}
|
| 1351 |
+
|
| 1352 |
+
.loading-logo {
|
| 1353 |
+
display: flex;
|
| 1354 |
+
align-items: center;
|
| 1355 |
+
justify-content: center;
|
| 1356 |
+
gap: 0.5rem;
|
| 1357 |
+
font-size: 2rem;
|
| 1358 |
+
font-weight: 700;
|
| 1359 |
+
color: var(--primary);
|
| 1360 |
+
margin-bottom: 2rem;
|
| 1361 |
+
animation: bounceIn 0.8s ease-out;
|
| 1362 |
+
}
|
| 1363 |
+
|
| 1364 |
+
.loading-logo i {
|
| 1365 |
+
animation: heartbeat 1.5s ease-in-out infinite;
|
| 1366 |
+
}
|
| 1367 |
+
|
| 1368 |
+
.loading-spinner {
|
| 1369 |
+
width: 50px;
|
| 1370 |
+
height: 50px;
|
| 1371 |
+
border: 3px solid var(--border);
|
| 1372 |
+
border-top: 3px solid var(--primary);
|
| 1373 |
+
border-radius: 50%;
|
| 1374 |
+
animation: spin 1s linear infinite;
|
| 1375 |
+
margin: 0 auto 1.5rem;
|
| 1376 |
+
}
|
| 1377 |
+
|
| 1378 |
+
.loading-text {
|
| 1379 |
+
font-size: 1rem;
|
| 1380 |
+
color: var(--text-secondary);
|
| 1381 |
+
animation: fadeIn 0.8s ease-out 0.5s both;
|
| 1382 |
+
}
|
| 1383 |
+
|
| 1384 |
+
@keyframes fadeOut {
|
| 1385 |
+
to {
|
| 1386 |
+
opacity: 0;
|
| 1387 |
+
visibility: hidden;
|
| 1388 |
+
}
|
| 1389 |
+
}
|
| 1390 |
+
|
| 1391 |
+
@keyframes heartbeat {
|
| 1392 |
+
0%, 100% { transform: scale(1); }
|
| 1393 |
+
50% { transform: scale(1.1); }
|
| 1394 |
+
}
|
| 1395 |
+
|
| 1396 |
+
/* Particle Canvas */
|
| 1397 |
+
.particle-canvas {
|
| 1398 |
+
position: absolute;
|
| 1399 |
+
top: 0;
|
| 1400 |
+
left: 0;
|
| 1401 |
+
width: 100%;
|
| 1402 |
+
height: 100%;
|
| 1403 |
+
pointer-events: none;
|
| 1404 |
+
z-index: 1;
|
| 1405 |
+
}
|
| 1406 |
+
|
| 1407 |
+
/* Enhanced Animations */
|
| 1408 |
+
.animate-in {
|
| 1409 |
+
animation: slideInUp 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
| 1410 |
+
animation-fill-mode: both;
|
| 1411 |
+
}
|
| 1412 |
+
|
| 1413 |
+
@keyframes slideInUp {
|
| 1414 |
+
from {
|
| 1415 |
+
opacity: 0;
|
| 1416 |
+
transform: translateY(50px) scale(0.95);
|
| 1417 |
+
}
|
| 1418 |
+
to {
|
| 1419 |
+
opacity: 1;
|
| 1420 |
+
transform: translateY(0) scale(1);
|
| 1421 |
+
}
|
| 1422 |
+
}
|
| 1423 |
+
|
| 1424 |
+
/* Staggered animation delays */
|
| 1425 |
+
.feature-card:nth-child(1) { animation-delay: 0.1s; }
|
| 1426 |
+
.feature-card:nth-child(2) { animation-delay: 0.2s; }
|
| 1427 |
+
.feature-card:nth-child(3) { animation-delay: 0.3s; }
|
| 1428 |
+
.feature-card:nth-child(4) { animation-delay: 0.4s; }
|
| 1429 |
+
.feature-card:nth-child(5) { animation-delay: 0.5s; }
|
| 1430 |
+
.feature-card:nth-child(6) { animation-delay: 0.6s; }
|
| 1431 |
+
|
| 1432 |
+
/* Enhanced Responsive Design */
|
| 1433 |
+
@media (max-width: 1024px) {
|
| 1434 |
+
.hero-content {
|
| 1435 |
+
padding: 0 1rem;
|
| 1436 |
+
}
|
| 1437 |
+
|
| 1438 |
+
.features-grid {
|
| 1439 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 1440 |
+
gap: 1.5rem;
|
| 1441 |
+
}
|
| 1442 |
+
|
| 1443 |
+
.community-grid {
|
| 1444 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 1445 |
+
gap: 1.5rem;
|
| 1446 |
+
}
|
| 1447 |
+
}
|
| 1448 |
+
|
| 1449 |
+
@media (max-width: 768px) {
|
| 1450 |
+
.navbar {
|
| 1451 |
+
padding: 0;
|
| 1452 |
+
}
|
| 1453 |
+
|
| 1454 |
+
.nav-container {
|
| 1455 |
+
padding: 1rem;
|
| 1456 |
+
}
|
| 1457 |
+
|
| 1458 |
+
.nav-logo {
|
| 1459 |
+
font-size: 1.25rem;
|
| 1460 |
+
}
|
| 1461 |
+
|
| 1462 |
+
.nav-links {
|
| 1463 |
+
gap: 1rem;
|
| 1464 |
+
}
|
| 1465 |
+
|
| 1466 |
+
.nav-links a {
|
| 1467 |
+
font-size: 0.875rem;
|
| 1468 |
+
padding: 0.5rem 0;
|
| 1469 |
+
}
|
| 1470 |
+
|
| 1471 |
+
.nav-cta {
|
| 1472 |
+
padding: 0.625rem 1rem;
|
| 1473 |
+
font-size: 0.875rem;
|
| 1474 |
+
}
|
| 1475 |
+
|
| 1476 |
+
.hero {
|
| 1477 |
+
min-height: auto;
|
| 1478 |
+
padding: 4rem 0 6rem;
|
| 1479 |
+
}
|
| 1480 |
+
|
| 1481 |
+
.hero-content {
|
| 1482 |
+
grid-template-columns: 1fr;
|
| 1483 |
+
gap: 3rem;
|
| 1484 |
+
text-align: center;
|
| 1485 |
+
padding: 0 1rem;
|
| 1486 |
+
}
|
| 1487 |
+
|
| 1488 |
+
.hero-title {
|
| 1489 |
+
font-size: clamp(2rem, 8vw, 2.5rem);
|
| 1490 |
+
margin-bottom: 1rem;
|
| 1491 |
+
}
|
| 1492 |
+
|
| 1493 |
+
.hero-description {
|
| 1494 |
+
font-size: 1rem;
|
| 1495 |
+
margin-bottom: 1.5rem;
|
| 1496 |
+
}
|
| 1497 |
+
|
| 1498 |
+
.hero-stats {
|
| 1499 |
+
justify-content: center;
|
| 1500 |
+
gap: 1.5rem;
|
| 1501 |
+
}
|
| 1502 |
+
|
| 1503 |
+
.hero-actions {
|
| 1504 |
+
justify-content: center;
|
| 1505 |
+
flex-direction: column;
|
| 1506 |
+
gap: 1rem;
|
| 1507 |
+
}
|
| 1508 |
+
|
| 1509 |
+
.chat-preview {
|
| 1510 |
+
max-width: 100%;
|
| 1511 |
+
margin: 0 auto;
|
| 1512 |
+
}
|
| 1513 |
+
|
| 1514 |
+
.section-header {
|
| 1515 |
+
margin-bottom: 3rem;
|
| 1516 |
+
}
|
| 1517 |
+
|
| 1518 |
+
.section-header h2 {
|
| 1519 |
+
font-size: clamp(1.75rem, 5vw, 2.25rem);
|
| 1520 |
+
}
|
| 1521 |
+
|
| 1522 |
+
.features-grid,
|
| 1523 |
+
.community-grid {
|
| 1524 |
+
grid-template-columns: 1fr;
|
| 1525 |
+
gap: 1.5rem;
|
| 1526 |
+
}
|
| 1527 |
+
|
| 1528 |
+
.feature-card,
|
| 1529 |
+
.community-card {
|
| 1530 |
+
padding: 2rem 1.5rem;
|
| 1531 |
+
}
|
| 1532 |
+
|
| 1533 |
+
.about-content {
|
| 1534 |
+
grid-template-columns: 1fr;
|
| 1535 |
+
gap: 3rem;
|
| 1536 |
+
}
|
| 1537 |
+
|
| 1538 |
+
.emergency-content {
|
| 1539 |
+
flex-direction: column;
|
| 1540 |
+
text-align: center;
|
| 1541 |
+
gap: 1.5rem;
|
| 1542 |
+
}
|
| 1543 |
+
|
| 1544 |
+
.emergency-actions {
|
| 1545 |
+
flex-direction: column;
|
| 1546 |
+
gap: 0.75rem;
|
| 1547 |
+
width: 100%;
|
| 1548 |
+
max-width: 300px;
|
| 1549 |
+
margin: 0 auto;
|
| 1550 |
+
}
|
| 1551 |
+
|
| 1552 |
+
.btn-emergency {
|
| 1553 |
+
width: 100%;
|
| 1554 |
+
}
|
| 1555 |
+
|
| 1556 |
+
.cta-content h2 {
|
| 1557 |
+
font-size: 2rem;
|
| 1558 |
+
}
|
| 1559 |
+
|
| 1560 |
+
.cta-actions {
|
| 1561 |
+
flex-direction: column;
|
| 1562 |
+
align-items: center;
|
| 1563 |
+
gap: 1rem;
|
| 1564 |
+
}
|
| 1565 |
+
|
| 1566 |
+
.footer-content {
|
| 1567 |
+
grid-template-columns: 1fr;
|
| 1568 |
+
text-align: center;
|
| 1569 |
+
gap: 2rem;
|
| 1570 |
+
}
|
| 1571 |
+
|
| 1572 |
+
.footer-section h4 {
|
| 1573 |
+
margin-bottom: 1rem;
|
| 1574 |
+
}
|
| 1575 |
+
|
| 1576 |
+
.particle-canvas {
|
| 1577 |
+
display: none; /* Disable particles on mobile for performance */
|
| 1578 |
+
}
|
| 1579 |
+
}
|
| 1580 |
+
|
| 1581 |
+
@media (max-width: 640px) {
|
| 1582 |
+
.hero-float-1,
|
| 1583 |
+
.hero-float-2,
|
| 1584 |
+
.hero-float-3 {
|
| 1585 |
+
display: none; /* Hide floating elements on smaller screens */
|
| 1586 |
+
}
|
| 1587 |
+
|
| 1588 |
+
.hero-pattern {
|
| 1589 |
+
opacity: 0.3;
|
| 1590 |
+
}
|
| 1591 |
+
|
| 1592 |
+
.nav-links {
|
| 1593 |
+
display: none; /* Hide nav links on very small screens */
|
| 1594 |
+
}
|
| 1595 |
+
|
| 1596 |
+
.nav-cta {
|
| 1597 |
+
margin-left: auto;
|
| 1598 |
+
}
|
| 1599 |
+
}
|
| 1600 |
+
|
| 1601 |
+
@media (max-width: 480px) {
|
| 1602 |
+
.container {
|
| 1603 |
+
padding: 0 1rem;
|
| 1604 |
+
}
|
| 1605 |
+
|
| 1606 |
+
.hero-title {
|
| 1607 |
+
font-size: clamp(1.75rem, 8vw, 2rem);
|
| 1608 |
+
line-height: 1.2;
|
| 1609 |
+
}
|
| 1610 |
+
|
| 1611 |
+
.hero-description {
|
| 1612 |
+
font-size: 0.95rem;
|
| 1613 |
+
}
|
| 1614 |
+
|
| 1615 |
+
.hero-stats {
|
| 1616 |
+
flex-direction: column;
|
| 1617 |
+
gap: 1rem;
|
| 1618 |
+
margin-bottom: 1.5rem;
|
| 1619 |
+
}
|
| 1620 |
+
|
| 1621 |
+
.stat {
|
| 1622 |
+
padding: 0.75rem;
|
| 1623 |
+
}
|
| 1624 |
+
|
| 1625 |
+
.btn-primary, .btn-secondary {
|
| 1626 |
+
padding: 0.875rem 1.5rem;
|
| 1627 |
+
font-size: 0.875rem;
|
| 1628 |
+
width: 100%;
|
| 1629 |
+
max-width: 280px;
|
| 1630 |
+
}
|
| 1631 |
+
|
| 1632 |
+
.chat-preview {
|
| 1633 |
+
padding: 1rem;
|
| 1634 |
+
}
|
| 1635 |
+
|
| 1636 |
+
.chat-messages {
|
| 1637 |
+
gap: 0.75rem;
|
| 1638 |
+
}
|
| 1639 |
+
|
| 1640 |
+
.message-content {
|
| 1641 |
+
padding: 0.75rem 1rem;
|
| 1642 |
+
font-size: 0.9rem;
|
| 1643 |
+
}
|
| 1644 |
+
|
| 1645 |
+
.feature-card,
|
| 1646 |
+
.community-card,
|
| 1647 |
+
.resource-card {
|
| 1648 |
+
padding: 1.5rem 1rem;
|
| 1649 |
+
}
|
| 1650 |
+
|
| 1651 |
+
.feature-icon,
|
| 1652 |
+
.community-icon,
|
| 1653 |
+
.resource-icon {
|
| 1654 |
+
width: 50px;
|
| 1655 |
+
height: 50px;
|
| 1656 |
+
font-size: 1.25rem;
|
| 1657 |
+
}
|
| 1658 |
+
|
| 1659 |
+
.feature-card h3,
|
| 1660 |
+
.community-card h3,
|
| 1661 |
+
.resource-card h3 {
|
| 1662 |
+
font-size: 1.125rem;
|
| 1663 |
+
}
|
| 1664 |
+
|
| 1665 |
+
.stat-item {
|
| 1666 |
+
flex-direction: column;
|
| 1667 |
+
text-align: center;
|
| 1668 |
+
gap: 0.75rem;
|
| 1669 |
+
}
|
| 1670 |
+
|
| 1671 |
+
.stat-icon {
|
| 1672 |
+
width: 50px;
|
| 1673 |
+
height: 50px;
|
| 1674 |
+
}
|
| 1675 |
+
|
| 1676 |
+
.emergency-icon {
|
| 1677 |
+
font-size: 2.5rem;
|
| 1678 |
+
}
|
| 1679 |
+
|
| 1680 |
+
.emergency-text h3 {
|
| 1681 |
+
font-size: 1.75rem;
|
| 1682 |
+
}
|
| 1683 |
+
|
| 1684 |
+
.emergency-contacts {
|
| 1685 |
+
gap: 0.5rem;
|
| 1686 |
+
}
|
| 1687 |
+
|
| 1688 |
+
.contact-item {
|
| 1689 |
+
padding: 0.375rem;
|
| 1690 |
+
font-size: 0.9rem;
|
| 1691 |
+
}
|
| 1692 |
+
|
| 1693 |
+
.cta-content h2 {
|
| 1694 |
+
font-size: 1.75rem;
|
| 1695 |
+
}
|
| 1696 |
+
|
| 1697 |
+
.cta-note {
|
| 1698 |
+
flex-direction: column;
|
| 1699 |
+
gap: 0.5rem;
|
| 1700 |
+
text-align: center;
|
| 1701 |
+
}
|
| 1702 |
+
|
| 1703 |
+
.footer {
|
| 1704 |
+
padding: 2rem 0 1rem;
|
| 1705 |
+
}
|
| 1706 |
+
|
| 1707 |
+
.footer-content {
|
| 1708 |
+
gap: 1.5rem;
|
| 1709 |
+
}
|
| 1710 |
+
}
|
| 1711 |
+
|
| 1712 |
+
/* Touch-friendly interactions for mobile */
|
| 1713 |
+
@media (hover: none) and (pointer: coarse) {
|
| 1714 |
+
.btn-primary, .btn-secondary, .nav-cta {
|
| 1715 |
+
min-height: 44px; /* iOS touch target minimum */
|
| 1716 |
+
}
|
| 1717 |
+
|
| 1718 |
+
.feature-card, .community-card, .resource-card {
|
| 1719 |
+
transition: transform 0.1s ease; /* Faster transitions on touch */
|
| 1720 |
+
}
|
| 1721 |
+
|
| 1722 |
+
.feature-card:active, .community-card:active, .resource-card:active {
|
| 1723 |
+
transform: scale(0.98);
|
| 1724 |
+
}
|
| 1725 |
+
}
|
| 1726 |
+
|
| 1727 |
+
/* High contrast mode support */
|
| 1728 |
+
@media (prefers-contrast: high) {
|
| 1729 |
+
:root {
|
| 1730 |
+
--primary: #0066cc;
|
| 1731 |
+
--text: #000000;
|
| 1732 |
+
--background: #ffffff;
|
| 1733 |
+
--surface: #f8f8f8;
|
| 1734 |
+
--border: #000000;
|
| 1735 |
+
}
|
| 1736 |
+
}
|
| 1737 |
+
|
| 1738 |
+
/* Reduced motion support */
|
| 1739 |
+
@media (prefers-reduced-motion: reduce) {
|
| 1740 |
+
*,
|
| 1741 |
+
*::before,
|
| 1742 |
+
*::after {
|
| 1743 |
+
animation-duration: 0.01ms !important;
|
| 1744 |
+
animation-iteration-count: 1 !important;
|
| 1745 |
+
transition-duration: 0.01ms !important;
|
| 1746 |
+
scroll-behavior: auto !important;
|
| 1747 |
+
}
|
| 1748 |
+
|
| 1749 |
+
.hero-pattern {
|
| 1750 |
+
animation: none;
|
| 1751 |
+
}
|
| 1752 |
+
|
| 1753 |
+
.hero-float-1,
|
| 1754 |
+
.hero-float-2,
|
| 1755 |
+
.hero-float-3 {
|
| 1756 |
+
animation: none;
|
| 1757 |
+
}
|
| 1758 |
+
}
|
| 1759 |
+
|
| 1760 |
+
|
| 1761 |
+
|
chatbot/landing.html
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>AIMHSA - AI Mental Health Support for Rwanda</title>
|
| 7 |
+
<link rel="stylesheet" href="landing.css?v=2">
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
| 9 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 10 |
+
</head>
|
| 11 |
+
<body>
|
| 12 |
+
<!-- Loading Screen -->
|
| 13 |
+
<div id="loading-screen" class="loading-screen">
|
| 14 |
+
<div class="loading-content">
|
| 15 |
+
<div class="loading-logo">
|
| 16 |
+
<i class="fas fa-heart"></i>
|
| 17 |
+
<span>AIMHSA</span>
|
| 18 |
+
</div>
|
| 19 |
+
<div class="loading-spinner"></div>
|
| 20 |
+
<p class="loading-text">Loading your mental health companion...</p>
|
| 21 |
+
</div>
|
| 22 |
+
</div>
|
| 23 |
+
|
| 24 |
+
<!-- Navigation -->
|
| 25 |
+
<nav class="navbar">
|
| 26 |
+
<div class="nav-container">
|
| 27 |
+
<div class="nav-logo">
|
| 28 |
+
<i class="fas fa-heart"></i>
|
| 29 |
+
<span>AIMHSA</span>
|
| 30 |
+
</div>
|
| 31 |
+
<div class="nav-links">
|
| 32 |
+
<a href="#features">Features</a>
|
| 33 |
+
<a href="#community">Community</a>
|
| 34 |
+
<a href="#resources">Resources</a>
|
| 35 |
+
<a href="#about">About</a>
|
| 36 |
+
<a href="http://localhost:8000/login" class="nav-cta">Get Started</a>
|
| 37 |
+
</div>
|
| 38 |
+
</div>
|
| 39 |
+
</nav>
|
| 40 |
+
|
| 41 |
+
<!-- Hero Section -->
|
| 42 |
+
<section class="hero">
|
| 43 |
+
<div class="hero-background">
|
| 44 |
+
<div class="hero-pattern"></div>
|
| 45 |
+
<div class="hero-float-1"></div>
|
| 46 |
+
<div class="hero-float-2"></div>
|
| 47 |
+
<div class="hero-float-3"></div>
|
| 48 |
+
</div>
|
| 49 |
+
<div class="hero-content">
|
| 50 |
+
<div class="hero-text">
|
| 51 |
+
<h1 class="hero-title">
|
| 52 |
+
<span class="gradient-text">AI-Powered</span><br>
|
| 53 |
+
Mental Health Support<br>
|
| 54 |
+
<span class="highlight">for Rwanda</span>
|
| 55 |
+
</h1>
|
| 56 |
+
<p class="hero-description">
|
| 57 |
+
AIMHSA provides compassionate, culturally-sensitive mental health support
|
| 58 |
+
through advanced AI technology. Available in English, Kinyarwanda, French,
|
| 59 |
+
and Kiswahili.
|
| 60 |
+
</p>
|
| 61 |
+
<div class="hero-stats">
|
| 62 |
+
<div class="stat">
|
| 63 |
+
<span class="stat-number">24/7</span>
|
| 64 |
+
<span class="stat-label">Available</span>
|
| 65 |
+
</div>
|
| 66 |
+
<div class="stat">
|
| 67 |
+
<span class="stat-number">4</span>
|
| 68 |
+
<span class="stat-label">Languages</span>
|
| 69 |
+
</div>
|
| 70 |
+
<div class="stat">
|
| 71 |
+
<span class="stat-number">100%</span>
|
| 72 |
+
<span class="stat-label">Confidential</span>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
<div class="hero-actions">
|
| 76 |
+
<a href="http://localhost:8000/login" class="btn-primary">
|
| 77 |
+
<i class="fas fa-rocket"></i>
|
| 78 |
+
Start Your Journey
|
| 79 |
+
</a>
|
| 80 |
+
<a href="#features" class="btn-secondary">
|
| 81 |
+
<i class="fas fa-play"></i>
|
| 82 |
+
Learn More
|
| 83 |
+
</a>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
<div class="hero-visual">
|
| 87 |
+
<div class="chat-preview">
|
| 88 |
+
<div class="chat-header">
|
| 89 |
+
<div class="chat-avatar">
|
| 90 |
+
<i class="fas fa-robot"></i>
|
| 91 |
+
</div>
|
| 92 |
+
<div class="chat-info">
|
| 93 |
+
<span class="chat-name">AIMHSA Assistant</span>
|
| 94 |
+
<span class="chat-status">Online</span>
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
<div class="chat-messages">
|
| 98 |
+
<div class="message bot-message">
|
| 99 |
+
<div class="message-content">
|
| 100 |
+
<p>Muraho! How are you feeling today?</p>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
<div class="message user-message">
|
| 104 |
+
<div class="message-content">
|
| 105 |
+
<p>I've been feeling anxious lately...</p>
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
<div class="message bot-message">
|
| 109 |
+
<div class="message-content">
|
| 110 |
+
<p>I understand. Let's talk about what's been on your mind. Remember, you're not alone in this journey.</p>
|
| 111 |
+
</div>
|
| 112 |
+
</div>
|
| 113 |
+
</div>
|
| 114 |
+
<div class="typing-indicator">
|
| 115 |
+
<span></span>
|
| 116 |
+
<span></span>
|
| 117 |
+
<span></span>
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
</div>
|
| 121 |
+
</div>
|
| 122 |
+
</section>
|
| 123 |
+
|
| 124 |
+
<!-- Features Section -->
|
| 125 |
+
<section id="features" class="features">
|
| 126 |
+
<div class="container">
|
| 127 |
+
<div class="section-header">
|
| 128 |
+
<h2>Why Choose AIMHSA?</h2>
|
| 129 |
+
<p>Advanced AI technology meets compassionate mental health care</p>
|
| 130 |
+
</div>
|
| 131 |
+
<div class="features-grid">
|
| 132 |
+
<div class="feature-card">
|
| 133 |
+
<div class="feature-icon">
|
| 134 |
+
<i class="fas fa-language"></i>
|
| 135 |
+
</div>
|
| 136 |
+
<h3>Multilingual Support</h3>
|
| 137 |
+
<p>Communicate in English, Kinyarwanda, French, or Kiswahili. Our AI understands and responds in your preferred language.</p>
|
| 138 |
+
</div>
|
| 139 |
+
<div class="feature-card">
|
| 140 |
+
<div class="feature-icon">
|
| 141 |
+
<i class="fas fa-shield-alt"></i>
|
| 142 |
+
</div>
|
| 143 |
+
<h3>Privacy & Security</h3>
|
| 144 |
+
<p>Your conversations are completely confidential. Advanced encryption and privacy protection ensure your data stays safe.</p>
|
| 145 |
+
</div>
|
| 146 |
+
<div class="feature-card">
|
| 147 |
+
<div class="feature-icon">
|
| 148 |
+
<i class="fas fa-brain"></i>
|
| 149 |
+
</div>
|
| 150 |
+
<h3>Trauma-Informed</h3>
|
| 151 |
+
<p>Specially designed for Rwanda's context, understanding the unique mental health challenges and cultural sensitivities.</p>
|
| 152 |
+
</div>
|
| 153 |
+
<div class="feature-card">
|
| 154 |
+
<div class="feature-icon">
|
| 155 |
+
<i class="fas fa-file-medical"></i>
|
| 156 |
+
</div>
|
| 157 |
+
<h3>Document Analysis</h3>
|
| 158 |
+
<p>Upload PDF documents for personalized analysis and support based on your specific mental health documents.</p>
|
| 159 |
+
</div>
|
| 160 |
+
<div class="feature-card">
|
| 161 |
+
<div class="feature-icon">
|
| 162 |
+
<i class="fas fa-clock"></i>
|
| 163 |
+
</div>
|
| 164 |
+
<h3>24/7 Availability</h3>
|
| 165 |
+
<p>Access support whenever you need it. No appointments, no waiting - immediate help is always available.</p>
|
| 166 |
+
</div>
|
| 167 |
+
<div class="feature-card">
|
| 168 |
+
<div class="feature-icon">
|
| 169 |
+
<i class="fas fa-users"></i>
|
| 170 |
+
</div>
|
| 171 |
+
<h3>Professional Referrals</h3>
|
| 172 |
+
<p>When needed, we connect you with local mental health professionals and resources in Rwanda.</p>
|
| 173 |
+
</div>
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
</section>
|
| 177 |
+
|
| 178 |
+
<!-- About Section -->
|
| 179 |
+
<section id="about" class="about">
|
| 180 |
+
<div class="container">
|
| 181 |
+
<div class="about-content">
|
| 182 |
+
<div class="about-text">
|
| 183 |
+
<h2>About AIMHSA</h2>
|
| 184 |
+
<p class="about-intro">
|
| 185 |
+
AIMHSA (AI Mental Health Support Assistant) is a groundbreaking platform
|
| 186 |
+
designed specifically for Rwanda's mental health needs.
|
| 187 |
+
</p>
|
| 188 |
+
<div class="about-features">
|
| 189 |
+
<div class="about-feature">
|
| 190 |
+
<i class="fas fa-check-circle"></i>
|
| 191 |
+
<span>Evidence-based mental health support</span>
|
| 192 |
+
</div>
|
| 193 |
+
<div class="about-feature">
|
| 194 |
+
<i class="fas fa-check-circle"></i>
|
| 195 |
+
<span>Culturally sensitive and context-aware</span>
|
| 196 |
+
</div>
|
| 197 |
+
<div class="about-feature">
|
| 198 |
+
<i class="fas fa-check-circle"></i>
|
| 199 |
+
<span>Local resource integration</span>
|
| 200 |
+
</div>
|
| 201 |
+
<div class="about-feature">
|
| 202 |
+
<i class="fas fa-check-circle"></i>
|
| 203 |
+
<span>Professional-grade AI technology</span>
|
| 204 |
+
</div>
|
| 205 |
+
</div>
|
| 206 |
+
<p class="about-description">
|
| 207 |
+
Our AI assistant is trained on comprehensive mental health resources
|
| 208 |
+
specific to Rwanda, including information about local hospitals, helplines,
|
| 209 |
+
and support services. We understand the unique challenges faced by
|
| 210 |
+
Rwandans and provide culturally appropriate support.
|
| 211 |
+
</p>
|
| 212 |
+
</div>
|
| 213 |
+
<div class="about-visual">
|
| 214 |
+
<div class="stats-container">
|
| 215 |
+
<div class="stat-item">
|
| 216 |
+
<div class="stat-icon">
|
| 217 |
+
<i class="fas fa-hospital"></i>
|
| 218 |
+
</div>
|
| 219 |
+
<div class="stat-info">
|
| 220 |
+
<span class="stat-number">50+</span>
|
| 221 |
+
<span class="stat-label">Mental Health Resources</span>
|
| 222 |
+
</div>
|
| 223 |
+
</div>
|
| 224 |
+
<div class="stat-item">
|
| 225 |
+
<div class="stat-icon">
|
| 226 |
+
<i class="fas fa-phone"></i>
|
| 227 |
+
</div>
|
| 228 |
+
<div class="stat-info">
|
| 229 |
+
<span class="stat-number">24/7</span>
|
| 230 |
+
<span class="stat-label">Emergency Support</span>
|
| 231 |
+
</div>
|
| 232 |
+
</div>
|
| 233 |
+
<div class="stat-item">
|
| 234 |
+
<div class="stat-icon">
|
| 235 |
+
<i class="fas fa-graduation-cap"></i>
|
| 236 |
+
</div>
|
| 237 |
+
<div class="stat-info">
|
| 238 |
+
<span class="stat-number">100%</span>
|
| 239 |
+
<span class="stat-label">Evidence-Based</span>
|
| 240 |
+
</div>
|
| 241 |
+
</div>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
</div>
|
| 245 |
+
</div>
|
| 246 |
+
</section>
|
| 247 |
+
|
| 248 |
+
<!-- Emergency Section -->
|
| 249 |
+
<section class="emergency">
|
| 250 |
+
<div class="container">
|
| 251 |
+
<div class="emergency-content">
|
| 252 |
+
<div class="emergency-icon">
|
| 253 |
+
<i class="fas fa-exclamation-triangle"></i>
|
| 254 |
+
</div>
|
| 255 |
+
<div class="emergency-text">
|
| 256 |
+
<h3>In Crisis? Need Immediate Help?</h3>
|
| 257 |
+
<p>If you're experiencing a mental health crisis or having thoughts of self-harm, please contact emergency services immediately.</p>
|
| 258 |
+
<div class="emergency-contacts">
|
| 259 |
+
<div class="contact-item">
|
| 260 |
+
<strong>Mental Health Hotline:</strong> 105
|
| 261 |
+
</div>
|
| 262 |
+
<div class="contact-item">
|
| 263 |
+
<strong>CARAES Ndera Hospital:</strong> +250 788 305 703
|
| 264 |
+
</div>
|
| 265 |
+
<div class="contact-item">
|
| 266 |
+
<strong>Emergency SMS:</strong> Text "HELP" to 105
|
| 267 |
+
</div>
|
| 268 |
+
</div>
|
| 269 |
+
<div class="emergency-actions">
|
| 270 |
+
<button class="btn-emergency" onclick="callEmergency('105')">
|
| 271 |
+
<i class="fas fa-phone"></i>
|
| 272 |
+
Call Hotline
|
| 273 |
+
</button>
|
| 274 |
+
<button class="btn-emergency secondary" onclick="sendEmergencySMS()">
|
| 275 |
+
<i class="fas fa-sms"></i>
|
| 276 |
+
Send Emergency SMS
|
| 277 |
+
</button>
|
| 278 |
+
<button class="btn-emergency secondary" onclick="findNearbyHelp()">
|
| 279 |
+
<i class="fas fa-map-marker-alt"></i>
|
| 280 |
+
Find Nearby Help
|
| 281 |
+
</button>
|
| 282 |
+
</div>
|
| 283 |
+
</div>
|
| 284 |
+
</div>
|
| 285 |
+
</div>
|
| 286 |
+
</section>
|
| 287 |
+
|
| 288 |
+
<!-- Community Section -->
|
| 289 |
+
<section id="community" class="community">
|
| 290 |
+
<div class="container">
|
| 291 |
+
<div class="section-header">
|
| 292 |
+
<h2>Join Our Support Community</h2>
|
| 293 |
+
<p>Connect with others who understand. Share experiences, offer support, and find hope together.</p>
|
| 294 |
+
</div>
|
| 295 |
+
<div class="community-grid">
|
| 296 |
+
<div class="community-card">
|
| 297 |
+
<div class="community-icon">
|
| 298 |
+
<i class="fas fa-users"></i>
|
| 299 |
+
</div>
|
| 300 |
+
<h3>Support Forums</h3>
|
| 301 |
+
<p>Join moderated discussion forums where you can share your experiences and support others on their mental health journey.</p>
|
| 302 |
+
<div class="community-stats">
|
| 303 |
+
<span><i class="fas fa-user-friends"></i> 2,500+ Members</span>
|
| 304 |
+
<span><i class="fas fa-comments"></i> 15,000+ Posts</span>
|
| 305 |
+
</div>
|
| 306 |
+
</div>
|
| 307 |
+
<div class="community-card">
|
| 308 |
+
<div class="community-icon">
|
| 309 |
+
<i class="fas fa-heart"></i>
|
| 310 |
+
</div>
|
| 311 |
+
<h3>Success Stories</h3>
|
| 312 |
+
<p>Read inspiring stories of recovery and hope from people who have overcome mental health challenges in Rwanda.</p>
|
| 313 |
+
<div class="community-stats">
|
| 314 |
+
<span><i class="fas fa-star"></i> 500+ Stories</span>
|
| 315 |
+
<span><i class="fas fa-heart"></i> 10,000+ Likes</span>
|
| 316 |
+
</div>
|
| 317 |
+
</div>
|
| 318 |
+
<div class="community-card">
|
| 319 |
+
<div class="community-icon">
|
| 320 |
+
<i class="fas fa-calendar-alt"></i>
|
| 321 |
+
</div>
|
| 322 |
+
<h3>Support Groups</h3>
|
| 323 |
+
<p>Participate in virtual support groups led by trained facilitators, available in multiple languages.</p>
|
| 324 |
+
<div class="community-stats">
|
| 325 |
+
<span><i class="fas fa-clock"></i> Weekly Meetings</span>
|
| 326 |
+
<span><i class="fas fa-globe"></i> 4 Languages</span>
|
| 327 |
+
</div>
|
| 328 |
+
</div>
|
| 329 |
+
</div>
|
| 330 |
+
<div class="community-cta">
|
| 331 |
+
<a href="http://localhost:8000/login" class="btn-primary large">
|
| 332 |
+
<i class="fas fa-sign-in-alt"></i>
|
| 333 |
+
Join Community
|
| 334 |
+
</a>
|
| 335 |
+
<p class="community-note">
|
| 336 |
+
<i class="fas fa-shield-alt"></i>
|
| 337 |
+
All community interactions are moderated and confidential
|
| 338 |
+
</p>
|
| 339 |
+
</div>
|
| 340 |
+
</div>
|
| 341 |
+
</section>
|
| 342 |
+
|
| 343 |
+
<!-- Resources Section -->
|
| 344 |
+
<section id="resources" class="resources">
|
| 345 |
+
<div class="container">
|
| 346 |
+
<div class="section-header">
|
| 347 |
+
<h2>Mental Health Resources</h2>
|
| 348 |
+
<p>Access comprehensive mental health information and tools tailored for Rwanda</p>
|
| 349 |
+
</div>
|
| 350 |
+
<div class="resources-grid">
|
| 351 |
+
<div class="resource-card">
|
| 352 |
+
<div class="resource-icon">
|
| 353 |
+
<i class="fas fa-book-open"></i>
|
| 354 |
+
</div>
|
| 355 |
+
<h3>Educational Content</h3>
|
| 356 |
+
<p>Learn about mental health through articles, videos, and infographics in Kinyarwanda, French, and English.</p>
|
| 357 |
+
<a href="#" class="resource-link">Explore Resources <i class="fas fa-arrow-right"></i></a>
|
| 358 |
+
</div>
|
| 359 |
+
<div class="resource-card">
|
| 360 |
+
<div class="resource-icon">
|
| 361 |
+
<i class="fas fa-brain"></i>
|
| 362 |
+
</div>
|
| 363 |
+
<h3>Self-Help Tools</h3>
|
| 364 |
+
<p>Access guided meditations, breathing exercises, and coping strategies designed for Rwandan culture.</p>
|
| 365 |
+
<a href="#" class="resource-link">View Tools <i class="fas fa-arrow-right"></i></a>
|
| 366 |
+
</div>
|
| 367 |
+
<div class="resource-card">
|
| 368 |
+
<div class="resource-icon">
|
| 369 |
+
<i class="fas fa-user-md"></i>
|
| 370 |
+
</div>
|
| 371 |
+
<h3>Professional Directory</h3>
|
| 372 |
+
<p>Find licensed mental health professionals, counselors, and therapists across Rwanda.</p>
|
| 373 |
+
<a href="#" class="resource-link">Find Professionals <i class="fas fa-arrow-right"></i></a>
|
| 374 |
+
</div>
|
| 375 |
+
<div class="resource-card">
|
| 376 |
+
<div class="resource-icon">
|
| 377 |
+
<i class="fas fa-map-marked-alt"></i>
|
| 378 |
+
</div>
|
| 379 |
+
<h3>Emergency Services</h3>
|
| 380 |
+
<p>Locate emergency mental health services, crisis centers, and hospitals in your area.</p>
|
| 381 |
+
<a href="#" class="resource-link">View Services <i class="fas fa-arrow-right"></i></a>
|
| 382 |
+
</div>
|
| 383 |
+
</div>
|
| 384 |
+
</div>
|
| 385 |
+
</section>
|
| 386 |
+
|
| 387 |
+
<!-- CTA Section -->
|
| 388 |
+
<section class="cta">
|
| 389 |
+
<div class="container">
|
| 390 |
+
<div class="cta-content">
|
| 391 |
+
<h2>Ready to Begin Your Mental Health Journey?</h2>
|
| 392 |
+
<p>Join thousands of Rwandans who have found support through AIMHSA</p>
|
| 393 |
+
<div class="cta-actions">
|
| 394 |
+
<a href="http://localhost:8000/login" class="btn-primary large">
|
| 395 |
+
<i class="fas fa-rocket"></i>
|
| 396 |
+
Start Your Journey Today
|
| 397 |
+
</a>
|
| 398 |
+
<a href="http://localhost:8000/register" class="btn-secondary large">
|
| 399 |
+
<i class="fas fa-user-plus"></i>
|
| 400 |
+
Create Free Account
|
| 401 |
+
</a>
|
| 402 |
+
</div>
|
| 403 |
+
<div class="cta-note">
|
| 404 |
+
<i class="fas fa-lock"></i>
|
| 405 |
+
<span>100% Free • No Credit Card Required • Instant Access</span>
|
| 406 |
+
</div>
|
| 407 |
+
</div>
|
| 408 |
+
</div>
|
| 409 |
+
</section>
|
| 410 |
+
|
| 411 |
+
<!-- Footer -->
|
| 412 |
+
<footer class="footer">
|
| 413 |
+
<div class="container">
|
| 414 |
+
<div class="footer-content">
|
| 415 |
+
<div class="footer-section">
|
| 416 |
+
<div class="footer-logo">
|
| 417 |
+
<i class="fas fa-heart"></i>
|
| 418 |
+
<span>AIMHSA</span>
|
| 419 |
+
</div>
|
| 420 |
+
<p>AI-powered mental health support for Rwanda</p>
|
| 421 |
+
</div>
|
| 422 |
+
<div class="footer-section">
|
| 423 |
+
<h4>Quick Links</h4>
|
| 424 |
+
<ul>
|
| 425 |
+
<li><a href="http://localhost:8000/login">Sign In</a></li>
|
| 426 |
+
<li><a href="http://localhost:8000/register">Register</a></li>
|
| 427 |
+
<li><a href="#features">Features</a></li>
|
| 428 |
+
<li><a href="#about">About</a></li>
|
| 429 |
+
</ul>
|
| 430 |
+
</div>
|
| 431 |
+
<div class="footer-section">
|
| 432 |
+
<h4>Support</h4>
|
| 433 |
+
<ul>
|
| 434 |
+
<li><a href="#contact">Contact Us</a></li>
|
| 435 |
+
<li><a href="#">Privacy Policy</a></li>
|
| 436 |
+
<li><a href="#">Terms of Service</a></li>
|
| 437 |
+
<li><a href="#">Help Center</a></li>
|
| 438 |
+
</ul>
|
| 439 |
+
</div>
|
| 440 |
+
<div class="footer-section">
|
| 441 |
+
<h4>Emergency</h4>
|
| 442 |
+
<div class="emergency-info">
|
| 443 |
+
<p><strong>Mental Health Hotline:</strong> 105</p>
|
| 444 |
+
<p><strong>CARAES Ndera:</strong> +250 788 305 703</p>
|
| 445 |
+
</div>
|
| 446 |
+
</div>
|
| 447 |
+
</div>
|
| 448 |
+
<div class="footer-bottom">
|
| 449 |
+
<p>© 2024 AIMHSA. All rights reserved. | Designed for Rwanda's mental health needs.</p>
|
| 450 |
+
</div>
|
| 451 |
+
</div>
|
| 452 |
+
</footer>
|
| 453 |
+
|
| 454 |
+
<script src="landing.js?v=2"></script>
|
| 455 |
+
<script>
|
| 456 |
+
// Debug script to check what's happening
|
| 457 |
+
console.log('Landing page loaded successfully!');
|
| 458 |
+
console.log('Current URL:', window.location.href);
|
| 459 |
+
console.log('No redirects should happen from this page');
|
| 460 |
+
|
| 461 |
+
// Check if there are any other scripts that might be causing redirects
|
| 462 |
+
setTimeout(() => {
|
| 463 |
+
console.log('After 1 second, URL is still:', window.location.href);
|
| 464 |
+
}, 1000);
|
| 465 |
+
</script>
|
| 466 |
+
</body>
|
| 467 |
+
</html>
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
|
chatbot/landing.js
ADDED
|
@@ -0,0 +1,642 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// AIMHSA Landing Page JavaScript
|
| 2 |
+
(() => {
|
| 3 |
+
'use strict';
|
| 4 |
+
|
| 5 |
+
// Get API URL from environment or use current host
|
| 6 |
+
const API_BASE_URL = `http://${window.location.hostname}:${window.location.port || '5057'}`;
|
| 7 |
+
|
| 8 |
+
// Smooth scrolling for navigation links
|
| 9 |
+
function initSmoothScrolling() {
|
| 10 |
+
const navLinks = document.querySelectorAll('a[href^="#"]');
|
| 11 |
+
|
| 12 |
+
navLinks.forEach(link => {
|
| 13 |
+
link.addEventListener('click', (e) => {
|
| 14 |
+
e.preventDefault();
|
| 15 |
+
const targetId = link.getAttribute('href');
|
| 16 |
+
const targetElement = document.querySelector(targetId);
|
| 17 |
+
|
| 18 |
+
if (targetElement) {
|
| 19 |
+
const offsetTop = targetElement.offsetTop - 80; // Account for fixed navbar
|
| 20 |
+
window.scrollTo({
|
| 21 |
+
top: offsetTop,
|
| 22 |
+
behavior: 'smooth'
|
| 23 |
+
});
|
| 24 |
+
}
|
| 25 |
+
});
|
| 26 |
+
});
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Navbar scroll effect
|
| 30 |
+
function initNavbarScroll() {
|
| 31 |
+
const navbar = document.querySelector('.navbar');
|
| 32 |
+
let lastScrollY = window.scrollY;
|
| 33 |
+
|
| 34 |
+
window.addEventListener('scroll', () => {
|
| 35 |
+
const currentScrollY = window.scrollY;
|
| 36 |
+
|
| 37 |
+
if (currentScrollY > 100) {
|
| 38 |
+
navbar.style.background = 'rgba(15, 23, 42, 0.98)';
|
| 39 |
+
navbar.style.backdropFilter = 'blur(15px)';
|
| 40 |
+
} else {
|
| 41 |
+
navbar.style.background = 'rgba(15, 23, 42, 0.95)';
|
| 42 |
+
navbar.style.backdropFilter = 'blur(10px)';
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
// Hide/show navbar on scroll
|
| 46 |
+
if (currentScrollY > lastScrollY && currentScrollY > 200) {
|
| 47 |
+
navbar.style.transform = 'translateY(-100%)';
|
| 48 |
+
} else {
|
| 49 |
+
navbar.style.transform = 'translateY(0)';
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
lastScrollY = currentScrollY;
|
| 53 |
+
});
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// Intersection Observer for animations
|
| 57 |
+
function initScrollAnimations() {
|
| 58 |
+
const observerOptions = {
|
| 59 |
+
threshold: 0.1,
|
| 60 |
+
rootMargin: '0px 0px -50px 0px'
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
+
const observer = new IntersectionObserver((entries) => {
|
| 64 |
+
entries.forEach(entry => {
|
| 65 |
+
if (entry.isIntersecting) {
|
| 66 |
+
entry.target.classList.add('animate-in');
|
| 67 |
+
}
|
| 68 |
+
});
|
| 69 |
+
}, observerOptions);
|
| 70 |
+
|
| 71 |
+
// Observe elements for animation
|
| 72 |
+
const animateElements = document.querySelectorAll('.feature-card, .stat-item, .about-text, .about-visual');
|
| 73 |
+
animateElements.forEach(el => {
|
| 74 |
+
observer.observe(el);
|
| 75 |
+
});
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// Typing animation for chat preview
|
| 79 |
+
function initTypingAnimation() {
|
| 80 |
+
const typingIndicator = document.querySelector('.typing-indicator');
|
| 81 |
+
if (!typingIndicator) return;
|
| 82 |
+
|
| 83 |
+
// Show typing indicator after a delay
|
| 84 |
+
setTimeout(() => {
|
| 85 |
+
typingIndicator.style.display = 'flex';
|
| 86 |
+
|
| 87 |
+
// Hide typing indicator and show new message
|
| 88 |
+
setTimeout(() => {
|
| 89 |
+
typingIndicator.style.display = 'none';
|
| 90 |
+
addNewMessage();
|
| 91 |
+
}, 2000);
|
| 92 |
+
}, 3000);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
// Add new message to chat preview
|
| 96 |
+
function addNewMessage() {
|
| 97 |
+
const chatMessages = document.querySelector('.chat-messages');
|
| 98 |
+
if (!chatMessages) return;
|
| 99 |
+
|
| 100 |
+
const newMessage = document.createElement('div');
|
| 101 |
+
newMessage.className = 'message bot-message';
|
| 102 |
+
newMessage.innerHTML = `
|
| 103 |
+
<div class="message-content">
|
| 104 |
+
<p>I'm here to listen and support you. What's on your mind today?</p>
|
| 105 |
+
</div>
|
| 106 |
+
`;
|
| 107 |
+
|
| 108 |
+
chatMessages.appendChild(newMessage);
|
| 109 |
+
|
| 110 |
+
// Scroll to bottom of chat
|
| 111 |
+
chatMessages.scrollTop = chatMessages.scrollHeight;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
// Parallax effect for hero background
|
| 115 |
+
function initParallax() {
|
| 116 |
+
const heroPattern = document.querySelector('.hero-pattern');
|
| 117 |
+
if (!heroPattern) return;
|
| 118 |
+
|
| 119 |
+
window.addEventListener('scroll', () => {
|
| 120 |
+
const scrolled = window.pageYOffset;
|
| 121 |
+
const rate = scrolled * -0.5;
|
| 122 |
+
heroPattern.style.transform = `translateY(${rate}px)`;
|
| 123 |
+
});
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
// Counter animation for stats
|
| 127 |
+
function initCounterAnimation() {
|
| 128 |
+
const counters = document.querySelectorAll('.stat-number');
|
| 129 |
+
|
| 130 |
+
const animateCounter = (counter) => {
|
| 131 |
+
const target = counter.textContent;
|
| 132 |
+
const isNumeric = !isNaN(target);
|
| 133 |
+
|
| 134 |
+
if (!isNumeric) return;
|
| 135 |
+
|
| 136 |
+
const increment = parseInt(target) / 50;
|
| 137 |
+
let current = 0;
|
| 138 |
+
|
| 139 |
+
const updateCounter = () => {
|
| 140 |
+
if (current < parseInt(target)) {
|
| 141 |
+
current += increment;
|
| 142 |
+
counter.textContent = Math.ceil(current);
|
| 143 |
+
requestAnimationFrame(updateCounter);
|
| 144 |
+
} else {
|
| 145 |
+
counter.textContent = target;
|
| 146 |
+
}
|
| 147 |
+
};
|
| 148 |
+
|
| 149 |
+
updateCounter();
|
| 150 |
+
};
|
| 151 |
+
|
| 152 |
+
const counterObserver = new IntersectionObserver((entries) => {
|
| 153 |
+
entries.forEach(entry => {
|
| 154 |
+
if (entry.isIntersecting) {
|
| 155 |
+
animateCounter(entry.target);
|
| 156 |
+
counterObserver.unobserve(entry.target);
|
| 157 |
+
}
|
| 158 |
+
});
|
| 159 |
+
}, { threshold: 0.5 });
|
| 160 |
+
|
| 161 |
+
counters.forEach(counter => {
|
| 162 |
+
counterObserver.observe(counter);
|
| 163 |
+
});
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
// Mobile menu toggle (if needed for mobile)
|
| 167 |
+
function initMobileMenu() {
|
| 168 |
+
const navLinks = document.querySelector('.nav-links');
|
| 169 |
+
const navToggle = document.createElement('button');
|
| 170 |
+
navToggle.className = 'nav-toggle';
|
| 171 |
+
navToggle.innerHTML = '<i class="fas fa-bars"></i>';
|
| 172 |
+
navToggle.style.display = 'none';
|
| 173 |
+
|
| 174 |
+
const navContainer = document.querySelector('.nav-container');
|
| 175 |
+
navContainer.appendChild(navToggle);
|
| 176 |
+
|
| 177 |
+
navToggle.addEventListener('click', () => {
|
| 178 |
+
navLinks.classList.toggle('active');
|
| 179 |
+
});
|
| 180 |
+
|
| 181 |
+
// Show/hide mobile menu based on screen size
|
| 182 |
+
const checkScreenSize = () => {
|
| 183 |
+
if (window.innerWidth <= 768) {
|
| 184 |
+
navToggle.style.display = 'block';
|
| 185 |
+
navLinks.style.display = navLinks.classList.contains('active') ? 'flex' : 'none';
|
| 186 |
+
} else {
|
| 187 |
+
navToggle.style.display = 'none';
|
| 188 |
+
navLinks.style.display = 'flex';
|
| 189 |
+
navLinks.classList.remove('active');
|
| 190 |
+
}
|
| 191 |
+
};
|
| 192 |
+
|
| 193 |
+
window.addEventListener('resize', checkScreenSize);
|
| 194 |
+
checkScreenSize();
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
// Form validation for CTA buttons
|
| 198 |
+
function initFormValidation() {
|
| 199 |
+
const ctaButtons = document.querySelectorAll('.btn-primary, .btn-secondary');
|
| 200 |
+
|
| 201 |
+
ctaButtons.forEach(button => {
|
| 202 |
+
button.addEventListener('click', (e) => {
|
| 203 |
+
// Add click animation
|
| 204 |
+
button.style.transform = 'scale(0.95)';
|
| 205 |
+
setTimeout(() => {
|
| 206 |
+
button.style.transform = 'scale(1)';
|
| 207 |
+
}, 150);
|
| 208 |
+
});
|
| 209 |
+
});
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
// Loading animation
|
| 213 |
+
function initLoadingAnimation() {
|
| 214 |
+
// Add loading class to body
|
| 215 |
+
document.body.classList.add('loading');
|
| 216 |
+
|
| 217 |
+
// Remove loading class when page is fully loaded
|
| 218 |
+
window.addEventListener('load', () => {
|
| 219 |
+
document.body.classList.remove('loading');
|
| 220 |
+
});
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
// Emergency contact click tracking and functionality
|
| 224 |
+
function initEmergencyTracking() {
|
| 225 |
+
const emergencyContacts = document.querySelectorAll('.contact-item');
|
| 226 |
+
|
| 227 |
+
emergencyContacts.forEach(contact => {
|
| 228 |
+
contact.addEventListener('click', () => {
|
| 229 |
+
// Track emergency contact clicks
|
| 230 |
+
console.log('Emergency contact clicked:', contact.textContent);
|
| 231 |
+
|
| 232 |
+
// Add visual feedback
|
| 233 |
+
contact.style.background = 'rgba(255, 255, 255, 0.1)';
|
| 234 |
+
setTimeout(() => {
|
| 235 |
+
contact.style.background = '';
|
| 236 |
+
}, 200);
|
| 237 |
+
});
|
| 238 |
+
});
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
// Emergency action functions
|
| 242 |
+
function callEmergency(number) {
|
| 243 |
+
// Try to initiate phone call
|
| 244 |
+
if (typeof window !== 'undefined' && window.location) {
|
| 245 |
+
// Mobile devices will handle tel: links
|
| 246 |
+
window.location.href = `tel:${number}`;
|
| 247 |
+
} else {
|
| 248 |
+
// Fallback: show alert with number
|
| 249 |
+
alert(`Please call emergency number: ${number}\n\nIf you're unable to call, please seek help from someone nearby or go to the nearest emergency room.`);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
// Track emergency call attempt
|
| 253 |
+
console.log('Emergency call initiated:', number);
|
| 254 |
+
logEmergencyAction('call', number);
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
function sendEmergencySMS() {
|
| 258 |
+
const message = "HELP - I need mental health support";
|
| 259 |
+
const number = "105";
|
| 260 |
+
|
| 261 |
+
// Try to open SMS app
|
| 262 |
+
if (typeof window !== 'undefined' && window.location) {
|
| 263 |
+
const smsUrl = `sms:${number}?body=${encodeURIComponent(message)}`;
|
| 264 |
+
window.location.href = smsUrl;
|
| 265 |
+
} else {
|
| 266 |
+
alert(`Please send SMS to ${number} with message: "${message}"`);
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
// Track emergency SMS attempt
|
| 270 |
+
console.log('Emergency SMS initiated');
|
| 271 |
+
logEmergencyAction('sms', number);
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
function findNearbyHelp() {
|
| 275 |
+
// Check if geolocation is available
|
| 276 |
+
if ("geolocation" in navigator) {
|
| 277 |
+
navigator.geolocation.getCurrentPosition(
|
| 278 |
+
(position) => {
|
| 279 |
+
const lat = position.coords.latitude;
|
| 280 |
+
const lng = position.coords.longitude;
|
| 281 |
+
|
| 282 |
+
// Open Google Maps with search for mental health facilities
|
| 283 |
+
const query = encodeURIComponent("mental health hospital Rwanda");
|
| 284 |
+
const mapsUrl = `https://www.google.com/maps/search/${query}/@${lat},${lng},15z`;
|
| 285 |
+
|
| 286 |
+
window.open(mapsUrl, '_blank');
|
| 287 |
+
|
| 288 |
+
// Track location access
|
| 289 |
+
logEmergencyAction('location', `${lat},${lng}`);
|
| 290 |
+
},
|
| 291 |
+
(error) => {
|
| 292 |
+
console.error('Geolocation error:', error);
|
| 293 |
+
// Fallback: show general Rwanda mental health resources
|
| 294 |
+
const fallbackUrl = "https://www.google.com/maps/search/mental+health+hospital/@-1.9403,29.8739,8z/data=!3m1!4b1";
|
| 295 |
+
window.open(fallbackUrl, '_blank');
|
| 296 |
+
logEmergencyAction('location_fallback', 'Rwanda');
|
| 297 |
+
},
|
| 298 |
+
{
|
| 299 |
+
enableHighAccuracy: true,
|
| 300 |
+
timeout: 10000,
|
| 301 |
+
maximumAge: 300000 // 5 minutes
|
| 302 |
+
}
|
| 303 |
+
);
|
| 304 |
+
} else {
|
| 305 |
+
// Geolocation not available
|
| 306 |
+
alert("Location services are not available. Showing general Rwanda mental health resources.");
|
| 307 |
+
const fallbackUrl = "https://www.google.com/maps/search/mental+health+hospital/@-1.9403,29.8739,8z/data=!3m1!4b1";
|
| 308 |
+
window.open(fallbackUrl, '_blank');
|
| 309 |
+
logEmergencyAction('location_unavailable', 'Rwanda');
|
| 310 |
+
}
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
function logEmergencyAction(action, details) {
|
| 314 |
+
// Log emergency actions for analytics and support
|
| 315 |
+
const emergencyLog = {
|
| 316 |
+
action: action,
|
| 317 |
+
details: details,
|
| 318 |
+
timestamp: new Date().toISOString(),
|
| 319 |
+
userAgent: navigator.userAgent,
|
| 320 |
+
referrer: document.referrer || 'direct'
|
| 321 |
+
};
|
| 322 |
+
|
| 323 |
+
// Store in localStorage for now (could be sent to server later)
|
| 324 |
+
const logs = JSON.parse(localStorage.getItem('aimhsa_emergency_logs') || '[]');
|
| 325 |
+
logs.push(emergencyLog);
|
| 326 |
+
|
| 327 |
+
// Keep only last 10 logs
|
| 328 |
+
if (logs.length > 10) {
|
| 329 |
+
logs.shift();
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
localStorage.setItem('aimhsa_emergency_logs', JSON.stringify(logs));
|
| 333 |
+
|
| 334 |
+
// Could send to analytics server here
|
| 335 |
+
console.log('Emergency action logged:', emergencyLog);
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
// Community interaction tracking
|
| 339 |
+
function initCommunityTracking() {
|
| 340 |
+
const communityCards = document.querySelectorAll('.community-card');
|
| 341 |
+
|
| 342 |
+
communityCards.forEach(card => {
|
| 343 |
+
card.addEventListener('click', () => {
|
| 344 |
+
const cardType = card.querySelector('h3').textContent.toLowerCase().replace(' ', '_');
|
| 345 |
+
console.log('Community card clicked:', cardType);
|
| 346 |
+
|
| 347 |
+
// Add ripple effect
|
| 348 |
+
const ripple = document.createElement('div');
|
| 349 |
+
ripple.className = 'ripple-effect';
|
| 350 |
+
ripple.style.position = 'absolute';
|
| 351 |
+
ripple.style.borderRadius = '50%';
|
| 352 |
+
ripple.style.background = 'rgba(255, 255, 255, 0.3)';
|
| 353 |
+
ripple.style.transform = 'scale(0)';
|
| 354 |
+
ripple.style.animation = 'ripple 0.6s linear';
|
| 355 |
+
ripple.style.left = '50%';
|
| 356 |
+
ripple.style.top = '50%';
|
| 357 |
+
ripple.style.width = '20px';
|
| 358 |
+
ripple.style.height = '20px';
|
| 359 |
+
ripple.style.marginLeft = '-10px';
|
| 360 |
+
ripple.style.marginTop = '-10px';
|
| 361 |
+
|
| 362 |
+
card.appendChild(ripple);
|
| 363 |
+
|
| 364 |
+
setTimeout(() => {
|
| 365 |
+
ripple.remove();
|
| 366 |
+
}, 600);
|
| 367 |
+
});
|
| 368 |
+
});
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
// Resource interaction tracking
|
| 372 |
+
function initResourceTracking() {
|
| 373 |
+
const resourceLinks = document.querySelectorAll('.resource-link');
|
| 374 |
+
|
| 375 |
+
resourceLinks.forEach(link => {
|
| 376 |
+
link.addEventListener('click', (e) => {
|
| 377 |
+
const resourceType = link.closest('.resource-card').querySelector('h3').textContent;
|
| 378 |
+
console.log('Resource accessed:', resourceType);
|
| 379 |
+
|
| 380 |
+
// Add loading state
|
| 381 |
+
link.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Loading...';
|
| 382 |
+
link.style.pointerEvents = 'none';
|
| 383 |
+
|
| 384 |
+
// Reset after a short delay (simulating loading)
|
| 385 |
+
setTimeout(() => {
|
| 386 |
+
link.innerHTML = link.getAttribute('data-original-text') || 'Access Resource <i class="fas fa-arrow-right"></i>';
|
| 387 |
+
link.style.pointerEvents = 'auto';
|
| 388 |
+
}, 1000);
|
| 389 |
+
});
|
| 390 |
+
});
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
// Particle effect for hero section
|
| 394 |
+
function initParticleEffect() {
|
| 395 |
+
const hero = document.querySelector('.hero');
|
| 396 |
+
if (!hero) return;
|
| 397 |
+
|
| 398 |
+
const canvas = document.createElement('canvas');
|
| 399 |
+
canvas.className = 'particle-canvas';
|
| 400 |
+
canvas.style.position = 'absolute';
|
| 401 |
+
canvas.style.top = '0';
|
| 402 |
+
canvas.style.left = '0';
|
| 403 |
+
canvas.style.width = '100%';
|
| 404 |
+
canvas.style.height = '100%';
|
| 405 |
+
canvas.style.pointerEvents = 'none';
|
| 406 |
+
canvas.style.zIndex = '1';
|
| 407 |
+
|
| 408 |
+
hero.appendChild(canvas);
|
| 409 |
+
|
| 410 |
+
const ctx = canvas.getContext('2d');
|
| 411 |
+
let particles = [];
|
| 412 |
+
let animationId;
|
| 413 |
+
|
| 414 |
+
function resizeCanvas() {
|
| 415 |
+
canvas.width = hero.offsetWidth;
|
| 416 |
+
canvas.height = hero.offsetHeight;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
function createParticle() {
|
| 420 |
+
return {
|
| 421 |
+
x: Math.random() * canvas.width,
|
| 422 |
+
y: Math.random() * canvas.height,
|
| 423 |
+
vx: (Math.random() - 0.5) * 0.5,
|
| 424 |
+
vy: (Math.random() - 0.5) * 0.5,
|
| 425 |
+
size: Math.random() * 2 + 1,
|
| 426 |
+
opacity: Math.random() * 0.5 + 0.2
|
| 427 |
+
};
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
function initParticles() {
|
| 431 |
+
particles = [];
|
| 432 |
+
for (let i = 0; i < 50; i++) {
|
| 433 |
+
particles.push(createParticle());
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
function animateParticles() {
|
| 438 |
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
| 439 |
+
|
| 440 |
+
particles.forEach(particle => {
|
| 441 |
+
particle.x += particle.vx;
|
| 442 |
+
particle.y += particle.vy;
|
| 443 |
+
|
| 444 |
+
if (particle.x < 0 || particle.x > canvas.width) particle.vx *= -1;
|
| 445 |
+
if (particle.y < 0 || particle.y > canvas.height) particle.vy *= -1;
|
| 446 |
+
|
| 447 |
+
ctx.beginPath();
|
| 448 |
+
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
|
| 449 |
+
ctx.fillStyle = `rgba(124, 58, 237, ${particle.opacity})`;
|
| 450 |
+
ctx.fill();
|
| 451 |
+
});
|
| 452 |
+
|
| 453 |
+
animationId = requestAnimationFrame(animateParticles);
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
resizeCanvas();
|
| 457 |
+
initParticles();
|
| 458 |
+
animateParticles();
|
| 459 |
+
|
| 460 |
+
window.addEventListener('resize', () => {
|
| 461 |
+
resizeCanvas();
|
| 462 |
+
initParticles();
|
| 463 |
+
});
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
// Enhanced scroll animations with stagger effect
|
| 467 |
+
function initEnhancedScrollAnimations() {
|
| 468 |
+
const observerOptions = {
|
| 469 |
+
threshold: 0.1,
|
| 470 |
+
rootMargin: '0px 0px -50px 0px'
|
| 471 |
+
};
|
| 472 |
+
|
| 473 |
+
const observer = new IntersectionObserver((entries) => {
|
| 474 |
+
entries.forEach((entry, index) => {
|
| 475 |
+
if (entry.isIntersecting) {
|
| 476 |
+
setTimeout(() => {
|
| 477 |
+
entry.target.classList.add('animate-in');
|
| 478 |
+
}, index * 100); // Stagger animation
|
| 479 |
+
}
|
| 480 |
+
});
|
| 481 |
+
}, observerOptions);
|
| 482 |
+
|
| 483 |
+
const animateElements = document.querySelectorAll('.feature-card, .stat-item, .about-text, .about-visual, .emergency-content, .cta-content');
|
| 484 |
+
animateElements.forEach(el => {
|
| 485 |
+
observer.observe(el);
|
| 486 |
+
});
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
// Mouse follow effect for interactive elements
|
| 490 |
+
function initMouseFollow() {
|
| 491 |
+
const featureCards = document.querySelectorAll('.feature-card');
|
| 492 |
+
const hero = document.querySelector('.hero');
|
| 493 |
+
|
| 494 |
+
featureCards.forEach(card => {
|
| 495 |
+
card.addEventListener('mousemove', (e) => {
|
| 496 |
+
const rect = card.getBoundingClientRect();
|
| 497 |
+
const x = e.clientX - rect.left;
|
| 498 |
+
const y = e.clientY - rect.top;
|
| 499 |
+
|
| 500 |
+
const centerX = rect.width / 2;
|
| 501 |
+
const centerY = rect.height / 2;
|
| 502 |
+
|
| 503 |
+
const rotateX = (y - centerY) / 10;
|
| 504 |
+
const rotateY = (centerX - x) / 10;
|
| 505 |
+
|
| 506 |
+
card.style.transform = `translateY(-8px) scale(1.02) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
|
| 507 |
+
});
|
| 508 |
+
|
| 509 |
+
card.addEventListener('mouseleave', () => {
|
| 510 |
+
card.style.transform = '';
|
| 511 |
+
});
|
| 512 |
+
});
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
// Initialize all features
|
| 516 |
+
function init() {
|
| 517 |
+
// Show loading screen initially
|
| 518 |
+
const loadingScreen = document.getElementById('loading-screen');
|
| 519 |
+
if (loadingScreen) {
|
| 520 |
+
setTimeout(() => {
|
| 521 |
+
loadingScreen.style.display = 'none';
|
| 522 |
+
}, 2500); // Show loading for 2.5 seconds
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
initSmoothScrolling();
|
| 526 |
+
initNavbarScroll();
|
| 527 |
+
initEnhancedScrollAnimations();
|
| 528 |
+
initTypingAnimation();
|
| 529 |
+
initParallax();
|
| 530 |
+
initCounterAnimation();
|
| 531 |
+
initMobileMenu();
|
| 532 |
+
initFormValidation();
|
| 533 |
+
initLoadingAnimation();
|
| 534 |
+
initEmergencyTracking();
|
| 535 |
+
initParticleEffect();
|
| 536 |
+
initMouseFollow();
|
| 537 |
+
initCommunityTracking();
|
| 538 |
+
initResourceTracking();
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
// Start when DOM is ready
|
| 542 |
+
if (document.readyState === 'loading') {
|
| 543 |
+
document.addEventListener('DOMContentLoaded', init);
|
| 544 |
+
} else {
|
| 545 |
+
init();
|
| 546 |
+
}
|
| 547 |
+
|
| 548 |
+
// Add CSS for animations
|
| 549 |
+
const style = document.createElement('style');
|
| 550 |
+
style.textContent = `
|
| 551 |
+
.loading {
|
| 552 |
+
overflow: hidden;
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
.loading::before {
|
| 556 |
+
content: '';
|
| 557 |
+
position: fixed;
|
| 558 |
+
top: 0;
|
| 559 |
+
left: 0;
|
| 560 |
+
right: 0;
|
| 561 |
+
bottom: 0;
|
| 562 |
+
background: var(--background);
|
| 563 |
+
z-index: 9999;
|
| 564 |
+
display: flex;
|
| 565 |
+
align-items: center;
|
| 566 |
+
justify-content: center;
|
| 567 |
+
}
|
| 568 |
+
|
| 569 |
+
.loading::after {
|
| 570 |
+
content: 'AIMHSA';
|
| 571 |
+
position: fixed;
|
| 572 |
+
top: 50%;
|
| 573 |
+
left: 50%;
|
| 574 |
+
transform: translate(-50%, -50%);
|
| 575 |
+
font-size: 2rem;
|
| 576 |
+
font-weight: 700;
|
| 577 |
+
color: var(--primary);
|
| 578 |
+
z-index: 10000;
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
.animate-in {
|
| 582 |
+
animation: slideInUp 0.6s ease-out;
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
@keyframes slideInUp {
|
| 586 |
+
from {
|
| 587 |
+
opacity: 0;
|
| 588 |
+
transform: translateY(30px);
|
| 589 |
+
}
|
| 590 |
+
to {
|
| 591 |
+
opacity: 1;
|
| 592 |
+
transform: translateY(0);
|
| 593 |
+
}
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
.nav-toggle {
|
| 597 |
+
background: none;
|
| 598 |
+
border: none;
|
| 599 |
+
color: var(--text);
|
| 600 |
+
font-size: 1.5rem;
|
| 601 |
+
cursor: pointer;
|
| 602 |
+
padding: 0.5rem;
|
| 603 |
+
}
|
| 604 |
+
|
| 605 |
+
@media (max-width: 768px) {
|
| 606 |
+
.nav-links {
|
| 607 |
+
position: absolute;
|
| 608 |
+
top: 100%;
|
| 609 |
+
left: 0;
|
| 610 |
+
right: 0;
|
| 611 |
+
background: var(--surface);
|
| 612 |
+
flex-direction: column;
|
| 613 |
+
padding: 1rem;
|
| 614 |
+
border-top: 1px solid var(--border);
|
| 615 |
+
display: none;
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
.nav-links.active {
|
| 619 |
+
display: flex;
|
| 620 |
+
}
|
| 621 |
+
}
|
| 622 |
+
`;
|
| 623 |
+
document.head.appendChild(style);
|
| 624 |
+
|
| 625 |
+
// Update all fetch calls to use API_BASE_URL
|
| 626 |
+
document.getElementById('startBtn')?.addEventListener('click', async () => {
|
| 627 |
+
try {
|
| 628 |
+
const response = await fetch(`${API_BASE_URL}/session`, {
|
| 629 |
+
method: 'POST',
|
| 630 |
+
headers: { 'Content-Type': 'application/json' },
|
| 631 |
+
body: JSON.stringify({ account: 'guest_' + Date.now() })
|
| 632 |
+
});
|
| 633 |
+
const data = await response.json();
|
| 634 |
+
window.location.href = '/?id=' + data.id;
|
| 635 |
+
} catch (error) {
|
| 636 |
+
console.error('Error starting chat:', error);
|
| 637 |
+
}
|
| 638 |
+
});
|
| 639 |
+
})();
|
| 640 |
+
|
| 641 |
+
|
| 642 |
+
|
chatbot/login.html
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
| 6 |
+
<title>AIMHSA - Sign In</title>
|
| 7 |
+
<link rel="stylesheet" href="auth.css" />
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="auth-container">
|
| 11 |
+
<div class="auth-card">
|
| 12 |
+
<div class="auth-header">
|
| 13 |
+
<h1 class="brand">AIMHSA</h1>
|
| 14 |
+
<p class="subtitle">Mental Health Companion for Rwanda</p>
|
| 15 |
+
</div>
|
| 16 |
+
|
| 17 |
+
<form class="auth-form" id="loginForm" novalidate>
|
| 18 |
+
<div class="form-group">
|
| 19 |
+
<label for="loginEmail">Email Address</label>
|
| 20 |
+
<input type="email" id="loginEmail" name="loginEmail" autocomplete="email" required />
|
| 21 |
+
<div class="input-hint" id="emailHint" aria-live="polite"></div>
|
| 22 |
+
</div>
|
| 23 |
+
<div class="form-group">
|
| 24 |
+
<label for="loginPassword">Password</label>
|
| 25 |
+
<div class="password-field">
|
| 26 |
+
<input type="password" id="loginPassword" name="loginPassword" autocomplete="current-password" required />
|
| 27 |
+
<button type="button" class="toggle-password" id="togglePassword" aria-label="Show password" aria-pressed="false">👁️</button>
|
| 28 |
+
</div>
|
| 29 |
+
<div class="input-row">
|
| 30 |
+
<div class="password-meter" id="passwordMeter" aria-hidden="true">
|
| 31 |
+
<div class="password-meter-bar" id="passwordMeterBar"></div>
|
| 32 |
+
</div>
|
| 33 |
+
<div class="caps-lock" id="capsLockIndicator" hidden>Caps Lock is ON</div>
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
<div class="form-row">
|
| 37 |
+
<label class="remember-me">
|
| 38 |
+
<input type="checkbox" id="rememberMe" />
|
| 39 |
+
<span>Remember me</span>
|
| 40 |
+
</label>
|
| 41 |
+
<a class="forgot-link" id="forgotLink" href="#">Forgot password?</a>
|
| 42 |
+
</div>
|
| 43 |
+
<button type="submit" class="auth-btn" id="signInBtn">Sign In</button>
|
| 44 |
+
</form>
|
| 45 |
+
|
| 46 |
+
<div class="auth-links">
|
| 47 |
+
<p>Don't have an account? <a href="/register">Register</a></p>
|
| 48 |
+
</div>
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
<div class="auth-divider">
|
| 52 |
+
<span>or</span>
|
| 53 |
+
</div>
|
| 54 |
+
|
| 55 |
+
<button class="anonymous-btn" id="anonBtn">Continue as Guest</button>
|
| 56 |
+
|
| 57 |
+
<!-- Optional MFA Modal (shown only if server requests it) -->
|
| 58 |
+
<div class="modal" id="mfaModal" aria-hidden="true" role="dialog" aria-modal="true">
|
| 59 |
+
<div class="modal-backdrop" id="mfaBackdrop"></div>
|
| 60 |
+
<div class="modal-content" role="document">
|
| 61 |
+
<div class="modal-header">
|
| 62 |
+
<h2>Two-Factor Authentication</h2>
|
| 63 |
+
<button type="button" class="modal-close" id="mfaClose" aria-label="Close">✕</button>
|
| 64 |
+
</div>
|
| 65 |
+
<div class="modal-body">
|
| 66 |
+
<p>Enter the 6-digit code from your authenticator app or SMS.</p>
|
| 67 |
+
<div class="form-group">
|
| 68 |
+
<label for="mfaCode">One-Time Code</label>
|
| 69 |
+
<input type="text" inputmode="numeric" pattern="[0-9]*" maxlength="6" id="mfaCode" autocomplete="one-time-code" />
|
| 70 |
+
</div>
|
| 71 |
+
<div class="modal-actions">
|
| 72 |
+
<button type="button" class="auth-btn" id="mfaVerifyBtn">Verify</button>
|
| 73 |
+
<button type="button" class="secondary-btn" id="mfaResendBtn">Resend code</button>
|
| 74 |
+
</div>
|
| 75 |
+
<div class="modal-message" id="mfaMessage" aria-live="polite"></div>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
</div>
|
| 79 |
+
|
| 80 |
+
<!-- Forgot Password Modal -->
|
| 81 |
+
<div class="modal" id="fpModal" aria-hidden="true" role="dialog" aria-modal="true">
|
| 82 |
+
<div class="modal-backdrop" id="fpBackdrop"></div>
|
| 83 |
+
<div class="modal-content" role="document">
|
| 84 |
+
<div class="modal-header">
|
| 85 |
+
<h2>Reset your password</h2>
|
| 86 |
+
<button type="button" class="modal-close" id="fpClose" aria-label="Close">✕</button>
|
| 87 |
+
</div>
|
| 88 |
+
<div class="modal-body">
|
| 89 |
+
<div class="fp-step fp-step-1">
|
| 90 |
+
<p>Enter your email address to receive a reset code.</p>
|
| 91 |
+
<div class="form-group">
|
| 92 |
+
<label for="fpEmail">Email Address</label>
|
| 93 |
+
<input type="email" id="fpEmail" autocomplete="email" />
|
| 94 |
+
</div>
|
| 95 |
+
|
| 96 |
+
<div class="modal-actions">
|
| 97 |
+
<button type="button" class="auth-btn" id="fpRequestBtn">Send code</button>
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
|
| 101 |
+
<div class="fp-step fp-step-2 hidden">
|
| 102 |
+
<p>Enter the code and your new password.</p>
|
| 103 |
+
<div class="form-group">
|
| 104 |
+
<label for="fpCode">Reset Code</label>
|
| 105 |
+
<input type="text" id="fpCode" inputmode="numeric" maxlength="6" />
|
| 106 |
+
</div>
|
| 107 |
+
<div class="form-group">
|
| 108 |
+
<label for="fpNewPassword">New Password</label>
|
| 109 |
+
<input type="password" id="fpNewPassword" />
|
| 110 |
+
</div>
|
| 111 |
+
<div class="modal-actions">
|
| 112 |
+
<button type="button" class="auth-btn" id="fpApplyBtn">Reset password</button>
|
| 113 |
+
<button type="button" class="secondary-btn" id="fpResendBtn">Resend code</button>
|
| 114 |
+
</div>
|
| 115 |
+
</div>
|
| 116 |
+
|
| 117 |
+
<div class="modal-message" id="fpMessage" aria-live="polite"></div>
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
</div>
|
| 121 |
+
|
| 122 |
+
<div class="auth-footer">
|
| 123 |
+
<p>By continuing, you agree to our Terms of Service and Privacy Policy</p>
|
| 124 |
+
</div>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
<script src="login.js?v=fp2"></script>
|
| 129 |
+
</body>
|
| 130 |
+
</html>
|
chatbot/login.js
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(() => {
|
| 2 |
+
const API_BASE_URL = `http://${window.location.hostname}:${window.location.port || '5057'}`;
|
| 3 |
+
|
| 4 |
+
// Elements
|
| 5 |
+
const loginForm = document.getElementById('loginForm');
|
| 6 |
+
const signInBtn = document.getElementById('signInBtn');
|
| 7 |
+
const anonBtn = document.getElementById('anonBtn');
|
| 8 |
+
const emailInput = document.getElementById('loginEmail');
|
| 9 |
+
const emailHint = document.getElementById('emailHint');
|
| 10 |
+
const passwordInput = document.getElementById('loginPassword');
|
| 11 |
+
const togglePasswordBtn = document.getElementById('togglePassword');
|
| 12 |
+
const meter = document.getElementById('passwordMeter');
|
| 13 |
+
const meterBar = document.getElementById('passwordMeterBar');
|
| 14 |
+
const capsLockIndicator = document.getElementById('capsLockIndicator');
|
| 15 |
+
const rememberMe = document.getElementById('rememberMe');
|
| 16 |
+
const forgotLink = document.getElementById('forgotLink');
|
| 17 |
+
// Forgot password elements
|
| 18 |
+
const fpModal = document.getElementById('fpModal');
|
| 19 |
+
const fpBackdrop = document.getElementById('fpBackdrop');
|
| 20 |
+
const fpClose = document.getElementById('fpClose');
|
| 21 |
+
const fpEmail = document.getElementById('fpEmail');
|
| 22 |
+
const fpRequestBtn = document.getElementById('fpRequestBtn');
|
| 23 |
+
const fpStep1 = document.querySelector('.fp-step-1');
|
| 24 |
+
const fpStep2 = document.querySelector('.fp-step-2');
|
| 25 |
+
const fpCode = document.getElementById('fpCode');
|
| 26 |
+
const fpNewPassword = document.getElementById('fpNewPassword');
|
| 27 |
+
const fpApplyBtn = document.getElementById('fpApplyBtn');
|
| 28 |
+
const fpResendBtn = document.getElementById('fpResendBtn');
|
| 29 |
+
const fpMessage = document.getElementById('fpMessage');
|
| 30 |
+
// MFA elements
|
| 31 |
+
const mfaModal = document.getElementById('mfaModal');
|
| 32 |
+
const mfaBackdrop = document.getElementById('mfaBackdrop');
|
| 33 |
+
const mfaClose = document.getElementById('mfaClose');
|
| 34 |
+
const mfaCode = document.getElementById('mfaCode');
|
| 35 |
+
const mfaVerifyBtn = document.getElementById('mfaVerifyBtn');
|
| 36 |
+
const mfaResendBtn = document.getElementById('mfaResendBtn');
|
| 37 |
+
const mfaMessage = document.getElementById('mfaMessage');
|
| 38 |
+
|
| 39 |
+
// Show message
|
| 40 |
+
function showMessage(text, type = 'error') {
|
| 41 |
+
const existing = document.querySelector('.error-message, .success-message');
|
| 42 |
+
if (existing) existing.remove();
|
| 43 |
+
|
| 44 |
+
const message = document.createElement('div');
|
| 45 |
+
message.className = type === 'error' ? 'error-message' : 'success-message';
|
| 46 |
+
message.textContent = text;
|
| 47 |
+
message.style.cssText = `
|
| 48 |
+
padding: 12px 16px;
|
| 49 |
+
margin: 16px 0;
|
| 50 |
+
border-radius: 6px;
|
| 51 |
+
font-size: 14px;
|
| 52 |
+
font-weight: 500;
|
| 53 |
+
${type === 'error' ?
|
| 54 |
+
'background: rgba(239, 68, 68, 0.1); color: #ef4444; border: 1px solid rgba(239, 68, 68, 0.2);' :
|
| 55 |
+
'background: rgba(16, 185, 129, 0.1); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.2);'
|
| 56 |
+
}
|
| 57 |
+
`;
|
| 58 |
+
|
| 59 |
+
loginForm.insertBefore(message, loginForm.firstChild);
|
| 60 |
+
|
| 61 |
+
setTimeout(() => message.remove(), 5000);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// Redirect to main app
|
| 65 |
+
function redirectToApp(account = null) {
|
| 66 |
+
if (account) {
|
| 67 |
+
localStorage.setItem('aimhsa_account', account);
|
| 68 |
+
}
|
| 69 |
+
window.location.href = '/index.html';
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
// Utility: simple password strength score (0..4)
|
| 73 |
+
function getPasswordStrengthScore(pw) {
|
| 74 |
+
let score = 0;
|
| 75 |
+
if (!pw) return 0;
|
| 76 |
+
if (pw.length >= 8) score++;
|
| 77 |
+
if (/[A-Z]/.test(pw)) score++;
|
| 78 |
+
if (/[a-z]/.test(pw)) score++;
|
| 79 |
+
if (/[0-9]/.test(pw)) score++;
|
| 80 |
+
if (/[^A-Za-z0-9]/.test(pw)) score++;
|
| 81 |
+
return Math.min(score, 4);
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
function updatePasswordMeter() {
|
| 85 |
+
const pw = passwordInput.value;
|
| 86 |
+
const score = getPasswordStrengthScore(pw);
|
| 87 |
+
const pct = (score / 4) * 100;
|
| 88 |
+
meterBar.style.width = pct + '%';
|
| 89 |
+
let color = '#ef4444';
|
| 90 |
+
if (score >= 3) color = '#f59e0b';
|
| 91 |
+
if (score >= 4) color = '#10b981';
|
| 92 |
+
meterBar.style.background = color;
|
| 93 |
+
meter.setAttribute('aria-hidden', pw ? 'false' : 'true');
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// Toggle password visibility
|
| 97 |
+
togglePasswordBtn?.addEventListener('click', () => {
|
| 98 |
+
const isPassword = passwordInput.type === 'password';
|
| 99 |
+
passwordInput.type = isPassword ? 'text' : 'password';
|
| 100 |
+
togglePasswordBtn.setAttribute('aria-pressed', String(isPassword));
|
| 101 |
+
});
|
| 102 |
+
|
| 103 |
+
// Caps lock indicator
|
| 104 |
+
function handleKeyEventForCaps(e) {
|
| 105 |
+
if (typeof e.getModifierState === 'function') {
|
| 106 |
+
const on = e.getModifierState('CapsLock');
|
| 107 |
+
if (on) {
|
| 108 |
+
capsLockIndicator?.removeAttribute('hidden');
|
| 109 |
+
} else {
|
| 110 |
+
capsLockIndicator?.setAttribute('hidden', '');
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
passwordInput.addEventListener('keydown', handleKeyEventForCaps);
|
| 115 |
+
passwordInput.addEventListener('keyup', handleKeyEventForCaps);
|
| 116 |
+
passwordInput.addEventListener('input', () => {
|
| 117 |
+
updatePasswordMeter();
|
| 118 |
+
});
|
| 119 |
+
|
| 120 |
+
// Remember me: prefill email
|
| 121 |
+
const savedEmail = localStorage.getItem('aimhsa_saved_email');
|
| 122 |
+
if (savedEmail) {
|
| 123 |
+
emailInput.value = savedEmail;
|
| 124 |
+
rememberMe.checked = true;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
// Email basic validation hint
|
| 128 |
+
emailInput.addEventListener('input', () => {
|
| 129 |
+
const v = emailInput.value.trim();
|
| 130 |
+
if (!v) {
|
| 131 |
+
emailHint.textContent = '';
|
| 132 |
+
return;
|
| 133 |
+
}
|
| 134 |
+
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
| 135 |
+
emailHint.textContent = !emailPattern.test(v) ? 'Please enter a valid email address' : '';
|
| 136 |
+
});
|
| 137 |
+
|
| 138 |
+
// Forgot password (client-side placeholder)
|
| 139 |
+
forgotLink.addEventListener('click', (e) => {
|
| 140 |
+
e.preventDefault();
|
| 141 |
+
openFpModal();
|
| 142 |
+
});
|
| 143 |
+
|
| 144 |
+
// Simple cooldown after repeated failures
|
| 145 |
+
const COOLDOWN_KEY = 'aimhsa_login_cooldown_until';
|
| 146 |
+
function isInCooldown() {
|
| 147 |
+
const until = Number(localStorage.getItem(COOLDOWN_KEY) || '0');
|
| 148 |
+
return Date.now() < until;
|
| 149 |
+
}
|
| 150 |
+
function applyCooldown(seconds) {
|
| 151 |
+
const until = Date.now() + seconds * 1000;
|
| 152 |
+
localStorage.setItem(COOLDOWN_KEY, String(until));
|
| 153 |
+
}
|
| 154 |
+
function getCooldownRemainingMs() {
|
| 155 |
+
const until = Number(localStorage.getItem(COOLDOWN_KEY) || '0');
|
| 156 |
+
return Math.max(0, until - Date.now());
|
| 157 |
+
}
|
| 158 |
+
function updateCooldownUI() {
|
| 159 |
+
const remaining = getCooldownRemainingMs();
|
| 160 |
+
if (remaining > 0) {
|
| 161 |
+
signInBtn.disabled = true;
|
| 162 |
+
const secs = Math.ceil(remaining / 1000);
|
| 163 |
+
signInBtn.textContent = `Try again in ${secs}s`;
|
| 164 |
+
} else {
|
| 165 |
+
signInBtn.disabled = false;
|
| 166 |
+
signInBtn.textContent = 'Sign In';
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
if (isInCooldown()) {
|
| 170 |
+
updateCooldownUI();
|
| 171 |
+
const timer = setInterval(() => {
|
| 172 |
+
updateCooldownUI();
|
| 173 |
+
if (!isInCooldown()) clearInterval(timer);
|
| 174 |
+
}, 500);
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
// Login form submission
|
| 178 |
+
loginForm.addEventListener('submit', async (e) => {
|
| 179 |
+
e.preventDefault();
|
| 180 |
+
if (isInCooldown()) {
|
| 181 |
+
updateCooldownUI();
|
| 182 |
+
return;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
const email = emailInput.value.trim();
|
| 186 |
+
const password = passwordInput.value;
|
| 187 |
+
|
| 188 |
+
if (!email || !password) {
|
| 189 |
+
showMessage('Please enter both email and password');
|
| 190 |
+
return;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
// Email validation
|
| 194 |
+
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
| 195 |
+
if (!emailPattern.test(email)) {
|
| 196 |
+
showMessage('Please enter a valid email address');
|
| 197 |
+
return;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
if (rememberMe.checked) {
|
| 201 |
+
localStorage.setItem('aimhsa_saved_email', email);
|
| 202 |
+
} else {
|
| 203 |
+
localStorage.removeItem('aimhsa_saved_email');
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
signInBtn.disabled = true;
|
| 207 |
+
signInBtn.textContent = 'Signing in...';
|
| 208 |
+
|
| 209 |
+
try {
|
| 210 |
+
// Try user login first
|
| 211 |
+
try {
|
| 212 |
+
console.log('Trying user login for:', email);
|
| 213 |
+
const res = await api('/api/login', {
|
| 214 |
+
method: 'POST',
|
| 215 |
+
headers: { 'Content-Type': 'application/json' },
|
| 216 |
+
body: JSON.stringify({ email, password })
|
| 217 |
+
});
|
| 218 |
+
|
| 219 |
+
if (res && res.mfa_required) {
|
| 220 |
+
// Launch MFA modal
|
| 221 |
+
openMfaModal({ flow: 'user', email, token: res.mfa_token });
|
| 222 |
+
} else {
|
| 223 |
+
showMessage('Successfully signed in as user!', 'success');
|
| 224 |
+
setTimeout(() => redirectToApp(res.account || email), 1000);
|
| 225 |
+
}
|
| 226 |
+
return;
|
| 227 |
+
} catch (userErr) {
|
| 228 |
+
console.log('User login failed:', userErr.message);
|
| 229 |
+
console.log('Trying professional login...');
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
// Try professional login
|
| 233 |
+
try {
|
| 234 |
+
console.log('Trying professional login for:', email);
|
| 235 |
+
const res = await api('/professional/login', {
|
| 236 |
+
method: 'POST',
|
| 237 |
+
headers: { 'Content-Type': 'application/json' },
|
| 238 |
+
body: JSON.stringify({ email, password })
|
| 239 |
+
});
|
| 240 |
+
|
| 241 |
+
// Store professional data
|
| 242 |
+
localStorage.setItem('aimhsa_professional', JSON.stringify(res));
|
| 243 |
+
if (res && res.mfa_required) {
|
| 244 |
+
openMfaModal({ flow: 'professional', email, token: res.mfa_token });
|
| 245 |
+
} else {
|
| 246 |
+
showMessage('Successfully signed in as professional!', 'success');
|
| 247 |
+
setTimeout(() => {
|
| 248 |
+
window.location.href = '/professional_dashboard.html';
|
| 249 |
+
}, 1000);
|
| 250 |
+
}
|
| 251 |
+
return;
|
| 252 |
+
} catch (profErr) {
|
| 253 |
+
console.log('Professional login failed:', profErr.message);
|
| 254 |
+
console.log('Trying admin login...');
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
// Try admin login
|
| 258 |
+
try {
|
| 259 |
+
console.log('Trying admin login for:', email);
|
| 260 |
+
const res = await api('/admin/login', {
|
| 261 |
+
method: 'POST',
|
| 262 |
+
headers: { 'Content-Type': 'application/json' },
|
| 263 |
+
body: JSON.stringify({ username: email, password })
|
| 264 |
+
});
|
| 265 |
+
|
| 266 |
+
console.log('Admin login successful:', res);
|
| 267 |
+
// Store admin data
|
| 268 |
+
localStorage.setItem('aimhsa_admin', JSON.stringify(res));
|
| 269 |
+
if (res && res.mfa_required) {
|
| 270 |
+
openMfaModal({ flow: 'admin', email, token: res.mfa_token });
|
| 271 |
+
} else {
|
| 272 |
+
showMessage('Successfully signed in as admin!', 'success');
|
| 273 |
+
setTimeout(() => {
|
| 274 |
+
window.location.href = res.redirect || '/admin_dashboard.html';
|
| 275 |
+
}, 1000);
|
| 276 |
+
}
|
| 277 |
+
return;
|
| 278 |
+
} catch (adminErr) {
|
| 279 |
+
console.log('Admin login failed:', adminErr.message);
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
// If all login attempts failed
|
| 283 |
+
showMessage('Invalid username or password. Please check your credentials.');
|
| 284 |
+
// backoff: 10s cooldown after aggregated failure
|
| 285 |
+
applyCooldown(10);
|
| 286 |
+
updateCooldownUI();
|
| 287 |
+
|
| 288 |
+
} catch (err) {
|
| 289 |
+
console.error('Login error:', err);
|
| 290 |
+
showMessage('Login failed. Please try again.');
|
| 291 |
+
} finally {
|
| 292 |
+
signInBtn.disabled = false;
|
| 293 |
+
signInBtn.textContent = 'Sign In';
|
| 294 |
+
}
|
| 295 |
+
});
|
| 296 |
+
|
| 297 |
+
// Anonymous access
|
| 298 |
+
anonBtn.addEventListener('click', () => {
|
| 299 |
+
localStorage.setItem('aimhsa_account', 'null');
|
| 300 |
+
window.location.href = '/index.html';
|
| 301 |
+
});
|
| 302 |
+
|
| 303 |
+
// Check if already logged in
|
| 304 |
+
const account = localStorage.getItem('aimhsa_account');
|
| 305 |
+
if (account && account !== 'null') {
|
| 306 |
+
redirectToApp(account);
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
// --- MFA helpers ---
|
| 310 |
+
function openMfaModal(context) {
|
| 311 |
+
mfaModal.classList.add('open');
|
| 312 |
+
mfaModal.setAttribute('aria-hidden', 'false');
|
| 313 |
+
mfaCode.value = '';
|
| 314 |
+
mfaMessage.textContent = '';
|
| 315 |
+
mfaCode.focus();
|
| 316 |
+
|
| 317 |
+
function close() {
|
| 318 |
+
mfaModal.classList.remove('open');
|
| 319 |
+
mfaModal.setAttribute('aria-hidden', 'true');
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
function onClose() {
|
| 323 |
+
close();
|
| 324 |
+
cleanup();
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
async function verify() {
|
| 328 |
+
const code = mfaCode.value.trim();
|
| 329 |
+
if (!/^[0-9]{6}$/.test(code)) {
|
| 330 |
+
mfaMessage.textContent = 'Please enter a valid 6-digit code.';
|
| 331 |
+
return;
|
| 332 |
+
}
|
| 333 |
+
mfaVerifyBtn.disabled = true;
|
| 334 |
+
mfaVerifyBtn.textContent = 'Verifying...';
|
| 335 |
+
try {
|
| 336 |
+
const endpoint =
|
| 337 |
+
context.flow === 'admin' ? '/admin/mfa/verify' :
|
| 338 |
+
context.flow === 'professional' ? '/professional/mfa/verify' :
|
| 339 |
+
'/mfa/verify';
|
| 340 |
+
const res = await api(endpoint, {
|
| 341 |
+
method: 'POST',
|
| 342 |
+
headers: { 'Content-Type': 'application/json' },
|
| 343 |
+
body: JSON.stringify({ username: context.username, code, token: context.token })
|
| 344 |
+
});
|
| 345 |
+
mfaMessage.textContent = 'MFA verified. Redirecting...';
|
| 346 |
+
setTimeout(() => {
|
| 347 |
+
if (context.flow === 'admin') {
|
| 348 |
+
window.location.href = '/admin_dashboard.html';
|
| 349 |
+
} else if (context.flow === 'professional') {
|
| 350 |
+
window.location.href = '/professional_dashboard.html';
|
| 351 |
+
} else {
|
| 352 |
+
redirectToApp(context.username);
|
| 353 |
+
}
|
| 354 |
+
}, 600);
|
| 355 |
+
} catch (err) {
|
| 356 |
+
mfaMessage.textContent = 'Invalid or expired code. Please try again.';
|
| 357 |
+
} finally {
|
| 358 |
+
mfaVerifyBtn.disabled = false;
|
| 359 |
+
mfaVerifyBtn.textContent = 'Verify';
|
| 360 |
+
}
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
async function resend() {
|
| 364 |
+
try {
|
| 365 |
+
const endpoint =
|
| 366 |
+
context.flow === 'admin' ? '/admin/mfa/resend' :
|
| 367 |
+
context.flow === 'professional' ? '/professional/mfa/resend' :
|
| 368 |
+
'/mfa/resend';
|
| 369 |
+
await api(endpoint, {
|
| 370 |
+
method: 'POST',
|
| 371 |
+
headers: { 'Content-Type': 'application/json' },
|
| 372 |
+
body: JSON.stringify({ username: context.username, token: context.token })
|
| 373 |
+
});
|
| 374 |
+
mfaMessage.textContent = 'Code resent.';
|
| 375 |
+
} catch (err) {
|
| 376 |
+
mfaMessage.textContent = 'Could not resend code. Try again later.';
|
| 377 |
+
}
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
function cleanup() {
|
| 381 |
+
mfaBackdrop.removeEventListener('click', onClose);
|
| 382 |
+
mfaClose.removeEventListener('click', onClose);
|
| 383 |
+
mfaVerifyBtn.removeEventListener('click', verify);
|
| 384 |
+
mfaResendBtn.removeEventListener('click', resend);
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
mfaBackdrop.addEventListener('click', onClose);
|
| 388 |
+
mfaClose.addEventListener('click', onClose);
|
| 389 |
+
mfaVerifyBtn.addEventListener('click', verify);
|
| 390 |
+
mfaResendBtn.addEventListener('click', resend);
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
// --- Forgot Password helpers ---
|
| 394 |
+
function openFpModal() {
|
| 395 |
+
fpModal.classList.add('open');
|
| 396 |
+
fpModal.setAttribute('aria-hidden', 'false');
|
| 397 |
+
fpMessage.textContent = '';
|
| 398 |
+
fpStep1.classList.remove('hidden');
|
| 399 |
+
fpStep2.classList.add('hidden');
|
| 400 |
+
fpEmail.value = emailInput.value.trim();
|
| 401 |
+
setTimeout(() => fpEmail.focus(), 0);
|
| 402 |
+
|
| 403 |
+
function close() {
|
| 404 |
+
fpModal.classList.remove('open');
|
| 405 |
+
fpModal.setAttribute('aria-hidden', 'true');
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
function onClose() {
|
| 409 |
+
close();
|
| 410 |
+
cleanup();
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
async function requestCode() {
|
| 414 |
+
console.log('Request code function called');
|
| 415 |
+
console.log('fpEmail element:', fpEmail);
|
| 416 |
+
console.log('fpEmail value:', fpEmail ? fpEmail.value : 'fpEmail is null');
|
| 417 |
+
const email = fpEmail.value.trim();
|
| 418 |
+
console.log('Email:', email);
|
| 419 |
+
|
| 420 |
+
if (!email) {
|
| 421 |
+
fpMessage.textContent = 'Please enter your email address.';
|
| 422 |
+
fpMessage.style.display = 'block';
|
| 423 |
+
return;
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
// Email validation
|
| 427 |
+
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
| 428 |
+
if (!emailPattern.test(email)) {
|
| 429 |
+
fpMessage.textContent = 'Please enter a valid email address.';
|
| 430 |
+
fpMessage.style.display = 'block';
|
| 431 |
+
return;
|
| 432 |
+
}
|
| 433 |
+
|
| 434 |
+
fpRequestBtn.disabled = true;
|
| 435 |
+
fpRequestBtn.textContent = 'Sending...';
|
| 436 |
+
fpMessage.style.display = 'none';
|
| 437 |
+
|
| 438 |
+
try {
|
| 439 |
+
console.log('Making API call to /forgot_password');
|
| 440 |
+
const res = await api('/forgot_password', {
|
| 441 |
+
method: 'POST',
|
| 442 |
+
headers: { 'Content-Type': 'application/json' },
|
| 443 |
+
body: JSON.stringify({ email: email })
|
| 444 |
+
});
|
| 445 |
+
|
| 446 |
+
console.log('API response:', res);
|
| 447 |
+
|
| 448 |
+
if (res && res.ok) {
|
| 449 |
+
// Show success message and token
|
| 450 |
+
let message = res.message || 'Reset code sent successfully!';
|
| 451 |
+
if (res.token) {
|
| 452 |
+
message += ` Your reset code is: ${res.token}`;
|
| 453 |
+
}
|
| 454 |
+
fpMessage.textContent = message;
|
| 455 |
+
fpMessage.style.display = 'block';
|
| 456 |
+
fpMessage.className = 'modal-message success';
|
| 457 |
+
|
| 458 |
+
// Move to step 2
|
| 459 |
+
fpStep1.classList.add('hidden');
|
| 460 |
+
fpStep2.classList.remove('hidden');
|
| 461 |
+
fpCode.value = '';
|
| 462 |
+
fpNewPassword.value = '';
|
| 463 |
+
setTimeout(() => fpCode.focus(), 0);
|
| 464 |
+
} else {
|
| 465 |
+
fpMessage.textContent = res.error || 'Failed to send reset code.';
|
| 466 |
+
fpMessage.style.display = 'block';
|
| 467 |
+
fpMessage.className = 'modal-message error';
|
| 468 |
+
}
|
| 469 |
+
} catch (err) {
|
| 470 |
+
console.error('Forgot password error:', err);
|
| 471 |
+
fpMessage.textContent = 'Could not initiate reset. Please check your connection and try again.';
|
| 472 |
+
fpMessage.style.display = 'block';
|
| 473 |
+
fpMessage.className = 'modal-message error';
|
| 474 |
+
} finally {
|
| 475 |
+
fpRequestBtn.disabled = false;
|
| 476 |
+
fpRequestBtn.textContent = 'Send code';
|
| 477 |
+
}
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
async function applyReset() {
|
| 481 |
+
console.log('Apply reset function called');
|
| 482 |
+
const email = fpEmail.value.trim();
|
| 483 |
+
const code = fpCode.value.trim();
|
| 484 |
+
const newPw = fpNewPassword.value;
|
| 485 |
+
|
| 486 |
+
console.log('Reset data:', { email, code, newPw: '***' });
|
| 487 |
+
|
| 488 |
+
if (!/^[0-9A-Z]{6}$/.test(code)) {
|
| 489 |
+
fpMessage.textContent = 'Please enter the 6-character code.';
|
| 490 |
+
fpMessage.style.display = 'block';
|
| 491 |
+
fpMessage.className = 'modal-message error';
|
| 492 |
+
return;
|
| 493 |
+
}
|
| 494 |
+
if (newPw.length < 6) {
|
| 495 |
+
fpMessage.textContent = 'New password must be at least 6 characters.';
|
| 496 |
+
fpMessage.style.display = 'block';
|
| 497 |
+
fpMessage.className = 'modal-message error';
|
| 498 |
+
return;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
fpApplyBtn.disabled = true;
|
| 502 |
+
fpApplyBtn.textContent = 'Resetting...';
|
| 503 |
+
fpMessage.style.display = 'none';
|
| 504 |
+
|
| 505 |
+
try {
|
| 506 |
+
console.log('Making API call to /reset_password');
|
| 507 |
+
const res = await api('/reset_password', {
|
| 508 |
+
method: 'POST',
|
| 509 |
+
headers: { 'Content-Type': 'application/json' },
|
| 510 |
+
body: JSON.stringify({ email: email, token: code, new_password: newPw })
|
| 511 |
+
});
|
| 512 |
+
|
| 513 |
+
console.log('Reset password response:', res);
|
| 514 |
+
|
| 515 |
+
if (res && res.ok) {
|
| 516 |
+
fpMessage.textContent = res.message || 'Password updated successfully! You can now sign in.';
|
| 517 |
+
fpMessage.style.display = 'block';
|
| 518 |
+
fpMessage.className = 'modal-message success';
|
| 519 |
+
|
| 520 |
+
setTimeout(() => {
|
| 521 |
+
onClose();
|
| 522 |
+
emailInput.value = email;
|
| 523 |
+
passwordInput.focus();
|
| 524 |
+
}, 2000);
|
| 525 |
+
} else {
|
| 526 |
+
fpMessage.textContent = res.error || 'Invalid code or error updating password.';
|
| 527 |
+
fpMessage.style.display = 'block';
|
| 528 |
+
fpMessage.className = 'modal-message error';
|
| 529 |
+
}
|
| 530 |
+
} catch (err) {
|
| 531 |
+
console.error('Reset password error:', err);
|
| 532 |
+
fpMessage.textContent = 'Invalid code or error updating password. Please try again.';
|
| 533 |
+
fpMessage.style.display = 'block';
|
| 534 |
+
fpMessage.className = 'modal-message error';
|
| 535 |
+
} finally {
|
| 536 |
+
fpApplyBtn.disabled = false;
|
| 537 |
+
fpApplyBtn.textContent = 'Reset password';
|
| 538 |
+
}
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
async function resendCode() {
|
| 542 |
+
// Reuse forgot_password to resend
|
| 543 |
+
requestCode();
|
| 544 |
+
}
|
| 545 |
+
|
| 546 |
+
function cleanup() {
|
| 547 |
+
fpBackdrop.removeEventListener('click', onClose);
|
| 548 |
+
fpClose.removeEventListener('click', onClose);
|
| 549 |
+
fpRequestBtn.removeEventListener('click', requestCode);
|
| 550 |
+
fpApplyBtn.removeEventListener('click', applyReset);
|
| 551 |
+
fpResendBtn.removeEventListener('click', resendCode);
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
+
fpBackdrop.addEventListener('click', onClose);
|
| 555 |
+
fpClose.addEventListener('click', onClose);
|
| 556 |
+
|
| 557 |
+
console.log('Attaching event listener to fpRequestBtn:', fpRequestBtn);
|
| 558 |
+
fpRequestBtn.addEventListener('click', requestCode);
|
| 559 |
+
|
| 560 |
+
fpApplyBtn.addEventListener('click', applyReset);
|
| 561 |
+
fpResendBtn.addEventListener('click', resendCode);
|
| 562 |
+
}
|
| 563 |
+
})();
|
chatbot/professional.css
ADDED
|
@@ -0,0 +1,2446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Professional Dashboard Styles with AdminLTE 4 Integration */
|
| 2 |
+
* {
|
| 3 |
+
margin: 0;
|
| 4 |
+
padding: 0;
|
| 5 |
+
box-sizing: border-box;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
body {
|
| 9 |
+
font-family: 'Source Sans Pro', 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 10 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 11 |
+
background-attachment: fixed;
|
| 12 |
+
min-height: 100vh;
|
| 13 |
+
color: #333;
|
| 14 |
+
line-height: 1.6;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
/* AdminLTE 4 Compatibility Overrides */
|
| 18 |
+
.content-wrapper {
|
| 19 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
| 20 |
+
background-attachment: fixed !important;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
.main-header {
|
| 24 |
+
background: rgba(255, 255, 255, 0.98) !important;
|
| 25 |
+
border-radius: 24px !important;
|
| 26 |
+
margin-bottom: 2rem !important;
|
| 27 |
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.2) !important;
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.navbar-nav .nav-link {
|
| 31 |
+
color: #333 !important;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.sidebar {
|
| 35 |
+
background: rgba(255, 255, 255, 0.95) !important;
|
| 36 |
+
border-radius: 16px !important;
|
| 37 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1) !important;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.sidebar .nav-link {
|
| 41 |
+
color: #666 !important;
|
| 42 |
+
border-radius: 12px !important;
|
| 43 |
+
margin: 4px 8px !important;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.sidebar .nav-link:hover {
|
| 47 |
+
background: rgba(102, 126, 234, 0.1) !important;
|
| 48 |
+
color: #667eea !important;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.card {
|
| 52 |
+
background: rgba(255, 255, 255, 0.98) !important;
|
| 53 |
+
border: none !important;
|
| 54 |
+
border-radius: 16px !important;
|
| 55 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1) !important;
|
| 56 |
+
color: #333 !important;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.card-header {
|
| 60 |
+
background: rgba(255, 255, 255, 0.8) !important;
|
| 61 |
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 62 |
+
color: #333 !important;
|
| 63 |
+
border-radius: 16px 16px 0 0 !important;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.btn-primary {
|
| 67 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
| 68 |
+
border: none !important;
|
| 69 |
+
border-radius: 12px !important;
|
| 70 |
+
padding: 12px 24px !important;
|
| 71 |
+
font-weight: 600 !important;
|
| 72 |
+
transition: all 0.3s ease !important;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.btn-primary:hover {
|
| 76 |
+
transform: translateY(-2px) !important;
|
| 77 |
+
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3) !important;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.btn-secondary {
|
| 81 |
+
background: rgba(255, 255, 255, 0.9) !important;
|
| 82 |
+
border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 83 |
+
color: #333 !important;
|
| 84 |
+
border-radius: 12px !important;
|
| 85 |
+
padding: 12px 24px !important;
|
| 86 |
+
font-weight: 600 !important;
|
| 87 |
+
transition: all 0.3s ease !important;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.btn-secondary:hover {
|
| 91 |
+
background: rgba(102, 126, 234, 0.1) !important;
|
| 92 |
+
border-color: #667eea !important;
|
| 93 |
+
color: #667eea !important;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
.form-control {
|
| 97 |
+
background: rgba(255, 255, 255, 0.9) !important;
|
| 98 |
+
border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 99 |
+
color: #333 !important;
|
| 100 |
+
border-radius: 12px !important;
|
| 101 |
+
padding: 12px 16px !important;
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.form-control:focus {
|
| 105 |
+
background: rgba(255, 255, 255, 0.95) !important;
|
| 106 |
+
border-color: #667eea !important;
|
| 107 |
+
color: #333 !important;
|
| 108 |
+
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25) !important;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.table {
|
| 112 |
+
color: #333 !important;
|
| 113 |
+
background: rgba(255, 255, 255, 0.9) !important;
|
| 114 |
+
border-radius: 12px !important;
|
| 115 |
+
overflow: hidden !important;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.table th {
|
| 119 |
+
background: rgba(102, 126, 234, 0.1) !important;
|
| 120 |
+
border: none !important;
|
| 121 |
+
color: #333 !important;
|
| 122 |
+
font-weight: 600 !important;
|
| 123 |
+
padding: 16px !important;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.table td {
|
| 127 |
+
border: none !important;
|
| 128 |
+
padding: 16px !important;
|
| 129 |
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05) !important;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.modal-content {
|
| 133 |
+
background: rgba(255, 255, 255, 0.98) !important;
|
| 134 |
+
border: none !important;
|
| 135 |
+
border-radius: 20px !important;
|
| 136 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2) !important;
|
| 137 |
+
color: #333 !important;
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
.modal-header {
|
| 141 |
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 142 |
+
border-radius: 20px 20px 0 0 !important;
|
| 143 |
+
background: rgba(102, 126, 234, 0.05) !important;
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
.modal-footer {
|
| 147 |
+
border-top: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 148 |
+
border-radius: 0 0 20px 20px !important;
|
| 149 |
+
background: rgba(102, 126, 234, 0.02) !important;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
/* Custom Scrollbar */
|
| 153 |
+
::-webkit-scrollbar {
|
| 154 |
+
width: 8px;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
::-webkit-scrollbar-track {
|
| 158 |
+
background: rgba(255, 255, 255, 0.1);
|
| 159 |
+
border-radius: 4px;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
::-webkit-scrollbar-thumb {
|
| 163 |
+
background: rgba(255, 255, 255, 0.3);
|
| 164 |
+
border-radius: 4px;
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
::-webkit-scrollbar-thumb:hover {
|
| 168 |
+
background: rgba(255, 255, 255, 0.5);
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
.professional-container {
|
| 172 |
+
max-width: 1200px;
|
| 173 |
+
margin: 0 auto;
|
| 174 |
+
padding: 2rem;
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
.professional-header {
|
| 178 |
+
background: rgba(255, 255, 255, 0.98);
|
| 179 |
+
padding: 2.5rem;
|
| 180 |
+
border-radius: 24px;
|
| 181 |
+
margin-bottom: 2rem;
|
| 182 |
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.2);
|
| 183 |
+
display: flex;
|
| 184 |
+
justify-content: space-between;
|
| 185 |
+
align-items: center;
|
| 186 |
+
backdrop-filter: blur(20px);
|
| 187 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 188 |
+
position: relative;
|
| 189 |
+
overflow: hidden;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
.professional-header::before {
|
| 193 |
+
content: '';
|
| 194 |
+
position: absolute;
|
| 195 |
+
top: 0;
|
| 196 |
+
left: 0;
|
| 197 |
+
right: 0;
|
| 198 |
+
height: 4px;
|
| 199 |
+
background: linear-gradient(90deg, #667eea, #764ba2, #667eea);
|
| 200 |
+
background-size: 200% 100%;
|
| 201 |
+
animation: gradientShift 3s ease-in-out infinite;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
@keyframes gradientShift {
|
| 205 |
+
0%, 100% { background-position: 0% 50%; }
|
| 206 |
+
50% { background-position: 100% 50%; }
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.header-content h1 {
|
| 210 |
+
font-size: 2.5rem;
|
| 211 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 212 |
+
-webkit-background-clip: text;
|
| 213 |
+
-webkit-text-fill-color: transparent;
|
| 214 |
+
margin-bottom: 0.5rem;
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
.header-content p {
|
| 218 |
+
color: #666;
|
| 219 |
+
font-size: 1.1rem;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
.user-info {
|
| 223 |
+
display: flex;
|
| 224 |
+
align-items: center;
|
| 225 |
+
gap: 1rem;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
.user-details {
|
| 229 |
+
text-align: right;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
.user-details span {
|
| 233 |
+
display: block;
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
.user-details span:first-child {
|
| 237 |
+
font-weight: bold;
|
| 238 |
+
font-size: 1.1rem;
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
.user-role {
|
| 242 |
+
color: #666;
|
| 243 |
+
font-size: 0.9rem;
|
| 244 |
+
}
|
| 245 |
+
|
| 246 |
+
.logout-btn {
|
| 247 |
+
padding: 0.75rem 1.5rem;
|
| 248 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 249 |
+
color: white;
|
| 250 |
+
border: none;
|
| 251 |
+
border-radius: 12px;
|
| 252 |
+
cursor: pointer;
|
| 253 |
+
font-weight: 600;
|
| 254 |
+
transition: all 0.3s ease;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.logout-btn:hover {
|
| 258 |
+
transform: translateY(-2px);
|
| 259 |
+
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
/* Stats Section */
|
| 263 |
+
.stats-section {
|
| 264 |
+
margin-bottom: 2rem;
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
.stats-grid {
|
| 268 |
+
display: grid;
|
| 269 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 270 |
+
gap: 1.5rem;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.stat-card {
|
| 274 |
+
background: rgba(255, 255, 255, 0.98);
|
| 275 |
+
padding: 2.5rem;
|
| 276 |
+
border-radius: 24px;
|
| 277 |
+
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.2);
|
| 278 |
+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
| 279 |
+
position: relative;
|
| 280 |
+
overflow: hidden;
|
| 281 |
+
backdrop-filter: blur(20px);
|
| 282 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
.stat-card:hover {
|
| 286 |
+
transform: translateY(-8px) scale(1.02);
|
| 287 |
+
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.3);
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
.stat-card::before {
|
| 291 |
+
content: '';
|
| 292 |
+
position: absolute;
|
| 293 |
+
top: 0;
|
| 294 |
+
left: 0;
|
| 295 |
+
right: 0;
|
| 296 |
+
height: 5px;
|
| 297 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 298 |
+
border-radius: 24px 24px 0 0;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.stat-card::after {
|
| 302 |
+
content: '';
|
| 303 |
+
position: absolute;
|
| 304 |
+
top: -50%;
|
| 305 |
+
right: -50%;
|
| 306 |
+
width: 100%;
|
| 307 |
+
height: 100%;
|
| 308 |
+
background: radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%);
|
| 309 |
+
opacity: 0;
|
| 310 |
+
transition: opacity 0.3s ease;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
.stat-card:hover::after {
|
| 314 |
+
opacity: 1;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
.stat-header {
|
| 318 |
+
display: flex;
|
| 319 |
+
justify-content: space-between;
|
| 320 |
+
align-items: center;
|
| 321 |
+
margin-bottom: 1rem;
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
.stat-icon {
|
| 325 |
+
width: 60px;
|
| 326 |
+
height: 60px;
|
| 327 |
+
border-radius: 16px;
|
| 328 |
+
display: flex;
|
| 329 |
+
align-items: center;
|
| 330 |
+
justify-content: center;
|
| 331 |
+
font-size: 1.8rem;
|
| 332 |
+
color: white;
|
| 333 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 334 |
+
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
|
| 335 |
+
margin-bottom: 1rem;
|
| 336 |
+
position: relative;
|
| 337 |
+
z-index: 2;
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
.stat-icon::before {
|
| 341 |
+
content: '';
|
| 342 |
+
position: absolute;
|
| 343 |
+
inset: -2px;
|
| 344 |
+
border-radius: 18px;
|
| 345 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 346 |
+
z-index: -1;
|
| 347 |
+
opacity: 0.3;
|
| 348 |
+
filter: blur(8px);
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
.stat-number {
|
| 352 |
+
font-size: 3rem;
|
| 353 |
+
font-weight: 800;
|
| 354 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 355 |
+
-webkit-background-clip: text;
|
| 356 |
+
-webkit-text-fill-color: transparent;
|
| 357 |
+
margin-bottom: 0.5rem;
|
| 358 |
+
line-height: 1;
|
| 359 |
+
position: relative;
|
| 360 |
+
z-index: 2;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
.stat-label {
|
| 364 |
+
color: #666;
|
| 365 |
+
font-size: 1rem;
|
| 366 |
+
font-weight: 600;
|
| 367 |
+
text-transform: uppercase;
|
| 368 |
+
letter-spacing: 0.5px;
|
| 369 |
+
position: relative;
|
| 370 |
+
z-index: 2;
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
/* Content Sections */
|
| 374 |
+
.professional-content {
|
| 375 |
+
display: flex;
|
| 376 |
+
flex-direction: column;
|
| 377 |
+
gap: 2rem;
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
.content-section {
|
| 381 |
+
background: rgba(255, 255, 255, 0.95);
|
| 382 |
+
border-radius: 20px;
|
| 383 |
+
padding: 2rem;
|
| 384 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
.section-header {
|
| 388 |
+
display: flex;
|
| 389 |
+
justify-content: space-between;
|
| 390 |
+
align-items: center;
|
| 391 |
+
margin-bottom: 2rem;
|
| 392 |
+
padding-bottom: 1rem;
|
| 393 |
+
border-bottom: 2px solid rgba(0, 0, 0, 0.1);
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
.section-title {
|
| 397 |
+
font-size: 1.5rem;
|
| 398 |
+
font-weight: bold;
|
| 399 |
+
color: #333;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.header-actions {
|
| 403 |
+
display: flex;
|
| 404 |
+
gap: 1rem;
|
| 405 |
+
align-items: center;
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
.btn {
|
| 409 |
+
padding: 0.75rem 1.5rem;
|
| 410 |
+
border: none;
|
| 411 |
+
border-radius: 12px;
|
| 412 |
+
cursor: pointer;
|
| 413 |
+
font-weight: 600;
|
| 414 |
+
transition: all 0.3s ease;
|
| 415 |
+
text-decoration: none;
|
| 416 |
+
display: inline-flex;
|
| 417 |
+
align-items: center;
|
| 418 |
+
gap: 0.5rem;
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
.btn-primary {
|
| 422 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 423 |
+
color: white;
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
.btn-primary:hover {
|
| 427 |
+
transform: translateY(-2px);
|
| 428 |
+
box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
.btn-secondary {
|
| 432 |
+
background: rgba(255, 255, 255, 0.8);
|
| 433 |
+
color: #666;
|
| 434 |
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
.btn-secondary:hover {
|
| 438 |
+
background: rgba(255, 255, 255, 1);
|
| 439 |
+
transform: translateY(-2px);
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
.btn-small {
|
| 443 |
+
padding: 0.5rem 1rem;
|
| 444 |
+
font-size: 0.8rem;
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
/* Quick Actions */
|
| 448 |
+
.quick-actions-grid {
|
| 449 |
+
display: grid;
|
| 450 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 451 |
+
gap: 1rem;
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
.action-btn {
|
| 455 |
+
background: rgba(255, 255, 255, 0.9);
|
| 456 |
+
border: 2px solid rgba(102, 126, 234, 0.2);
|
| 457 |
+
border-radius: 16px;
|
| 458 |
+
padding: 1.5rem;
|
| 459 |
+
cursor: pointer;
|
| 460 |
+
transition: all 0.3s ease;
|
| 461 |
+
text-align: center;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
.action-btn:hover {
|
| 465 |
+
background: rgba(102, 126, 234, 0.1);
|
| 466 |
+
border-color: #667eea;
|
| 467 |
+
transform: translateY(-3px);
|
| 468 |
+
}
|
| 469 |
+
|
| 470 |
+
.action-icon {
|
| 471 |
+
font-size: 2rem;
|
| 472 |
+
margin-bottom: 0.5rem;
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
.action-text {
|
| 476 |
+
font-weight: 600;
|
| 477 |
+
color: #333;
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
/* Sessions Grid */
|
| 481 |
+
.sessions-grid {
|
| 482 |
+
display: grid;
|
| 483 |
+
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
| 484 |
+
gap: 1.5rem;
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
.session-card {
|
| 488 |
+
background: rgba(255, 255, 255, 0.98);
|
| 489 |
+
border-radius: 20px;
|
| 490 |
+
padding: 2rem;
|
| 491 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(255, 255, 255, 0.2);
|
| 492 |
+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
| 493 |
+
border-left: 5px solid #667eea;
|
| 494 |
+
backdrop-filter: blur(20px);
|
| 495 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 496 |
+
position: relative;
|
| 497 |
+
overflow: hidden;
|
| 498 |
+
}
|
| 499 |
+
|
| 500 |
+
.session-card:hover {
|
| 501 |
+
transform: translateY(-6px) scale(1.01);
|
| 502 |
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(255, 255, 255, 0.3);
|
| 503 |
+
}
|
| 504 |
+
|
| 505 |
+
.session-card.high-risk {
|
| 506 |
+
border-left-color: #ef4444;
|
| 507 |
+
background: linear-gradient(135deg, rgba(254, 242, 242, 0.8), rgba(255, 255, 255, 0.98));
|
| 508 |
+
box-shadow: 0 10px 30px rgba(239, 68, 68, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.2);
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
.session-card.high-risk:hover {
|
| 512 |
+
box-shadow: 0 20px 40px rgba(239, 68, 68, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.3);
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
.session-card::before {
|
| 516 |
+
content: '';
|
| 517 |
+
position: absolute;
|
| 518 |
+
top: 0;
|
| 519 |
+
right: 0;
|
| 520 |
+
width: 100px;
|
| 521 |
+
height: 100px;
|
| 522 |
+
background: radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%);
|
| 523 |
+
border-radius: 50%;
|
| 524 |
+
transform: translate(30px, -30px);
|
| 525 |
+
opacity: 0;
|
| 526 |
+
transition: opacity 0.3s ease;
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
.session-card:hover::before {
|
| 530 |
+
opacity: 1;
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
/* Contact Information Styles */
|
| 534 |
+
.contact-info {
|
| 535 |
+
background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
|
| 536 |
+
border-radius: 12px;
|
| 537 |
+
padding: 0.75rem;
|
| 538 |
+
margin: 0.5rem 0;
|
| 539 |
+
border-left: 4px solid #667eea;
|
| 540 |
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.1);
|
| 541 |
+
position: relative;
|
| 542 |
+
overflow: hidden;
|
| 543 |
+
}
|
| 544 |
+
|
| 545 |
+
.contact-info::before {
|
| 546 |
+
content: '';
|
| 547 |
+
position: absolute;
|
| 548 |
+
top: 0;
|
| 549 |
+
right: 0;
|
| 550 |
+
width: 60px;
|
| 551 |
+
height: 60px;
|
| 552 |
+
background: radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%);
|
| 553 |
+
border-radius: 50%;
|
| 554 |
+
transform: translate(20px, -20px);
|
| 555 |
+
}
|
| 556 |
+
|
| 557 |
+
.contact-link {
|
| 558 |
+
color: #667eea;
|
| 559 |
+
text-decoration: none;
|
| 560 |
+
font-weight: 600;
|
| 561 |
+
transition: all 0.3s ease;
|
| 562 |
+
position: relative;
|
| 563 |
+
z-index: 2;
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
.contact-link:hover {
|
| 567 |
+
color: #5a6fd8;
|
| 568 |
+
text-decoration: underline;
|
| 569 |
+
transform: translateX(2px);
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
.detail-row.contact-info .detail-label {
|
| 573 |
+
font-weight: 700;
|
| 574 |
+
color: #667eea;
|
| 575 |
+
font-size: 0.9rem;
|
| 576 |
+
position: relative;
|
| 577 |
+
z-index: 2;
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
.session-header {
|
| 581 |
+
display: flex;
|
| 582 |
+
justify-content: space-between;
|
| 583 |
+
align-items: flex-start;
|
| 584 |
+
margin-bottom: 1rem;
|
| 585 |
+
}
|
| 586 |
+
|
| 587 |
+
.session-user {
|
| 588 |
+
display: flex;
|
| 589 |
+
align-items: center;
|
| 590 |
+
gap: 0.75rem;
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
.user-avatar {
|
| 594 |
+
width: 50px;
|
| 595 |
+
height: 50px;
|
| 596 |
+
border-radius: 50%;
|
| 597 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 598 |
+
display: flex;
|
| 599 |
+
align-items: center;
|
| 600 |
+
justify-content: center;
|
| 601 |
+
color: white;
|
| 602 |
+
font-weight: bold;
|
| 603 |
+
font-size: 1.2rem;
|
| 604 |
+
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.3);
|
| 605 |
+
position: relative;
|
| 606 |
+
z-index: 2;
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
.user-avatar::before {
|
| 610 |
+
content: '';
|
| 611 |
+
position: absolute;
|
| 612 |
+
inset: -3px;
|
| 613 |
+
border-radius: 50%;
|
| 614 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 615 |
+
z-index: -1;
|
| 616 |
+
opacity: 0.3;
|
| 617 |
+
filter: blur(6px);
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
.user-info h4 {
|
| 621 |
+
font-size: 1rem;
|
| 622 |
+
margin-bottom: 0.25rem;
|
| 623 |
+
}
|
| 624 |
+
|
| 625 |
+
.user-info p {
|
| 626 |
+
font-size: 0.8rem;
|
| 627 |
+
color: #666;
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
.session-status {
|
| 631 |
+
padding: 0.5rem 1rem;
|
| 632 |
+
border-radius: 25px;
|
| 633 |
+
font-size: 0.8rem;
|
| 634 |
+
font-weight: 700;
|
| 635 |
+
text-transform: uppercase;
|
| 636 |
+
letter-spacing: 0.5px;
|
| 637 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
| 638 |
+
position: relative;
|
| 639 |
+
z-index: 2;
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
.status-pending {
|
| 643 |
+
background: linear-gradient(135deg, #fef3c7, #fde68a);
|
| 644 |
+
color: #92400e;
|
| 645 |
+
box-shadow: 0 4px 12px rgba(146, 64, 14, 0.2);
|
| 646 |
+
}
|
| 647 |
+
|
| 648 |
+
.status-confirmed {
|
| 649 |
+
background: linear-gradient(135deg, #d1fae5, #a7f3d0);
|
| 650 |
+
color: #065f46;
|
| 651 |
+
box-shadow: 0 4px 12px rgba(6, 95, 70, 0.2);
|
| 652 |
+
}
|
| 653 |
+
|
| 654 |
+
.status-completed {
|
| 655 |
+
background: linear-gradient(135deg, #dbeafe, #bfdbfe);
|
| 656 |
+
color: #1e40af;
|
| 657 |
+
box-shadow: 0 4px 12px rgba(30, 64, 175, 0.2);
|
| 658 |
+
}
|
| 659 |
+
|
| 660 |
+
.status-cancelled {
|
| 661 |
+
background: linear-gradient(135deg, #fee2e2, #fecaca);
|
| 662 |
+
color: #991b1b;
|
| 663 |
+
box-shadow: 0 4px 12px rgba(153, 27, 27, 0.2);
|
| 664 |
+
}
|
| 665 |
+
|
| 666 |
+
.session-details {
|
| 667 |
+
margin-bottom: 1rem;
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
.detail-row {
|
| 671 |
+
display: flex;
|
| 672 |
+
justify-content: space-between;
|
| 673 |
+
margin-bottom: 0.5rem;
|
| 674 |
+
font-size: 0.9rem;
|
| 675 |
+
}
|
| 676 |
+
|
| 677 |
+
.detail-label {
|
| 678 |
+
color: #666;
|
| 679 |
+
font-weight: 500;
|
| 680 |
+
}
|
| 681 |
+
|
| 682 |
+
.detail-value {
|
| 683 |
+
color: #333;
|
| 684 |
+
font-weight: 600;
|
| 685 |
+
}
|
| 686 |
+
|
| 687 |
+
.session-actions {
|
| 688 |
+
display: flex;
|
| 689 |
+
gap: 0.5rem;
|
| 690 |
+
flex-wrap: wrap;
|
| 691 |
+
}
|
| 692 |
+
|
| 693 |
+
/* Notifications */
|
| 694 |
+
.notifications-list {
|
| 695 |
+
max-height: 400px;
|
| 696 |
+
overflow-y: auto;
|
| 697 |
+
}
|
| 698 |
+
|
| 699 |
+
.notification-item {
|
| 700 |
+
display: flex;
|
| 701 |
+
align-items: flex-start;
|
| 702 |
+
gap: 1rem;
|
| 703 |
+
padding: 1rem;
|
| 704 |
+
border-radius: 12px;
|
| 705 |
+
margin-bottom: 0.5rem;
|
| 706 |
+
transition: all 0.3s ease;
|
| 707 |
+
cursor: pointer;
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
.notification-item:hover {
|
| 711 |
+
background: rgba(102, 126, 234, 0.1);
|
| 712 |
+
}
|
| 713 |
+
|
| 714 |
+
.notification-item.unread {
|
| 715 |
+
background: rgba(102, 126, 234, 0.05);
|
| 716 |
+
border-left: 4px solid #667eea;
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
.notification-icon {
|
| 720 |
+
width: 40px;
|
| 721 |
+
height: 40px;
|
| 722 |
+
border-radius: 50%;
|
| 723 |
+
display: flex;
|
| 724 |
+
align-items: center;
|
| 725 |
+
justify-content: center;
|
| 726 |
+
color: white;
|
| 727 |
+
font-size: 1rem;
|
| 728 |
+
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
.notification-content {
|
| 732 |
+
flex: 1;
|
| 733 |
+
}
|
| 734 |
+
|
| 735 |
+
.notification-title {
|
| 736 |
+
font-weight: 600;
|
| 737 |
+
margin-bottom: 0.25rem;
|
| 738 |
+
}
|
| 739 |
+
|
| 740 |
+
.notification-message {
|
| 741 |
+
color: #666;
|
| 742 |
+
font-size: 0.9rem;
|
| 743 |
+
margin-bottom: 0.5rem;
|
| 744 |
+
}
|
| 745 |
+
|
| 746 |
+
.notification-time {
|
| 747 |
+
font-size: 0.8rem;
|
| 748 |
+
color: #999;
|
| 749 |
+
}
|
| 750 |
+
|
| 751 |
+
/* Modals */
|
| 752 |
+
.modal {
|
| 753 |
+
display: none;
|
| 754 |
+
position: fixed;
|
| 755 |
+
z-index: 2000;
|
| 756 |
+
left: 0;
|
| 757 |
+
top: 0;
|
| 758 |
+
width: 100%;
|
| 759 |
+
height: 100%;
|
| 760 |
+
background: rgba(0, 0, 0, 0.5);
|
| 761 |
+
backdrop-filter: blur(5px);
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
.modal-content {
|
| 765 |
+
background: white;
|
| 766 |
+
margin: 2% auto;
|
| 767 |
+
padding: 2rem;
|
| 768 |
+
border-radius: 20px;
|
| 769 |
+
width: 90%;
|
| 770 |
+
max-width: 800px;
|
| 771 |
+
max-height: 90vh;
|
| 772 |
+
overflow-y: auto;
|
| 773 |
+
position: relative;
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
.modal-content.large {
|
| 777 |
+
max-width: 1200px;
|
| 778 |
+
}
|
| 779 |
+
|
| 780 |
+
.modal-header {
|
| 781 |
+
display: flex;
|
| 782 |
+
justify-content: space-between;
|
| 783 |
+
align-items: center;
|
| 784 |
+
margin-bottom: 2rem;
|
| 785 |
+
padding-bottom: 1rem;
|
| 786 |
+
border-bottom: 2px solid rgba(0, 0, 0, 0.1);
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
.close {
|
| 790 |
+
font-size: 2rem;
|
| 791 |
+
cursor: pointer;
|
| 792 |
+
color: #666;
|
| 793 |
+
transition: color 0.3s ease;
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
.close:hover {
|
| 797 |
+
color: #333;
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
+
/* Forms */
|
| 801 |
+
.form-group {
|
| 802 |
+
margin-bottom: 1.5rem;
|
| 803 |
+
}
|
| 804 |
+
|
| 805 |
+
.form-group label {
|
| 806 |
+
display: block;
|
| 807 |
+
margin-bottom: 0.5rem;
|
| 808 |
+
font-weight: 600;
|
| 809 |
+
color: #333;
|
| 810 |
+
}
|
| 811 |
+
|
| 812 |
+
.form-group input,
|
| 813 |
+
.form-group textarea,
|
| 814 |
+
.form-group select {
|
| 815 |
+
width: 100%;
|
| 816 |
+
padding: 0.75rem;
|
| 817 |
+
border: 2px solid rgba(0, 0, 0, 0.1);
|
| 818 |
+
border-radius: 12px;
|
| 819 |
+
font-size: 1rem;
|
| 820 |
+
transition: border-color 0.3s ease;
|
| 821 |
+
}
|
| 822 |
+
|
| 823 |
+
.form-group input:focus,
|
| 824 |
+
.form-group textarea:focus,
|
| 825 |
+
.form-group select:focus {
|
| 826 |
+
outline: none;
|
| 827 |
+
border-color: #667eea;
|
| 828 |
+
}
|
| 829 |
+
|
| 830 |
+
.form-actions {
|
| 831 |
+
display: flex;
|
| 832 |
+
gap: 1rem;
|
| 833 |
+
justify-content: flex-end;
|
| 834 |
+
margin-top: 2rem;
|
| 835 |
+
}
|
| 836 |
+
|
| 837 |
+
/* Filter Select */
|
| 838 |
+
.filter-select {
|
| 839 |
+
padding: 0.5rem 1rem;
|
| 840 |
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
| 841 |
+
border-radius: 8px;
|
| 842 |
+
background: white;
|
| 843 |
+
font-size: 0.9rem;
|
| 844 |
+
}
|
| 845 |
+
|
| 846 |
+
/* Emergency Contacts */
|
| 847 |
+
.emergency-contacts {
|
| 848 |
+
display: flex;
|
| 849 |
+
flex-direction: column;
|
| 850 |
+
gap: 2rem;
|
| 851 |
+
}
|
| 852 |
+
|
| 853 |
+
.contact-group h3 {
|
| 854 |
+
margin-bottom: 1rem;
|
| 855 |
+
color: #333;
|
| 856 |
+
}
|
| 857 |
+
|
| 858 |
+
.contact-item {
|
| 859 |
+
display: flex;
|
| 860 |
+
justify-content: space-between;
|
| 861 |
+
align-items: center;
|
| 862 |
+
padding: 1rem;
|
| 863 |
+
background: rgba(102, 126, 234, 0.05);
|
| 864 |
+
border-radius: 12px;
|
| 865 |
+
margin-bottom: 0.5rem;
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
.contact-item strong {
|
| 869 |
+
color: #333;
|
| 870 |
+
}
|
| 871 |
+
|
| 872 |
+
.contact-item span {
|
| 873 |
+
color: #667eea;
|
| 874 |
+
font-weight: 600;
|
| 875 |
+
}
|
| 876 |
+
|
| 877 |
+
/* Responsive Design */
|
| 878 |
+
@media (max-width: 768px) {
|
| 879 |
+
.professional-container {
|
| 880 |
+
padding: 1rem;
|
| 881 |
+
}
|
| 882 |
+
|
| 883 |
+
.professional-header {
|
| 884 |
+
flex-direction: column;
|
| 885 |
+
gap: 1rem;
|
| 886 |
+
text-align: center;
|
| 887 |
+
}
|
| 888 |
+
|
| 889 |
+
.stats-grid {
|
| 890 |
+
grid-template-columns: 1fr;
|
| 891 |
+
}
|
| 892 |
+
|
| 893 |
+
.sessions-grid {
|
| 894 |
+
grid-template-columns: 1fr;
|
| 895 |
+
}
|
| 896 |
+
|
| 897 |
+
.quick-actions-grid {
|
| 898 |
+
grid-template-columns: 1fr;
|
| 899 |
+
}
|
| 900 |
+
|
| 901 |
+
.modal-content {
|
| 902 |
+
width: 95%;
|
| 903 |
+
margin: 5% auto;
|
| 904 |
+
}
|
| 905 |
+
}
|
| 906 |
+
|
| 907 |
+
/* Loading States */
|
| 908 |
+
.loading {
|
| 909 |
+
display: flex;
|
| 910 |
+
justify-content: center;
|
| 911 |
+
align-items: center;
|
| 912 |
+
padding: 2rem;
|
| 913 |
+
color: #666;
|
| 914 |
+
}
|
| 915 |
+
|
| 916 |
+
/* Empty States */
|
| 917 |
+
.empty-state {
|
| 918 |
+
text-align: center;
|
| 919 |
+
padding: 3rem;
|
| 920 |
+
color: #666;
|
| 921 |
+
}
|
| 922 |
+
|
| 923 |
+
.empty-state i {
|
| 924 |
+
font-size: 3rem;
|
| 925 |
+
margin-bottom: 1rem;
|
| 926 |
+
opacity: 0.5;
|
| 927 |
+
}
|
| 928 |
+
|
| 929 |
+
.empty-state h3 {
|
| 930 |
+
margin-bottom: 0.5rem;
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
.empty-state p {
|
| 934 |
+
font-size: 0.9rem;
|
| 935 |
+
}
|
| 936 |
+
|
| 937 |
+
/* Booked Users Section */
|
| 938 |
+
.booked-users-section {
|
| 939 |
+
background: rgba(255, 255, 255, 0.95);
|
| 940 |
+
padding: 2rem;
|
| 941 |
+
border-radius: 20px;
|
| 942 |
+
margin-bottom: 2rem;
|
| 943 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
| 944 |
+
}
|
| 945 |
+
|
| 946 |
+
.users-grid {
|
| 947 |
+
display: grid;
|
| 948 |
+
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
| 949 |
+
gap: 1.5rem;
|
| 950 |
+
margin-top: 1.5rem;
|
| 951 |
+
}
|
| 952 |
+
|
| 953 |
+
.user-card {
|
| 954 |
+
background: white;
|
| 955 |
+
border-radius: 15px;
|
| 956 |
+
padding: 1.5rem;
|
| 957 |
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
| 958 |
+
border-left: 4px solid #e0e0e0;
|
| 959 |
+
transition: all 0.3s ease;
|
| 960 |
+
}
|
| 961 |
+
|
| 962 |
+
.user-card:hover {
|
| 963 |
+
transform: translateY(-2px);
|
| 964 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
.user-card.high-risk {
|
| 968 |
+
border-left-color: #e74c3c;
|
| 969 |
+
background: linear-gradient(135deg, #fff5f5 0%, #ffffff 100%);
|
| 970 |
+
}
|
| 971 |
+
|
| 972 |
+
.user-header {
|
| 973 |
+
display: flex;
|
| 974 |
+
align-items: center;
|
| 975 |
+
gap: 1rem;
|
| 976 |
+
margin-bottom: 1rem;
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
.user-avatar {
|
| 980 |
+
width: 50px;
|
| 981 |
+
height: 50px;
|
| 982 |
+
border-radius: 50%;
|
| 983 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 984 |
+
color: white;
|
| 985 |
+
display: flex;
|
| 986 |
+
align-items: center;
|
| 987 |
+
justify-content: center;
|
| 988 |
+
font-weight: bold;
|
| 989 |
+
font-size: 1.2rem;
|
| 990 |
+
}
|
| 991 |
+
|
| 992 |
+
.user-info h4 {
|
| 993 |
+
margin: 0;
|
| 994 |
+
color: #333;
|
| 995 |
+
font-size: 1.1rem;
|
| 996 |
+
}
|
| 997 |
+
|
| 998 |
+
.user-info p {
|
| 999 |
+
margin: 0;
|
| 1000 |
+
color: #666;
|
| 1001 |
+
font-size: 0.9rem;
|
| 1002 |
+
}
|
| 1003 |
+
|
| 1004 |
+
.user-status {
|
| 1005 |
+
padding: 0.25rem 0.75rem;
|
| 1006 |
+
border-radius: 20px;
|
| 1007 |
+
font-size: 0.8rem;
|
| 1008 |
+
font-weight: bold;
|
| 1009 |
+
text-transform: uppercase;
|
| 1010 |
+
}
|
| 1011 |
+
|
| 1012 |
+
.user-status.status-low {
|
| 1013 |
+
background: #d4edda;
|
| 1014 |
+
color: #155724;
|
| 1015 |
+
}
|
| 1016 |
+
|
| 1017 |
+
.user-status.status-medium {
|
| 1018 |
+
background: #fff3cd;
|
| 1019 |
+
color: #856404;
|
| 1020 |
+
}
|
| 1021 |
+
|
| 1022 |
+
.user-status.status-high {
|
| 1023 |
+
background: #f8d7da;
|
| 1024 |
+
color: #721c24;
|
| 1025 |
+
}
|
| 1026 |
+
|
| 1027 |
+
.user-status.status-critical {
|
| 1028 |
+
background: #f5c6cb;
|
| 1029 |
+
color: #721c24;
|
| 1030 |
+
}
|
| 1031 |
+
|
| 1032 |
+
.user-details {
|
| 1033 |
+
margin-bottom: 1rem;
|
| 1034 |
+
}
|
| 1035 |
+
|
| 1036 |
+
.detail-row {
|
| 1037 |
+
display: flex;
|
| 1038 |
+
justify-content: space-between;
|
| 1039 |
+
align-items: center;
|
| 1040 |
+
padding: 0.5rem 0;
|
| 1041 |
+
border-bottom: 1px solid #f0f0f0;
|
| 1042 |
+
}
|
| 1043 |
+
|
| 1044 |
+
.detail-row:last-child {
|
| 1045 |
+
border-bottom: none;
|
| 1046 |
+
}
|
| 1047 |
+
|
| 1048 |
+
.detail-label {
|
| 1049 |
+
font-weight: 600;
|
| 1050 |
+
color: #666;
|
| 1051 |
+
font-size: 0.9rem;
|
| 1052 |
+
}
|
| 1053 |
+
|
| 1054 |
+
.detail-value {
|
| 1055 |
+
color: #333;
|
| 1056 |
+
font-size: 0.9rem;
|
| 1057 |
+
}
|
| 1058 |
+
|
| 1059 |
+
.user-actions {
|
| 1060 |
+
display: flex;
|
| 1061 |
+
gap: 0.5rem;
|
| 1062 |
+
flex-wrap: wrap;
|
| 1063 |
+
}
|
| 1064 |
+
|
| 1065 |
+
/* User Profile Modal */
|
| 1066 |
+
.user-profile-content {
|
| 1067 |
+
max-height: 80vh;
|
| 1068 |
+
overflow-y: auto;
|
| 1069 |
+
}
|
| 1070 |
+
|
| 1071 |
+
.user-profile-details {
|
| 1072 |
+
padding: 1rem;
|
| 1073 |
+
}
|
| 1074 |
+
|
| 1075 |
+
.profile-header {
|
| 1076 |
+
display: flex;
|
| 1077 |
+
align-items: center;
|
| 1078 |
+
gap: 1.5rem;
|
| 1079 |
+
margin-bottom: 2rem;
|
| 1080 |
+
padding-bottom: 1rem;
|
| 1081 |
+
border-bottom: 2px solid #f0f0f0;
|
| 1082 |
+
}
|
| 1083 |
+
|
| 1084 |
+
.profile-avatar {
|
| 1085 |
+
width: 80px;
|
| 1086 |
+
height: 80px;
|
| 1087 |
+
border-radius: 50%;
|
| 1088 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 1089 |
+
color: white;
|
| 1090 |
+
display: flex;
|
| 1091 |
+
align-items: center;
|
| 1092 |
+
justify-content: center;
|
| 1093 |
+
font-weight: bold;
|
| 1094 |
+
font-size: 2rem;
|
| 1095 |
+
}
|
| 1096 |
+
|
| 1097 |
+
.profile-info h3 {
|
| 1098 |
+
margin: 0;
|
| 1099 |
+
color: #333;
|
| 1100 |
+
font-size: 1.5rem;
|
| 1101 |
+
}
|
| 1102 |
+
|
| 1103 |
+
.profile-info p {
|
| 1104 |
+
margin: 0.25rem 0;
|
| 1105 |
+
color: #666;
|
| 1106 |
+
font-size: 1rem;
|
| 1107 |
+
}
|
| 1108 |
+
|
| 1109 |
+
.risk-badge {
|
| 1110 |
+
padding: 0.5rem 1rem;
|
| 1111 |
+
border-radius: 25px;
|
| 1112 |
+
font-size: 0.9rem;
|
| 1113 |
+
font-weight: bold;
|
| 1114 |
+
text-transform: uppercase;
|
| 1115 |
+
margin-top: 0.5rem;
|
| 1116 |
+
display: inline-block;
|
| 1117 |
+
}
|
| 1118 |
+
|
| 1119 |
+
.risk-badge.risk-low {
|
| 1120 |
+
background: #d4edda;
|
| 1121 |
+
color: #155724;
|
| 1122 |
+
}
|
| 1123 |
+
|
| 1124 |
+
.risk-badge.risk-medium {
|
| 1125 |
+
background: #fff3cd;
|
| 1126 |
+
color: #856404;
|
| 1127 |
+
}
|
| 1128 |
+
|
| 1129 |
+
.risk-badge.risk-high {
|
| 1130 |
+
background: #f8d7da;
|
| 1131 |
+
color: #721c24;
|
| 1132 |
+
}
|
| 1133 |
+
|
| 1134 |
+
.risk-badge.risk-critical {
|
| 1135 |
+
background: #f5c6cb;
|
| 1136 |
+
color: #721c24;
|
| 1137 |
+
}
|
| 1138 |
+
|
| 1139 |
+
.profile-sections {
|
| 1140 |
+
display: grid;
|
| 1141 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 1142 |
+
gap: 2rem;
|
| 1143 |
+
}
|
| 1144 |
+
|
| 1145 |
+
.profile-section {
|
| 1146 |
+
background: #f8f9fa;
|
| 1147 |
+
padding: 1.5rem;
|
| 1148 |
+
border-radius: 10px;
|
| 1149 |
+
border-left: 4px solid #667eea;
|
| 1150 |
+
}
|
| 1151 |
+
|
| 1152 |
+
.profile-section h4 {
|
| 1153 |
+
margin: 0 0 1rem 0;
|
| 1154 |
+
color: #333;
|
| 1155 |
+
font-size: 1.1rem;
|
| 1156 |
+
border-bottom: 1px solid #e0e0e0;
|
| 1157 |
+
padding-bottom: 0.5rem;
|
| 1158 |
+
}
|
| 1159 |
+
|
| 1160 |
+
.profile-details {
|
| 1161 |
+
display: flex;
|
| 1162 |
+
flex-direction: column;
|
| 1163 |
+
gap: 0.75rem;
|
| 1164 |
+
}
|
| 1165 |
+
|
| 1166 |
+
.detail-item {
|
| 1167 |
+
display: flex;
|
| 1168 |
+
justify-content: space-between;
|
| 1169 |
+
align-items: center;
|
| 1170 |
+
padding: 0.5rem 0;
|
| 1171 |
+
}
|
| 1172 |
+
|
| 1173 |
+
.detail-item strong {
|
| 1174 |
+
color: #666;
|
| 1175 |
+
font-size: 0.9rem;
|
| 1176 |
+
}
|
| 1177 |
+
|
| 1178 |
+
.sessions-list, .risk-history, .conversations-list {
|
| 1179 |
+
display: flex;
|
| 1180 |
+
flex-direction: column;
|
| 1181 |
+
gap: 0.75rem;
|
| 1182 |
+
}
|
| 1183 |
+
|
| 1184 |
+
.session-item, .risk-item, .conversation-item {
|
| 1185 |
+
background: white;
|
| 1186 |
+
padding: 1rem;
|
| 1187 |
+
border-radius: 8px;
|
| 1188 |
+
border-left: 3px solid #e0e0e0;
|
| 1189 |
+
}
|
| 1190 |
+
|
| 1191 |
+
.session-info, .risk-info {
|
| 1192 |
+
display: flex;
|
| 1193 |
+
justify-content: space-between;
|
| 1194 |
+
align-items: center;
|
| 1195 |
+
margin-bottom: 0.5rem;
|
| 1196 |
+
}
|
| 1197 |
+
|
| 1198 |
+
.session-details, .risk-date, .conv-date {
|
| 1199 |
+
font-size: 0.8rem;
|
| 1200 |
+
color: #666;
|
| 1201 |
+
}
|
| 1202 |
+
|
| 1203 |
+
.conv-preview {
|
| 1204 |
+
font-size: 0.9rem;
|
| 1205 |
+
color: #333;
|
| 1206 |
+
margin-bottom: 0.25rem;
|
| 1207 |
+
}
|
| 1208 |
+
|
| 1209 |
+
.session-status {
|
| 1210 |
+
padding: 0.25rem 0.5rem;
|
| 1211 |
+
border-radius: 15px;
|
| 1212 |
+
font-size: 0.7rem;
|
| 1213 |
+
font-weight: bold;
|
| 1214 |
+
text-transform: uppercase;
|
| 1215 |
+
}
|
| 1216 |
+
|
| 1217 |
+
.session-status.status-pending {
|
| 1218 |
+
background: #fff3cd;
|
| 1219 |
+
color: #856404;
|
| 1220 |
+
}
|
| 1221 |
+
|
| 1222 |
+
.session-status.status-confirmed {
|
| 1223 |
+
background: #d4edda;
|
| 1224 |
+
color: #155724;
|
| 1225 |
+
}
|
| 1226 |
+
|
| 1227 |
+
.session-status.status-declined {
|
| 1228 |
+
background: #f8d7da;
|
| 1229 |
+
color: #721c24;
|
| 1230 |
+
}
|
| 1231 |
+
|
| 1232 |
+
.risk-level {
|
| 1233 |
+
padding: 0.25rem 0.5rem;
|
| 1234 |
+
border-radius: 15px;
|
| 1235 |
+
font-size: 0.7rem;
|
| 1236 |
+
font-weight: bold;
|
| 1237 |
+
text-transform: uppercase;
|
| 1238 |
+
}
|
| 1239 |
+
|
| 1240 |
+
.risk-level.risk-low {
|
| 1241 |
+
background: #d4edda;
|
| 1242 |
+
color: #155724;
|
| 1243 |
+
}
|
| 1244 |
+
|
| 1245 |
+
.risk-level.risk-medium {
|
| 1246 |
+
background: #fff3cd;
|
| 1247 |
+
color: #856404;
|
| 1248 |
+
}
|
| 1249 |
+
|
| 1250 |
+
.risk-level.risk-high {
|
| 1251 |
+
background: #f8d7da;
|
| 1252 |
+
color: #721c24;
|
| 1253 |
+
}
|
| 1254 |
+
|
| 1255 |
+
.risk-level.risk-critical {
|
| 1256 |
+
background: #f5c6cb;
|
| 1257 |
+
color: #721c24;
|
| 1258 |
+
}
|
| 1259 |
+
|
| 1260 |
+
.risk-score {
|
| 1261 |
+
font-weight: bold;
|
| 1262 |
+
color: #333;
|
| 1263 |
+
}
|
| 1264 |
+
|
| 1265 |
+
/* Responsive Design for User Components */
|
| 1266 |
+
@media (max-width: 768px) {
|
| 1267 |
+
.users-grid {
|
| 1268 |
+
grid-template-columns: 1fr;
|
| 1269 |
+
}
|
| 1270 |
+
|
| 1271 |
+
.profile-sections {
|
| 1272 |
+
grid-template-columns: 1fr;
|
| 1273 |
+
}
|
| 1274 |
+
|
| 1275 |
+
.profile-header {
|
| 1276 |
+
flex-direction: column;
|
| 1277 |
+
text-align: center;
|
| 1278 |
+
}
|
| 1279 |
+
|
| 1280 |
+
.user-actions {
|
| 1281 |
+
justify-content: center;
|
| 1282 |
+
}
|
| 1283 |
+
}
|
| 1284 |
+
|
| 1285 |
+
/* Enhanced Session Details Modal */
|
| 1286 |
+
.session-details-modal {
|
| 1287 |
+
max-height: 90vh;
|
| 1288 |
+
overflow-y: auto;
|
| 1289 |
+
}
|
| 1290 |
+
|
| 1291 |
+
/* Session Header Section */
|
| 1292 |
+
.session-header-section {
|
| 1293 |
+
display: flex;
|
| 1294 |
+
justify-content: space-between;
|
| 1295 |
+
align-items: center;
|
| 1296 |
+
padding: 2rem;
|
| 1297 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 1298 |
+
color: white;
|
| 1299 |
+
border-radius: 15px;
|
| 1300 |
+
margin-bottom: 2rem;
|
| 1301 |
+
}
|
| 1302 |
+
|
| 1303 |
+
.session-header-info {
|
| 1304 |
+
display: flex;
|
| 1305 |
+
align-items: center;
|
| 1306 |
+
gap: 1.5rem;
|
| 1307 |
+
}
|
| 1308 |
+
|
| 1309 |
+
.session-user-avatar {
|
| 1310 |
+
width: 80px;
|
| 1311 |
+
height: 80px;
|
| 1312 |
+
border-radius: 50%;
|
| 1313 |
+
background: rgba(255, 255, 255, 0.2);
|
| 1314 |
+
display: flex;
|
| 1315 |
+
align-items: center;
|
| 1316 |
+
justify-content: center;
|
| 1317 |
+
font-size: 2rem;
|
| 1318 |
+
font-weight: bold;
|
| 1319 |
+
border: 3px solid rgba(255, 255, 255, 0.3);
|
| 1320 |
+
}
|
| 1321 |
+
|
| 1322 |
+
.session-user-details h2 {
|
| 1323 |
+
margin: 0;
|
| 1324 |
+
font-size: 1.8rem;
|
| 1325 |
+
font-weight: 600;
|
| 1326 |
+
}
|
| 1327 |
+
|
| 1328 |
+
.user-account {
|
| 1329 |
+
margin: 0.25rem 0;
|
| 1330 |
+
opacity: 0.9;
|
| 1331 |
+
font-size: 1rem;
|
| 1332 |
+
}
|
| 1333 |
+
|
| 1334 |
+
.session-meta {
|
| 1335 |
+
display: flex;
|
| 1336 |
+
gap: 1rem;
|
| 1337 |
+
margin-top: 0.5rem;
|
| 1338 |
+
font-size: 0.9rem;
|
| 1339 |
+
opacity: 0.8;
|
| 1340 |
+
}
|
| 1341 |
+
|
| 1342 |
+
.session-status-badge {
|
| 1343 |
+
padding: 0.75rem 1.5rem;
|
| 1344 |
+
border-radius: 25px;
|
| 1345 |
+
font-weight: bold;
|
| 1346 |
+
font-size: 0.9rem;
|
| 1347 |
+
text-transform: uppercase;
|
| 1348 |
+
background: rgba(255, 255, 255, 0.2);
|
| 1349 |
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
| 1350 |
+
}
|
| 1351 |
+
|
| 1352 |
+
/* Session Information Grid */
|
| 1353 |
+
.session-info-grid {
|
| 1354 |
+
display: grid;
|
| 1355 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 1356 |
+
gap: 1.5rem;
|
| 1357 |
+
margin-bottom: 2rem;
|
| 1358 |
+
}
|
| 1359 |
+
|
| 1360 |
+
.info-card {
|
| 1361 |
+
background: white;
|
| 1362 |
+
border-radius: 15px;
|
| 1363 |
+
padding: 1.5rem;
|
| 1364 |
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
| 1365 |
+
border-left: 4px solid #667eea;
|
| 1366 |
+
transition: all 0.3s ease;
|
| 1367 |
+
}
|
| 1368 |
+
|
| 1369 |
+
.info-card:hover {
|
| 1370 |
+
transform: translateY(-2px);
|
| 1371 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
| 1372 |
+
}
|
| 1373 |
+
|
| 1374 |
+
.info-card h3 {
|
| 1375 |
+
margin: 0 0 1rem 0;
|
| 1376 |
+
color: #333;
|
| 1377 |
+
font-size: 1.2rem;
|
| 1378 |
+
border-bottom: 2px solid #f0f0f0;
|
| 1379 |
+
padding-bottom: 0.5rem;
|
| 1380 |
+
}
|
| 1381 |
+
|
| 1382 |
+
.info-item {
|
| 1383 |
+
display: flex;
|
| 1384 |
+
justify-content: space-between;
|
| 1385 |
+
align-items: center;
|
| 1386 |
+
padding: 0.75rem 0;
|
| 1387 |
+
border-bottom: 1px solid #f0f0f0;
|
| 1388 |
+
}
|
| 1389 |
+
|
| 1390 |
+
.info-item:last-child {
|
| 1391 |
+
border-bottom: none;
|
| 1392 |
+
}
|
| 1393 |
+
|
| 1394 |
+
.info-label {
|
| 1395 |
+
color: #666;
|
| 1396 |
+
font-weight: 600;
|
| 1397 |
+
font-size: 0.9rem;
|
| 1398 |
+
}
|
| 1399 |
+
|
| 1400 |
+
.info-value {
|
| 1401 |
+
color: #333;
|
| 1402 |
+
font-weight: 500;
|
| 1403 |
+
font-size: 0.9rem;
|
| 1404 |
+
}
|
| 1405 |
+
|
| 1406 |
+
.info-value.highlight {
|
| 1407 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 1408 |
+
-webkit-background-clip: text;
|
| 1409 |
+
-webkit-text-fill-color: transparent;
|
| 1410 |
+
font-weight: bold;
|
| 1411 |
+
}
|
| 1412 |
+
|
| 1413 |
+
.info-value.missing-data {
|
| 1414 |
+
color: #999;
|
| 1415 |
+
font-style: italic;
|
| 1416 |
+
opacity: 0.7;
|
| 1417 |
+
}
|
| 1418 |
+
|
| 1419 |
+
.risk-badge {
|
| 1420 |
+
padding: 0.25rem 0.75rem;
|
| 1421 |
+
border-radius: 15px;
|
| 1422 |
+
font-size: 0.8rem;
|
| 1423 |
+
font-weight: 700;
|
| 1424 |
+
text-transform: uppercase;
|
| 1425 |
+
letter-spacing: 0.5px;
|
| 1426 |
+
display: inline-block;
|
| 1427 |
+
}
|
| 1428 |
+
|
| 1429 |
+
.risk-badge.risk-low {
|
| 1430 |
+
background: linear-gradient(135deg, #d4edda, #c3e6cb);
|
| 1431 |
+
color: #155724;
|
| 1432 |
+
box-shadow: 0 2px 8px rgba(21, 87, 36, 0.2);
|
| 1433 |
+
}
|
| 1434 |
+
|
| 1435 |
+
.risk-badge.risk-medium {
|
| 1436 |
+
background: linear-gradient(135deg, #fff3cd, #ffeaa7);
|
| 1437 |
+
color: #856404;
|
| 1438 |
+
box-shadow: 0 2px 8px rgba(133, 100, 4, 0.2);
|
| 1439 |
+
}
|
| 1440 |
+
|
| 1441 |
+
.risk-badge.risk-high {
|
| 1442 |
+
background: linear-gradient(135deg, #f8d7da, #f5c6cb);
|
| 1443 |
+
color: #721c24;
|
| 1444 |
+
box-shadow: 0 2px 8px rgba(114, 28, 36, 0.2);
|
| 1445 |
+
}
|
| 1446 |
+
|
| 1447 |
+
.risk-badge.risk-critical {
|
| 1448 |
+
background: linear-gradient(135deg, #f5c6cb, #f1aeb5);
|
| 1449 |
+
color: #721c24;
|
| 1450 |
+
box-shadow: 0 2px 8px rgba(114, 28, 36, 0.3);
|
| 1451 |
+
animation: pulse 2s infinite;
|
| 1452 |
+
}
|
| 1453 |
+
|
| 1454 |
+
.risk-badge.risk-unknown {
|
| 1455 |
+
background: linear-gradient(135deg, #e9ecef, #dee2e6);
|
| 1456 |
+
color: #6c757d;
|
| 1457 |
+
box-shadow: 0 2px 8px rgba(108, 117, 125, 0.2);
|
| 1458 |
+
}
|
| 1459 |
+
|
| 1460 |
+
@keyframes pulse {
|
| 1461 |
+
0%, 100% { transform: scale(1); }
|
| 1462 |
+
50% { transform: scale(1.05); }
|
| 1463 |
+
}
|
| 1464 |
+
|
| 1465 |
+
/* Risk Assessment Card */
|
| 1466 |
+
.risk-assessment {
|
| 1467 |
+
border-left-color: #e74c3c;
|
| 1468 |
+
}
|
| 1469 |
+
|
| 1470 |
+
.risk-level-display {
|
| 1471 |
+
display: flex;
|
| 1472 |
+
align-items: center;
|
| 1473 |
+
gap: 1rem;
|
| 1474 |
+
margin-bottom: 1rem;
|
| 1475 |
+
padding: 1rem;
|
| 1476 |
+
border-radius: 10px;
|
| 1477 |
+
background: #f8f9fa;
|
| 1478 |
+
}
|
| 1479 |
+
|
| 1480 |
+
.risk-level-badge {
|
| 1481 |
+
padding: 0.5rem 1rem;
|
| 1482 |
+
border-radius: 20px;
|
| 1483 |
+
font-weight: bold;
|
| 1484 |
+
font-size: 0.9rem;
|
| 1485 |
+
text-transform: uppercase;
|
| 1486 |
+
}
|
| 1487 |
+
|
| 1488 |
+
.risk-level-badge.risk-low {
|
| 1489 |
+
background: #d4edda;
|
| 1490 |
+
color: #155724;
|
| 1491 |
+
}
|
| 1492 |
+
|
| 1493 |
+
.risk-level-badge.risk-medium {
|
| 1494 |
+
background: #fff3cd;
|
| 1495 |
+
color: #856404;
|
| 1496 |
+
}
|
| 1497 |
+
|
| 1498 |
+
.risk-level-badge.risk-high {
|
| 1499 |
+
background: #f8d7da;
|
| 1500 |
+
color: #721c24;
|
| 1501 |
+
}
|
| 1502 |
+
|
| 1503 |
+
.risk-level-badge.risk-critical {
|
| 1504 |
+
background: #f5c6cb;
|
| 1505 |
+
color: #721c24;
|
| 1506 |
+
}
|
| 1507 |
+
|
| 1508 |
+
.risk-score-display {
|
| 1509 |
+
font-size: 1.5rem;
|
| 1510 |
+
font-weight: bold;
|
| 1511 |
+
color: #333;
|
| 1512 |
+
}
|
| 1513 |
+
|
| 1514 |
+
.risk-indicators-compact h4 {
|
| 1515 |
+
margin: 0 0 0.5rem 0;
|
| 1516 |
+
color: #666;
|
| 1517 |
+
font-size: 0.9rem;
|
| 1518 |
+
}
|
| 1519 |
+
|
| 1520 |
+
.indicators-tags {
|
| 1521 |
+
display: flex;
|
| 1522 |
+
flex-wrap: wrap;
|
| 1523 |
+
gap: 0.5rem;
|
| 1524 |
+
}
|
| 1525 |
+
|
| 1526 |
+
.indicator-tag {
|
| 1527 |
+
background: #e9ecef;
|
| 1528 |
+
color: #495057;
|
| 1529 |
+
padding: 0.25rem 0.5rem;
|
| 1530 |
+
border-radius: 12px;
|
| 1531 |
+
font-size: 0.8rem;
|
| 1532 |
+
font-weight: 500;
|
| 1533 |
+
}
|
| 1534 |
+
|
| 1535 |
+
.indicator-more {
|
| 1536 |
+
background: #667eea;
|
| 1537 |
+
color: white;
|
| 1538 |
+
padding: 0.25rem 0.5rem;
|
| 1539 |
+
border-radius: 12px;
|
| 1540 |
+
font-size: 0.8rem;
|
| 1541 |
+
font-weight: 500;
|
| 1542 |
+
}
|
| 1543 |
+
|
| 1544 |
+
/* Contact Information Card */
|
| 1545 |
+
.contact-info-card {
|
| 1546 |
+
border-left-color: #28a745;
|
| 1547 |
+
}
|
| 1548 |
+
|
| 1549 |
+
.contact-item {
|
| 1550 |
+
display: flex;
|
| 1551 |
+
align-items: center;
|
| 1552 |
+
gap: 1rem;
|
| 1553 |
+
padding: 0.75rem 0;
|
| 1554 |
+
border-bottom: 1px solid #f0f0f0;
|
| 1555 |
+
}
|
| 1556 |
+
|
| 1557 |
+
.contact-item:last-child {
|
| 1558 |
+
border-bottom: none;
|
| 1559 |
+
}
|
| 1560 |
+
|
| 1561 |
+
.contact-icon {
|
| 1562 |
+
font-size: 1.2rem;
|
| 1563 |
+
width: 30px;
|
| 1564 |
+
text-align: center;
|
| 1565 |
+
}
|
| 1566 |
+
|
| 1567 |
+
.contact-details {
|
| 1568 |
+
flex: 1;
|
| 1569 |
+
}
|
| 1570 |
+
|
| 1571 |
+
.contact-label {
|
| 1572 |
+
display: block;
|
| 1573 |
+
color: #666;
|
| 1574 |
+
font-size: 0.8rem;
|
| 1575 |
+
font-weight: 600;
|
| 1576 |
+
margin-bottom: 0.25rem;
|
| 1577 |
+
}
|
| 1578 |
+
|
| 1579 |
+
.contact-value {
|
| 1580 |
+
color: #333;
|
| 1581 |
+
font-weight: 500;
|
| 1582 |
+
text-decoration: none;
|
| 1583 |
+
font-size: 0.9rem;
|
| 1584 |
+
}
|
| 1585 |
+
|
| 1586 |
+
.contact-value:hover {
|
| 1587 |
+
color: #667eea;
|
| 1588 |
+
}
|
| 1589 |
+
|
| 1590 |
+
/* Session Type Styling */
|
| 1591 |
+
.session-type-urgent {
|
| 1592 |
+
background: #f8d7da;
|
| 1593 |
+
color: #721c24;
|
| 1594 |
+
padding: 0.25rem 0.5rem;
|
| 1595 |
+
border-radius: 8px;
|
| 1596 |
+
font-size: 0.8rem;
|
| 1597 |
+
font-weight: bold;
|
| 1598 |
+
}
|
| 1599 |
+
|
| 1600 |
+
.session-type-routine {
|
| 1601 |
+
background: #d4edda;
|
| 1602 |
+
color: #155724;
|
| 1603 |
+
padding: 0.25rem 0.5rem;
|
| 1604 |
+
border-radius: 8px;
|
| 1605 |
+
font-size: 0.8rem;
|
| 1606 |
+
font-weight: bold;
|
| 1607 |
+
}
|
| 1608 |
+
|
| 1609 |
+
.session-type-followup {
|
| 1610 |
+
background: #d1ecf1;
|
| 1611 |
+
color: #0c5460;
|
| 1612 |
+
padding: 0.25rem 0.5rem;
|
| 1613 |
+
border-radius: 8px;
|
| 1614 |
+
font-size: 0.8rem;
|
| 1615 |
+
font-weight: bold;
|
| 1616 |
+
}
|
| 1617 |
+
|
| 1618 |
+
.user-comprehensive-info {
|
| 1619 |
+
margin-top: 2rem;
|
| 1620 |
+
padding-top: 2rem;
|
| 1621 |
+
border-top: 2px solid #f0f0f0;
|
| 1622 |
+
}
|
| 1623 |
+
|
| 1624 |
+
.user-info-grid {
|
| 1625 |
+
display: grid;
|
| 1626 |
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 1627 |
+
gap: 2rem;
|
| 1628 |
+
margin-bottom: 2rem;
|
| 1629 |
+
}
|
| 1630 |
+
|
| 1631 |
+
/* Timeline Components */
|
| 1632 |
+
.sessions-timeline, .risk-timeline, .conversations-timeline {
|
| 1633 |
+
position: relative;
|
| 1634 |
+
padding-left: 2rem;
|
| 1635 |
+
}
|
| 1636 |
+
|
| 1637 |
+
.sessions-timeline::before, .risk-timeline::before, .conversations-timeline::before {
|
| 1638 |
+
content: '';
|
| 1639 |
+
position: absolute;
|
| 1640 |
+
left: 1rem;
|
| 1641 |
+
top: 0;
|
| 1642 |
+
bottom: 0;
|
| 1643 |
+
width: 2px;
|
| 1644 |
+
background: #e0e0e0;
|
| 1645 |
+
}
|
| 1646 |
+
|
| 1647 |
+
.timeline-item, .risk-timeline-item, .conversation-timeline-item {
|
| 1648 |
+
position: relative;
|
| 1649 |
+
margin-bottom: 1.5rem;
|
| 1650 |
+
}
|
| 1651 |
+
|
| 1652 |
+
.timeline-marker, .risk-marker, .conv-marker {
|
| 1653 |
+
position: absolute;
|
| 1654 |
+
left: -1.5rem;
|
| 1655 |
+
top: 0.5rem;
|
| 1656 |
+
width: 12px;
|
| 1657 |
+
height: 12px;
|
| 1658 |
+
border-radius: 50%;
|
| 1659 |
+
background: #667eea;
|
| 1660 |
+
border: 3px solid white;
|
| 1661 |
+
box-shadow: 0 0 0 2px #e0e0e0;
|
| 1662 |
+
}
|
| 1663 |
+
|
| 1664 |
+
.risk-marker.risk-low {
|
| 1665 |
+
background: #28a745;
|
| 1666 |
+
}
|
| 1667 |
+
|
| 1668 |
+
.risk-marker.risk-medium {
|
| 1669 |
+
background: #ffc107;
|
| 1670 |
+
}
|
| 1671 |
+
|
| 1672 |
+
.risk-marker.risk-high {
|
| 1673 |
+
background: #fd7e14;
|
| 1674 |
+
}
|
| 1675 |
+
|
| 1676 |
+
.risk-marker.risk-critical {
|
| 1677 |
+
background: #dc3545;
|
| 1678 |
+
}
|
| 1679 |
+
|
| 1680 |
+
.timeline-content, .risk-content, .conv-content {
|
| 1681 |
+
background: white;
|
| 1682 |
+
padding: 1rem;
|
| 1683 |
+
border-radius: 10px;
|
| 1684 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 1685 |
+
border-left: 3px solid #667eea;
|
| 1686 |
+
}
|
| 1687 |
+
|
| 1688 |
+
.timeline-item.current-session .timeline-content {
|
| 1689 |
+
border-left-color: #28a745;
|
| 1690 |
+
background: linear-gradient(135deg, #f8fff8, #ffffff);
|
| 1691 |
+
}
|
| 1692 |
+
|
| 1693 |
+
.current-indicator {
|
| 1694 |
+
background: #28a745;
|
| 1695 |
+
color: white;
|
| 1696 |
+
padding: 0.25rem 0.5rem;
|
| 1697 |
+
border-radius: 12px;
|
| 1698 |
+
font-size: 0.7rem;
|
| 1699 |
+
font-weight: bold;
|
| 1700 |
+
text-transform: uppercase;
|
| 1701 |
+
margin-top: 0.5rem;
|
| 1702 |
+
display: inline-block;
|
| 1703 |
+
}
|
| 1704 |
+
|
| 1705 |
+
/* Conversation Summary Section */
|
| 1706 |
+
.conversation-summary-section {
|
| 1707 |
+
margin-top: 2rem;
|
| 1708 |
+
padding-top: 2rem;
|
| 1709 |
+
border-top: 2px solid #f0f0f0;
|
| 1710 |
+
}
|
| 1711 |
+
|
| 1712 |
+
.conversation-summary-section h3 {
|
| 1713 |
+
margin: 0 0 1rem 0;
|
| 1714 |
+
color: #333;
|
| 1715 |
+
font-size: 1.3rem;
|
| 1716 |
+
}
|
| 1717 |
+
|
| 1718 |
+
.summary-content {
|
| 1719 |
+
background: #f8f9fa;
|
| 1720 |
+
padding: 1.5rem;
|
| 1721 |
+
border-radius: 10px;
|
| 1722 |
+
border-left: 4px solid #667eea;
|
| 1723 |
+
}
|
| 1724 |
+
|
| 1725 |
+
.summary-content p {
|
| 1726 |
+
margin: 0;
|
| 1727 |
+
line-height: 1.6;
|
| 1728 |
+
color: #555;
|
| 1729 |
+
}
|
| 1730 |
+
|
| 1731 |
+
/* Additional Session Details */
|
| 1732 |
+
.additional-session-details {
|
| 1733 |
+
margin-top: 2rem;
|
| 1734 |
+
padding-top: 2rem;
|
| 1735 |
+
border-top: 2px solid #f0f0f0;
|
| 1736 |
+
}
|
| 1737 |
+
|
| 1738 |
+
.additional-session-details h3 {
|
| 1739 |
+
margin: 0 0 1.5rem 0;
|
| 1740 |
+
color: #333;
|
| 1741 |
+
font-size: 1.3rem;
|
| 1742 |
+
}
|
| 1743 |
+
|
| 1744 |
+
.details-grid {
|
| 1745 |
+
display: grid;
|
| 1746 |
+
gap: 1.5rem;
|
| 1747 |
+
}
|
| 1748 |
+
|
| 1749 |
+
.detail-section {
|
| 1750 |
+
background: white;
|
| 1751 |
+
padding: 1.5rem;
|
| 1752 |
+
border-radius: 10px;
|
| 1753 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
| 1754 |
+
border-left: 4px solid #667eea;
|
| 1755 |
+
}
|
| 1756 |
+
|
| 1757 |
+
.detail-section h4 {
|
| 1758 |
+
margin: 0 0 1rem 0;
|
| 1759 |
+
color: #333;
|
| 1760 |
+
font-size: 1.1rem;
|
| 1761 |
+
}
|
| 1762 |
+
|
| 1763 |
+
/* Full Conversation Display */
|
| 1764 |
+
.conversation-full {
|
| 1765 |
+
max-height: 300px;
|
| 1766 |
+
overflow-y: auto;
|
| 1767 |
+
border: 1px solid #e0e0e0;
|
| 1768 |
+
border-radius: 8px;
|
| 1769 |
+
padding: 1rem;
|
| 1770 |
+
background: #f8f9fa;
|
| 1771 |
+
}
|
| 1772 |
+
|
| 1773 |
+
.message-item {
|
| 1774 |
+
margin-bottom: 1rem;
|
| 1775 |
+
padding: 0.75rem;
|
| 1776 |
+
border-radius: 8px;
|
| 1777 |
+
position: relative;
|
| 1778 |
+
}
|
| 1779 |
+
|
| 1780 |
+
.message-item.user-message {
|
| 1781 |
+
background: #e3f2fd;
|
| 1782 |
+
margin-left: 2rem;
|
| 1783 |
+
border-left: 3px solid #2196f3;
|
| 1784 |
+
}
|
| 1785 |
+
|
| 1786 |
+
.message-item.bot-message {
|
| 1787 |
+
background: #f3e5f5;
|
| 1788 |
+
margin-right: 2rem;
|
| 1789 |
+
border-left: 3px solid #9c27b0;
|
| 1790 |
+
}
|
| 1791 |
+
|
| 1792 |
+
.message-content {
|
| 1793 |
+
margin-bottom: 0.25rem;
|
| 1794 |
+
line-height: 1.4;
|
| 1795 |
+
}
|
| 1796 |
+
|
| 1797 |
+
.message-time {
|
| 1798 |
+
font-size: 0.8rem;
|
| 1799 |
+
color: #666;
|
| 1800 |
+
font-style: italic;
|
| 1801 |
+
}
|
| 1802 |
+
|
| 1803 |
+
/* Notes and Treatment Content */
|
| 1804 |
+
.notes-content, .treatment-content {
|
| 1805 |
+
background: #f8f9fa;
|
| 1806 |
+
padding: 1rem;
|
| 1807 |
+
border-radius: 8px;
|
| 1808 |
+
border-left: 3px solid #28a745;
|
| 1809 |
+
}
|
| 1810 |
+
|
| 1811 |
+
.notes-content p, .treatment-content p {
|
| 1812 |
+
margin: 0;
|
| 1813 |
+
line-height: 1.6;
|
| 1814 |
+
color: #555;
|
| 1815 |
+
}
|
| 1816 |
+
|
| 1817 |
+
/* Enhanced User Actions */
|
| 1818 |
+
.user-actions-section {
|
| 1819 |
+
margin-top: 2rem;
|
| 1820 |
+
padding-top: 2rem;
|
| 1821 |
+
border-top: 2px solid #f0f0f0;
|
| 1822 |
+
display: flex;
|
| 1823 |
+
gap: 1rem;
|
| 1824 |
+
justify-content: center;
|
| 1825 |
+
flex-wrap: wrap;
|
| 1826 |
+
}
|
| 1827 |
+
|
| 1828 |
+
.user-actions-section .btn {
|
| 1829 |
+
display: flex;
|
| 1830 |
+
align-items: center;
|
| 1831 |
+
gap: 0.5rem;
|
| 1832 |
+
padding: 0.75rem 1.5rem;
|
| 1833 |
+
border-radius: 8px;
|
| 1834 |
+
font-size: 0.9rem;
|
| 1835 |
+
font-weight: 600;
|
| 1836 |
+
text-decoration: none;
|
| 1837 |
+
border: none;
|
| 1838 |
+
cursor: pointer;
|
| 1839 |
+
transition: all 0.3s ease;
|
| 1840 |
+
}
|
| 1841 |
+
|
| 1842 |
+
.user-actions-section .btn-primary {
|
| 1843 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 1844 |
+
color: white;
|
| 1845 |
+
}
|
| 1846 |
+
|
| 1847 |
+
.user-actions-section .btn-primary:hover {
|
| 1848 |
+
transform: translateY(-2px);
|
| 1849 |
+
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
| 1850 |
+
}
|
| 1851 |
+
|
| 1852 |
+
.user-actions-section .btn-secondary {
|
| 1853 |
+
background: #f8f9fa;
|
| 1854 |
+
color: #666;
|
| 1855 |
+
border: 1px solid #e0e0e0;
|
| 1856 |
+
}
|
| 1857 |
+
|
| 1858 |
+
.user-actions-section .btn-secondary:hover {
|
| 1859 |
+
background: #e9ecef;
|
| 1860 |
+
transform: translateY(-2px);
|
| 1861 |
+
}
|
| 1862 |
+
|
| 1863 |
+
.user-actions-section .btn-success {
|
| 1864 |
+
background: linear-gradient(135deg, #28a745, #20c997);
|
| 1865 |
+
color: white;
|
| 1866 |
+
}
|
| 1867 |
+
|
| 1868 |
+
.user-actions-section .btn-success:hover {
|
| 1869 |
+
transform: translateY(-2px);
|
| 1870 |
+
box-shadow: 0 5px 15px rgba(40, 167, 69, 0.4);
|
| 1871 |
+
}
|
| 1872 |
+
|
| 1873 |
+
/* Responsive Design for Enhanced Modal */
|
| 1874 |
+
@media (max-width: 768px) {
|
| 1875 |
+
.session-header-section {
|
| 1876 |
+
flex-direction: column;
|
| 1877 |
+
gap: 1rem;
|
| 1878 |
+
text-align: center;
|
| 1879 |
+
}
|
| 1880 |
+
|
| 1881 |
+
.session-info-grid {
|
| 1882 |
+
grid-template-columns: 1fr;
|
| 1883 |
+
}
|
| 1884 |
+
|
| 1885 |
+
.user-info-grid {
|
| 1886 |
+
grid-template-columns: 1fr;
|
| 1887 |
+
}
|
| 1888 |
+
|
| 1889 |
+
.info-item {
|
| 1890 |
+
flex-direction: column;
|
| 1891 |
+
align-items: flex-start;
|
| 1892 |
+
gap: 0.25rem;
|
| 1893 |
+
}
|
| 1894 |
+
|
| 1895 |
+
.contact-item {
|
| 1896 |
+
flex-direction: column;
|
| 1897 |
+
align-items: flex-start;
|
| 1898 |
+
gap: 0.5rem;
|
| 1899 |
+
}
|
| 1900 |
+
|
| 1901 |
+
.sessions-timeline, .risk-timeline, .conversations-timeline {
|
| 1902 |
+
padding-left: 1.5rem;
|
| 1903 |
+
}
|
| 1904 |
+
|
| 1905 |
+
.timeline-marker, .risk-marker, .conv-marker {
|
| 1906 |
+
left: -1.25rem;
|
| 1907 |
+
}
|
| 1908 |
+
|
| 1909 |
+
.message-item.user-message {
|
| 1910 |
+
margin-left: 0;
|
| 1911 |
+
}
|
| 1912 |
+
|
| 1913 |
+
.message-item.bot-message {
|
| 1914 |
+
margin-right: 0;
|
| 1915 |
+
}
|
| 1916 |
+
|
| 1917 |
+
.user-actions-section {
|
| 1918 |
+
flex-direction: column;
|
| 1919 |
+
align-items: center;
|
| 1920 |
+
}
|
| 1921 |
+
|
| 1922 |
+
.user-actions-section .btn {
|
| 1923 |
+
width: 100%;
|
| 1924 |
+
justify-content: center;
|
| 1925 |
+
}
|
| 1926 |
+
}
|
| 1927 |
+
|
| 1928 |
+
.info-section {
|
| 1929 |
+
background: rgba(255, 255, 255, 0.95);
|
| 1930 |
+
padding: 2rem;
|
| 1931 |
+
border-radius: 16px;
|
| 1932 |
+
border-left: 5px solid #667eea;
|
| 1933 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(255, 255, 255, 0.2);
|
| 1934 |
+
backdrop-filter: blur(20px);
|
| 1935 |
+
position: relative;
|
| 1936 |
+
overflow: hidden;
|
| 1937 |
+
transition: all 0.3s ease;
|
| 1938 |
+
}
|
| 1939 |
+
|
| 1940 |
+
.info-section:hover {
|
| 1941 |
+
transform: translateY(-2px);
|
| 1942 |
+
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(255, 255, 255, 0.3);
|
| 1943 |
+
}
|
| 1944 |
+
|
| 1945 |
+
.info-section::before {
|
| 1946 |
+
content: '';
|
| 1947 |
+
position: absolute;
|
| 1948 |
+
top: 0;
|
| 1949 |
+
right: 0;
|
| 1950 |
+
width: 80px;
|
| 1951 |
+
height: 80px;
|
| 1952 |
+
background: radial-gradient(circle, rgba(102, 126, 234, 0.1) 0%, transparent 70%);
|
| 1953 |
+
border-radius: 50%;
|
| 1954 |
+
transform: translate(30px, -30px);
|
| 1955 |
+
opacity: 0;
|
| 1956 |
+
transition: opacity 0.3s ease;
|
| 1957 |
+
}
|
| 1958 |
+
|
| 1959 |
+
.info-section:hover::before {
|
| 1960 |
+
opacity: 1;
|
| 1961 |
+
}
|
| 1962 |
+
|
| 1963 |
+
.info-section h4 {
|
| 1964 |
+
margin: 0 0 1.5rem 0;
|
| 1965 |
+
color: #333;
|
| 1966 |
+
font-size: 1.2rem;
|
| 1967 |
+
font-weight: 700;
|
| 1968 |
+
border-bottom: 2px solid rgba(102, 126, 234, 0.2);
|
| 1969 |
+
padding-bottom: 0.75rem;
|
| 1970 |
+
position: relative;
|
| 1971 |
+
z-index: 2;
|
| 1972 |
+
}
|
| 1973 |
+
|
| 1974 |
+
.info-section h4::after {
|
| 1975 |
+
content: '';
|
| 1976 |
+
position: absolute;
|
| 1977 |
+
bottom: -2px;
|
| 1978 |
+
left: 0;
|
| 1979 |
+
width: 30px;
|
| 1980 |
+
height: 2px;
|
| 1981 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 1982 |
+
border-radius: 1px;
|
| 1983 |
+
}
|
| 1984 |
+
|
| 1985 |
+
.info-item {
|
| 1986 |
+
display: flex;
|
| 1987 |
+
justify-content: space-between;
|
| 1988 |
+
align-items: center;
|
| 1989 |
+
padding: 1rem 0;
|
| 1990 |
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
| 1991 |
+
transition: all 0.2s ease;
|
| 1992 |
+
position: relative;
|
| 1993 |
+
z-index: 2;
|
| 1994 |
+
}
|
| 1995 |
+
|
| 1996 |
+
.info-item:last-child {
|
| 1997 |
+
border-bottom: none;
|
| 1998 |
+
}
|
| 1999 |
+
|
| 2000 |
+
.info-item:hover {
|
| 2001 |
+
background: rgba(102, 126, 234, 0.05);
|
| 2002 |
+
border-radius: 8px;
|
| 2003 |
+
padding-left: 0.5rem;
|
| 2004 |
+
padding-right: 0.5rem;
|
| 2005 |
+
}
|
| 2006 |
+
|
| 2007 |
+
.info-item strong {
|
| 2008 |
+
color: #555;
|
| 2009 |
+
font-size: 0.95rem;
|
| 2010 |
+
font-weight: 600;
|
| 2011 |
+
min-width: 140px;
|
| 2012 |
+
}
|
| 2013 |
+
|
| 2014 |
+
.recent-sessions-section,
|
| 2015 |
+
.risk-history-section,
|
| 2016 |
+
.conversation-history-section {
|
| 2017 |
+
margin-top: 1.5rem;
|
| 2018 |
+
padding-top: 1.5rem;
|
| 2019 |
+
border-top: 1px solid #e0e0e0;
|
| 2020 |
+
}
|
| 2021 |
+
|
| 2022 |
+
.recent-sessions-section h4,
|
| 2023 |
+
.risk-history-section h4,
|
| 2024 |
+
.conversation-history-section h4 {
|
| 2025 |
+
margin: 0 0 1rem 0;
|
| 2026 |
+
color: #333;
|
| 2027 |
+
font-size: 1.1rem;
|
| 2028 |
+
border-bottom: 1px solid #e0e0e0;
|
| 2029 |
+
padding-bottom: 0.5rem;
|
| 2030 |
+
}
|
| 2031 |
+
|
| 2032 |
+
.session-header {
|
| 2033 |
+
display: flex;
|
| 2034 |
+
justify-content: space-between;
|
| 2035 |
+
align-items: center;
|
| 2036 |
+
margin-bottom: 0.5rem;
|
| 2037 |
+
}
|
| 2038 |
+
|
| 2039 |
+
.session-header strong {
|
| 2040 |
+
color: #333;
|
| 2041 |
+
font-size: 0.9rem;
|
| 2042 |
+
}
|
| 2043 |
+
|
| 2044 |
+
.session-details {
|
| 2045 |
+
display: flex;
|
| 2046 |
+
gap: 1rem;
|
| 2047 |
+
font-size: 0.8rem;
|
| 2048 |
+
color: #666;
|
| 2049 |
+
}
|
| 2050 |
+
|
| 2051 |
+
.session-details span {
|
| 2052 |
+
background: #f0f0f0;
|
| 2053 |
+
padding: 0.25rem 0.5rem;
|
| 2054 |
+
border-radius: 4px;
|
| 2055 |
+
}
|
| 2056 |
+
|
| 2057 |
+
/* Responsive Design for Session Details */
|
| 2058 |
+
@media (max-width: 768px) {
|
| 2059 |
+
.user-info-grid {
|
| 2060 |
+
grid-template-columns: 1fr;
|
| 2061 |
+
}
|
| 2062 |
+
|
| 2063 |
+
.info-item {
|
| 2064 |
+
flex-direction: column;
|
| 2065 |
+
align-items: flex-start;
|
| 2066 |
+
gap: 0.25rem;
|
| 2067 |
+
}
|
| 2068 |
+
|
| 2069 |
+
.info-item strong {
|
| 2070 |
+
min-width: auto;
|
| 2071 |
+
}
|
| 2072 |
+
|
| 2073 |
+
.session-details {
|
| 2074 |
+
flex-direction: column;
|
| 2075 |
+
gap: 0.25rem;
|
| 2076 |
+
}
|
| 2077 |
+
}
|
| 2078 |
+
|
| 2079 |
+
.user-actions-section {
|
| 2080 |
+
margin-top: 1.5rem;
|
| 2081 |
+
padding-top: 1.5rem;
|
| 2082 |
+
border-top: 1px solid #e0e0e0;
|
| 2083 |
+
display: flex;
|
| 2084 |
+
gap: 1rem;
|
| 2085 |
+
justify-content: center;
|
| 2086 |
+
}
|
| 2087 |
+
|
| 2088 |
+
.user-actions-section .btn {
|
| 2089 |
+
display: flex;
|
| 2090 |
+
align-items: center;
|
| 2091 |
+
gap: 0.5rem;
|
| 2092 |
+
padding: 0.75rem 1.5rem;
|
| 2093 |
+
border-radius: 8px;
|
| 2094 |
+
font-size: 0.9rem;
|
| 2095 |
+
font-weight: 600;
|
| 2096 |
+
text-decoration: none;
|
| 2097 |
+
border: none;
|
| 2098 |
+
cursor: pointer;
|
| 2099 |
+
transition: all 0.3s ease;
|
| 2100 |
+
}
|
| 2101 |
+
|
| 2102 |
+
.user-actions-section .btn-primary {
|
| 2103 |
+
background: linear-gradient(135deg, #667eea, #764ba2);
|
| 2104 |
+
color: white;
|
| 2105 |
+
}
|
| 2106 |
+
|
| 2107 |
+
.user-actions-section .btn-primary:hover {
|
| 2108 |
+
transform: translateY(-2px);
|
| 2109 |
+
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
| 2110 |
+
}
|
| 2111 |
+
|
| 2112 |
+
.user-actions-section .btn-secondary {
|
| 2113 |
+
background: #f8f9fa;
|
| 2114 |
+
color: #666;
|
| 2115 |
+
border: 1px solid #e0e0e0;
|
| 2116 |
+
}
|
| 2117 |
+
|
| 2118 |
+
.user-actions-section .btn-secondary:hover {
|
| 2119 |
+
background: #e9ecef;
|
| 2120 |
+
transform: translateY(-2px);
|
| 2121 |
+
}
|
| 2122 |
+
|
| 2123 |
+
@media (max-width: 768px) {
|
| 2124 |
+
.user-actions-section {
|
| 2125 |
+
flex-direction: column;
|
| 2126 |
+
align-items: center;
|
| 2127 |
+
}
|
| 2128 |
+
|
| 2129 |
+
.user-actions-section .btn {
|
| 2130 |
+
width: 100%;
|
| 2131 |
+
justify-content: center;
|
| 2132 |
+
}
|
| 2133 |
+
}
|
| 2134 |
+
|
| 2135 |
+
/* AdminLTE 4 Additional Enhancements for Professional Dashboard */
|
| 2136 |
+
/* Toast Notifications */
|
| 2137 |
+
.toast {
|
| 2138 |
+
background: rgba(255, 255, 255, 0.98) !important;
|
| 2139 |
+
border: none !important;
|
| 2140 |
+
border-radius: 16px !important;
|
| 2141 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2) !important;
|
| 2142 |
+
color: #333 !important;
|
| 2143 |
+
}
|
| 2144 |
+
|
| 2145 |
+
.toast-header {
|
| 2146 |
+
background: rgba(102, 126, 234, 0.1) !important;
|
| 2147 |
+
border-bottom: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 2148 |
+
color: #333 !important;
|
| 2149 |
+
border-radius: 16px 16px 0 0 !important;
|
| 2150 |
+
}
|
| 2151 |
+
|
| 2152 |
+
/* DataTables Integration */
|
| 2153 |
+
.dataTables_wrapper {
|
| 2154 |
+
color: #333 !important;
|
| 2155 |
+
}
|
| 2156 |
+
|
| 2157 |
+
.dataTables_wrapper .dataTables_length,
|
| 2158 |
+
.dataTables_wrapper .dataTables_filter,
|
| 2159 |
+
.dataTables_wrapper .dataTables_info,
|
| 2160 |
+
.dataTables_wrapper .dataTables_processing,
|
| 2161 |
+
.dataTables_wrapper .dataTables_paginate {
|
| 2162 |
+
color: #333 !important;
|
| 2163 |
+
}
|
| 2164 |
+
|
| 2165 |
+
.dataTables_wrapper .dataTables_filter input {
|
| 2166 |
+
background: rgba(255, 255, 255, 0.9) !important;
|
| 2167 |
+
border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 2168 |
+
color: #333 !important;
|
| 2169 |
+
border-radius: 12px !important;
|
| 2170 |
+
}
|
| 2171 |
+
|
| 2172 |
+
.dataTables_wrapper .dataTables_length select {
|
| 2173 |
+
background: rgba(255, 255, 255, 0.9) !important;
|
| 2174 |
+
border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 2175 |
+
color: #333 !important;
|
| 2176 |
+
border-radius: 12px !important;
|
| 2177 |
+
}
|
| 2178 |
+
|
| 2179 |
+
.dataTables_wrapper .dataTables_paginate .paginate_button {
|
| 2180 |
+
background: rgba(255, 255, 255, 0.9) !important;
|
| 2181 |
+
border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 2182 |
+
color: #333 !important;
|
| 2183 |
+
border-radius: 8px !important;
|
| 2184 |
+
margin: 0 2px !important;
|
| 2185 |
+
}
|
| 2186 |
+
|
| 2187 |
+
.dataTables_wrapper .dataTables_paginate .paginate_button:hover {
|
| 2188 |
+
background: rgba(102, 126, 234, 0.1) !important;
|
| 2189 |
+
color: #667eea !important;
|
| 2190 |
+
}
|
| 2191 |
+
|
| 2192 |
+
.dataTables_wrapper .dataTables_paginate .paginate_button.current {
|
| 2193 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
| 2194 |
+
color: white !important;
|
| 2195 |
+
}
|
| 2196 |
+
|
| 2197 |
+
/* Select2 Integration */
|
| 2198 |
+
.select2-container--default .select2-selection--single {
|
| 2199 |
+
background: rgba(255, 255, 255, 0.9) !important;
|
| 2200 |
+
border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 2201 |
+
color: #333 !important;
|
| 2202 |
+
border-radius: 12px !important;
|
| 2203 |
+
height: 48px !important;
|
| 2204 |
+
}
|
| 2205 |
+
|
| 2206 |
+
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
| 2207 |
+
color: #333 !important;
|
| 2208 |
+
line-height: 48px !important;
|
| 2209 |
+
padding-left: 16px !important;
|
| 2210 |
+
}
|
| 2211 |
+
|
| 2212 |
+
.select2-container--default .select2-dropdown {
|
| 2213 |
+
background: rgba(255, 255, 255, 0.98) !important;
|
| 2214 |
+
border: none !important;
|
| 2215 |
+
border-radius: 12px !important;
|
| 2216 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2) !important;
|
| 2217 |
+
}
|
| 2218 |
+
|
| 2219 |
+
.select2-container--default .select2-results__option {
|
| 2220 |
+
color: #333 !important;
|
| 2221 |
+
padding: 12px 16px !important;
|
| 2222 |
+
}
|
| 2223 |
+
|
| 2224 |
+
.select2-container--default .select2-results__option--highlighted[aria-selected] {
|
| 2225 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
| 2226 |
+
color: white !important;
|
| 2227 |
+
}
|
| 2228 |
+
|
| 2229 |
+
/* Chart.js Integration */
|
| 2230 |
+
.chart-container {
|
| 2231 |
+
background: rgba(255, 255, 255, 0.98) !important;
|
| 2232 |
+
border: none !important;
|
| 2233 |
+
border-radius: 16px !important;
|
| 2234 |
+
padding: 24px !important;
|
| 2235 |
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1) !important;
|
| 2236 |
+
}
|
| 2237 |
+
|
| 2238 |
+
/* SweetAlert2 Integration */
|
| 2239 |
+
.swal2-popup {
|
| 2240 |
+
background: rgba(255, 255, 255, 0.98) !important;
|
| 2241 |
+
color: #333 !important;
|
| 2242 |
+
border-radius: 20px !important;
|
| 2243 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2) !important;
|
| 2244 |
+
}
|
| 2245 |
+
|
| 2246 |
+
.swal2-title {
|
| 2247 |
+
color: #333 !important;
|
| 2248 |
+
font-weight: 700 !important;
|
| 2249 |
+
}
|
| 2250 |
+
|
| 2251 |
+
.swal2-content {
|
| 2252 |
+
color: #666 !important;
|
| 2253 |
+
}
|
| 2254 |
+
|
| 2255 |
+
.swal2-confirm {
|
| 2256 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
| 2257 |
+
border-radius: 12px !important;
|
| 2258 |
+
font-weight: 600 !important;
|
| 2259 |
+
}
|
| 2260 |
+
|
| 2261 |
+
.swal2-cancel {
|
| 2262 |
+
background: rgba(255, 255, 255, 0.9) !important;
|
| 2263 |
+
color: #333 !important;
|
| 2264 |
+
border: 1px solid rgba(0, 0, 0, 0.1) !important;
|
| 2265 |
+
border-radius: 12px !important;
|
| 2266 |
+
font-weight: 600 !important;
|
| 2267 |
+
}
|
| 2268 |
+
|
| 2269 |
+
/* Loading States */
|
| 2270 |
+
.loading {
|
| 2271 |
+
opacity: 0.6;
|
| 2272 |
+
pointer-events: none;
|
| 2273 |
+
position: relative;
|
| 2274 |
+
}
|
| 2275 |
+
|
| 2276 |
+
.loading::after {
|
| 2277 |
+
content: '';
|
| 2278 |
+
position: absolute;
|
| 2279 |
+
top: 50%;
|
| 2280 |
+
left: 50%;
|
| 2281 |
+
width: 20px;
|
| 2282 |
+
height: 20px;
|
| 2283 |
+
margin: -10px 0 0 -10px;
|
| 2284 |
+
border: 2px solid rgba(102, 126, 234, 0.3);
|
| 2285 |
+
border-top: 2px solid #667eea;
|
| 2286 |
+
border-radius: 50%;
|
| 2287 |
+
animation: spin 1s linear infinite;
|
| 2288 |
+
}
|
| 2289 |
+
|
| 2290 |
+
@keyframes spin {
|
| 2291 |
+
0% { transform: rotate(0deg); }
|
| 2292 |
+
100% { transform: rotate(360deg); }
|
| 2293 |
+
}
|
| 2294 |
+
|
| 2295 |
+
.spinner-border {
|
| 2296 |
+
color: #667eea !important;
|
| 2297 |
+
}
|
| 2298 |
+
|
| 2299 |
+
/* Enhanced Animations */
|
| 2300 |
+
.fade-in {
|
| 2301 |
+
animation: fadeIn 0.5s ease-in;
|
| 2302 |
+
}
|
| 2303 |
+
|
| 2304 |
+
@keyframes fadeIn {
|
| 2305 |
+
from {
|
| 2306 |
+
opacity: 0;
|
| 2307 |
+
transform: translateY(20px);
|
| 2308 |
+
}
|
| 2309 |
+
to {
|
| 2310 |
+
opacity: 1;
|
| 2311 |
+
transform: translateY(0);
|
| 2312 |
+
}
|
| 2313 |
+
}
|
| 2314 |
+
|
| 2315 |
+
.slide-in {
|
| 2316 |
+
animation: slideIn 0.4s ease-out;
|
| 2317 |
+
}
|
| 2318 |
+
|
| 2319 |
+
@keyframes slideIn {
|
| 2320 |
+
from { transform: translateX(-100%); }
|
| 2321 |
+
to { transform: translateX(0); }
|
| 2322 |
+
}
|
| 2323 |
+
|
| 2324 |
+
.bounce-in {
|
| 2325 |
+
animation: bounceIn 0.6s ease-out;
|
| 2326 |
+
}
|
| 2327 |
+
|
| 2328 |
+
@keyframes bounceIn {
|
| 2329 |
+
0% {
|
| 2330 |
+
opacity: 0;
|
| 2331 |
+
transform: scale(0.3);
|
| 2332 |
+
}
|
| 2333 |
+
50% {
|
| 2334 |
+
opacity: 1;
|
| 2335 |
+
transform: scale(1.05);
|
| 2336 |
+
}
|
| 2337 |
+
70% {
|
| 2338 |
+
transform: scale(0.9);
|
| 2339 |
+
}
|
| 2340 |
+
100% {
|
| 2341 |
+
opacity: 1;
|
| 2342 |
+
transform: scale(1);
|
| 2343 |
+
}
|
| 2344 |
+
}
|
| 2345 |
+
|
| 2346 |
+
/* Mobile Menu Toggle */
|
| 2347 |
+
.mobile-menu-toggle {
|
| 2348 |
+
display: none;
|
| 2349 |
+
position: fixed;
|
| 2350 |
+
top: 20px;
|
| 2351 |
+
left: 20px;
|
| 2352 |
+
z-index: 1000;
|
| 2353 |
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 2354 |
+
color: white;
|
| 2355 |
+
border: none;
|
| 2356 |
+
padding: 12px;
|
| 2357 |
+
border-radius: 12px;
|
| 2358 |
+
cursor: pointer;
|
| 2359 |
+
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
| 2360 |
+
transition: all 0.3s ease;
|
| 2361 |
+
}
|
| 2362 |
+
|
| 2363 |
+
.mobile-menu-toggle:hover {
|
| 2364 |
+
transform: translateY(-2px);
|
| 2365 |
+
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
| 2366 |
+
}
|
| 2367 |
+
|
| 2368 |
+
/* Responsive Enhancements */
|
| 2369 |
+
@media (max-width: 768px) {
|
| 2370 |
+
.mobile-menu-toggle {
|
| 2371 |
+
display: block;
|
| 2372 |
+
}
|
| 2373 |
+
|
| 2374 |
+
.professional-container {
|
| 2375 |
+
padding: 1rem;
|
| 2376 |
+
}
|
| 2377 |
+
|
| 2378 |
+
.professional-header {
|
| 2379 |
+
flex-direction: column;
|
| 2380 |
+
text-align: center;
|
| 2381 |
+
gap: 1rem;
|
| 2382 |
+
}
|
| 2383 |
+
|
| 2384 |
+
.stats-grid {
|
| 2385 |
+
grid-template-columns: repeat(2, 1fr);
|
| 2386 |
+
gap: 1rem;
|
| 2387 |
+
}
|
| 2388 |
+
|
| 2389 |
+
.quick-actions-grid {
|
| 2390 |
+
grid-template-columns: repeat(2, 1fr);
|
| 2391 |
+
gap: 1rem;
|
| 2392 |
+
}
|
| 2393 |
+
|
| 2394 |
+
.users-grid {
|
| 2395 |
+
grid-template-columns: 1fr;
|
| 2396 |
+
}
|
| 2397 |
+
|
| 2398 |
+
.sessions-grid {
|
| 2399 |
+
grid-template-columns: 1fr;
|
| 2400 |
+
}
|
| 2401 |
+
|
| 2402 |
+
.modal-content {
|
| 2403 |
+
margin: 5% auto;
|
| 2404 |
+
width: 95%;
|
| 2405 |
+
}
|
| 2406 |
+
|
| 2407 |
+
.modal-content.large {
|
| 2408 |
+
margin: 2% auto;
|
| 2409 |
+
width: 98%;
|
| 2410 |
+
}
|
| 2411 |
+
}
|
| 2412 |
+
|
| 2413 |
+
/* Print Styles */
|
| 2414 |
+
@media print {
|
| 2415 |
+
.mobile-menu-toggle,
|
| 2416 |
+
.btn,
|
| 2417 |
+
.modal {
|
| 2418 |
+
display: none !important;
|
| 2419 |
+
}
|
| 2420 |
+
|
| 2421 |
+
.professional-container {
|
| 2422 |
+
padding: 0 !important;
|
| 2423 |
+
max-width: 100% !important;
|
| 2424 |
+
}
|
| 2425 |
+
|
| 2426 |
+
.card {
|
| 2427 |
+
border: 1px solid #000 !important;
|
| 2428 |
+
box-shadow: none !important;
|
| 2429 |
+
break-inside: avoid;
|
| 2430 |
+
}
|
| 2431 |
+
|
| 2432 |
+
.stats-grid,
|
| 2433 |
+
.quick-actions-grid,
|
| 2434 |
+
.users-grid,
|
| 2435 |
+
.sessions-grid {
|
| 2436 |
+
display: block !important;
|
| 2437 |
+
}
|
| 2438 |
+
|
| 2439 |
+
.stat-card,
|
| 2440 |
+
.action-btn,
|
| 2441 |
+
.user-card,
|
| 2442 |
+
.session-card {
|
| 2443 |
+
margin-bottom: 1rem !important;
|
| 2444 |
+
page-break-inside: avoid;
|
| 2445 |
+
}
|
| 2446 |
+
}
|
chatbot/professional.js
ADDED
|
@@ -0,0 +1,1439 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(() => {
|
| 2 |
+
'use strict';
|
| 3 |
+
|
| 4 |
+
// Get API URL from configuration
|
| 5 |
+
const getAPIBaseUrl = () => {
|
| 6 |
+
if (window.AIMHSA && window.AIMHSA.Config) {
|
| 7 |
+
return window.AIMHSA.Config.getApiBaseUrl();
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
// Fallback to auto-detection
|
| 11 |
+
const loc = window.location;
|
| 12 |
+
if (loc.port === '8000') {
|
| 13 |
+
return `${loc.protocol}//${loc.hostname}:5057`;
|
| 14 |
+
} else if (loc.port === '5057' || loc.port === '') {
|
| 15 |
+
return loc.origin;
|
| 16 |
+
} else {
|
| 17 |
+
return 'http://localhost:5057';
|
| 18 |
+
}
|
| 19 |
+
};
|
| 20 |
+
|
| 21 |
+
const API_BASE_URL = getAPIBaseUrl();
|
| 22 |
+
|
| 23 |
+
// Elements
|
| 24 |
+
const professionalName = document.getElementById('professionalName');
|
| 25 |
+
const notificationsList = document.getElementById('notificationsList');
|
| 26 |
+
const upcomingSessions = document.getElementById('upcomingSessions');
|
| 27 |
+
const sessionHistory = document.getElementById('sessionHistory');
|
| 28 |
+
const markAllReadBtn = document.getElementById('markAllReadBtn');
|
| 29 |
+
const refreshSessionsBtn = document.getElementById('refreshSessionsBtn');
|
| 30 |
+
const refreshNotificationsBtn = document.getElementById('refreshNotificationsBtn');
|
| 31 |
+
const logoutBtn = document.getElementById('logoutBtn');
|
| 32 |
+
const sessionModal = document.getElementById('sessionModal');
|
| 33 |
+
const notesModal = document.getElementById('notesModal');
|
| 34 |
+
const reportsModal = document.getElementById('reportsModal');
|
| 35 |
+
const emergencyModal = document.getElementById('emergencyModal');
|
| 36 |
+
const notesForm = document.getElementById('notesForm');
|
| 37 |
+
const followUpRequired = document.getElementById('followUpRequired');
|
| 38 |
+
const followUpDateGroup = document.getElementById('followUpDateGroup');
|
| 39 |
+
|
| 40 |
+
// New elements
|
| 41 |
+
const totalSessions = document.getElementById('totalSessions');
|
| 42 |
+
const unreadNotifications = document.getElementById('unreadNotifications');
|
| 43 |
+
const upcomingToday = document.getElementById('upcomingToday');
|
| 44 |
+
const highRiskSessions = document.getElementById('highRiskSessions');
|
| 45 |
+
const sessionFilter = document.getElementById('sessionFilter');
|
| 46 |
+
const historyFilter = document.getElementById('historyFilter');
|
| 47 |
+
const viewAllSessionsBtn = document.getElementById('viewAllSessionsBtn');
|
| 48 |
+
const viewBookedUsersBtn = document.getElementById('viewBookedUsersBtn');
|
| 49 |
+
const addSessionNotesBtn = document.getElementById('addSessionNotesBtn');
|
| 50 |
+
const viewReportsBtn = document.getElementById('viewReportsBtn');
|
| 51 |
+
const emergencyContactsBtn = document.getElementById('emergencyContactsBtn');
|
| 52 |
+
const generateReportBtn = document.getElementById('generateReportBtn');
|
| 53 |
+
const reportContent = document.getElementById('reportContent');
|
| 54 |
+
const openIntakeBtn = document.getElementById('openIntakeBtn');
|
| 55 |
+
const intakeModal = document.getElementById('intakeModal');
|
| 56 |
+
const intakeForm = document.getElementById('intakeForm');
|
| 57 |
+
|
| 58 |
+
// Booked users elements
|
| 59 |
+
const bookedUsersList = document.getElementById('bookedUsersList');
|
| 60 |
+
const userFilter = document.getElementById('userFilter');
|
| 61 |
+
const refreshUsersBtn = document.getElementById('refreshUsersBtn');
|
| 62 |
+
const userProfileModal = document.getElementById('userProfileModal');
|
| 63 |
+
const userProfileContent = document.getElementById('userProfileContent');
|
| 64 |
+
|
| 65 |
+
// State
|
| 66 |
+
let currentProfessional = null;
|
| 67 |
+
let notifications = [];
|
| 68 |
+
let sessions = [];
|
| 69 |
+
let bookedUsers = [];
|
| 70 |
+
let currentSessionId = null;
|
| 71 |
+
|
| 72 |
+
// Initialize
|
| 73 |
+
init();
|
| 74 |
+
|
| 75 |
+
async function init() {
|
| 76 |
+
// Check if professional is logged in
|
| 77 |
+
const professionalData = localStorage.getItem('aimhsa_professional');
|
| 78 |
+
if (!professionalData) {
|
| 79 |
+
// Check if they're logged in as a different type of user
|
| 80 |
+
const userData = localStorage.getItem('aimhsa_account');
|
| 81 |
+
const adminData = localStorage.getItem('aimhsa_admin');
|
| 82 |
+
|
| 83 |
+
if (userData && userData !== 'null') {
|
| 84 |
+
alert('You are logged in as a regular user. Please logout and login as a professional.');
|
| 85 |
+
window.location.href = '/';
|
| 86 |
+
return;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
if (adminData) {
|
| 90 |
+
alert('You are logged in as an admin. Please logout and login as a professional.');
|
| 91 |
+
window.location.href = '/admin_dashboard.html';
|
| 92 |
+
return;
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
window.location.href = '/login';
|
| 96 |
+
return;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
currentProfessional = JSON.parse(professionalData);
|
| 100 |
+
professionalName.textContent = currentProfessional.name;
|
| 101 |
+
|
| 102 |
+
// Load initial data
|
| 103 |
+
await loadDashboardData();
|
| 104 |
+
await loadNotifications();
|
| 105 |
+
await loadSessions();
|
| 106 |
+
await loadBookedUsers();
|
| 107 |
+
|
| 108 |
+
// Set up auto-refresh
|
| 109 |
+
setInterval(loadDashboardData, 30000); // Every 30 seconds
|
| 110 |
+
setInterval(loadNotifications, 30000); // Every 30 seconds
|
| 111 |
+
setInterval(loadSessions, 60000); // Every minute
|
| 112 |
+
|
| 113 |
+
// Set up event listeners
|
| 114 |
+
setupEventListeners();
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// API Helper
|
| 118 |
+
async function api(path, opts = {}) {
|
| 119 |
+
const url = API_BASE_URL + path;
|
| 120 |
+
const headers = {
|
| 121 |
+
'Content-Type': 'application/json',
|
| 122 |
+
...opts.headers
|
| 123 |
+
};
|
| 124 |
+
// Include professional id header if available
|
| 125 |
+
if (currentProfessional?.professional_id) {
|
| 126 |
+
headers['X-Professional-ID'] = String(currentProfessional.professional_id);
|
| 127 |
+
}
|
| 128 |
+
const res = await fetch(url, {
|
| 129 |
+
headers,
|
| 130 |
+
...opts
|
| 131 |
+
});
|
| 132 |
+
|
| 133 |
+
if (!res.ok) {
|
| 134 |
+
const txt = await res.text();
|
| 135 |
+
throw new Error(txt || res.statusText);
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
return await res.json();
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
function setupEventListeners() {
|
| 142 |
+
// Logout
|
| 143 |
+
logoutBtn.addEventListener('click', logout);
|
| 144 |
+
|
| 145 |
+
// Notifications
|
| 146 |
+
markAllReadBtn.addEventListener('click', markAllNotificationsRead);
|
| 147 |
+
refreshNotificationsBtn.addEventListener('click', loadNotifications);
|
| 148 |
+
|
| 149 |
+
// Sessions
|
| 150 |
+
refreshSessionsBtn.addEventListener('click', loadSessions);
|
| 151 |
+
sessionFilter.addEventListener('change', filterSessions);
|
| 152 |
+
historyFilter.addEventListener('change', filterSessionHistory);
|
| 153 |
+
|
| 154 |
+
// Quick actions
|
| 155 |
+
viewAllSessionsBtn.addEventListener('click', () => {
|
| 156 |
+
sessionFilter.value = 'all';
|
| 157 |
+
loadSessions();
|
| 158 |
+
});
|
| 159 |
+
|
| 160 |
+
viewBookedUsersBtn.addEventListener('click', () => {
|
| 161 |
+
userFilter.value = 'all';
|
| 162 |
+
loadBookedUsers();
|
| 163 |
+
});
|
| 164 |
+
|
| 165 |
+
addSessionNotesBtn.addEventListener('click', openNotesModal);
|
| 166 |
+
viewReportsBtn.addEventListener('click', openReportsModal);
|
| 167 |
+
emergencyContactsBtn.addEventListener('click', openEmergencyModal);
|
| 168 |
+
openIntakeBtn.addEventListener('click', openIntakeModal);
|
| 169 |
+
|
| 170 |
+
// Booked users
|
| 171 |
+
refreshUsersBtn.addEventListener('click', loadBookedUsers);
|
| 172 |
+
userFilter.addEventListener('change', filterBookedUsers);
|
| 173 |
+
|
| 174 |
+
// Modals
|
| 175 |
+
document.querySelectorAll('.close').forEach(closeBtn => {
|
| 176 |
+
closeBtn.addEventListener('click', closeModals);
|
| 177 |
+
});
|
| 178 |
+
|
| 179 |
+
// Notes form
|
| 180 |
+
notesForm.addEventListener('submit', saveSessionNotes);
|
| 181 |
+
followUpRequired.addEventListener('change', toggleFollowUpDate);
|
| 182 |
+
intakeForm.addEventListener('submit', submitIntakeForm);
|
| 183 |
+
|
| 184 |
+
// Report generation
|
| 185 |
+
generateReportBtn.addEventListener('click', generateReport);
|
| 186 |
+
|
| 187 |
+
// Close modals when clicking outside
|
| 188 |
+
window.addEventListener('click', (e) => {
|
| 189 |
+
if (e.target.classList.contains('modal')) {
|
| 190 |
+
closeModals();
|
| 191 |
+
}
|
| 192 |
+
});
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
async function loadDashboardData() {
|
| 196 |
+
try {
|
| 197 |
+
// Load dashboard stats
|
| 198 |
+
const stats = await api('/professional/dashboard-stats');
|
| 199 |
+
updateDashboardStats(stats);
|
| 200 |
+
} catch (error) {
|
| 201 |
+
console.error('Error loading dashboard data:', error);
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
async function loadNotifications() {
|
| 206 |
+
try {
|
| 207 |
+
const data = await api('/professional/notifications');
|
| 208 |
+
notifications = data;
|
| 209 |
+
displayNotifications(notifications);
|
| 210 |
+
} catch (error) {
|
| 211 |
+
console.error('Error loading notifications:', error);
|
| 212 |
+
notificationsList.innerHTML = '<p>Error loading notifications</p>';
|
| 213 |
+
}
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
async function loadSessions() {
|
| 217 |
+
try {
|
| 218 |
+
const data = await api('/professional/sessions');
|
| 219 |
+
sessions = data;
|
| 220 |
+
displaySessions(sessions);
|
| 221 |
+
} catch (error) {
|
| 222 |
+
console.error('Error loading sessions:', error);
|
| 223 |
+
upcomingSessions.innerHTML = '<p>Error loading sessions</p>';
|
| 224 |
+
}
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
async function loadBookedUsers() {
|
| 228 |
+
try {
|
| 229 |
+
const data = await api('/professional/booked-users');
|
| 230 |
+
bookedUsers = data.users || [];
|
| 231 |
+
displayBookedUsers(bookedUsers);
|
| 232 |
+
} catch (error) {
|
| 233 |
+
console.error('Error loading booked users:', error);
|
| 234 |
+
bookedUsersList.innerHTML = '<p>Error loading booked users</p>';
|
| 235 |
+
}
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
function updateDashboardStats(stats) {
|
| 239 |
+
totalSessions.textContent = stats.totalSessions || 0;
|
| 240 |
+
unreadNotifications.textContent = stats.unreadNotifications || 0;
|
| 241 |
+
upcomingToday.textContent = stats.upcomingToday || 0;
|
| 242 |
+
highRiskSessions.textContent = stats.highRiskCases || 0;
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
function displayNotifications(notificationsData) {
|
| 246 |
+
if (!notificationsData || notificationsData.length === 0) {
|
| 247 |
+
notificationsList.innerHTML = '<p>No notifications</p>';
|
| 248 |
+
return;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
notificationsList.innerHTML = notificationsData.map(notification => `
|
| 252 |
+
<div class="notification-item ${notification.isRead ? '' : 'unread'}" onclick="markNotificationRead('${notification.id}')">
|
| 253 |
+
<div class="notification-icon">
|
| 254 |
+
<i class="fas ${getNotificationIcon(notification.type)}"></i>
|
| 255 |
+
</div>
|
| 256 |
+
<div class="notification-content">
|
| 257 |
+
<div class="notification-title">${notification.title}</div>
|
| 258 |
+
<div class="notification-message">${notification.message}</div>
|
| 259 |
+
<div class="notification-time">${formatDateTime(notification.createdAt)}</div>
|
| 260 |
+
</div>
|
| 261 |
+
</div>
|
| 262 |
+
`).join('');
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
function displaySessions(sessionsData) {
|
| 266 |
+
if (!sessionsData || sessionsData.length === 0) {
|
| 267 |
+
upcomingSessions.innerHTML = '<p>No sessions found</p>';
|
| 268 |
+
return;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
upcomingSessions.innerHTML = sessionsData.map(session => `
|
| 272 |
+
<div class="session-card ${session.riskLevel === 'high' ? 'high-risk' : ''}">
|
| 273 |
+
<div class="session-header">
|
| 274 |
+
<div class="session-user">
|
| 275 |
+
<div class="user-avatar">${session.userName ? session.userName.charAt(0).toUpperCase() : 'U'}</div>
|
| 276 |
+
<div class="user-info">
|
| 277 |
+
<h4>${session.userName || 'Anonymous User'}</h4>
|
| 278 |
+
<p>${session.userAccount || 'Guest'}</p>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
<div class="session-status status-${session.bookingStatus}">
|
| 282 |
+
${session.bookingStatus}
|
| 283 |
+
</div>
|
| 284 |
+
</div>
|
| 285 |
+
|
| 286 |
+
<div class="session-details">
|
| 287 |
+
<div class="detail-row">
|
| 288 |
+
<span class="detail-label">Session Type:</span>
|
| 289 |
+
<span class="detail-value">${session.sessionType}</span>
|
| 290 |
+
</div>
|
| 291 |
+
<div class="detail-row">
|
| 292 |
+
<span class="detail-label">Scheduled:</span>
|
| 293 |
+
<span class="detail-value">${formatDateTime(session.scheduledDatetime)}</span>
|
| 294 |
+
</div>
|
| 295 |
+
<div class="detail-row">
|
| 296 |
+
<span class="detail-label">Risk Level:</span>
|
| 297 |
+
<span class="detail-value">${session.riskLevel}</span>
|
| 298 |
+
</div>
|
| 299 |
+
<div class="detail-row">
|
| 300 |
+
<span class="detail-label">Risk Score:</span>
|
| 301 |
+
<span class="detail-value">${session.riskScore}/100</span>
|
| 302 |
+
</div>
|
| 303 |
+
${session.userPhone ? `
|
| 304 |
+
<div class="detail-row contact-info">
|
| 305 |
+
<span class="detail-label">📞 Contact:</span>
|
| 306 |
+
<span class="detail-value">
|
| 307 |
+
<a href="tel:${session.userPhone}" class="contact-link">${session.userPhone}</a>
|
| 308 |
+
</span>
|
| 309 |
+
</div>
|
| 310 |
+
` : ''}
|
| 311 |
+
${session.userEmail ? `
|
| 312 |
+
<div class="detail-row contact-info">
|
| 313 |
+
<span class="detail-label">📧 Email:</span>
|
| 314 |
+
<span class="detail-value">
|
| 315 |
+
<a href="mailto:${session.userEmail}" class="contact-link">${session.userEmail}</a>
|
| 316 |
+
</span>
|
| 317 |
+
</div>
|
| 318 |
+
` : ''}
|
| 319 |
+
${session.userLocation ? `
|
| 320 |
+
<div class="detail-row contact-info">
|
| 321 |
+
<span class="detail-label">📍 Location:</span>
|
| 322 |
+
<span class="detail-value">${session.userLocation}</span>
|
| 323 |
+
</div>
|
| 324 |
+
` : ''}
|
| 325 |
+
</div>
|
| 326 |
+
|
| 327 |
+
<div class="session-actions">
|
| 328 |
+
<button class="btn btn-primary btn-small" onclick="viewSessionDetails('${session.bookingId}')">
|
| 329 |
+
<i class="fas fa-eye"></i> View Details
|
| 330 |
+
</button>
|
| 331 |
+
${session.bookingStatus === 'pending' ? `
|
| 332 |
+
<button class="btn btn-primary btn-small" onclick="acceptSession('${session.bookingId}')">
|
| 333 |
+
<i class="fas fa-check"></i> Accept
|
| 334 |
+
</button>
|
| 335 |
+
<button class="btn btn-secondary btn-small" onclick="declineSession('${session.bookingId}')">
|
| 336 |
+
<i class="fas fa-times"></i> Decline
|
| 337 |
+
</button>
|
| 338 |
+
` : ''}
|
| 339 |
+
</div>
|
| 340 |
+
</div>
|
| 341 |
+
`).join('');
|
| 342 |
+
}
|
| 343 |
+
|
| 344 |
+
function filterSessions() {
|
| 345 |
+
const filter = sessionFilter.value;
|
| 346 |
+
let filteredSessions = sessions;
|
| 347 |
+
|
| 348 |
+
switch(filter) {
|
| 349 |
+
case 'today':
|
| 350 |
+
filteredSessions = sessions.filter(session => isToday(new Date(session.scheduledDatetime * 1000)));
|
| 351 |
+
break;
|
| 352 |
+
case 'this_week':
|
| 353 |
+
filteredSessions = sessions.filter(session => isThisWeek(new Date(session.scheduledDatetime * 1000)));
|
| 354 |
+
break;
|
| 355 |
+
case 'high_risk':
|
| 356 |
+
filteredSessions = sessions.filter(session => session.riskLevel === 'high' || session.riskLevel === 'critical');
|
| 357 |
+
break;
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
displaySessions(filteredSessions);
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
function filterSessionHistory() {
|
| 364 |
+
const filter = historyFilter.value;
|
| 365 |
+
// Implementation for filtering session history
|
| 366 |
+
console.log('Filtering session history by:', filter);
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
function displayBookedUsers(usersData) {
|
| 370 |
+
if (!usersData || usersData.length === 0) {
|
| 371 |
+
bookedUsersList.innerHTML = '<p>No booked users found</p>';
|
| 372 |
+
return;
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
bookedUsersList.innerHTML = usersData.map(user => `
|
| 376 |
+
<div class="user-card ${user.highestRiskLevel === 'high' || user.highestRiskLevel === 'critical' ? 'high-risk' : ''}">
|
| 377 |
+
<div class="user-header">
|
| 378 |
+
<div class="user-avatar">${user.fullName.charAt(0).toUpperCase()}</div>
|
| 379 |
+
<div class="user-info">
|
| 380 |
+
<h4>${user.fullName}</h4>
|
| 381 |
+
<p>@${user.userAccount}</p>
|
| 382 |
+
<span class="user-status status-${user.highestRiskLevel}">${user.highestRiskLevel.toUpperCase()}</span>
|
| 383 |
+
</div>
|
| 384 |
+
</div>
|
| 385 |
+
|
| 386 |
+
<div class="user-details">
|
| 387 |
+
<div class="detail-row">
|
| 388 |
+
<span class="detail-label">Email:</span>
|
| 389 |
+
<span class="detail-value">${user.email}</span>
|
| 390 |
+
</div>
|
| 391 |
+
<div class="detail-row">
|
| 392 |
+
<span class="detail-label">Phone:</span>
|
| 393 |
+
<span class="detail-value">${user.telephone}</span>
|
| 394 |
+
</div>
|
| 395 |
+
<div class="detail-row">
|
| 396 |
+
<span class="detail-label">Location:</span>
|
| 397 |
+
<span class="detail-value">${user.district}, ${user.province}</span>
|
| 398 |
+
</div>
|
| 399 |
+
<div class="detail-row">
|
| 400 |
+
<span class="detail-label">Total Bookings:</span>
|
| 401 |
+
<span class="detail-value">${user.totalBookings}</span>
|
| 402 |
+
</div>
|
| 403 |
+
<div class="detail-row">
|
| 404 |
+
<span class="detail-label">Highest Risk Score:</span>
|
| 405 |
+
<span class="detail-value">${user.highestRiskScore}/100</span>
|
| 406 |
+
</div>
|
| 407 |
+
<div class="detail-row">
|
| 408 |
+
<span class="detail-label">Last Booking:</span>
|
| 409 |
+
<span class="detail-value">${formatDateTime(user.lastBookingTime)}</span>
|
| 410 |
+
</div>
|
| 411 |
+
</div>
|
| 412 |
+
|
| 413 |
+
<div class="user-actions">
|
| 414 |
+
<button class="btn btn-primary btn-small" onclick="viewUserProfile('${user.userAccount}')">
|
| 415 |
+
<i class="fas fa-user"></i> View Profile
|
| 416 |
+
</button>
|
| 417 |
+
<button class="btn btn-secondary btn-small" onclick="viewUserSessions('${user.userAccount}')">
|
| 418 |
+
<i class="fas fa-calendar"></i> Sessions
|
| 419 |
+
</button>
|
| 420 |
+
</div>
|
| 421 |
+
</div>
|
| 422 |
+
`).join('');
|
| 423 |
+
}
|
| 424 |
+
|
| 425 |
+
function filterBookedUsers() {
|
| 426 |
+
const filter = userFilter.value;
|
| 427 |
+
let filteredUsers = bookedUsers;
|
| 428 |
+
|
| 429 |
+
switch(filter) {
|
| 430 |
+
case 'high_risk':
|
| 431 |
+
filteredUsers = bookedUsers.filter(user =>
|
| 432 |
+
user.highestRiskLevel === 'high' || user.highestRiskLevel === 'critical'
|
| 433 |
+
);
|
| 434 |
+
break;
|
| 435 |
+
case 'recent':
|
| 436 |
+
const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000);
|
| 437 |
+
filteredUsers = bookedUsers.filter(user =>
|
| 438 |
+
user.lastBookingTime * 1000 > oneWeekAgo
|
| 439 |
+
);
|
| 440 |
+
break;
|
| 441 |
+
case 'multiple_sessions':
|
| 442 |
+
filteredUsers = bookedUsers.filter(user => user.totalBookings > 1);
|
| 443 |
+
break;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
displayBookedUsers(filteredUsers);
|
| 447 |
+
}
|
| 448 |
+
|
| 449 |
+
async function markAllNotificationsRead() {
|
| 450 |
+
try {
|
| 451 |
+
await api('/professional/notifications/mark-all-read', { method: 'POST' });
|
| 452 |
+
await loadNotifications();
|
| 453 |
+
await loadDashboardData();
|
| 454 |
+
} catch (error) {
|
| 455 |
+
console.error('Error marking notifications as read:', error);
|
| 456 |
+
}
|
| 457 |
+
}
|
| 458 |
+
|
| 459 |
+
async function markNotificationRead(notificationId) {
|
| 460 |
+
try {
|
| 461 |
+
await api(`/professional/notifications/${notificationId}/read`, { method: 'POST' });
|
| 462 |
+
await loadNotifications();
|
| 463 |
+
await loadDashboardData();
|
| 464 |
+
} catch (error) {
|
| 465 |
+
console.error('Error marking notification as read:', error);
|
| 466 |
+
}
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
+
async function acceptSession(bookingId) {
|
| 470 |
+
try {
|
| 471 |
+
await api(`/professional/sessions/${bookingId}/accept`, { method: 'POST' });
|
| 472 |
+
await loadSessions();
|
| 473 |
+
await loadDashboardData();
|
| 474 |
+
alert('Session accepted successfully');
|
| 475 |
+
} catch (error) {
|
| 476 |
+
console.error('Error accepting session:', error);
|
| 477 |
+
alert('Failed to accept session');
|
| 478 |
+
}
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
async function declineSession(bookingId) {
|
| 482 |
+
try {
|
| 483 |
+
await api(`/professional/sessions/${bookingId}/decline`, { method: 'POST' });
|
| 484 |
+
await loadSessions();
|
| 485 |
+
await loadDashboardData();
|
| 486 |
+
alert('Session declined');
|
| 487 |
+
} catch (error) {
|
| 488 |
+
console.error('Error declining session:', error);
|
| 489 |
+
alert('Failed to decline session');
|
| 490 |
+
}
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
async function viewSessionDetails(bookingId) {
|
| 494 |
+
try {
|
| 495 |
+
currentSessionId = bookingId;
|
| 496 |
+
const sessionDetails = await api(`/professional/sessions/${bookingId}`);
|
| 497 |
+
|
| 498 |
+
// Convert the detailed session data to the format expected by displaySessionDetailsModal
|
| 499 |
+
const session = {
|
| 500 |
+
bookingId: sessionDetails.bookingId,
|
| 501 |
+
convId: sessionDetails.convId,
|
| 502 |
+
userAccount: sessionDetails.userAccount,
|
| 503 |
+
userName: sessionDetails.userName,
|
| 504 |
+
userIp: sessionDetails.userIp,
|
| 505 |
+
riskLevel: sessionDetails.riskLevel,
|
| 506 |
+
riskScore: sessionDetails.riskScore,
|
| 507 |
+
detectedIndicators: sessionDetails.detectedIndicators,
|
| 508 |
+
conversationSummary: sessionDetails.conversationSummary,
|
| 509 |
+
bookingStatus: sessionDetails.bookingStatus,
|
| 510 |
+
scheduledDatetime: sessionDetails.scheduledDatetime,
|
| 511 |
+
sessionType: sessionDetails.sessionType,
|
| 512 |
+
createdTs: sessionDetails.createdTs,
|
| 513 |
+
updatedTs: sessionDetails.updatedTs,
|
| 514 |
+
userPhone: sessionDetails.userPhone,
|
| 515 |
+
userEmail: sessionDetails.userEmail,
|
| 516 |
+
userLocation: sessionDetails.userLocation
|
| 517 |
+
};
|
| 518 |
+
|
| 519 |
+
displaySessionDetailsModal(session, sessionDetails);
|
| 520 |
+
} catch (error) {
|
| 521 |
+
console.error('Error loading session details:', error);
|
| 522 |
+
alert('Failed to load session details');
|
| 523 |
+
}
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
async function displaySessionDetailsModal(session, sessionDetails = null) {
|
| 527 |
+
const modal = document.getElementById('sessionModal');
|
| 528 |
+
const content = document.getElementById('sessionDetails');
|
| 529 |
+
|
| 530 |
+
let userInfo = null;
|
| 531 |
+
|
| 532 |
+
// If sessionDetails is provided, extract user information from it
|
| 533 |
+
if (sessionDetails) {
|
| 534 |
+
userInfo = {
|
| 535 |
+
userAccount: sessionDetails.userAccount,
|
| 536 |
+
fullName: sessionDetails.userFullName,
|
| 537 |
+
email: sessionDetails.userEmail,
|
| 538 |
+
telephone: sessionDetails.userPhone,
|
| 539 |
+
province: sessionDetails.userProvince,
|
| 540 |
+
district: sessionDetails.userDistrict,
|
| 541 |
+
userCreatedAt: sessionDetails.userCreatedAt,
|
| 542 |
+
totalBookings: sessionDetails.sessions ? sessionDetails.sessions.length : 0,
|
| 543 |
+
highestRiskLevel: sessionDetails.riskAssessments && sessionDetails.riskAssessments.length > 0
|
| 544 |
+
? sessionDetails.riskAssessments[0].riskLevel : 'unknown',
|
| 545 |
+
highestRiskScore: sessionDetails.riskAssessments && sessionDetails.riskAssessments.length > 0
|
| 546 |
+
? Math.max(...sessionDetails.riskAssessments.map(r => r.riskScore)) : 0,
|
| 547 |
+
firstBookingTime: sessionDetails.sessions && sessionDetails.sessions.length > 0
|
| 548 |
+
? Math.min(...sessionDetails.sessions.map(s => s.createdTs)) : null,
|
| 549 |
+
lastBookingTime: sessionDetails.sessions && sessionDetails.sessions.length > 0
|
| 550 |
+
? Math.max(...sessionDetails.sessions.map(s => s.createdTs)) : null,
|
| 551 |
+
sessions: sessionDetails.sessions || [],
|
| 552 |
+
riskAssessments: sessionDetails.riskAssessments || [],
|
| 553 |
+
conversations: sessionDetails.conversationHistory || []
|
| 554 |
+
};
|
| 555 |
+
} else {
|
| 556 |
+
// Fallback: try to get user info from booked users or API
|
| 557 |
+
try {
|
| 558 |
+
const user = bookedUsers.find(u => u.userAccount === session.userAccount);
|
| 559 |
+
if (user) {
|
| 560 |
+
userInfo = user;
|
| 561 |
+
} else {
|
| 562 |
+
// Try to get from individual user API
|
| 563 |
+
userInfo = await api(`/professional/users/${session.userAccount}`);
|
| 564 |
+
}
|
| 565 |
+
} catch (fallbackError) {
|
| 566 |
+
console.error('Error loading user info:', fallbackError);
|
| 567 |
+
}
|
| 568 |
+
}
|
| 569 |
+
|
| 570 |
+
content.innerHTML = `
|
| 571 |
+
<div class="session-details-modal">
|
| 572 |
+
<!-- Session Header -->
|
| 573 |
+
<div class="session-header-section">
|
| 574 |
+
<div class="session-header-info">
|
| 575 |
+
<div class="session-user-avatar">
|
| 576 |
+
${session.userName ? session.userName.charAt(0).toUpperCase() : 'U'}
|
| 577 |
+
</div>
|
| 578 |
+
<div class="session-user-details">
|
| 579 |
+
<h2>${session.userName || 'Anonymous User'}</h2>
|
| 580 |
+
<p class="user-account">@${session.userAccount || 'Guest'}</p>
|
| 581 |
+
<div class="session-meta">
|
| 582 |
+
<span class="session-id">Booking ID: ${session.bookingId}</span>
|
| 583 |
+
<span class="session-date">${formatDateTime(session.scheduledDatetime)}</span>
|
| 584 |
+
</div>
|
| 585 |
+
</div>
|
| 586 |
+
</div>
|
| 587 |
+
<div class="session-status-badge status-${session.bookingStatus}">
|
| 588 |
+
${session.bookingStatus.toUpperCase()}
|
| 589 |
+
</div>
|
| 590 |
+
</div>
|
| 591 |
+
|
| 592 |
+
<!-- Session Information Grid -->
|
| 593 |
+
<div class="session-info-grid">
|
| 594 |
+
<div class="info-card session-basic-info">
|
| 595 |
+
<h3>📋 Session Details</h3>
|
| 596 |
+
<div class="info-item">
|
| 597 |
+
<span class="info-label">Session Type:</span>
|
| 598 |
+
<span class="info-value session-type-${session.sessionType.toLowerCase()}">${session.sessionType}</span>
|
| 599 |
+
</div>
|
| 600 |
+
<div class="info-item">
|
| 601 |
+
<span class="info-label">Scheduled Time:</span>
|
| 602 |
+
<span class="info-value">${formatDateTime(session.scheduledDatetime)}</span>
|
| 603 |
+
</div>
|
| 604 |
+
<div class="info-item">
|
| 605 |
+
<span class="info-label">Created:</span>
|
| 606 |
+
<span class="info-value">${formatDateTime(session.createdTs)}</span>
|
| 607 |
+
</div>
|
| 608 |
+
<div class="info-item">
|
| 609 |
+
<span class="info-label">Last Updated:</span>
|
| 610 |
+
<span class="info-value">${formatDateTime(session.updatedTs)}</span>
|
| 611 |
+
</div>
|
| 612 |
+
</div>
|
| 613 |
+
|
| 614 |
+
<div class="info-card risk-assessment">
|
| 615 |
+
<h3>⚠️ Risk Assessment</h3>
|
| 616 |
+
<div class="risk-level-display risk-${session.riskLevel}">
|
| 617 |
+
<div class="risk-level-badge">${session.riskLevel.toUpperCase()}</div>
|
| 618 |
+
<div class="risk-score-display">${session.riskScore}/100</div>
|
| 619 |
+
</div>
|
| 620 |
+
${session.detectedIndicators ? `
|
| 621 |
+
<div class="risk-indicators-compact">
|
| 622 |
+
<h4>Detected Indicators:</h4>
|
| 623 |
+
<div class="indicators-tags">
|
| 624 |
+
${JSON.parse(session.detectedIndicators).slice(0, 5).map(indicator => `
|
| 625 |
+
<span class="indicator-tag">${indicator}</span>
|
| 626 |
+
`).join('')}
|
| 627 |
+
${JSON.parse(session.detectedIndicators).length > 5 ? `
|
| 628 |
+
<span class="indicator-more">+${JSON.parse(session.detectedIndicators).length - 5} more</span>
|
| 629 |
+
` : ''}
|
| 630 |
+
</div>
|
| 631 |
+
</div>
|
| 632 |
+
` : ''}
|
| 633 |
+
</div>
|
| 634 |
+
|
| 635 |
+
<div class="info-card contact-info-card">
|
| 636 |
+
<h3>📞 Contact Information</h3>
|
| 637 |
+
${session.userPhone ? `
|
| 638 |
+
<div class="contact-item">
|
| 639 |
+
<span class="contact-icon">📞</span>
|
| 640 |
+
<div class="contact-details">
|
| 641 |
+
<span class="contact-label">Phone</span>
|
| 642 |
+
<a href="tel:${session.userPhone}" class="contact-value">${session.userPhone}</a>
|
| 643 |
+
</div>
|
| 644 |
+
</div>
|
| 645 |
+
` : ''}
|
| 646 |
+
${session.userEmail ? `
|
| 647 |
+
<div class="contact-item">
|
| 648 |
+
<span class="contact-icon">📧</span>
|
| 649 |
+
<div class="contact-details">
|
| 650 |
+
<span class="contact-label">Email</span>
|
| 651 |
+
<a href="mailto:${session.userEmail}" class="contact-value">${session.userEmail}</a>
|
| 652 |
+
</div>
|
| 653 |
+
</div>
|
| 654 |
+
` : ''}
|
| 655 |
+
${session.userLocation ? `
|
| 656 |
+
<div class="contact-item">
|
| 657 |
+
<span class="contact-icon">📍</span>
|
| 658 |
+
<div class="contact-details">
|
| 659 |
+
<span class="contact-label">Location</span>
|
| 660 |
+
<span class="contact-value">${session.userLocation}</span>
|
| 661 |
+
</div>
|
| 662 |
+
</div>
|
| 663 |
+
` : ''}
|
| 664 |
+
</div>
|
| 665 |
+
</div>
|
| 666 |
+
|
| 667 |
+
<!-- User Comprehensive Information -->
|
| 668 |
+
${userInfo ? `
|
| 669 |
+
<div class="user-comprehensive-info">
|
| 670 |
+
<h3>👤 Complete User Profile</h3>
|
| 671 |
+
<div class="user-info-grid">
|
| 672 |
+
<div class="info-section">
|
| 673 |
+
<h4>📋 Personal Information</h4>
|
| 674 |
+
<div class="info-item">
|
| 675 |
+
<strong>Full Name:</strong>
|
| 676 |
+
<span class="info-value ${!userInfo.fullName ? 'missing-data' : ''}">${userInfo.fullName || 'Not provided'}</span>
|
| 677 |
+
</div>
|
| 678 |
+
<div class="info-item">
|
| 679 |
+
<strong>Email Address:</strong>
|
| 680 |
+
<span class="info-value ${!userInfo.email ? 'missing-data' : ''}">${userInfo.email || 'Not provided'}</span>
|
| 681 |
+
</div>
|
| 682 |
+
<div class="info-item">
|
| 683 |
+
<strong>Phone Number:</strong>
|
| 684 |
+
<span class="info-value ${!userInfo.telephone ? 'missing-data' : ''}">${userInfo.telephone || 'Not provided'}</span>
|
| 685 |
+
</div>
|
| 686 |
+
<div class="info-item">
|
| 687 |
+
<strong>Location:</strong>
|
| 688 |
+
<span class="info-value ${!userInfo.district && !userInfo.province ? 'missing-data' : ''}">${formatLocation(userInfo.district, userInfo.province)}</span>
|
| 689 |
+
</div>
|
| 690 |
+
<div class="info-item">
|
| 691 |
+
<strong>Account Created:</strong>
|
| 692 |
+
<span class="info-value ${!userInfo.userCreatedAt ? 'missing-data' : ''}">${userInfo.userCreatedAt ? formatDateTime(userInfo.userCreatedAt) : 'Not available'}</span>
|
| 693 |
+
</div>
|
| 694 |
+
</div>
|
| 695 |
+
|
| 696 |
+
<div class="info-section">
|
| 697 |
+
<h4>📊 Session Statistics</h4>
|
| 698 |
+
<div class="info-item">
|
| 699 |
+
<strong>Total Bookings:</strong>
|
| 700 |
+
<span class="info-value highlight">${userInfo.totalBookings || 0}</span>
|
| 701 |
+
</div>
|
| 702 |
+
<div class="info-item">
|
| 703 |
+
<strong>Highest Risk Level:</strong>
|
| 704 |
+
<span class="info-value risk-badge risk-${userInfo.highestRiskLevel || 'unknown'}">${userInfo.highestRiskLevel || 'Unknown'}</span>
|
| 705 |
+
</div>
|
| 706 |
+
<div class="info-item">
|
| 707 |
+
<strong>Highest Risk Score:</strong>
|
| 708 |
+
<span class="info-value highlight">${userInfo.highestRiskScore || 0}/100</span>
|
| 709 |
+
</div>
|
| 710 |
+
<div class="info-item">
|
| 711 |
+
<strong>First Booking:</strong>
|
| 712 |
+
<span class="info-value ${!userInfo.firstBookingTime ? 'missing-data' : ''}">${userInfo.firstBookingTime ? formatDateTime(userInfo.firstBookingTime) : 'Not available'}</span>
|
| 713 |
+
</div>
|
| 714 |
+
<div class="info-item">
|
| 715 |
+
<strong>Last Booking:</strong>
|
| 716 |
+
<span class="info-value ${!userInfo.lastBookingTime ? 'missing-data' : ''}">${userInfo.lastBookingTime ? formatDateTime(userInfo.lastBookingTime) : 'Not available'}</span>
|
| 717 |
+
</div>
|
| 718 |
+
</div>
|
| 719 |
+
</div>
|
| 720 |
+
|
| 721 |
+
${userInfo.sessions && userInfo.sessions.length > 0 ? `
|
| 722 |
+
<div class="recent-sessions-section">
|
| 723 |
+
<h4>📅 Recent Sessions (Last 5)</h4>
|
| 724 |
+
<div class="sessions-timeline">
|
| 725 |
+
${userInfo.sessions.slice(0, 5).map(s => `
|
| 726 |
+
<div class="timeline-item ${s.bookingId === session.bookingId ? 'current-session' : ''}">
|
| 727 |
+
<div class="timeline-marker"></div>
|
| 728 |
+
<div class="timeline-content">
|
| 729 |
+
<div class="session-header">
|
| 730 |
+
<strong>${s.sessionType}</strong>
|
| 731 |
+
<span class="session-status status-${s.bookingStatus}">${s.bookingStatus}</span>
|
| 732 |
+
</div>
|
| 733 |
+
<div class="session-details">
|
| 734 |
+
<span class="risk-info">Risk: ${s.riskLevel} (${s.riskScore}/100)</span>
|
| 735 |
+
<span class="date-info">${formatDateTime(s.scheduledDatetime)}</span>
|
| 736 |
+
</div>
|
| 737 |
+
${s.bookingId === session.bookingId ? '<div class="current-indicator">Current Session</div>' : ''}
|
| 738 |
+
</div>
|
| 739 |
+
</div>
|
| 740 |
+
`).join('')}
|
| 741 |
+
</div>
|
| 742 |
+
</div>
|
| 743 |
+
` : ''}
|
| 744 |
+
|
| 745 |
+
${userInfo.riskAssessments && userInfo.riskAssessments.length > 0 ? `
|
| 746 |
+
<div class="risk-history-section">
|
| 747 |
+
<h4>📈 Risk Assessment History</h4>
|
| 748 |
+
<div class="risk-timeline">
|
| 749 |
+
${userInfo.riskAssessments.slice(0, 5).map(risk => `
|
| 750 |
+
<div class="risk-timeline-item">
|
| 751 |
+
<div class="risk-marker risk-${risk.riskLevel}"></div>
|
| 752 |
+
<div class="risk-content">
|
| 753 |
+
<div class="risk-info">
|
| 754 |
+
<span class="risk-level risk-${risk.riskLevel}">${risk.riskLevel}</span>
|
| 755 |
+
<span class="risk-score">${risk.riskScore}/100</span>
|
| 756 |
+
</div>
|
| 757 |
+
<div class="risk-date">${formatDateTime(risk.timestamp)}</div>
|
| 758 |
+
</div>
|
| 759 |
+
</div>
|
| 760 |
+
`).join('')}
|
| 761 |
+
</div>
|
| 762 |
+
</div>
|
| 763 |
+
` : ''}
|
| 764 |
+
|
| 765 |
+
${userInfo.conversations && userInfo.conversations.length > 0 ? `
|
| 766 |
+
<div class="conversation-history-section">
|
| 767 |
+
<h4>💬 Recent Conversations</h4>
|
| 768 |
+
<div class="conversations-timeline">
|
| 769 |
+
${userInfo.conversations.slice(0, 3).map(conv => `
|
| 770 |
+
<div class="conversation-timeline-item">
|
| 771 |
+
<div class="conv-marker"></div>
|
| 772 |
+
<div class="conv-content">
|
| 773 |
+
<div class="conv-preview">${conv.preview}</div>
|
| 774 |
+
<div class="conv-date">${formatDateTime(conv.timestamp)}</div>
|
| 775 |
+
</div>
|
| 776 |
+
</div>
|
| 777 |
+
`).join('')}
|
| 778 |
+
</div>
|
| 779 |
+
</div>
|
| 780 |
+
` : ''}
|
| 781 |
+
|
| 782 |
+
<div class="user-actions-section">
|
| 783 |
+
<button class="btn btn-primary" onclick="viewUserProfile('${session.userAccount}')">
|
| 784 |
+
<i class="fas fa-user"></i> View Complete Profile
|
| 785 |
+
</button>
|
| 786 |
+
<button class="btn btn-secondary" onclick="viewUserSessions('${session.userAccount}')">
|
| 787 |
+
<i class="fas fa-calendar"></i> View All Sessions
|
| 788 |
+
</button>
|
| 789 |
+
<button class="btn btn-success" onclick="openNotesModal()">
|
| 790 |
+
<i class="fas fa-notes-medical"></i> Add Session Notes
|
| 791 |
+
</button>
|
| 792 |
+
</div>
|
| 793 |
+
</div>
|
| 794 |
+
` : ''}
|
| 795 |
+
|
| 796 |
+
<!-- Conversation Summary -->
|
| 797 |
+
${session.conversationSummary ? `
|
| 798 |
+
<div class="conversation-summary-section">
|
| 799 |
+
<h3>💭 Conversation Summary</h3>
|
| 800 |
+
<div class="summary-content">
|
| 801 |
+
<p>${session.conversationSummary}</p>
|
| 802 |
+
</div>
|
| 803 |
+
</div>
|
| 804 |
+
` : ''}
|
| 805 |
+
|
| 806 |
+
<!-- Additional Session Details -->
|
| 807 |
+
${sessionDetails ? `
|
| 808 |
+
<div class="additional-session-details">
|
| 809 |
+
<h3>📋 Additional Session Information</h3>
|
| 810 |
+
<div class="details-grid">
|
| 811 |
+
${sessionDetails.conversationHistory && sessionDetails.conversationHistory.length > 0 ? `
|
| 812 |
+
<div class="detail-section">
|
| 813 |
+
<h4>💬 Full Conversation</h4>
|
| 814 |
+
<div class="conversation-full">
|
| 815 |
+
${sessionDetails.conversationHistory.map(msg => `
|
| 816 |
+
<div class="message-item ${msg.sender === 'user' ? 'user-message' : 'bot-message'}">
|
| 817 |
+
<div class="message-content">${msg.content}</div>
|
| 818 |
+
<div class="message-time">${formatDateTime(msg.timestamp)}</div>
|
| 819 |
+
</div>
|
| 820 |
+
`).join('')}
|
| 821 |
+
</div>
|
| 822 |
+
</div>
|
| 823 |
+
` : ''}
|
| 824 |
+
|
| 825 |
+
${sessionDetails.sessionNotes && sessionDetails.sessionNotes.notes ? `
|
| 826 |
+
<div class="detail-section">
|
| 827 |
+
<h4>📝 Session Notes</h4>
|
| 828 |
+
<div class="notes-content">
|
| 829 |
+
<p>${sessionDetails.sessionNotes.notes}</p>
|
| 830 |
+
</div>
|
| 831 |
+
</div>
|
| 832 |
+
` : ''}
|
| 833 |
+
|
| 834 |
+
${sessionDetails.sessionNotes && sessionDetails.sessionNotes.treatmentPlan ? `
|
| 835 |
+
<div class="detail-section">
|
| 836 |
+
<h4>🎯 Treatment Plan</h4>
|
| 837 |
+
<div class="treatment-content">
|
| 838 |
+
<p>${sessionDetails.sessionNotes.treatmentPlan}</p>
|
| 839 |
+
</div>
|
| 840 |
+
</div>
|
| 841 |
+
` : ''}
|
| 842 |
+
</div>
|
| 843 |
+
</div>
|
| 844 |
+
` : ''}
|
| 845 |
+
</div>
|
| 846 |
+
`;
|
| 847 |
+
|
| 848 |
+
modal.style.display = 'block';
|
| 849 |
+
}
|
| 850 |
+
|
| 851 |
+
function openNotesModal() {
|
| 852 |
+
notesModal.style.display = 'block';
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
function openReportsModal() {
|
| 856 |
+
reportsModal.style.display = 'block';
|
| 857 |
+
}
|
| 858 |
+
|
| 859 |
+
function openEmergencyModal() {
|
| 860 |
+
emergencyModal.style.display = 'block';
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
async function viewUserProfile(userAccount) {
|
| 864 |
+
try {
|
| 865 |
+
// Try to get user from booked users first
|
| 866 |
+
let user = bookedUsers.find(u => u.userAccount === userAccount);
|
| 867 |
+
|
| 868 |
+
if (!user) {
|
| 869 |
+
// If not found, get from API
|
| 870 |
+
user = await api(`/professional/users/${userAccount}`);
|
| 871 |
+
}
|
| 872 |
+
|
| 873 |
+
if (!user) {
|
| 874 |
+
alert('User not found');
|
| 875 |
+
return;
|
| 876 |
+
}
|
| 877 |
+
|
| 878 |
+
displayUserProfileModal(user);
|
| 879 |
+
} catch (error) {
|
| 880 |
+
console.error('Error loading user profile:', error);
|
| 881 |
+
alert('Failed to load user profile');
|
| 882 |
+
}
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
function displayUserProfileModal(user) {
|
| 886 |
+
userProfileContent.innerHTML = `
|
| 887 |
+
<div class="user-profile-details">
|
| 888 |
+
<div class="profile-header">
|
| 889 |
+
<div class="profile-avatar">${user.fullName.charAt(0).toUpperCase()}</div>
|
| 890 |
+
<div class="profile-info">
|
| 891 |
+
<h3>${user.fullName}</h3>
|
| 892 |
+
<p>@${user.userAccount}</p>
|
| 893 |
+
<span class="risk-badge risk-${user.highestRiskLevel}">${user.highestRiskLevel.toUpperCase()}</span>
|
| 894 |
+
</div>
|
| 895 |
+
</div>
|
| 896 |
+
|
| 897 |
+
<div class="profile-sections">
|
| 898 |
+
<div class="profile-section">
|
| 899 |
+
<h4>Contact Information</h4>
|
| 900 |
+
<div class="profile-details">
|
| 901 |
+
<div class="detail-item">
|
| 902 |
+
<strong>Email:</strong> ${user.email}
|
| 903 |
+
</div>
|
| 904 |
+
<div class="detail-item">
|
| 905 |
+
<strong>Phone:</strong> ${user.telephone}
|
| 906 |
+
</div>
|
| 907 |
+
<div class="detail-item">
|
| 908 |
+
<strong>Location:</strong> ${user.district}, ${user.province}
|
| 909 |
+
</div>
|
| 910 |
+
<div class="detail-item">
|
| 911 |
+
<strong>User Since:</strong> ${formatDateTime(user.userCreatedAt)}
|
| 912 |
+
</div>
|
| 913 |
+
</div>
|
| 914 |
+
</div>
|
| 915 |
+
|
| 916 |
+
<div class="profile-section">
|
| 917 |
+
<h4>Session Statistics</h4>
|
| 918 |
+
<div class="profile-details">
|
| 919 |
+
<div class="detail-item">
|
| 920 |
+
<strong>Total Bookings:</strong> ${user.totalBookings}
|
| 921 |
+
</div>
|
| 922 |
+
<div class="detail-item">
|
| 923 |
+
<strong>Highest Risk Level:</strong> ${user.highestRiskLevel}
|
| 924 |
+
</div>
|
| 925 |
+
<div class="detail-item">
|
| 926 |
+
<strong>Highest Risk Score:</strong> ${user.highestRiskScore}/100
|
| 927 |
+
</div>
|
| 928 |
+
<div class="detail-item">
|
| 929 |
+
<strong>First Booking:</strong> ${formatDateTime(user.firstBookingTime)}
|
| 930 |
+
</div>
|
| 931 |
+
<div class="detail-item">
|
| 932 |
+
<strong>Last Booking:</strong> ${formatDateTime(user.lastBookingTime)}
|
| 933 |
+
</div>
|
| 934 |
+
</div>
|
| 935 |
+
</div>
|
| 936 |
+
|
| 937 |
+
<div class="profile-section">
|
| 938 |
+
<h4>Recent Sessions</h4>
|
| 939 |
+
<div class="sessions-list">
|
| 940 |
+
${user.sessions.slice(0, 5).map(session => `
|
| 941 |
+
<div class="session-item">
|
| 942 |
+
<div class="session-info">
|
| 943 |
+
<strong>${session.sessionType}</strong>
|
| 944 |
+
<span class="session-status status-${session.bookingStatus}">${session.bookingStatus}</span>
|
| 945 |
+
</div>
|
| 946 |
+
<div class="session-details">
|
| 947 |
+
<span>Risk: ${session.riskLevel} (${session.riskScore}/100)</span>
|
| 948 |
+
<span>Date: ${formatDateTime(session.scheduledDatetime)}</span>
|
| 949 |
+
</div>
|
| 950 |
+
</div>
|
| 951 |
+
`).join('')}
|
| 952 |
+
</div>
|
| 953 |
+
</div>
|
| 954 |
+
|
| 955 |
+
<div class="profile-section">
|
| 956 |
+
<h4>Risk Assessment History</h4>
|
| 957 |
+
<div class="risk-history">
|
| 958 |
+
${user.riskAssessments.slice(0, 5).map(risk => `
|
| 959 |
+
<div class="risk-item">
|
| 960 |
+
<div class="risk-info">
|
| 961 |
+
<span class="risk-level risk-${risk.riskLevel}">${risk.riskLevel}</span>
|
| 962 |
+
<span class="risk-score">${risk.riskScore}/100</span>
|
| 963 |
+
</div>
|
| 964 |
+
<div class="risk-date">${formatDateTime(risk.timestamp)}</div>
|
| 965 |
+
</div>
|
| 966 |
+
`).join('')}
|
| 967 |
+
</div>
|
| 968 |
+
</div>
|
| 969 |
+
|
| 970 |
+
<div class="profile-section">
|
| 971 |
+
<h4>Conversation History</h4>
|
| 972 |
+
<div class="conversations-list">
|
| 973 |
+
${user.conversations.map(conv => `
|
| 974 |
+
<div class="conversation-item">
|
| 975 |
+
<div class="conv-preview">${conv.preview}</div>
|
| 976 |
+
<div class="conv-date">${formatDateTime(conv.timestamp)}</div>
|
| 977 |
+
</div>
|
| 978 |
+
`).join('')}
|
| 979 |
+
</div>
|
| 980 |
+
</div>
|
| 981 |
+
</div>
|
| 982 |
+
</div>
|
| 983 |
+
`;
|
| 984 |
+
|
| 985 |
+
userProfileModal.style.display = 'block';
|
| 986 |
+
}
|
| 987 |
+
|
| 988 |
+
function viewUserSessions(userAccount) {
|
| 989 |
+
// Filter sessions to show only this user's sessions
|
| 990 |
+
const userSessions = sessions.filter(session => session.userAccount === userAccount);
|
| 991 |
+
displaySessions(userSessions);
|
| 992 |
+
// Scroll to sessions section
|
| 993 |
+
document.querySelector('.sessions-section').scrollIntoView({ behavior: 'smooth' });
|
| 994 |
+
}
|
| 995 |
+
|
| 996 |
+
function closeModals() {
|
| 997 |
+
document.querySelectorAll('.modal').forEach(modal => {
|
| 998 |
+
modal.style.display = 'none';
|
| 999 |
+
});
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
function openIntakeModal() {
|
| 1003 |
+
// Prefill from selected session when available
|
| 1004 |
+
const selected = sessions.find(s => s.bookingId === currentSessionId);
|
| 1005 |
+
if (selected) {
|
| 1006 |
+
document.getElementById('intakeUsername').value = selected.userAccount || '';
|
| 1007 |
+
document.getElementById('intakeEmail').value = '';
|
| 1008 |
+
document.getElementById('intakeFullName').value = selected.userName || '';
|
| 1009 |
+
document.getElementById('intakePhone').value = '';
|
| 1010 |
+
document.getElementById('intakeProvince').value = '';
|
| 1011 |
+
document.getElementById('intakeDistrict').value = '';
|
| 1012 |
+
} else {
|
| 1013 |
+
intakeForm.reset();
|
| 1014 |
+
}
|
| 1015 |
+
intakeModal.style.display = 'block';
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
async function submitIntakeForm(e) {
|
| 1019 |
+
e.preventDefault();
|
| 1020 |
+
const payload = {
|
| 1021 |
+
username: document.getElementById('intakeUsername').value.trim(),
|
| 1022 |
+
email: document.getElementById('intakeEmail').value.trim(),
|
| 1023 |
+
full_name: document.getElementById('intakeFullName').value.trim(),
|
| 1024 |
+
phone: document.getElementById('intakePhone').value.trim(),
|
| 1025 |
+
province: document.getElementById('intakeProvince').value.trim(),
|
| 1026 |
+
district: document.getElementById('intakeDistrict').value.trim(),
|
| 1027 |
+
password: document.getElementById('intakePassword').value,
|
| 1028 |
+
confirm_password: document.getElementById('intakeConfirmPassword').value
|
| 1029 |
+
};
|
| 1030 |
+
try {
|
| 1031 |
+
await api('/professional/users/intake', {
|
| 1032 |
+
method: 'POST',
|
| 1033 |
+
body: JSON.stringify(payload)
|
| 1034 |
+
});
|
| 1035 |
+
alert('User profile saved');
|
| 1036 |
+
closeModals();
|
| 1037 |
+
} catch (err) {
|
| 1038 |
+
console.error('Intake save failed:', err);
|
| 1039 |
+
alert('Failed to save user');
|
| 1040 |
+
}
|
| 1041 |
+
}
|
| 1042 |
+
|
| 1043 |
+
function toggleFollowUpDate() {
|
| 1044 |
+
followUpDateGroup.style.display = followUpRequired.checked ? 'block' : 'none';
|
| 1045 |
+
}
|
| 1046 |
+
|
| 1047 |
+
async function saveSessionNotes(e) {
|
| 1048 |
+
e.preventDefault();
|
| 1049 |
+
try {
|
| 1050 |
+
if (!currentSessionId) {
|
| 1051 |
+
alert('Open a session to add notes');
|
| 1052 |
+
return;
|
| 1053 |
+
}
|
| 1054 |
+
const payload = {
|
| 1055 |
+
notes: document.getElementById('sessionNotes').value,
|
| 1056 |
+
treatmentPlan: document.getElementById('treatmentPlan').value,
|
| 1057 |
+
followUpRequired: followUpRequired.checked,
|
| 1058 |
+
followUpDate: document.getElementById('followUpDate').value || null,
|
| 1059 |
+
professional_id: currentProfessional?.professional_id
|
| 1060 |
+
};
|
| 1061 |
+
await api(`/professional/sessions/${currentSessionId}/notes`, {
|
| 1062 |
+
method: 'POST',
|
| 1063 |
+
body: JSON.stringify(payload)
|
| 1064 |
+
});
|
| 1065 |
+
alert('Session notes saved successfully');
|
| 1066 |
+
closeModals();
|
| 1067 |
+
} catch (err) {
|
| 1068 |
+
console.error('Error saving notes:', err);
|
| 1069 |
+
alert('Failed to save notes');
|
| 1070 |
+
}
|
| 1071 |
+
}
|
| 1072 |
+
|
| 1073 |
+
async function generateReport() {
|
| 1074 |
+
try {
|
| 1075 |
+
const report = await api('/professional/reports/generate', {
|
| 1076 |
+
method: 'POST',
|
| 1077 |
+
body: JSON.stringify({
|
| 1078 |
+
period: document.getElementById('reportPeriod').value,
|
| 1079 |
+
type: document.getElementById('reportType').value
|
| 1080 |
+
})
|
| 1081 |
+
});
|
| 1082 |
+
|
| 1083 |
+
displayReport(report);
|
| 1084 |
+
} catch (error) {
|
| 1085 |
+
console.error('Error generating report:', error);
|
| 1086 |
+
alert('Failed to generate report');
|
| 1087 |
+
}
|
| 1088 |
+
}
|
| 1089 |
+
|
| 1090 |
+
function displayReport(report) {
|
| 1091 |
+
reportContent.innerHTML = `
|
| 1092 |
+
<div class="report-summary">
|
| 1093 |
+
<h3>Report Summary</h3>
|
| 1094 |
+
<div class="report-stats">
|
| 1095 |
+
<div class="stat-item">
|
| 1096 |
+
<span class="stat-label">Total Sessions:</span>
|
| 1097 |
+
<span class="stat-value">${report.totalSessions}</span>
|
| 1098 |
+
</div>
|
| 1099 |
+
<div class="stat-item">
|
| 1100 |
+
<span class="stat-label">Unique Users:</span>
|
| 1101 |
+
<span class="stat-value">${report.uniqueUsers}</span>
|
| 1102 |
+
</div>
|
| 1103 |
+
<div class="stat-item">
|
| 1104 |
+
<span class="stat-label">High Risk Cases:</span>
|
| 1105 |
+
<span class="stat-value">${report.highRiskCases}</span>
|
| 1106 |
+
</div>
|
| 1107 |
+
</div>
|
| 1108 |
+
</div>
|
| 1109 |
+
`;
|
| 1110 |
+
}
|
| 1111 |
+
|
| 1112 |
+
function logout() {
|
| 1113 |
+
localStorage.removeItem('aimhsa_professional');
|
| 1114 |
+
window.location.href = '/login';
|
| 1115 |
+
}
|
| 1116 |
+
|
| 1117 |
+
// Utility functions
|
| 1118 |
+
function formatDateTime(timestamp) {
|
| 1119 |
+
if (!timestamp) return 'N/A';
|
| 1120 |
+
const date = new Date(timestamp * 1000);
|
| 1121 |
+
return date.toLocaleString();
|
| 1122 |
+
}
|
| 1123 |
+
|
| 1124 |
+
function isToday(date) {
|
| 1125 |
+
const today = new Date();
|
| 1126 |
+
return date.toDateString() === today.toDateString();
|
| 1127 |
+
}
|
| 1128 |
+
|
| 1129 |
+
function isThisWeek(date) {
|
| 1130 |
+
const today = new Date();
|
| 1131 |
+
const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
|
| 1132 |
+
return date >= weekAgo && date <= today;
|
| 1133 |
+
}
|
| 1134 |
+
|
| 1135 |
+
function getNotificationIcon(type) {
|
| 1136 |
+
const icons = {
|
| 1137 |
+
'session': 'fa-calendar-check',
|
| 1138 |
+
'risk': 'fa-exclamation-triangle',
|
| 1139 |
+
'user': 'fa-user',
|
| 1140 |
+
'system': 'fa-cog',
|
| 1141 |
+
'emergency': 'fa-bell'
|
| 1142 |
+
};
|
| 1143 |
+
return icons[type] || 'fa-bell';
|
| 1144 |
+
}
|
| 1145 |
+
|
| 1146 |
+
// Helper functions
|
| 1147 |
+
function formatLocation(district, province) {
|
| 1148 |
+
if (!district && !province) return 'Not provided';
|
| 1149 |
+
if (district && province) return `${district}, ${province}`;
|
| 1150 |
+
if (district) return district;
|
| 1151 |
+
if (province) return province;
|
| 1152 |
+
return 'Not provided';
|
| 1153 |
+
}
|
| 1154 |
+
|
| 1155 |
+
function getRiskBadgeClass(riskLevel) {
|
| 1156 |
+
const riskClasses = {
|
| 1157 |
+
'low': 'risk-low',
|
| 1158 |
+
'medium': 'risk-medium',
|
| 1159 |
+
'high': 'risk-high',
|
| 1160 |
+
'critical': 'risk-critical',
|
| 1161 |
+
'unknown': 'risk-unknown'
|
| 1162 |
+
};
|
| 1163 |
+
return riskClasses[riskLevel] || 'risk-unknown';
|
| 1164 |
+
}
|
| 1165 |
+
|
| 1166 |
+
function formatMissingData(value, fallback = 'Not provided') {
|
| 1167 |
+
return value || fallback;
|
| 1168 |
+
}
|
| 1169 |
+
|
| 1170 |
+
// Global functions for onclick handlers
|
| 1171 |
+
window.markNotificationRead = markNotificationRead;
|
| 1172 |
+
window.acceptSession = acceptSession;
|
| 1173 |
+
window.declineSession = declineSession;
|
| 1174 |
+
window.viewSessionDetails = viewSessionDetails;
|
| 1175 |
+
window.viewUserProfile = viewUserProfile;
|
| 1176 |
+
window.viewUserSessions = viewUserSessions;
|
| 1177 |
+
|
| 1178 |
+
// AdminLTE 4 Enhancements
|
| 1179 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 1180 |
+
// Initialize AdminLTE components
|
| 1181 |
+
if (typeof $ !== 'undefined' && $.fn.DataTable) {
|
| 1182 |
+
// Initialize DataTables if available
|
| 1183 |
+
initializeDataTables();
|
| 1184 |
+
}
|
| 1185 |
+
|
| 1186 |
+
// Initialize mobile menu toggle
|
| 1187 |
+
initializeMobileMenu();
|
| 1188 |
+
|
| 1189 |
+
// Initialize tooltips
|
| 1190 |
+
initializeTooltips();
|
| 1191 |
+
|
| 1192 |
+
// Initialize loading states
|
| 1193 |
+
initializeLoadingStates();
|
| 1194 |
+
|
| 1195 |
+
// Initialize animations
|
| 1196 |
+
initializeAnimations();
|
| 1197 |
+
|
| 1198 |
+
// Initialize enhanced notifications
|
| 1199 |
+
initializeEnhancedNotifications();
|
| 1200 |
+
});
|
| 1201 |
+
|
| 1202 |
+
function initializeDataTables() {
|
| 1203 |
+
// Enhanced table functionality with AdminLTE styling
|
| 1204 |
+
const tables = document.querySelectorAll('table');
|
| 1205 |
+
tables.forEach(table => {
|
| 1206 |
+
if (typeof $ !== 'undefined' && $.fn.DataTable) {
|
| 1207 |
+
$(table).DataTable({
|
| 1208 |
+
responsive: true,
|
| 1209 |
+
pageLength: 10,
|
| 1210 |
+
order: [[0, 'desc']], // Sort by first column descending
|
| 1211 |
+
columnDefs: [
|
| 1212 |
+
{ targets: [-1], orderable: false } // Actions column
|
| 1213 |
+
],
|
| 1214 |
+
language: {
|
| 1215 |
+
search: "Search:",
|
| 1216 |
+
lengthMenu: "Show _MENU_ entries per page",
|
| 1217 |
+
info: "Showing _START_ to _END_ of _TOTAL_ entries",
|
| 1218 |
+
paginate: {
|
| 1219 |
+
first: "First",
|
| 1220 |
+
last: "Last",
|
| 1221 |
+
next: "Next",
|
| 1222 |
+
previous: "Previous"
|
| 1223 |
+
}
|
| 1224 |
+
}
|
| 1225 |
+
});
|
| 1226 |
+
}
|
| 1227 |
+
});
|
| 1228 |
+
}
|
| 1229 |
+
|
| 1230 |
+
function initializeMobileMenu() {
|
| 1231 |
+
const mobileToggle = document.getElementById('mobileMenuToggle');
|
| 1232 |
+
const professionalHeader = document.querySelector('.professional-header');
|
| 1233 |
+
|
| 1234 |
+
if (mobileToggle && professionalHeader) {
|
| 1235 |
+
mobileToggle.addEventListener('click', function() {
|
| 1236 |
+
professionalHeader.classList.toggle('mobile-open');
|
| 1237 |
+
});
|
| 1238 |
+
|
| 1239 |
+
// Close mobile menu when clicking outside
|
| 1240 |
+
document.addEventListener('click', function(e) {
|
| 1241 |
+
if (!professionalHeader.contains(e.target) && !mobileToggle.contains(e.target)) {
|
| 1242 |
+
professionalHeader.classList.remove('mobile-open');
|
| 1243 |
+
}
|
| 1244 |
+
});
|
| 1245 |
+
}
|
| 1246 |
+
}
|
| 1247 |
+
|
| 1248 |
+
function initializeTooltips() {
|
| 1249 |
+
// Initialize Bootstrap tooltips if available
|
| 1250 |
+
if (typeof $ !== 'undefined' && $.fn.tooltip) {
|
| 1251 |
+
$('[data-toggle="tooltip"]').tooltip();
|
| 1252 |
+
}
|
| 1253 |
+
}
|
| 1254 |
+
|
| 1255 |
+
function initializeLoadingStates() {
|
| 1256 |
+
// Add loading states to buttons and forms
|
| 1257 |
+
const forms = document.querySelectorAll('form');
|
| 1258 |
+
forms.forEach(form => {
|
| 1259 |
+
form.addEventListener('submit', function() {
|
| 1260 |
+
const submitBtn = form.querySelector('button[type="submit"]');
|
| 1261 |
+
if (submitBtn) {
|
| 1262 |
+
submitBtn.classList.add('loading');
|
| 1263 |
+
submitBtn.disabled = true;
|
| 1264 |
+
|
| 1265 |
+
// Remove loading state after 3 seconds (adjust as needed)
|
| 1266 |
+
setTimeout(() => {
|
| 1267 |
+
submitBtn.classList.remove('loading');
|
| 1268 |
+
submitBtn.disabled = false;
|
| 1269 |
+
}, 3000);
|
| 1270 |
+
}
|
| 1271 |
+
});
|
| 1272 |
+
});
|
| 1273 |
+
|
| 1274 |
+
// Add loading states to refresh buttons
|
| 1275 |
+
const refreshButtons = document.querySelectorAll('[id$="Btn"]');
|
| 1276 |
+
refreshButtons.forEach(btn => {
|
| 1277 |
+
if (btn.id.includes('refresh') || btn.id.includes('Refresh')) {
|
| 1278 |
+
btn.addEventListener('click', function() {
|
| 1279 |
+
btn.classList.add('loading');
|
| 1280 |
+
btn.disabled = true;
|
| 1281 |
+
|
| 1282 |
+
setTimeout(() => {
|
| 1283 |
+
btn.classList.remove('loading');
|
| 1284 |
+
btn.disabled = false;
|
| 1285 |
+
}, 2000);
|
| 1286 |
+
});
|
| 1287 |
+
}
|
| 1288 |
+
});
|
| 1289 |
+
}
|
| 1290 |
+
|
| 1291 |
+
function initializeAnimations() {
|
| 1292 |
+
// Add fade-in animation to cards
|
| 1293 |
+
const cards = document.querySelectorAll('.stat-card, .action-btn, .user-card, .session-card, .notification-item');
|
| 1294 |
+
cards.forEach((card, index) => {
|
| 1295 |
+
card.classList.add('fade-in');
|
| 1296 |
+
card.style.animationDelay = `${index * 0.1}s`;
|
| 1297 |
+
});
|
| 1298 |
+
|
| 1299 |
+
// Add bounce-in animation to modals
|
| 1300 |
+
const modals = document.querySelectorAll('.modal');
|
| 1301 |
+
modals.forEach(modal => {
|
| 1302 |
+
modal.addEventListener('show.bs.modal', function() {
|
| 1303 |
+
const modalContent = modal.querySelector('.modal-content');
|
| 1304 |
+
if (modalContent) {
|
| 1305 |
+
modalContent.classList.add('bounce-in');
|
| 1306 |
+
}
|
| 1307 |
+
});
|
| 1308 |
+
});
|
| 1309 |
+
}
|
| 1310 |
+
|
| 1311 |
+
function initializeEnhancedNotifications() {
|
| 1312 |
+
// Enhanced notification system using AdminLTE toast
|
| 1313 |
+
window.showProfessionalMessage = function(text, type = 'info') {
|
| 1314 |
+
if (typeof $ !== 'undefined' && $.fn.toast) {
|
| 1315 |
+
// Use AdminLTE toast if available
|
| 1316 |
+
const toastHtml = `
|
| 1317 |
+
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
| 1318 |
+
<div class="toast-header">
|
| 1319 |
+
<i class="fas fa-${getToastIcon(type)} mr-2"></i>
|
| 1320 |
+
<strong class="mr-auto">AIMHSA Professional</strong>
|
| 1321 |
+
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast">
|
| 1322 |
+
<span aria-hidden="true">×</span>
|
| 1323 |
+
</button>
|
| 1324 |
+
</div>
|
| 1325 |
+
<div class="toast-body">
|
| 1326 |
+
${text}
|
| 1327 |
+
</div>
|
| 1328 |
+
</div>
|
| 1329 |
+
`;
|
| 1330 |
+
|
| 1331 |
+
// Create toast container if it doesn't exist
|
| 1332 |
+
let toastContainer = document.querySelector('.toast-container');
|
| 1333 |
+
if (!toastContainer) {
|
| 1334 |
+
toastContainer = document.createElement('div');
|
| 1335 |
+
toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3';
|
| 1336 |
+
toastContainer.style.zIndex = '9999';
|
| 1337 |
+
document.body.appendChild(toastContainer);
|
| 1338 |
+
}
|
| 1339 |
+
|
| 1340 |
+
// Add toast to container
|
| 1341 |
+
toastContainer.insertAdjacentHTML('beforeend', toastHtml);
|
| 1342 |
+
|
| 1343 |
+
// Initialize and show toast
|
| 1344 |
+
const toastElement = toastContainer.lastElementChild;
|
| 1345 |
+
$(toastElement).toast({
|
| 1346 |
+
autohide: true,
|
| 1347 |
+
delay: 4000
|
| 1348 |
+
});
|
| 1349 |
+
$(toastElement).toast('show');
|
| 1350 |
+
|
| 1351 |
+
// Remove toast after it's hidden
|
| 1352 |
+
$(toastElement).on('hidden.bs.toast', function() {
|
| 1353 |
+
$(this).remove();
|
| 1354 |
+
});
|
| 1355 |
+
} else {
|
| 1356 |
+
// Fallback to alert
|
| 1357 |
+
alert(text);
|
| 1358 |
+
}
|
| 1359 |
+
};
|
| 1360 |
+
|
| 1361 |
+
function getToastIcon(type) {
|
| 1362 |
+
const icons = {
|
| 1363 |
+
'success': 'check-circle',
|
| 1364 |
+
'error': 'exclamation-triangle',
|
| 1365 |
+
'warning': 'exclamation-circle',
|
| 1366 |
+
'info': 'info-circle',
|
| 1367 |
+
'loading': 'spinner'
|
| 1368 |
+
};
|
| 1369 |
+
return icons[type] || 'info-circle';
|
| 1370 |
+
}
|
| 1371 |
+
}
|
| 1372 |
+
|
| 1373 |
+
// Enhanced refresh functionality
|
| 1374 |
+
function refreshAllData() {
|
| 1375 |
+
const refreshButtons = document.querySelectorAll('[id$="Btn"]');
|
| 1376 |
+
refreshButtons.forEach(btn => {
|
| 1377 |
+
if (btn.id.includes('refresh') || btn.id.includes('Refresh')) {
|
| 1378 |
+
btn.classList.add('loading');
|
| 1379 |
+
btn.disabled = true;
|
| 1380 |
+
}
|
| 1381 |
+
});
|
| 1382 |
+
|
| 1383 |
+
// Refresh all data
|
| 1384 |
+
Promise.all([
|
| 1385 |
+
loadNotifications(),
|
| 1386 |
+
loadUpcomingSessions(),
|
| 1387 |
+
loadSessionHistory(),
|
| 1388 |
+
loadBookedUsers(),
|
| 1389 |
+
loadDashboardStats()
|
| 1390 |
+
]).finally(() => {
|
| 1391 |
+
refreshButtons.forEach(btn => {
|
| 1392 |
+
if (btn.id.includes('refresh') || btn.id.includes('Refresh')) {
|
| 1393 |
+
btn.classList.remove('loading');
|
| 1394 |
+
btn.disabled = false;
|
| 1395 |
+
}
|
| 1396 |
+
});
|
| 1397 |
+
if (window.showProfessionalMessage) {
|
| 1398 |
+
window.showProfessionalMessage('All data refreshed successfully', 'success');
|
| 1399 |
+
}
|
| 1400 |
+
});
|
| 1401 |
+
}
|
| 1402 |
+
|
| 1403 |
+
// Add refresh functionality to all refresh buttons
|
| 1404 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 1405 |
+
const refreshButtons = document.querySelectorAll('[id$="Btn"]');
|
| 1406 |
+
refreshButtons.forEach(btn => {
|
| 1407 |
+
if (btn.id.includes('refresh') || btn.id.includes('Refresh')) {
|
| 1408 |
+
btn.addEventListener('click', function() {
|
| 1409 |
+
refreshAllData();
|
| 1410 |
+
});
|
| 1411 |
+
}
|
| 1412 |
+
});
|
| 1413 |
+
});
|
| 1414 |
+
|
| 1415 |
+
// Enhanced modal functionality
|
| 1416 |
+
function initializeEnhancedModals() {
|
| 1417 |
+
const modals = document.querySelectorAll('.modal');
|
| 1418 |
+
modals.forEach(modal => {
|
| 1419 |
+
// Add AdminLTE modal functionality
|
| 1420 |
+
if (typeof $ !== 'undefined' && $.fn.modal) {
|
| 1421 |
+
$(modal).on('show.bs.modal', function() {
|
| 1422 |
+
const modalContent = $(this).find('.modal-content');
|
| 1423 |
+
modalContent.addClass('bounce-in');
|
| 1424 |
+
});
|
| 1425 |
+
|
| 1426 |
+
$(modal).on('hidden.bs.modal', function() {
|
| 1427 |
+
const modalContent = $(this).find('.modal-content');
|
| 1428 |
+
modalContent.removeClass('bounce-in');
|
| 1429 |
+
});
|
| 1430 |
+
}
|
| 1431 |
+
});
|
| 1432 |
+
}
|
| 1433 |
+
|
| 1434 |
+
// Initialize enhanced modals
|
| 1435 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 1436 |
+
initializeEnhancedModals();
|
| 1437 |
+
});
|
| 1438 |
+
|
| 1439 |
+
})();
|
chatbot/professional_advanced.js
ADDED
|
@@ -0,0 +1,1587 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* AIMHSA Professional Dashboard JavaScript
|
| 3 |
+
* Simplified version without AdminLTE dependencies
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
(() => {
|
| 7 |
+
'use strict';
|
| 8 |
+
|
| 9 |
+
// API Configuration
|
| 10 |
+
let apiRoot;
|
| 11 |
+
try {
|
| 12 |
+
const loc = window.location;
|
| 13 |
+
if (loc.port === '8000') {
|
| 14 |
+
apiRoot = `${loc.protocol}//${loc.hostname}:5057`;
|
| 15 |
+
} else if (loc.port === '5057' || loc.port === '') {
|
| 16 |
+
apiRoot = loc.origin;
|
| 17 |
+
} else {
|
| 18 |
+
apiRoot = 'http://localhost:5057';
|
| 19 |
+
}
|
| 20 |
+
} catch (_) {
|
| 21 |
+
apiRoot = 'http://localhost:5057';
|
| 22 |
+
}
|
| 23 |
+
const API_ROOT = apiRoot;
|
| 24 |
+
|
| 25 |
+
// Global variables
|
| 26 |
+
let currentSection = 'dashboard';
|
| 27 |
+
let charts = {};
|
| 28 |
+
let dataTables = {};
|
| 29 |
+
let currentUser = null;
|
| 30 |
+
|
| 31 |
+
// Initialize when DOM is ready
|
| 32 |
+
$(document).ready(function() {
|
| 33 |
+
console.log('Professional Dashboard Initializing...');
|
| 34 |
+
|
| 35 |
+
// Initialize components (simplified)
|
| 36 |
+
initializeNavigation();
|
| 37 |
+
initializeDataTables();
|
| 38 |
+
initializeCharts();
|
| 39 |
+
initializeEventHandlers();
|
| 40 |
+
|
| 41 |
+
// Get current user and load data
|
| 42 |
+
getCurrentUser();
|
| 43 |
+
|
| 44 |
+
// Start auto-refresh
|
| 45 |
+
startAutoRefresh();
|
| 46 |
+
|
| 47 |
+
// Force initial data load after a short delay
|
| 48 |
+
setTimeout(() => {
|
| 49 |
+
console.log('Force loading dashboard data...');
|
| 50 |
+
loadDashboardData();
|
| 51 |
+
}, 1000);
|
| 52 |
+
|
| 53 |
+
// Add refresh button functionality
|
| 54 |
+
$(document).on('click', '#refreshDashboardBtn', function() {
|
| 55 |
+
console.log('Manual dashboard refresh triggered');
|
| 56 |
+
loadDashboardData();
|
| 57 |
+
});
|
| 58 |
+
});
|
| 59 |
+
|
| 60 |
+
/**
|
| 61 |
+
* Initialize basic components (no AdminLTE dependencies)
|
| 62 |
+
*/
|
| 63 |
+
function initializeBasicComponents() {
|
| 64 |
+
// Initialize tooltips if Bootstrap is available
|
| 65 |
+
if (typeof $ !== 'undefined' && $.fn.tooltip) {
|
| 66 |
+
$('[data-toggle="tooltip"]').tooltip();
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// Initialize popovers if Bootstrap is available
|
| 70 |
+
if (typeof $ !== 'undefined' && $.fn.popover) {
|
| 71 |
+
$('[data-toggle="popover"]').popover();
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
/**
|
| 76 |
+
* Initialize navigation system
|
| 77 |
+
*/
|
| 78 |
+
function initializeNavigation() {
|
| 79 |
+
// Handle sidebar navigation
|
| 80 |
+
$('.nav-sidebar .nav-link').on('click', function(e) {
|
| 81 |
+
e.preventDefault();
|
| 82 |
+
|
| 83 |
+
const section = $(this).data('section');
|
| 84 |
+
if (section) {
|
| 85 |
+
showSection(section);
|
| 86 |
+
updateActiveNavItem($(this));
|
| 87 |
+
updateBreadcrumb(section);
|
| 88 |
+
}
|
| 89 |
+
});
|
| 90 |
+
|
| 91 |
+
// Handle breadcrumb navigation
|
| 92 |
+
$('.breadcrumb a').on('click', function(e) {
|
| 93 |
+
e.preventDefault();
|
| 94 |
+
const section = $(this).attr('href').substring(1);
|
| 95 |
+
showSection(section);
|
| 96 |
+
});
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
/**
|
| 100 |
+
* Show specific section and hide others
|
| 101 |
+
*/
|
| 102 |
+
function showSection(sectionName) {
|
| 103 |
+
// Hide all sections
|
| 104 |
+
$('.content-section').hide();
|
| 105 |
+
|
| 106 |
+
// Show target section
|
| 107 |
+
$(`#${sectionName}-section`).show();
|
| 108 |
+
|
| 109 |
+
// Update current section
|
| 110 |
+
currentSection = sectionName;
|
| 111 |
+
|
| 112 |
+
// Load section-specific data
|
| 113 |
+
loadSectionData(sectionName);
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
/**
|
| 117 |
+
* Update active navigation item
|
| 118 |
+
*/
|
| 119 |
+
function updateActiveNavItem(activeItem) {
|
| 120 |
+
$('.nav-sidebar .nav-link').removeClass('active');
|
| 121 |
+
activeItem.addClass('active');
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
/**
|
| 125 |
+
* Update breadcrumb
|
| 126 |
+
*/
|
| 127 |
+
function updateBreadcrumb(section) {
|
| 128 |
+
const sectionNames = {
|
| 129 |
+
'dashboard': 'Dashboard',
|
| 130 |
+
'sessions': 'My Sessions',
|
| 131 |
+
'notifications': 'Notifications',
|
| 132 |
+
'reports': 'Reports',
|
| 133 |
+
'profile': 'Profile',
|
| 134 |
+
'settings': 'Settings'
|
| 135 |
+
};
|
| 136 |
+
|
| 137 |
+
$('#pageTitle').text(sectionNames[section] || 'Dashboard');
|
| 138 |
+
$('#breadcrumbActive').text(sectionNames[section] || 'Dashboard');
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
/**
|
| 142 |
+
* Initialize DataTables (if available)
|
| 143 |
+
*/
|
| 144 |
+
function initializeDataTables() {
|
| 145 |
+
// Sessions table
|
| 146 |
+
if ($('#sessionsTable').length && typeof $ !== 'undefined' && $.fn.DataTable) {
|
| 147 |
+
dataTables.sessions = $('#sessionsTable').DataTable({
|
| 148 |
+
responsive: true,
|
| 149 |
+
processing: true,
|
| 150 |
+
serverSide: false,
|
| 151 |
+
pageLength: 25,
|
| 152 |
+
order: [[0, 'desc']],
|
| 153 |
+
columnDefs: [
|
| 154 |
+
{ targets: [-1], orderable: false }
|
| 155 |
+
],
|
| 156 |
+
language: {
|
| 157 |
+
search: "Search:",
|
| 158 |
+
lengthMenu: "Show _MENU_ entries per page",
|
| 159 |
+
info: "Showing _START_ to _END_ of _TOTAL_ entries",
|
| 160 |
+
paginate: {
|
| 161 |
+
first: "First",
|
| 162 |
+
last: "Last",
|
| 163 |
+
next: "Next",
|
| 164 |
+
previous: "Previous"
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
});
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
/**
|
| 173 |
+
* Initialize charts (if Chart.js is available)
|
| 174 |
+
*/
|
| 175 |
+
function initializeCharts() {
|
| 176 |
+
// Only initialize if Chart.js is available
|
| 177 |
+
if (typeof Chart === 'undefined') {
|
| 178 |
+
console.log('Chart.js not available, skipping chart initialization');
|
| 179 |
+
return;
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
// Session trend chart
|
| 183 |
+
if ($('#sessionTrendChart').length) {
|
| 184 |
+
const ctx = document.getElementById('sessionTrendChart').getContext('2d');
|
| 185 |
+
charts.sessionTrend = new Chart(ctx, {
|
| 186 |
+
type: 'line',
|
| 187 |
+
data: {
|
| 188 |
+
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
|
| 189 |
+
datasets: [{
|
| 190 |
+
label: 'Completed Sessions',
|
| 191 |
+
data: [12, 15, 18, 14, 16, 19],
|
| 192 |
+
borderColor: '#28a745',
|
| 193 |
+
backgroundColor: 'rgba(40, 167, 69, 0.1)',
|
| 194 |
+
tension: 0.4
|
| 195 |
+
}, {
|
| 196 |
+
label: 'Scheduled Sessions',
|
| 197 |
+
data: [8, 12, 10, 15, 13, 17],
|
| 198 |
+
borderColor: '#007bff',
|
| 199 |
+
backgroundColor: 'rgba(0, 123, 255, 0.1)',
|
| 200 |
+
tension: 0.4
|
| 201 |
+
}, {
|
| 202 |
+
label: 'Cancelled Sessions',
|
| 203 |
+
data: [2, 3, 1, 4, 2, 3],
|
| 204 |
+
borderColor: '#dc3545',
|
| 205 |
+
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
| 206 |
+
tension: 0.4
|
| 207 |
+
}]
|
| 208 |
+
},
|
| 209 |
+
options: {
|
| 210 |
+
responsive: true,
|
| 211 |
+
maintainAspectRatio: false,
|
| 212 |
+
scales: {
|
| 213 |
+
y: {
|
| 214 |
+
beginAtZero: true
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
}
|
| 218 |
+
});
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// Risk distribution chart
|
| 222 |
+
if ($('#riskDistributionChart').length) {
|
| 223 |
+
const ctx = document.getElementById('riskDistributionChart').getContext('2d');
|
| 224 |
+
charts.riskDistribution = new Chart(ctx, {
|
| 225 |
+
type: 'doughnut',
|
| 226 |
+
data: {
|
| 227 |
+
labels: ['Low Risk', 'Medium Risk', 'High Risk', 'Critical'],
|
| 228 |
+
datasets: [{
|
| 229 |
+
data: [45, 35, 15, 5],
|
| 230 |
+
backgroundColor: ['#28a745', '#17a2b8', '#ffc107', '#dc3545'],
|
| 231 |
+
borderWidth: 2
|
| 232 |
+
}]
|
| 233 |
+
},
|
| 234 |
+
options: {
|
| 235 |
+
responsive: true,
|
| 236 |
+
maintainAspectRatio: false,
|
| 237 |
+
plugins: {
|
| 238 |
+
legend: {
|
| 239 |
+
position: 'bottom'
|
| 240 |
+
}
|
| 241 |
+
}
|
| 242 |
+
}
|
| 243 |
+
});
|
| 244 |
+
}
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
/**
|
| 248 |
+
* Initialize Select2 (if available)
|
| 249 |
+
*/
|
| 250 |
+
function initializeSelect2() {
|
| 251 |
+
if (typeof $ !== 'undefined' && $.fn.select2) {
|
| 252 |
+
$('.select2').select2({
|
| 253 |
+
theme: 'bootstrap4',
|
| 254 |
+
width: '100%'
|
| 255 |
+
});
|
| 256 |
+
}
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
/**
|
| 260 |
+
* Initialize event handlers
|
| 261 |
+
*/
|
| 262 |
+
function initializeEventHandlers() {
|
| 263 |
+
// Session management
|
| 264 |
+
$('#addNewSessionBtn').on('click', function() {
|
| 265 |
+
if (typeof $ !== 'undefined' && $.fn.modal) {
|
| 266 |
+
$('#sessionNotesModal').modal('show');
|
| 267 |
+
} else {
|
| 268 |
+
// Fallback for when Bootstrap modal is not available
|
| 269 |
+
document.getElementById('sessionNotesModal').style.display = 'block';
|
| 270 |
+
}
|
| 271 |
+
});
|
| 272 |
+
|
| 273 |
+
$('#saveNotesBtn').on('click', function() {
|
| 274 |
+
saveSessionNotes();
|
| 275 |
+
});
|
| 276 |
+
|
| 277 |
+
// Follow-up checkbox handler
|
| 278 |
+
$('#followUpRequired').on('change', function() {
|
| 279 |
+
if ($(this).is(':checked')) {
|
| 280 |
+
$('#followUpDateGroup').show();
|
| 281 |
+
} else {
|
| 282 |
+
$('#followUpDateGroup').hide();
|
| 283 |
+
}
|
| 284 |
+
});
|
| 285 |
+
|
| 286 |
+
// Session notes form handler
|
| 287 |
+
$('#saveNotesBtn').on('click', function() {
|
| 288 |
+
const bookingId = $('#sessionId').val();
|
| 289 |
+
const notes = $('#sessionNotes').val();
|
| 290 |
+
const followUpRequired = $('#followUpRequired').is(':checked');
|
| 291 |
+
const followUpDate = $('#followUpDate').val();
|
| 292 |
+
|
| 293 |
+
if (!notes.trim()) {
|
| 294 |
+
if (typeof Swal !== 'undefined') {
|
| 295 |
+
Swal.fire('Error', 'Please enter session notes', 'error');
|
| 296 |
+
} else {
|
| 297 |
+
alert('Please enter session notes');
|
| 298 |
+
}
|
| 299 |
+
return;
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
// For now, just show success message
|
| 303 |
+
if (typeof Swal !== 'undefined') {
|
| 304 |
+
Swal.fire('Success', 'Session notes saved successfully', 'success');
|
| 305 |
+
$('#sessionNotesModal').modal('hide');
|
| 306 |
+
} else {
|
| 307 |
+
alert('Session notes saved successfully');
|
| 308 |
+
document.getElementById('sessionNotesModal').style.display = 'none';
|
| 309 |
+
}
|
| 310 |
+
loadSessions(); // Refresh sessions
|
| 311 |
+
});
|
| 312 |
+
|
| 313 |
+
// Refresh buttons
|
| 314 |
+
$('[id$="RefreshBtn"], [id$="refreshBtn"]').on('click', function() {
|
| 315 |
+
const section = $(this).closest('.content-section').attr('id').replace('-section', '');
|
| 316 |
+
loadSectionData(section);
|
| 317 |
+
});
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
// Filter functionality
|
| 321 |
+
$('#sessionStatusFilter, #riskLevelFilter').on('change', function() {
|
| 322 |
+
applyFilters();
|
| 323 |
+
});
|
| 324 |
+
|
| 325 |
+
// Logout
|
| 326 |
+
$('#logoutBtn').on('click', function() {
|
| 327 |
+
if (typeof Swal !== 'undefined') {
|
| 328 |
+
Swal.fire({
|
| 329 |
+
title: 'Logout?',
|
| 330 |
+
text: 'Are you sure you want to logout?',
|
| 331 |
+
icon: 'question',
|
| 332 |
+
showCancelButton: true,
|
| 333 |
+
confirmButtonColor: '#3085d6',
|
| 334 |
+
cancelButtonColor: '#d33',
|
| 335 |
+
confirmButtonText: 'Yes, logout!'
|
| 336 |
+
}).then((result) => {
|
| 337 |
+
if (result.isConfirmed) {
|
| 338 |
+
// Clear all session data
|
| 339 |
+
localStorage.removeItem('aimhsa_professional');
|
| 340 |
+
localStorage.removeItem('aimhsa_account');
|
| 341 |
+
localStorage.removeItem('aimhsa_admin');
|
| 342 |
+
localStorage.removeItem('currentUser');
|
| 343 |
+
|
| 344 |
+
// Redirect to login page
|
| 345 |
+
window.location.href = '/login.html';
|
| 346 |
+
}
|
| 347 |
+
});
|
| 348 |
+
} else {
|
| 349 |
+
// Fallback for when SweetAlert is not available
|
| 350 |
+
if (confirm('Are you sure you want to logout?')) {
|
| 351 |
+
// Clear all session data
|
| 352 |
+
localStorage.removeItem('aimhsa_professional');
|
| 353 |
+
localStorage.removeItem('aimhsa_account');
|
| 354 |
+
localStorage.removeItem('aimhsa_admin');
|
| 355 |
+
localStorage.removeItem('currentUser');
|
| 356 |
+
|
| 357 |
+
// Redirect to login page
|
| 358 |
+
window.location.href = '/login.html';
|
| 359 |
+
}
|
| 360 |
+
}
|
| 361 |
+
});
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
/**
|
| 365 |
+
* Get current user information
|
| 366 |
+
*/
|
| 367 |
+
function getCurrentUser() {
|
| 368 |
+
console.log('Getting current user...');
|
| 369 |
+
|
| 370 |
+
// Check for professional login session
|
| 371 |
+
const professionalSession = localStorage.getItem('aimhsa_professional');
|
| 372 |
+
if (professionalSession) {
|
| 373 |
+
try {
|
| 374 |
+
currentUser = JSON.parse(professionalSession);
|
| 375 |
+
console.log('Professional user authenticated:', currentUser.name);
|
| 376 |
+
updateUserInfo();
|
| 377 |
+
loadDashboardData();
|
| 378 |
+
return;
|
| 379 |
+
} catch (error) {
|
| 380 |
+
console.warn('Invalid professional session:', error);
|
| 381 |
+
}
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
// Check for other user types and redirect if needed
|
| 385 |
+
const userSession = localStorage.getItem('aimhsa_account');
|
| 386 |
+
const adminSession = localStorage.getItem('aimhsa_admin');
|
| 387 |
+
|
| 388 |
+
if (userSession) {
|
| 389 |
+
console.warn('User is logged in as regular user, redirecting to login');
|
| 390 |
+
alert('You are logged in as a regular user. Please logout and login as a professional.');
|
| 391 |
+
window.location.href = '/login.html';
|
| 392 |
+
return;
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
if (adminSession) {
|
| 396 |
+
console.warn('User is logged in as admin, redirecting to login');
|
| 397 |
+
alert('You are logged in as an admin. Please logout and login as a professional.');
|
| 398 |
+
window.location.href = '/login.html';
|
| 399 |
+
return;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
// No valid session found - create a test professional session for demo purposes
|
| 403 |
+
console.log('No professional session found, creating test session for demo');
|
| 404 |
+
createTestProfessionalSession();
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
/**
|
| 408 |
+
* Create a test professional session for demo purposes
|
| 409 |
+
*/
|
| 410 |
+
function createTestProfessionalSession() {
|
| 411 |
+
// Create a test professional user
|
| 412 |
+
currentUser = {
|
| 413 |
+
professional_id: 18,
|
| 414 |
+
name: 'Dashboard Test Professional',
|
| 415 |
+
username: 'dashboard_test_prof',
|
| 416 |
+
email: 'dashboard.test@example.com',
|
| 417 |
+
fullname: 'Dashboard Test Professional',
|
| 418 |
+
specialization: 'Psychologist'
|
| 419 |
+
};
|
| 420 |
+
|
| 421 |
+
// Store in localStorage
|
| 422 |
+
localStorage.setItem('aimhsa_professional', JSON.stringify(currentUser));
|
| 423 |
+
|
| 424 |
+
console.log('Test professional session created:', currentUser);
|
| 425 |
+
updateUserInfo();
|
| 426 |
+
loadDashboardData();
|
| 427 |
+
}
|
| 428 |
+
|
| 429 |
+
/**
|
| 430 |
+
* Update user information display
|
| 431 |
+
*/
|
| 432 |
+
function updateUserInfo() {
|
| 433 |
+
if (currentUser) {
|
| 434 |
+
console.log('Updating UI with user info:', currentUser);
|
| 435 |
+
|
| 436 |
+
// Update user name in sidebar
|
| 437 |
+
$('#professionalNameSidebar').text(currentUser.name || currentUser.fullname || 'Professional');
|
| 438 |
+
$('#professionalName').text(currentUser.name || currentUser.fullname || 'Professional');
|
| 439 |
+
|
| 440 |
+
// Update user role/specialization
|
| 441 |
+
$('#professionalRole').text(currentUser.specialization || 'Mental Health Professional');
|
| 442 |
+
|
| 443 |
+
// Update user email if available
|
| 444 |
+
if (currentUser.email) {
|
| 445 |
+
$('.user-panel .info span').text(currentUser.email);
|
| 446 |
+
}
|
| 447 |
+
|
| 448 |
+
// Update user profile image with initials
|
| 449 |
+
updateUserProfileImage(currentUser.name || currentUser.fullname || 'Professional');
|
| 450 |
+
|
| 451 |
+
console.log('User info updated:', currentUser.name);
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
/**
|
| 456 |
+
* Update user profile image with initials
|
| 457 |
+
*/
|
| 458 |
+
function updateUserProfileImage(fullName) {
|
| 459 |
+
if (!fullName) return;
|
| 460 |
+
|
| 461 |
+
// Extract initials from full name
|
| 462 |
+
const initials = fullName.split(' ')
|
| 463 |
+
.map(name => name.charAt(0).toUpperCase())
|
| 464 |
+
.join('')
|
| 465 |
+
.substring(0, 2);
|
| 466 |
+
|
| 467 |
+
// Create SVG with initials
|
| 468 |
+
const svgData = `data:image/svg+xml;base64,${btoa(`
|
| 469 |
+
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
|
| 470 |
+
<rect width="160" height="160" rx="80" fill="#28a745"/>
|
| 471 |
+
<text x="80" y="95" font-family="Arial, sans-serif" font-size="64" font-weight="bold" fill="white" text-anchor="middle">${initials}</text>
|
| 472 |
+
</svg>
|
| 473 |
+
`)}`;
|
| 474 |
+
|
| 475 |
+
// Update the profile image
|
| 476 |
+
$('#userProfileImage').attr('src', svgData);
|
| 477 |
+
$('#profileUserImage').attr('src', svgData);
|
| 478 |
+
}
|
| 479 |
+
|
| 480 |
+
/**
|
| 481 |
+
* Load dashboard data from database
|
| 482 |
+
*/
|
| 483 |
+
function loadDashboardData() {
|
| 484 |
+
console.log('🔄 Loading professional dashboard data...');
|
| 485 |
+
|
| 486 |
+
if (!currentUser) {
|
| 487 |
+
console.log('⚠️ No current user, waiting...');
|
| 488 |
+
setTimeout(loadDashboardData, 1000);
|
| 489 |
+
return;
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
console.log('✅ Current user found:', currentUser.name);
|
| 493 |
+
|
| 494 |
+
// Update user info in UI
|
| 495 |
+
updateUserInfo();
|
| 496 |
+
|
| 497 |
+
// Load dashboard statistics from API
|
| 498 |
+
loadDashboardStats();
|
| 499 |
+
|
| 500 |
+
// Load today's schedule
|
| 501 |
+
loadTodaySchedule();
|
| 502 |
+
|
| 503 |
+
// Load recent notifications
|
| 504 |
+
loadRecentNotifications();
|
| 505 |
+
|
| 506 |
+
// Load dashboard charts
|
| 507 |
+
loadDashboardCharts();
|
| 508 |
+
|
| 509 |
+
console.log('✅ Dashboard data loading completed');
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
/**
|
| 513 |
+
* Update user information in the UI
|
| 514 |
+
*/
|
| 515 |
+
function updateUserInfo() {
|
| 516 |
+
if (currentUser) {
|
| 517 |
+
console.log(' Updating UI with user info:', currentUser);
|
| 518 |
+
$('#professionalName').text(currentUser.fullname || currentUser.username);
|
| 519 |
+
$('#professionalNameSidebar').text(currentUser.fullname || currentUser.username);
|
| 520 |
+
$('#professionalRole').text(currentUser.specialization || 'Mental Health Professional');
|
| 521 |
+
$('#profileName').text(currentUser.fullname || currentUser.username);
|
| 522 |
+
$('#profileRole').text(currentUser.specialization || 'Mental Health Professional');
|
| 523 |
+
}
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
/**
|
| 527 |
+
* Load dashboard statistics from API
|
| 528 |
+
*/
|
| 529 |
+
function loadDashboardStats() {
|
| 530 |
+
console.log('Loading dashboard statistics...');
|
| 531 |
+
|
| 532 |
+
if (!currentUser) {
|
| 533 |
+
console.log('No current user for stats');
|
| 534 |
+
return;
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
console.log('Fetching data from:', `${API_ROOT}/admin/bookings`);
|
| 538 |
+
|
| 539 |
+
// Show loading state
|
| 540 |
+
$('#totalSessions').html('<i class="fas fa-spinner fa-spin"></i>');
|
| 541 |
+
$('#unreadNotifications').html('<i class="fas fa-spinner fa-spin"></i>');
|
| 542 |
+
$('#upcomingToday').html('<i class="fas fa-spinner fa-spin"></i>');
|
| 543 |
+
$('#highRiskSessions').html('<i class="fas fa-spinner fa-spin"></i>');
|
| 544 |
+
|
| 545 |
+
// Get user's booking statistics
|
| 546 |
+
fetch(`${API_ROOT}/admin/bookings`)
|
| 547 |
+
.then(response => {
|
| 548 |
+
console.log('API response status:', response.status);
|
| 549 |
+
if (!response.ok) {
|
| 550 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
| 551 |
+
}
|
| 552 |
+
return response.json();
|
| 553 |
+
})
|
| 554 |
+
.then(data => {
|
| 555 |
+
console.log('Dashboard stats received:', data);
|
| 556 |
+
|
| 557 |
+
// Calculate statistics
|
| 558 |
+
const totalSessions = data.total || 0;
|
| 559 |
+
const confirmedSessions = data.confirmed || 0;
|
| 560 |
+
const pendingSessions = data.pending || 0;
|
| 561 |
+
const criticalSessions = data.critical || 0;
|
| 562 |
+
|
| 563 |
+
// Calculate today's sessions
|
| 564 |
+
const today = new Date().toDateString();
|
| 565 |
+
const todaySessions = data.bookings ? data.bookings.filter(booking => {
|
| 566 |
+
const bookingDate = new Date(booking.scheduled_datetime * 1000).toDateString();
|
| 567 |
+
return bookingDate === today;
|
| 568 |
+
}).length : 0;
|
| 569 |
+
|
| 570 |
+
console.log('Updating UI with:', {
|
| 571 |
+
totalSessions,
|
| 572 |
+
confirmedSessions,
|
| 573 |
+
pendingSessions,
|
| 574 |
+
criticalSessions,
|
| 575 |
+
todaySessions
|
| 576 |
+
});
|
| 577 |
+
|
| 578 |
+
// Update KPI cards with real data
|
| 579 |
+
$('#totalSessions').text(totalSessions);
|
| 580 |
+
$('#unreadNotifications').text(pendingSessions + criticalSessions); // Show urgent notifications
|
| 581 |
+
$('#upcomingToday').text(todaySessions);
|
| 582 |
+
$('#highRiskSessions').text(criticalSessions);
|
| 583 |
+
|
| 584 |
+
// Update notification badge in navbar
|
| 585 |
+
$('#notificationBadge').text(pendingSessions + criticalSessions);
|
| 586 |
+
$('#notificationCount').text(pendingSessions + criticalSessions);
|
| 587 |
+
|
| 588 |
+
// Update notification dropdown
|
| 589 |
+
updateNotificationDropdown(data.bookings || []);
|
| 590 |
+
|
| 591 |
+
console.log('Dashboard statistics updated successfully with real data');
|
| 592 |
+
})
|
| 593 |
+
.catch(error => {
|
| 594 |
+
console.error('Error loading dashboard stats:', error);
|
| 595 |
+
|
| 596 |
+
// Show error state
|
| 597 |
+
$('#totalSessions').html('<i class="fas fa-exclamation-triangle text-warning"></i>');
|
| 598 |
+
$('#unreadNotifications').html('<i class="fas fa-exclamation-triangle text-warning"></i>');
|
| 599 |
+
$('#upcomingToday').html('<i class="fas fa-exclamation-triangle text-warning"></i>');
|
| 600 |
+
$('#highRiskSessions').html('<i class="fas fa-exclamation-triangle text-warning"></i>');
|
| 601 |
+
|
| 602 |
+
// Show error message
|
| 603 |
+
if (typeof Swal !== 'undefined') {
|
| 604 |
+
Swal.fire({
|
| 605 |
+
title: 'Data Loading Error',
|
| 606 |
+
text: 'Unable to load dashboard data. Please check your connection and try again.',
|
| 607 |
+
icon: 'warning',
|
| 608 |
+
timer: 5000,
|
| 609 |
+
showConfirmButton: false
|
| 610 |
+
});
|
| 611 |
+
} else {
|
| 612 |
+
console.error('Unable to load dashboard data. Please check your connection and try again.');
|
| 613 |
+
}
|
| 614 |
+
});
|
| 615 |
+
}
|
| 616 |
+
|
| 617 |
+
/**
|
| 618 |
+
* Update notification dropdown with real data
|
| 619 |
+
*/
|
| 620 |
+
function updateNotificationDropdown(bookings) {
|
| 621 |
+
const notificationMenu = $('.dropdown-menu.dropdown-menu-lg');
|
| 622 |
+
const urgentBookings = bookings.filter(booking =>
|
| 623 |
+
booking.risk_level === 'critical' || booking.booking_status === 'pending'
|
| 624 |
+
).slice(0, 5); // Show top 5 urgent items
|
| 625 |
+
|
| 626 |
+
let notificationHtml = `<span class="dropdown-item dropdown-header">${urgentBookings.length} Urgent Items</span>`;
|
| 627 |
+
|
| 628 |
+
if (urgentBookings.length === 0) {
|
| 629 |
+
notificationHtml += `
|
| 630 |
+
<div class="dropdown-divider"></div>
|
| 631 |
+
<a href="#" class="dropdown-item">
|
| 632 |
+
<i class="fas fa-check-circle mr-2 text-success"></i> All caught up!
|
| 633 |
+
<span class="float-right text-muted text-sm">No urgent items</span>
|
| 634 |
+
</a>
|
| 635 |
+
`;
|
| 636 |
+
} else {
|
| 637 |
+
urgentBookings.forEach(booking => {
|
| 638 |
+
const timeAgo = getTimeAgo(booking.created_ts);
|
| 639 |
+
const icon = booking.risk_level === 'critical' ? 'fa-exclamation-triangle' : 'fa-calendar';
|
| 640 |
+
const color = booking.risk_level === 'critical' ? 'text-danger' : 'text-warning';
|
| 641 |
+
|
| 642 |
+
notificationHtml += `
|
| 643 |
+
<div class="dropdown-divider"></div>
|
| 644 |
+
<a href="#" class="dropdown-item" onclick="viewBookingDetails('${booking.booking_id}')">
|
| 645 |
+
<i class="fas ${icon} mr-2 ${color}"></i>
|
| 646 |
+
${booking.user_fullname || 'Patient'} - ${booking.risk_level || 'New booking'}
|
| 647 |
+
<span class="float-right text-muted text-sm">${timeAgo}</span>
|
| 648 |
+
</a>
|
| 649 |
+
`;
|
| 650 |
+
});
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
notificationHtml += `
|
| 654 |
+
<div class="dropdown-divider"></div>
|
| 655 |
+
<a href="#notifications" class="dropdown-item dropdown-footer" data-section="notifications">See All Notifications</a>
|
| 656 |
+
`;
|
| 657 |
+
|
| 658 |
+
notificationMenu.html(notificationHtml);
|
| 659 |
+
}
|
| 660 |
+
|
| 661 |
+
/**
|
| 662 |
+
* Get time ago string from timestamp
|
| 663 |
+
*/
|
| 664 |
+
function getTimeAgo(timestamp) {
|
| 665 |
+
if (!timestamp) return 'Unknown';
|
| 666 |
+
|
| 667 |
+
const now = Date.now() / 1000;
|
| 668 |
+
const diff = now - timestamp;
|
| 669 |
+
|
| 670 |
+
if (diff < 60) return 'Just now';
|
| 671 |
+
if (diff < 3600) return `${Math.floor(diff / 60)} mins ago`;
|
| 672 |
+
if (diff < 86400) return `${Math.floor(diff / 3600)} hours ago`;
|
| 673 |
+
return `${Math.floor(diff / 86400)} days ago`;
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
/**
|
| 677 |
+
* View booking details modal
|
| 678 |
+
*/
|
| 679 |
+
window.viewBookingDetails = function(bookingId) {
|
| 680 |
+
console.log('Viewing booking details for:', bookingId);
|
| 681 |
+
|
| 682 |
+
// Find the booking in the current data
|
| 683 |
+
fetch(`${API_ROOT}/admin/bookings`)
|
| 684 |
+
.then(response => response.json())
|
| 685 |
+
.then(data => {
|
| 686 |
+
const booking = data.bookings.find(b => b.booking_id === bookingId);
|
| 687 |
+
if (booking) {
|
| 688 |
+
showBookingDetailsModal(booking);
|
| 689 |
+
} else {
|
| 690 |
+
Swal.fire('Error', 'Booking not found', 'error');
|
| 691 |
+
}
|
| 692 |
+
})
|
| 693 |
+
.catch(error => {
|
| 694 |
+
console.error('Error fetching booking details:', error);
|
| 695 |
+
Swal.fire('Error', 'Failed to load booking details', 'error');
|
| 696 |
+
});
|
| 697 |
+
};
|
| 698 |
+
|
| 699 |
+
/**
|
| 700 |
+
* Show booking details modal
|
| 701 |
+
*/
|
| 702 |
+
function showBookingDetailsModal(booking) {
|
| 703 |
+
const scheduledTime = new Date(booking.scheduled_datetime * 1000).toLocaleString();
|
| 704 |
+
const riskBadgeClass = getRiskBadgeClass(booking.risk_level);
|
| 705 |
+
const statusBadgeClass = getStatusBadgeClass(booking.booking_status);
|
| 706 |
+
|
| 707 |
+
const modalHtml = `
|
| 708 |
+
<div class="modal fade" id="bookingDetailsModal" tabindex="-1" role="dialog">
|
| 709 |
+
<div class="modal-dialog modal-lg" role="document">
|
| 710 |
+
<div class="modal-content">
|
| 711 |
+
<div class="modal-header">
|
| 712 |
+
<h5 class="modal-title">
|
| 713 |
+
<i class="fas fa-calendar-check text-primary"></i>
|
| 714 |
+
Booking Details
|
| 715 |
+
</h5>
|
| 716 |
+
<button type="button" class="close" data-dismiss="modal">
|
| 717 |
+
<span>×</span>
|
| 718 |
+
</button>
|
| 719 |
+
</div>
|
| 720 |
+
<div class="modal-body">
|
| 721 |
+
<div class="row">
|
| 722 |
+
<div class="col-md-6">
|
| 723 |
+
<h6><i class="fas fa-user text-info"></i> Patient Information</h6>
|
| 724 |
+
<p><strong>Name:</strong> ${booking.user_fullname || 'Unknown'}</p>
|
| 725 |
+
<p><strong>Email:</strong> ${booking.user_email || 'N/A'}</p>
|
| 726 |
+
<p><strong>Phone:</strong> ${booking.user_phone || 'N/A'}</p>
|
| 727 |
+
<p><strong>Location:</strong> ${booking.user_location || 'N/A'}</p>
|
| 728 |
+
</div>
|
| 729 |
+
<div class="col-md-6">
|
| 730 |
+
<h6><i class="fas fa-calendar text-success"></i> Session Details</h6>
|
| 731 |
+
<p><strong>Scheduled:</strong> ${scheduledTime}</p>
|
| 732 |
+
<p><strong>Status:</strong> <span class="badge badge-${statusBadgeClass}">${booking.booking_status}</span></p>
|
| 733 |
+
<p><strong>Risk Level:</strong> <span class="badge badge-${riskBadgeClass}">${booking.risk_level}</span></p>
|
| 734 |
+
<p><strong>Session Type:</strong> ${booking.session_type || 'Routine'}</p>
|
| 735 |
+
</div>
|
| 736 |
+
</div>
|
| 737 |
+
${booking.detected_indicators && booking.detected_indicators.length > 0 ? `
|
| 738 |
+
<div class="row mt-3">
|
| 739 |
+
<div class="col-12">
|
| 740 |
+
<h6><i class="fas fa-exclamation-triangle text-warning"></i> Detected Indicators</h6>
|
| 741 |
+
<div class="alert alert-warning">
|
| 742 |
+
${booking.detected_indicators.map(indicator => `<span class="badge badge-warning mr-1">${indicator}</span>`).join('')}
|
| 743 |
+
</div>
|
| 744 |
+
</div>
|
| 745 |
+
</div>
|
| 746 |
+
` : ''}
|
| 747 |
+
</div>
|
| 748 |
+
<div class="modal-footer">
|
| 749 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
| 750 |
+
<button type="button" class="btn btn-primary" onclick="startSession('${booking.booking_id}')">
|
| 751 |
+
<i class="fas fa-play"></i> Start Session
|
| 752 |
+
</button>
|
| 753 |
+
</div>
|
| 754 |
+
</div>
|
| 755 |
+
</div>
|
| 756 |
+
</div>
|
| 757 |
+
`;
|
| 758 |
+
|
| 759 |
+
// Remove existing modal if any
|
| 760 |
+
$('#bookingDetailsModal').remove();
|
| 761 |
+
|
| 762 |
+
// Add modal to body
|
| 763 |
+
$('body').append(modalHtml);
|
| 764 |
+
|
| 765 |
+
// Show modal
|
| 766 |
+
if (typeof $ !== 'undefined' && $.fn.modal) {
|
| 767 |
+
$('#bookingDetailsModal').modal('show');
|
| 768 |
+
} else {
|
| 769 |
+
// Fallback for when Bootstrap modal is not available
|
| 770 |
+
document.getElementById('bookingDetailsModal').style.display = 'block';
|
| 771 |
+
}
|
| 772 |
+
};
|
| 773 |
+
|
| 774 |
+
/**
|
| 775 |
+
* Get risk badge class
|
| 776 |
+
*/
|
| 777 |
+
function getRiskBadgeClass(riskLevel) {
|
| 778 |
+
switch (riskLevel) {
|
| 779 |
+
case 'critical': return 'danger';
|
| 780 |
+
case 'high': return 'warning';
|
| 781 |
+
case 'medium': return 'info';
|
| 782 |
+
case 'low': return 'success';
|
| 783 |
+
default: return 'secondary';
|
| 784 |
+
}
|
| 785 |
+
}
|
| 786 |
+
|
| 787 |
+
/**
|
| 788 |
+
* Get status badge class
|
| 789 |
+
*/
|
| 790 |
+
function getStatusBadgeClass(status) {
|
| 791 |
+
switch (status) {
|
| 792 |
+
case 'confirmed': return 'success';
|
| 793 |
+
case 'pending': return 'warning';
|
| 794 |
+
case 'completed': return 'info';
|
| 795 |
+
case 'cancelled': return 'danger';
|
| 796 |
+
default: return 'secondary';
|
| 797 |
+
}
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
+
/**
|
| 801 |
+
* Start session function
|
| 802 |
+
*/
|
| 803 |
+
window.startSession = function(bookingId) {
|
| 804 |
+
if (typeof Swal !== 'undefined') {
|
| 805 |
+
Swal.fire({
|
| 806 |
+
title: 'Start Session',
|
| 807 |
+
text: 'Are you ready to start this session?',
|
| 808 |
+
icon: 'question',
|
| 809 |
+
showCancelButton: true,
|
| 810 |
+
confirmButtonText: 'Yes, start session',
|
| 811 |
+
cancelButtonText: 'Cancel'
|
| 812 |
+
}).then((result) => {
|
| 813 |
+
if (result.isConfirmed) {
|
| 814 |
+
// Here you would implement the actual session start logic
|
| 815 |
+
Swal.fire('Session Started', 'The session has been started successfully!', 'success');
|
| 816 |
+
if (typeof $ !== 'undefined' && $.fn.modal) {
|
| 817 |
+
$('#bookingDetailsModal').modal('hide');
|
| 818 |
+
} else {
|
| 819 |
+
document.getElementById('bookingDetailsModal').style.display = 'none';
|
| 820 |
+
}
|
| 821 |
+
|
| 822 |
+
// Refresh the dashboard data
|
| 823 |
+
loadDashboardData();
|
| 824 |
+
}
|
| 825 |
+
});
|
| 826 |
+
} else {
|
| 827 |
+
// Fallback for when SweetAlert is not available
|
| 828 |
+
if (confirm('Are you ready to start this session?')) {
|
| 829 |
+
alert('Session Started! The session has been started successfully!');
|
| 830 |
+
if (typeof $ !== 'undefined' && $.fn.modal) {
|
| 831 |
+
$('#bookingDetailsModal').modal('hide');
|
| 832 |
+
} else {
|
| 833 |
+
document.getElementById('bookingDetailsModal').style.display = 'none';
|
| 834 |
+
}
|
| 835 |
+
|
| 836 |
+
// Refresh the dashboard data
|
| 837 |
+
loadDashboardData();
|
| 838 |
+
}
|
| 839 |
+
}
|
| 840 |
+
};
|
| 841 |
+
|
| 842 |
+
/**
|
| 843 |
+
* View session function
|
| 844 |
+
*/
|
| 845 |
+
window.viewSession = function(bookingId) {
|
| 846 |
+
console.log('Viewing session:', bookingId);
|
| 847 |
+
viewBookingDetails(bookingId);
|
| 848 |
+
};
|
| 849 |
+
|
| 850 |
+
/**
|
| 851 |
+
* Add session notes function
|
| 852 |
+
*/
|
| 853 |
+
window.addSessionNotes = function(bookingId) {
|
| 854 |
+
if (typeof Swal !== 'undefined') {
|
| 855 |
+
Swal.fire({
|
| 856 |
+
title: 'Add Session Notes',
|
| 857 |
+
input: 'textarea',
|
| 858 |
+
inputLabel: 'Session Notes',
|
| 859 |
+
inputPlaceholder: 'Enter your session notes here...',
|
| 860 |
+
inputAttributes: {
|
| 861 |
+
'aria-label': 'Type your session notes here'
|
| 862 |
+
},
|
| 863 |
+
showCancelButton: true,
|
| 864 |
+
confirmButtonText: 'Save Notes',
|
| 865 |
+
cancelButtonText: 'Cancel'
|
| 866 |
+
}).then((result) => {
|
| 867 |
+
if (result.isConfirmed) {
|
| 868 |
+
// Here you would save the notes to the database
|
| 869 |
+
Swal.fire('Notes Saved', 'Session notes have been saved successfully!', 'success');
|
| 870 |
+
console.log('Session notes for', bookingId, ':', result.value);
|
| 871 |
+
}
|
| 872 |
+
});
|
| 873 |
+
} else {
|
| 874 |
+
// Fallback for when SweetAlert is not available
|
| 875 |
+
const notes = prompt('Enter your session notes here:');
|
| 876 |
+
if (notes !== null && notes.trim() !== '') {
|
| 877 |
+
alert('Notes Saved! Session notes have been saved successfully!');
|
| 878 |
+
console.log('Session notes for', bookingId, ':', notes);
|
| 879 |
+
}
|
| 880 |
+
}
|
| 881 |
+
};
|
| 882 |
+
|
| 883 |
+
/**
|
| 884 |
+
* Complete session function
|
| 885 |
+
*/
|
| 886 |
+
window.completeSession = function(bookingId) {
|
| 887 |
+
if (typeof Swal !== 'undefined') {
|
| 888 |
+
Swal.fire({
|
| 889 |
+
title: 'Complete Session',
|
| 890 |
+
text: 'Mark this session as completed?',
|
| 891 |
+
icon: 'question',
|
| 892 |
+
showCancelButton: true,
|
| 893 |
+
confirmButtonText: 'Yes, complete',
|
| 894 |
+
cancelButtonText: 'Cancel'
|
| 895 |
+
}).then((result) => {
|
| 896 |
+
if (result.isConfirmed) {
|
| 897 |
+
// Here you would update the session status in the database
|
| 898 |
+
Swal.fire('Session Completed', 'The session has been marked as completed!', 'success');
|
| 899 |
+
console.log('Session completed:', bookingId);
|
| 900 |
+
|
| 901 |
+
// Refresh the dashboard data
|
| 902 |
+
loadDashboardData();
|
| 903 |
+
}
|
| 904 |
+
});
|
| 905 |
+
} else {
|
| 906 |
+
// Fallback for when SweetAlert is not available
|
| 907 |
+
if (confirm('Mark this session as completed?')) {
|
| 908 |
+
alert('Session Completed! The session has been marked as completed!');
|
| 909 |
+
console.log('Session completed:', bookingId);
|
| 910 |
+
|
| 911 |
+
// Refresh the dashboard data
|
| 912 |
+
loadDashboardData();
|
| 913 |
+
}
|
| 914 |
+
}
|
| 915 |
+
};
|
| 916 |
+
|
| 917 |
+
/**
|
| 918 |
+
* Load section-specific data
|
| 919 |
+
*/
|
| 920 |
+
function loadSectionData(section) {
|
| 921 |
+
switch (section) {
|
| 922 |
+
case 'sessions':
|
| 923 |
+
loadSessions();
|
| 924 |
+
break;
|
| 925 |
+
case 'notifications':
|
| 926 |
+
loadNotifications();
|
| 927 |
+
break;
|
| 928 |
+
case 'reports':
|
| 929 |
+
loadReports();
|
| 930 |
+
break;
|
| 931 |
+
case 'profile':
|
| 932 |
+
loadProfile();
|
| 933 |
+
break;
|
| 934 |
+
case 'settings':
|
| 935 |
+
loadSettings();
|
| 936 |
+
break;
|
| 937 |
+
case 'dashboard':
|
| 938 |
+
loadDashboardCharts();
|
| 939 |
+
break;
|
| 940 |
+
}
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
/**
|
| 944 |
+
* Load dashboard charts
|
| 945 |
+
*/
|
| 946 |
+
function loadDashboardCharts() {
|
| 947 |
+
console.log('Loading dashboard charts...');
|
| 948 |
+
|
| 949 |
+
// Load session trends chart
|
| 950 |
+
loadSessionTrendsChart();
|
| 951 |
+
|
| 952 |
+
// Load risk distribution chart
|
| 953 |
+
loadRiskDistributionChart();
|
| 954 |
+
}
|
| 955 |
+
|
| 956 |
+
/**
|
| 957 |
+
* Load session trends chart
|
| 958 |
+
*/
|
| 959 |
+
function loadSessionTrendsChart() {
|
| 960 |
+
// Only proceed if Chart.js is available
|
| 961 |
+
if (typeof Chart === 'undefined') {
|
| 962 |
+
console.log('Chart.js not available, skipping session trends chart');
|
| 963 |
+
return;
|
| 964 |
+
}
|
| 965 |
+
|
| 966 |
+
fetch(`${API_ROOT}/admin/bookings`)
|
| 967 |
+
.then(response => response.json())
|
| 968 |
+
.then(data => {
|
| 969 |
+
const bookings = data.bookings || [];
|
| 970 |
+
|
| 971 |
+
// Group bookings by date for the last 7 days
|
| 972 |
+
const last7Days = [];
|
| 973 |
+
for (let i = 6; i >= 0; i--) {
|
| 974 |
+
const date = new Date();
|
| 975 |
+
date.setDate(date.getDate() - i);
|
| 976 |
+
last7Days.push(date.toISOString().split('T')[0]);
|
| 977 |
+
}
|
| 978 |
+
|
| 979 |
+
const sessionData = last7Days.map(date => {
|
| 980 |
+
return bookings.filter(booking => {
|
| 981 |
+
const bookingDate = new Date(booking.scheduled_datetime * 1000).toISOString().split('T')[0];
|
| 982 |
+
return bookingDate === date;
|
| 983 |
+
}).length;
|
| 984 |
+
});
|
| 985 |
+
|
| 986 |
+
// Create or update chart
|
| 987 |
+
const ctx = document.getElementById('sessionTrendsChart');
|
| 988 |
+
if (ctx) {
|
| 989 |
+
if (charts.sessionTrends) {
|
| 990 |
+
charts.sessionTrends.destroy();
|
| 991 |
+
}
|
| 992 |
+
|
| 993 |
+
charts.sessionTrends = new Chart(ctx, {
|
| 994 |
+
type: 'line',
|
| 995 |
+
data: {
|
| 996 |
+
labels: last7Days.map(date => new Date(date).toLocaleDateString('en-US', { weekday: 'short' })),
|
| 997 |
+
datasets: [{
|
| 998 |
+
label: 'Sessions',
|
| 999 |
+
data: sessionData,
|
| 1000 |
+
borderColor: 'rgb(75, 192, 192)',
|
| 1001 |
+
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
| 1002 |
+
tension: 0.1
|
| 1003 |
+
}]
|
| 1004 |
+
},
|
| 1005 |
+
options: {
|
| 1006 |
+
responsive: true,
|
| 1007 |
+
maintainAspectRatio: false,
|
| 1008 |
+
scales: {
|
| 1009 |
+
y: {
|
| 1010 |
+
beginAtZero: true,
|
| 1011 |
+
ticks: {
|
| 1012 |
+
stepSize: 1
|
| 1013 |
+
}
|
| 1014 |
+
}
|
| 1015 |
+
}
|
| 1016 |
+
}
|
| 1017 |
+
});
|
| 1018 |
+
}
|
| 1019 |
+
})
|
| 1020 |
+
.catch(error => {
|
| 1021 |
+
console.error('Error loading session trends chart:', error);
|
| 1022 |
+
});
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
+
/**
|
| 1026 |
+
* Load risk distribution chart
|
| 1027 |
+
*/
|
| 1028 |
+
function loadRiskDistributionChart() {
|
| 1029 |
+
// Only proceed if Chart.js is available
|
| 1030 |
+
if (typeof Chart === 'undefined') {
|
| 1031 |
+
console.log('Chart.js not available, skipping risk distribution chart');
|
| 1032 |
+
return;
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
fetch(`${API_ROOT}/admin/bookings`)
|
| 1036 |
+
.then(response => response.json())
|
| 1037 |
+
.then(data => {
|
| 1038 |
+
const bookings = data.bookings || [];
|
| 1039 |
+
|
| 1040 |
+
// Count bookings by risk level
|
| 1041 |
+
const riskCounts = {
|
| 1042 |
+
low: 0,
|
| 1043 |
+
medium: 0,
|
| 1044 |
+
high: 0,
|
| 1045 |
+
critical: 0
|
| 1046 |
+
};
|
| 1047 |
+
|
| 1048 |
+
bookings.forEach(booking => {
|
| 1049 |
+
const riskLevel = booking.risk_level || 'low';
|
| 1050 |
+
if (riskCounts.hasOwnProperty(riskLevel)) {
|
| 1051 |
+
riskCounts[riskLevel]++;
|
| 1052 |
+
}
|
| 1053 |
+
});
|
| 1054 |
+
|
| 1055 |
+
// Create or update chart
|
| 1056 |
+
const ctx = document.getElementById('riskDistributionChart');
|
| 1057 |
+
if (ctx) {
|
| 1058 |
+
if (charts.riskDistribution) {
|
| 1059 |
+
charts.riskDistribution.destroy();
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
+
charts.riskDistribution = new Chart(ctx, {
|
| 1063 |
+
type: 'doughnut',
|
| 1064 |
+
data: {
|
| 1065 |
+
labels: ['Low Risk', 'Medium Risk', 'High Risk', 'Critical'],
|
| 1066 |
+
datasets: [{
|
| 1067 |
+
data: [riskCounts.low, riskCounts.medium, riskCounts.high, riskCounts.critical],
|
| 1068 |
+
backgroundColor: [
|
| 1069 |
+
'rgba(40, 167, 69, 0.8)',
|
| 1070 |
+
'rgba(23, 162, 184, 0.8)',
|
| 1071 |
+
'rgba(255, 193, 7, 0.8)',
|
| 1072 |
+
'rgba(220, 53, 69, 0.8)'
|
| 1073 |
+
],
|
| 1074 |
+
borderColor: [
|
| 1075 |
+
'rgba(40, 167, 69, 1)',
|
| 1076 |
+
'rgba(23, 162, 184, 1)',
|
| 1077 |
+
'rgba(255, 193, 7, 1)',
|
| 1078 |
+
'rgba(220, 53, 69, 1)'
|
| 1079 |
+
],
|
| 1080 |
+
borderWidth: 2
|
| 1081 |
+
}]
|
| 1082 |
+
},
|
| 1083 |
+
options: {
|
| 1084 |
+
responsive: true,
|
| 1085 |
+
maintainAspectRatio: false,
|
| 1086 |
+
plugins: {
|
| 1087 |
+
legend: {
|
| 1088 |
+
position: 'bottom'
|
| 1089 |
+
}
|
| 1090 |
+
}
|
| 1091 |
+
}
|
| 1092 |
+
});
|
| 1093 |
+
}
|
| 1094 |
+
})
|
| 1095 |
+
.catch(error => {
|
| 1096 |
+
console.error('Error loading risk distribution chart:', error);
|
| 1097 |
+
});
|
| 1098 |
+
}
|
| 1099 |
+
|
| 1100 |
+
/**
|
| 1101 |
+
* Load KPI data
|
| 1102 |
+
*/
|
| 1103 |
+
function loadKPIData() {
|
| 1104 |
+
// Simulate API call
|
| 1105 |
+
setTimeout(() => {
|
| 1106 |
+
$('#totalSessions').text('15');
|
| 1107 |
+
$('#unreadNotifications').text('8');
|
| 1108 |
+
$('#upcomingToday').text('3');
|
| 1109 |
+
$('#highRiskSessions').text('2');
|
| 1110 |
+
}, 500);
|
| 1111 |
+
}
|
| 1112 |
+
|
| 1113 |
+
/**
|
| 1114 |
+
* Load today's schedule
|
| 1115 |
+
*/
|
| 1116 |
+
function loadTodaySchedule() {
|
| 1117 |
+
console.log('Loading today\'s schedule...');
|
| 1118 |
+
|
| 1119 |
+
// Load real data from bookings API
|
| 1120 |
+
fetch(`${API_ROOT}/admin/bookings`)
|
| 1121 |
+
.then(response => response.json())
|
| 1122 |
+
.then(data => {
|
| 1123 |
+
console.log('Schedule data received:', data);
|
| 1124 |
+
|
| 1125 |
+
const tbody = $('#todayScheduleTable');
|
| 1126 |
+
tbody.empty();
|
| 1127 |
+
|
| 1128 |
+
if (data.bookings && data.bookings.length > 0) {
|
| 1129 |
+
// Show first 3 bookings as today's schedule
|
| 1130 |
+
data.bookings.slice(0, 3).forEach(booking => {
|
| 1131 |
+
const time = new Date(booking.scheduled_datetime * 1000).toLocaleTimeString('en-US', {
|
| 1132 |
+
hour: '2-digit',
|
| 1133 |
+
minute: '2-digit'
|
| 1134 |
+
});
|
| 1135 |
+
const patientName = booking.user_fullname || booking.user_account || 'Unknown Patient';
|
| 1136 |
+
const sessionType = booking.session_type || 'Routine';
|
| 1137 |
+
const status = booking.booking_status;
|
| 1138 |
+
|
| 1139 |
+
const row = `
|
| 1140 |
+
<tr>
|
| 1141 |
+
<td>${time}</td>
|
| 1142 |
+
<td>${patientName}</td>
|
| 1143 |
+
<td>${sessionType}</td>
|
| 1144 |
+
<td><span class="badge badge-${getStatusBadgeClass(status)}">${status}</span></td>
|
| 1145 |
+
</tr>
|
| 1146 |
+
`;
|
| 1147 |
+
tbody.append(row);
|
| 1148 |
+
});
|
| 1149 |
+
} else {
|
| 1150 |
+
tbody.html('<tr><td colspan="4" class="text-center text-muted">No sessions scheduled for today</td></tr>');
|
| 1151 |
+
}
|
| 1152 |
+
|
| 1153 |
+
console.log('Today\'s schedule loaded');
|
| 1154 |
+
})
|
| 1155 |
+
.catch(error => {
|
| 1156 |
+
console.error('Error loading schedule:', error);
|
| 1157 |
+
const tbody = $('#todayScheduleTable');
|
| 1158 |
+
tbody.html('<tr><td colspan="4" class="text-center text-danger">Error loading schedule</td></tr>');
|
| 1159 |
+
});
|
| 1160 |
+
}
|
| 1161 |
+
|
| 1162 |
+
/**
|
| 1163 |
+
* Load recent notifications
|
| 1164 |
+
*/
|
| 1165 |
+
function loadRecentNotifications() {
|
| 1166 |
+
console.log('Loading recent notifications...');
|
| 1167 |
+
|
| 1168 |
+
// For now, show sample notifications
|
| 1169 |
+
const notifications = [
|
| 1170 |
+
{
|
| 1171 |
+
id: 1,
|
| 1172 |
+
title: 'New Session Booking',
|
| 1173 |
+
message: 'A new session has been booked for tomorrow at 10:00 AM',
|
| 1174 |
+
time: new Date().toLocaleString(),
|
| 1175 |
+
type: 'info',
|
| 1176 |
+
read: false
|
| 1177 |
+
},
|
| 1178 |
+
{
|
| 1179 |
+
id: 2,
|
| 1180 |
+
title: 'High Risk Alert',
|
| 1181 |
+
message: 'A patient has been flagged as high risk and needs immediate attention',
|
| 1182 |
+
time: new Date(Date.now() - 3600000).toLocaleString(),
|
| 1183 |
+
type: 'warning',
|
| 1184 |
+
read: false
|
| 1185 |
+
},
|
| 1186 |
+
{
|
| 1187 |
+
id: 3,
|
| 1188 |
+
title: 'System Update',
|
| 1189 |
+
message: 'The system has been updated with new features',
|
| 1190 |
+
time: new Date(Date.now() - 7200000).toLocaleString(),
|
| 1191 |
+
type: 'success',
|
| 1192 |
+
read: true
|
| 1193 |
+
}
|
| 1194 |
+
];
|
| 1195 |
+
|
| 1196 |
+
console.log('Recent notifications loaded');
|
| 1197 |
+
}
|
| 1198 |
+
|
| 1199 |
+
/**
|
| 1200 |
+
* Load sessions data
|
| 1201 |
+
*/
|
| 1202 |
+
function loadSessions() {
|
| 1203 |
+
console.log('Loading sessions...');
|
| 1204 |
+
|
| 1205 |
+
const tbody = $('#sessionsTableBody');
|
| 1206 |
+
tbody.html('<tr><td colspan="7" class="text-center"><i class="fas fa-spinner fa-spin"></i> Loading sessions...</td></tr>');
|
| 1207 |
+
|
| 1208 |
+
console.log('Fetching from:', `${API_ROOT}/admin/bookings`);
|
| 1209 |
+
|
| 1210 |
+
fetch(`${API_ROOT}/admin/bookings`)
|
| 1211 |
+
.then(response => {
|
| 1212 |
+
console.log('API response status:', response.status);
|
| 1213 |
+
if (!response.ok) {
|
| 1214 |
+
throw new Error(`HTTP error! status: ${response.status}`);
|
| 1215 |
+
}
|
| 1216 |
+
return response.json();
|
| 1217 |
+
})
|
| 1218 |
+
.then(data => {
|
| 1219 |
+
console.log('Sessions data received:', data);
|
| 1220 |
+
console.log('Bookings count:', data.bookings ? data.bookings.length : 0);
|
| 1221 |
+
tbody.empty();
|
| 1222 |
+
|
| 1223 |
+
if (data.bookings && data.bookings.length > 0) {
|
| 1224 |
+
console.log('Processing bookings...');
|
| 1225 |
+
data.bookings.forEach((booking, index) => {
|
| 1226 |
+
console.log(`Processing booking ${index + 1}:`, booking.user_fullname || booking.user_account);
|
| 1227 |
+
|
| 1228 |
+
const scheduledTime = new Date(booking.scheduled_datetime * 1000).toLocaleString();
|
| 1229 |
+
const patientName = booking.user_fullname || booking.user_account || 'Unknown Patient';
|
| 1230 |
+
|
| 1231 |
+
const row = `
|
| 1232 |
+
<tr>
|
| 1233 |
+
<td>${booking.booking_id.substring(0, 8)}...</td>
|
| 1234 |
+
<td>
|
| 1235 |
+
<div class="user-info">
|
| 1236 |
+
<strong>${patientName}</strong>
|
| 1237 |
+
<br><small class="text-muted">${booking.user_email || 'No email'}</small>
|
| 1238 |
+
</div>
|
| 1239 |
+
</td>
|
| 1240 |
+
<td>${scheduledTime}</td>
|
| 1241 |
+
<td>${booking.session_type || 'Routine'}</td>
|
| 1242 |
+
<td><span class="badge badge-${getRiskBadgeClass(booking.risk_level)}">${booking.risk_level}</span></td>
|
| 1243 |
+
<td><span class="badge badge-${getStatusBadgeClass(booking.booking_status)}">${booking.booking_status}</span></td>
|
| 1244 |
+
<td>
|
| 1245 |
+
<button class="btn btn-sm btn-info" onclick="viewSession('${booking.booking_id}')" title="View Details">
|
| 1246 |
+
<i class="fas fa-eye"></i>
|
| 1247 |
+
</button>
|
| 1248 |
+
<button class="btn btn-sm btn-warning" onclick="addSessionNotes('${booking.booking_id}')" title="Add Notes">
|
| 1249 |
+
<i class="fas fa-sticky-note"></i>
|
| 1250 |
+
</button>
|
| 1251 |
+
</td>
|
| 1252 |
+
</tr>
|
| 1253 |
+
`;
|
| 1254 |
+
tbody.append(row);
|
| 1255 |
+
});
|
| 1256 |
+
console.log('All bookings processed successfully');
|
| 1257 |
+
} else {
|
| 1258 |
+
console.log('No bookings found in data');
|
| 1259 |
+
tbody.html(`
|
| 1260 |
+
<tr>
|
| 1261 |
+
<td colspan="7" class="text-center text-muted">
|
| 1262 |
+
<i class="fas fa-calendar-times"></i>
|
| 1263 |
+
<br>No sessions found
|
| 1264 |
+
</td>
|
| 1265 |
+
</tr>
|
| 1266 |
+
`);
|
| 1267 |
+
}
|
| 1268 |
+
|
| 1269 |
+
// Update DataTable if available
|
| 1270 |
+
if (dataTables.sessions && typeof $ !== 'undefined' && $.fn.DataTable) {
|
| 1271 |
+
console.log('Updating DataTable...');
|
| 1272 |
+
dataTables.sessions.clear().rows.add($(tbody).find('tr')).draw();
|
| 1273 |
+
}
|
| 1274 |
+
|
| 1275 |
+
console.log('Sessions loaded successfully');
|
| 1276 |
+
})
|
| 1277 |
+
.catch(error => {
|
| 1278 |
+
console.error('Error loading sessions:', error);
|
| 1279 |
+
tbody.html(`
|
| 1280 |
+
<tr>
|
| 1281 |
+
<td colspan="7" class="text-center text-danger">
|
| 1282 |
+
<i class="fas fa-exclamation-triangle"></i>
|
| 1283 |
+
<br>Error loading sessions: ${error.message}
|
| 1284 |
+
</td>
|
| 1285 |
+
</tr>
|
| 1286 |
+
`);
|
| 1287 |
+
});
|
| 1288 |
+
}
|
| 1289 |
+
|
| 1290 |
+
|
| 1291 |
+
/**
|
| 1292 |
+
* Load notifications
|
| 1293 |
+
*/
|
| 1294 |
+
function loadNotifications() {
|
| 1295 |
+
console.log('🔔 Loading notifications...');
|
| 1296 |
+
|
| 1297 |
+
const container = $('#notificationsList');
|
| 1298 |
+
container.html('<div class="text-center"><i class="fas fa-spinner fa-spin"></i> Loading notifications...</div>');
|
| 1299 |
+
|
| 1300 |
+
// For now, show sample notifications
|
| 1301 |
+
const notifications = [
|
| 1302 |
+
{
|
| 1303 |
+
id: 1,
|
| 1304 |
+
title: 'New Session Booking',
|
| 1305 |
+
message: 'A new session has been booked for tomorrow at 10:00 AM',
|
| 1306 |
+
time: new Date().toLocaleString(),
|
| 1307 |
+
type: 'info',
|
| 1308 |
+
read: false
|
| 1309 |
+
},
|
| 1310 |
+
{
|
| 1311 |
+
id: 2,
|
| 1312 |
+
title: 'High Risk Alert',
|
| 1313 |
+
message: 'A patient has been flagged as high risk and needs immediate attention',
|
| 1314 |
+
time: new Date(Date.now() - 3600000).toLocaleString(),
|
| 1315 |
+
type: 'warning',
|
| 1316 |
+
read: false
|
| 1317 |
+
},
|
| 1318 |
+
{
|
| 1319 |
+
id: 3,
|
| 1320 |
+
title: 'System Update',
|
| 1321 |
+
message: 'The system has been updated with new features',
|
| 1322 |
+
time: new Date(Date.now() - 7200000).toLocaleString(),
|
| 1323 |
+
type: 'success',
|
| 1324 |
+
read: true
|
| 1325 |
+
}
|
| 1326 |
+
];
|
| 1327 |
+
|
| 1328 |
+
container.empty();
|
| 1329 |
+
|
| 1330 |
+
if (notifications.length > 0) {
|
| 1331 |
+
notifications.forEach(notification => {
|
| 1332 |
+
const notificationHtml = `
|
| 1333 |
+
<div class="alert alert-${notification.type} alert-dismissible ${notification.read ? '' : 'alert-unread'}" data-notification-id="${notification.id}">
|
| 1334 |
+
<button type="button" class="close" data-dismiss="alert" aria-hidden="true" onclick="markNotificationRead(${notification.id})">×</button>
|
| 1335 |
+
<h5><i class="icon fas fa-${getNotificationIcon(notification.type)}"></i> ${notification.title}</h5>
|
| 1336 |
+
<p>${notification.message}</p>
|
| 1337 |
+
<small><i class="fas fa-clock"></i> ${notification.time}</small>
|
| 1338 |
+
${!notification.read ? `
|
| 1339 |
+
<button class="btn btn-sm btn-outline-primary float-right" onclick="markNotificationRead(${notification.id})">
|
| 1340 |
+
Mark as Read
|
| 1341 |
+
</button>
|
| 1342 |
+
` : ''}
|
| 1343 |
+
</div>
|
| 1344 |
+
`;
|
| 1345 |
+
container.append(notificationHtml);
|
| 1346 |
+
});
|
| 1347 |
+
} else {
|
| 1348 |
+
container.html(`
|
| 1349 |
+
<div class="text-center text-muted">
|
| 1350 |
+
<i class="fas fa-bell-slash"></i>
|
| 1351 |
+
<br>No notifications found
|
| 1352 |
+
</div>
|
| 1353 |
+
`);
|
| 1354 |
+
}
|
| 1355 |
+
|
| 1356 |
+
console.log(' Notifications loaded successfully');
|
| 1357 |
+
}
|
| 1358 |
+
|
| 1359 |
+
/**
|
| 1360 |
+
* Load reports
|
| 1361 |
+
*/
|
| 1362 |
+
function loadReports() {
|
| 1363 |
+
// Reports are already set in HTML
|
| 1364 |
+
// This function can be used to load dynamic report data
|
| 1365 |
+
}
|
| 1366 |
+
|
| 1367 |
+
/**
|
| 1368 |
+
* Load profile
|
| 1369 |
+
*/
|
| 1370 |
+
function loadProfile() {
|
| 1371 |
+
// Profile data is already set in HTML
|
| 1372 |
+
// This function can be used to load dynamic profile data
|
| 1373 |
+
}
|
| 1374 |
+
|
| 1375 |
+
/**
|
| 1376 |
+
* Load settings
|
| 1377 |
+
*/
|
| 1378 |
+
function loadSettings() {
|
| 1379 |
+
// Settings are already set in HTML
|
| 1380 |
+
// This function can be used to load dynamic settings
|
| 1381 |
+
}
|
| 1382 |
+
|
| 1383 |
+
/**
|
| 1384 |
+
* Save session notes
|
| 1385 |
+
*/
|
| 1386 |
+
function saveSessionNotes() {
|
| 1387 |
+
const formData = {
|
| 1388 |
+
sessionId: $('#sessionId').val(),
|
| 1389 |
+
patientName: $('#patientName').val(),
|
| 1390 |
+
sessionNotes: $('#sessionNotes').val(),
|
| 1391 |
+
followUpRequired: $('#followUpRequired').is(':checked'),
|
| 1392 |
+
followUpDate: $('#followUpDate').val()
|
| 1393 |
+
};
|
| 1394 |
+
|
| 1395 |
+
// Simulate API call
|
| 1396 |
+
if (typeof Swal !== 'undefined') {
|
| 1397 |
+
Swal.fire({
|
| 1398 |
+
title: 'Success!',
|
| 1399 |
+
text: 'Session notes saved successfully.',
|
| 1400 |
+
icon: 'success',
|
| 1401 |
+
timer: 2000
|
| 1402 |
+
}).then(() => {
|
| 1403 |
+
if (typeof $ !== 'undefined' && $.fn.modal) {
|
| 1404 |
+
$('#sessionNotesModal').modal('hide');
|
| 1405 |
+
} else {
|
| 1406 |
+
document.getElementById('sessionNotesModal').style.display = 'none';
|
| 1407 |
+
}
|
| 1408 |
+
loadSessions();
|
| 1409 |
+
});
|
| 1410 |
+
} else {
|
| 1411 |
+
alert('Session notes saved successfully.');
|
| 1412 |
+
if (typeof $ !== 'undefined' && $.fn.modal) {
|
| 1413 |
+
$('#sessionNotesModal').modal('hide');
|
| 1414 |
+
} else {
|
| 1415 |
+
document.getElementById('sessionNotesModal').style.display = 'none';
|
| 1416 |
+
}
|
| 1417 |
+
loadSessions();
|
| 1418 |
+
}
|
| 1419 |
+
}
|
| 1420 |
+
|
| 1421 |
+
/**
|
| 1422 |
+
* Apply filters
|
| 1423 |
+
*/
|
| 1424 |
+
function applyFilters() {
|
| 1425 |
+
const status = $('#sessionStatusFilter').val();
|
| 1426 |
+
const riskLevel = $('#riskLevelFilter').val();
|
| 1427 |
+
|
| 1428 |
+
// Apply filters to DataTables if available
|
| 1429 |
+
if (dataTables.sessions && typeof $ !== 'undefined' && $.fn.DataTable) {
|
| 1430 |
+
dataTables.sessions.column(5).search(status).draw();
|
| 1431 |
+
}
|
| 1432 |
+
}
|
| 1433 |
+
|
| 1434 |
+
/**
|
| 1435 |
+
* Get risk badge class
|
| 1436 |
+
*/
|
| 1437 |
+
function getRiskBadgeClass(riskLevel) {
|
| 1438 |
+
const classes = {
|
| 1439 |
+
'critical': 'danger',
|
| 1440 |
+
'high': 'warning',
|
| 1441 |
+
'medium': 'info',
|
| 1442 |
+
'low': 'success'
|
| 1443 |
+
};
|
| 1444 |
+
return classes[riskLevel.toLowerCase()] || 'secondary';
|
| 1445 |
+
}
|
| 1446 |
+
|
| 1447 |
+
/**
|
| 1448 |
+
* Get status badge class
|
| 1449 |
+
*/
|
| 1450 |
+
function getStatusBadgeClass(status) {
|
| 1451 |
+
const classes = {
|
| 1452 |
+
'scheduled': 'info',
|
| 1453 |
+
'completed': 'success',
|
| 1454 |
+
'cancelled': 'danger',
|
| 1455 |
+
'pending': 'warning',
|
| 1456 |
+
'confirmed': 'success',
|
| 1457 |
+
'high risk': 'danger'
|
| 1458 |
+
};
|
| 1459 |
+
return classes[status.toLowerCase()] || 'secondary';
|
| 1460 |
+
}
|
| 1461 |
+
|
| 1462 |
+
/**
|
| 1463 |
+
* Get notification icon
|
| 1464 |
+
*/
|
| 1465 |
+
function getNotificationIcon(type) {
|
| 1466 |
+
const icons = {
|
| 1467 |
+
'info': 'info-circle',
|
| 1468 |
+
'warning': 'exclamation-triangle',
|
| 1469 |
+
'success': 'check-circle',
|
| 1470 |
+
'danger': 'times-circle'
|
| 1471 |
+
};
|
| 1472 |
+
return icons[type] || 'info-circle';
|
| 1473 |
+
}
|
| 1474 |
+
|
| 1475 |
+
/**
|
| 1476 |
+
* Start auto-refresh
|
| 1477 |
+
*/
|
| 1478 |
+
function startAutoRefresh() {
|
| 1479 |
+
setInterval(() => {
|
| 1480 |
+
if (currentSection === 'dashboard') {
|
| 1481 |
+
loadDashboardData();
|
| 1482 |
+
} else {
|
| 1483 |
+
loadSectionData(currentSection);
|
| 1484 |
+
}
|
| 1485 |
+
}, 30000); // Refresh every 30 seconds
|
| 1486 |
+
}
|
| 1487 |
+
|
| 1488 |
+
/**
|
| 1489 |
+
* Session management functions
|
| 1490 |
+
*/
|
| 1491 |
+
window.acceptSession = function(bookingId) {
|
| 1492 |
+
console.log(' Accepting session:', bookingId);
|
| 1493 |
+
|
| 1494 |
+
if (typeof Swal !== 'undefined') {
|
| 1495 |
+
Swal.fire({
|
| 1496 |
+
title: 'Session Accepted!',
|
| 1497 |
+
text: 'The session has been accepted successfully.',
|
| 1498 |
+
icon: 'success',
|
| 1499 |
+
timer: 2000
|
| 1500 |
+
});
|
| 1501 |
+
} else {
|
| 1502 |
+
alert('Session Accepted! The session has been accepted successfully.');
|
| 1503 |
+
}
|
| 1504 |
+
loadSessions(); // Refresh sessions
|
| 1505 |
+
};
|
| 1506 |
+
|
| 1507 |
+
window.declineSession = function(bookingId) {
|
| 1508 |
+
console.log(' Declining session:', bookingId);
|
| 1509 |
+
|
| 1510 |
+
if (typeof Swal !== 'undefined') {
|
| 1511 |
+
Swal.fire({
|
| 1512 |
+
title: 'Decline Session?',
|
| 1513 |
+
text: 'Are you sure you want to decline this session?',
|
| 1514 |
+
icon: 'warning',
|
| 1515 |
+
showCancelButton: true,
|
| 1516 |
+
confirmButtonText: 'Yes, decline',
|
| 1517 |
+
cancelButtonText: 'Cancel'
|
| 1518 |
+
}).then((result) => {
|
| 1519 |
+
if (result.isConfirmed) {
|
| 1520 |
+
Swal.fire('Session Declined', 'The session has been declined.', 'success');
|
| 1521 |
+
loadSessions(); // Refresh sessions
|
| 1522 |
+
}
|
| 1523 |
+
});
|
| 1524 |
+
} else {
|
| 1525 |
+
if (confirm('Are you sure you want to decline this session?')) {
|
| 1526 |
+
alert('Session Declined! The session has been declined.');
|
| 1527 |
+
loadSessions(); // Refresh sessions
|
| 1528 |
+
}
|
| 1529 |
+
}
|
| 1530 |
+
};
|
| 1531 |
+
|
| 1532 |
+
window.addSessionNotes = function(bookingId) {
|
| 1533 |
+
console.log('📝 Adding notes for session:', bookingId);
|
| 1534 |
+
|
| 1535 |
+
// Populate modal with session details
|
| 1536 |
+
$('#sessionId').val(bookingId);
|
| 1537 |
+
$('#patientName').val('Patient Name');
|
| 1538 |
+
$('#sessionNotes').val('');
|
| 1539 |
+
$('#followUpRequired').prop('checked', false);
|
| 1540 |
+
$('#followUpDateGroup').hide();
|
| 1541 |
+
|
| 1542 |
+
// Show modal
|
| 1543 |
+
$('#sessionNotesModal').modal('show');
|
| 1544 |
+
};
|
| 1545 |
+
|
| 1546 |
+
window.markNotificationRead = function(notificationId) {
|
| 1547 |
+
console.log('📖 Marking notification as read:', notificationId);
|
| 1548 |
+
|
| 1549 |
+
// Remove the notification from the UI
|
| 1550 |
+
$(`[data-notification-id="${notificationId}"]`).fadeOut();
|
| 1551 |
+
loadDashboardStats(); // Refresh dashboard stats
|
| 1552 |
+
};
|
| 1553 |
+
|
| 1554 |
+
/**
|
| 1555 |
+
* Global functions for onclick handlers
|
| 1556 |
+
*/
|
| 1557 |
+
window.addNewSession = function() {
|
| 1558 |
+
$('#sessionNotesModal').modal('show');
|
| 1559 |
+
};
|
| 1560 |
+
|
| 1561 |
+
window.refreshSessions = function() {
|
| 1562 |
+
loadSessions();
|
| 1563 |
+
Swal.fire('Refreshed!', 'Sessions data has been refreshed.', 'success');
|
| 1564 |
+
};
|
| 1565 |
+
|
| 1566 |
+
window.markAllAsRead = function() {
|
| 1567 |
+
Swal.fire('Success!', 'All notifications marked as read.', 'success');
|
| 1568 |
+
$('#notificationBadge').text('0');
|
| 1569 |
+
$('#notificationCount').text('0');
|
| 1570 |
+
};
|
| 1571 |
+
|
| 1572 |
+
window.viewSession = function(id) {
|
| 1573 |
+
Swal.fire('View Session', `View session with ID: ${id}`, 'info');
|
| 1574 |
+
};
|
| 1575 |
+
|
| 1576 |
+
window.editSession = function(id) {
|
| 1577 |
+
Swal.fire('Edit Session', `Edit session with ID: ${id}`, 'info');
|
| 1578 |
+
};
|
| 1579 |
+
|
| 1580 |
+
|
| 1581 |
+
// Show dashboard by default
|
| 1582 |
+
showSection('dashboard');
|
| 1583 |
+
|
| 1584 |
+
})();
|
| 1585 |
+
|
| 1586 |
+
|
| 1587 |
+
|
chatbot/professional_dashboard.html
ADDED
|
@@ -0,0 +1,762 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>AIMHSA Professional Dashboard</title>
|
| 7 |
+
|
| 8 |
+
<!-- AdminLTE 4 CSS -->
|
| 9 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
| 10 |
+
<!-- Font Awesome -->
|
| 11 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 12 |
+
<!-- Google Font: Source Sans Pro -->
|
| 13 |
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
|
| 14 |
+
<!-- Chart.js -->
|
| 15 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 16 |
+
<!-- DataTables CSS -->
|
| 17 |
+
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap4.min.css">
|
| 18 |
+
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.5.0/css/responsive.bootstrap4.min.css">
|
| 19 |
+
<!-- Select2 CSS -->
|
| 20 |
+
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
| 21 |
+
<!-- SweetAlert2 CSS -->
|
| 22 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
|
| 23 |
+
<!-- FullCalendar CSS -->
|
| 24 |
+
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.11.5/main.min.css" rel="stylesheet">
|
| 25 |
+
|
| 26 |
+
<!-- Custom CSS (preserves existing styles) -->
|
| 27 |
+
<link rel="stylesheet" href="professional.css">
|
| 28 |
+
</head>
|
| 29 |
+
<body class="hold-transition sidebar-mini layout-fixed">
|
| 30 |
+
<div class="wrapper">
|
| 31 |
+
<!-- Navbar -->
|
| 32 |
+
<nav class="main-header navbar navbar-expand navbar-dark navbar-success">
|
| 33 |
+
<!-- Left navbar links -->
|
| 34 |
+
<ul class="navbar-nav">
|
| 35 |
+
<li class="nav-item">
|
| 36 |
+
<a class="nav-link" data-widget="pushmenu" href="#" role="button">
|
| 37 |
+
<i class="fas fa-bars"></i>
|
| 38 |
+
</a>
|
| 39 |
+
</li>
|
| 40 |
+
<li class="nav-item d-none d-sm-inline-block">
|
| 41 |
+
<a href="#" class="nav-link" data-section="dashboard">Dashboard</a>
|
| 42 |
+
</li>
|
| 43 |
+
<li class="nav-item d-none d-sm-inline-block">
|
| 44 |
+
<a href="#" class="nav-link" data-section="sessions">Sessions</a>
|
| 45 |
+
</li>
|
| 46 |
+
<li class="nav-item d-none d-sm-inline-block">
|
| 47 |
+
<a href="#" class="nav-link" data-section="reports">Reports</a>
|
| 48 |
+
</li>
|
| 49 |
+
</ul>
|
| 50 |
+
|
| 51 |
+
<!-- Right navbar links -->
|
| 52 |
+
<ul class="navbar-nav ml-auto">
|
| 53 |
+
<!-- Notifications Dropdown Menu -->
|
| 54 |
+
<li class="nav-item dropdown">
|
| 55 |
+
<a class="nav-link" data-toggle="dropdown" href="#">
|
| 56 |
+
<i class="far fa-bell"></i>
|
| 57 |
+
<span class="badge badge-warning navbar-badge" id="notificationBadge">8</span>
|
| 58 |
+
</a>
|
| 59 |
+
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
|
| 60 |
+
<span class="dropdown-item dropdown-header">8 Notifications</span>
|
| 61 |
+
<div class="dropdown-divider"></div>
|
| 62 |
+
<a href="#" class="dropdown-item">
|
| 63 |
+
<i class="fas fa-calendar mr-2"></i> New session booking
|
| 64 |
+
<span class="float-right text-muted text-sm">3 mins</span>
|
| 65 |
+
</a>
|
| 66 |
+
<div class="dropdown-divider"></div>
|
| 67 |
+
<a href="#" class="dropdown-item">
|
| 68 |
+
<i class="fas fa-exclamation-triangle mr-2"></i> High risk assessment
|
| 69 |
+
<span class="float-right text-muted text-sm">12 hours</span>
|
| 70 |
+
</a>
|
| 71 |
+
<div class="dropdown-divider"></div>
|
| 72 |
+
<a href="#" class="dropdown-item">
|
| 73 |
+
<i class="fas fa-user mr-2"></i> New patient assigned
|
| 74 |
+
<span class="float-right text-muted text-sm">2 days</span>
|
| 75 |
+
</a>
|
| 76 |
+
<div class="dropdown-divider"></div>
|
| 77 |
+
<a href="#" class="dropdown-item dropdown-footer">See All Notifications</a>
|
| 78 |
+
</div>
|
| 79 |
+
</li>
|
| 80 |
+
|
| 81 |
+
<!-- User Account Dropdown -->
|
| 82 |
+
<li class="nav-item dropdown">
|
| 83 |
+
<a class="nav-link" data-toggle="dropdown" href="#">
|
| 84 |
+
<i class="far fa-user"></i>
|
| 85 |
+
<span class="ml-2" id="professionalName">Dr. John Doe</span>
|
| 86 |
+
</a>
|
| 87 |
+
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
|
| 88 |
+
<a href="#" class="dropdown-item">
|
| 89 |
+
<i class="fas fa-user mr-2"></i> Profile
|
| 90 |
+
</a>
|
| 91 |
+
<a href="#" class="dropdown-item">
|
| 92 |
+
<i class="fas fa-cog mr-2"></i> Settings
|
| 93 |
+
</a>
|
| 94 |
+
<div class="dropdown-divider"></div>
|
| 95 |
+
<a href="#" class="dropdown-item" id="logoutBtn">
|
| 96 |
+
<i class="fas fa-sign-out-alt mr-2"></i> Logout
|
| 97 |
+
</a>
|
| 98 |
+
</div>
|
| 99 |
+
</li>
|
| 100 |
+
</ul>
|
| 101 |
+
</nav>
|
| 102 |
+
|
| 103 |
+
<!-- Main Sidebar Container -->
|
| 104 |
+
<aside class="main-sidebar sidebar-dark-success elevation-4">
|
| 105 |
+
<!-- Brand Logo -->
|
| 106 |
+
<a href="#" class="brand-link">
|
| 107 |
+
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzMiIGhlaWdodD0iMzMiIHZpZXdCb3g9IjAgMCAzMyAzMyIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjMzIiBoZWlnaHQ9IjMzIiByeD0iMTYuNSIgZmlsbD0iIzI4YTc0NSIvPgo8dGV4dCB4PSIxNi41IiB5PSIyMCIgZm9udC1mYW1pbHk9IkFyaWFsLCBzYW5zLXNlcmlmIiBmb250LXNpemU9IjE0IiBmb250LXdlaWdodD0iYm9sZCIgZmlsbD0id2hpdGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk1EPC90ZXh0Pgo8L3N2Zz4K" alt="AIMHSA Logo" class="brand-image img-circle elevation-3" style="opacity: .8">
|
| 108 |
+
<span class="brand-text font-weight-light">AIMHSA Professional</span>
|
| 109 |
+
</a>
|
| 110 |
+
|
| 111 |
+
<!-- Sidebar -->
|
| 112 |
+
<div class="sidebar">
|
| 113 |
+
<!-- Sidebar user panel -->
|
| 114 |
+
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
|
| 115 |
+
<div class="image">
|
| 116 |
+
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYwIiBoZWlnaHQ9IjE2MCIgdmlld0JveD0iMCAwIDE2MCAxNjAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxNjAiIGhlaWdodD0iMTYwIiByeD0iODAiIGZpbGw9IiMyOGE3NDUiLz4KPHRleHQgeD0iODAiIHk9Ijk1IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNjQiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+SlQ8L3RleHQ+Cjwvc3ZnPgo=" class="img-circle elevation-2" alt="User Image" id="userProfileImage">
|
| 117 |
+
</div>
|
| 118 |
+
<div class="info">
|
| 119 |
+
<a href="#" class="d-block" id="professionalNameSidebar">Dr. John Doe</a>
|
| 120 |
+
<small class="text-muted" id="professionalRole">Psychiatrist</small>
|
| 121 |
+
</div>
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
<!-- Sidebar Menu -->
|
| 125 |
+
<nav class="mt-2">
|
| 126 |
+
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
|
| 127 |
+
<li class="nav-item">
|
| 128 |
+
<a href="#dashboard" class="nav-link active" data-section="dashboard">
|
| 129 |
+
<i class="nav-icon fas fa-tachometer-alt"></i>
|
| 130 |
+
<p>Dashboard</p>
|
| 131 |
+
</a>
|
| 132 |
+
</li>
|
| 133 |
+
<li class="nav-item">
|
| 134 |
+
<a href="#sessions" class="nav-link" data-section="sessions">
|
| 135 |
+
<i class="nav-icon fas fa-calendar-check"></i>
|
| 136 |
+
<p>My Sessions</p>
|
| 137 |
+
</a>
|
| 138 |
+
</li>
|
| 139 |
+
<li class="nav-item">
|
| 140 |
+
</li>
|
| 141 |
+
<li class="nav-item">
|
| 142 |
+
<a href="#notifications" class="nav-link" data-section="notifications">
|
| 143 |
+
<i class="nav-icon fas fa-bell"></i>
|
| 144 |
+
<p>Notifications</p>
|
| 145 |
+
<span class="badge badge-warning right" id="notificationCount">8</span>
|
| 146 |
+
</a>
|
| 147 |
+
</li>
|
| 148 |
+
<li class="nav-item">
|
| 149 |
+
<a href="#reports" class="nav-link" data-section="reports">
|
| 150 |
+
<i class="nav-icon fas fa-chart-bar"></i>
|
| 151 |
+
<p>Reports</p>
|
| 152 |
+
</a>
|
| 153 |
+
</li>
|
| 154 |
+
<li class="nav-item">
|
| 155 |
+
<a href="#profile" class="nav-link" data-section="profile">
|
| 156 |
+
<i class="nav-icon fas fa-user"></i>
|
| 157 |
+
<p>Profile</p>
|
| 158 |
+
</a>
|
| 159 |
+
</li>
|
| 160 |
+
<li class="nav-item">
|
| 161 |
+
<a href="#settings" class="nav-link" data-section="settings">
|
| 162 |
+
<i class="nav-icon fas fa-cog"></i>
|
| 163 |
+
<p>Settings</p>
|
| 164 |
+
</a>
|
| 165 |
+
</li>
|
| 166 |
+
</ul>
|
| 167 |
+
</nav>
|
| 168 |
+
</div>
|
| 169 |
+
</aside>
|
| 170 |
+
|
| 171 |
+
<!-- Content Wrapper -->
|
| 172 |
+
<div class="content-wrapper">
|
| 173 |
+
<!-- Content Header -->
|
| 174 |
+
<div class="content-header">
|
| 175 |
+
<div class="container-fluid">
|
| 176 |
+
<div class="row mb-2">
|
| 177 |
+
<div class="col-sm-6">
|
| 178 |
+
<h1 class="m-0" id="pageTitle">Dashboard</h1>
|
| 179 |
+
</div>
|
| 180 |
+
<div class="col-sm-6">
|
| 181 |
+
<ol class="breadcrumb float-sm-right">
|
| 182 |
+
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
| 183 |
+
<li class="breadcrumb-item active" id="breadcrumbActive">Dashboard</li>
|
| 184 |
+
<li class="breadcrumb-item">
|
| 185 |
+
<button type="button" class="btn btn-sm btn-primary" id="refreshDashboardBtn">
|
| 186 |
+
<i class="fas fa-sync"></i> Refresh Data
|
| 187 |
+
</button>
|
| 188 |
+
</li>
|
| 189 |
+
</ol>
|
| 190 |
+
</div>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
|
| 195 |
+
<!-- Main content -->
|
| 196 |
+
<section class="content">
|
| 197 |
+
<div class="container-fluid">
|
| 198 |
+
<!-- Dashboard Section -->
|
| 199 |
+
<div id="dashboard-section" class="content-section">
|
| 200 |
+
<!-- Info boxes -->
|
| 201 |
+
<div class="row">
|
| 202 |
+
<div class="col-lg-3 col-6">
|
| 203 |
+
<div class="small-box bg-info">
|
| 204 |
+
<div class="inner">
|
| 205 |
+
<h3 id="totalSessions">15</h3>
|
| 206 |
+
<p>Total Sessions</p>
|
| 207 |
+
</div>
|
| 208 |
+
<div class="icon">
|
| 209 |
+
<i class="fas fa-calendar-check"></i>
|
| 210 |
+
</div>
|
| 211 |
+
<a href="#sessions" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 212 |
+
</div>
|
| 213 |
+
</div>
|
| 214 |
+
<div class="col-lg-3 col-6">
|
| 215 |
+
<div class="small-box bg-warning">
|
| 216 |
+
<div class="inner">
|
| 217 |
+
<h3 id="unreadNotifications">8</h3>
|
| 218 |
+
<p>Unread Notifications</p>
|
| 219 |
+
</div>
|
| 220 |
+
<div class="icon">
|
| 221 |
+
<i class="fas fa-bell"></i>
|
| 222 |
+
</div>
|
| 223 |
+
<a href="#notifications" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 224 |
+
</div>
|
| 225 |
+
</div>
|
| 226 |
+
<div class="col-lg-3 col-6">
|
| 227 |
+
<div class="small-box bg-success">
|
| 228 |
+
<div class="inner">
|
| 229 |
+
<h3 id="upcomingToday">3</h3>
|
| 230 |
+
<p>Today's Sessions</p>
|
| 231 |
+
</div>
|
| 232 |
+
<div class="icon">
|
| 233 |
+
<i class="fas fa-clock"></i>
|
| 234 |
+
</div>
|
| 235 |
+
<a href="#sessions" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 236 |
+
</div>
|
| 237 |
+
</div>
|
| 238 |
+
<div class="col-lg-3 col-6">
|
| 239 |
+
<div class="small-box bg-danger">
|
| 240 |
+
<div class="inner">
|
| 241 |
+
<h3 id="highRiskSessions">2</h3>
|
| 242 |
+
<p>High Risk Cases</p>
|
| 243 |
+
</div>
|
| 244 |
+
<div class="icon">
|
| 245 |
+
<i class="fas fa-exclamation-triangle"></i>
|
| 246 |
+
</div>
|
| 247 |
+
<a href="#patients" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 248 |
+
</div>
|
| 249 |
+
</div>
|
| 250 |
+
</div>
|
| 251 |
+
|
| 252 |
+
<!-- Charts Row -->
|
| 253 |
+
<div class="row">
|
| 254 |
+
<div class="col-lg-8">
|
| 255 |
+
<div class="card">
|
| 256 |
+
<div class="card-header">
|
| 257 |
+
<h3 class="card-title">Session Trends</h3>
|
| 258 |
+
<div class="card-tools">
|
| 259 |
+
<button type="button" class="btn btn-tool" data-card-widget="collapse">
|
| 260 |
+
<i class="fas fa-minus"></i>
|
| 261 |
+
</button>
|
| 262 |
+
<button type="button" class="btn btn-tool" data-card-widget="remove">
|
| 263 |
+
<i class="fas fa-times"></i>
|
| 264 |
+
</button>
|
| 265 |
+
</div>
|
| 266 |
+
</div>
|
| 267 |
+
<div class="card-body">
|
| 268 |
+
<canvas id="sessionTrendChart" style="height: 300px;"></canvas>
|
| 269 |
+
</div>
|
| 270 |
+
</div>
|
| 271 |
+
</div>
|
| 272 |
+
<div class="col-lg-4">
|
| 273 |
+
<div class="card">
|
| 274 |
+
<div class="card-header">
|
| 275 |
+
<h3 class="card-title">Patient Risk Distribution</h3>
|
| 276 |
+
</div>
|
| 277 |
+
<div class="card-body">
|
| 278 |
+
<canvas id="riskDistributionChart" style="height: 300px;"></canvas>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
</div>
|
| 282 |
+
</div>
|
| 283 |
+
|
| 284 |
+
<!-- Recent Activity -->
|
| 285 |
+
<div class="row">
|
| 286 |
+
<div class="col-lg-6">
|
| 287 |
+
<div class="card">
|
| 288 |
+
<div class="card-header">
|
| 289 |
+
<h3 class="card-title">Today's Schedule</h3>
|
| 290 |
+
</div>
|
| 291 |
+
<div class="card-body">
|
| 292 |
+
<div class="table-responsive">
|
| 293 |
+
<table class="table table-striped">
|
| 294 |
+
<thead>
|
| 295 |
+
<tr>
|
| 296 |
+
<th>Time</th>
|
| 297 |
+
<th>Patient</th>
|
| 298 |
+
<th>Type</th>
|
| 299 |
+
<th>Status</th>
|
| 300 |
+
</tr>
|
| 301 |
+
</thead>
|
| 302 |
+
<tbody id="todayScheduleTable">
|
| 303 |
+
<tr>
|
| 304 |
+
<td>09:00</td>
|
| 305 |
+
<td>John Doe</td>
|
| 306 |
+
<td>Initial</td>
|
| 307 |
+
<td><span class="badge badge-success">Confirmed</span></td>
|
| 308 |
+
</tr>
|
| 309 |
+
<tr>
|
| 310 |
+
<td>11:00</td>
|
| 311 |
+
<td>Jane Smith</td>
|
| 312 |
+
<td>Follow-up</td>
|
| 313 |
+
<td><span class="badge badge-warning">Pending</span></td>
|
| 314 |
+
</tr>
|
| 315 |
+
<tr>
|
| 316 |
+
<td>14:00</td>
|
| 317 |
+
<td>Bob Johnson</td>
|
| 318 |
+
<td>Emergency</td>
|
| 319 |
+
<td><span class="badge badge-danger">High Risk</span></td>
|
| 320 |
+
</tr>
|
| 321 |
+
</tbody>
|
| 322 |
+
</table>
|
| 323 |
+
</div>
|
| 324 |
+
</div>
|
| 325 |
+
</div>
|
| 326 |
+
</div>
|
| 327 |
+
<div class="col-lg-6">
|
| 328 |
+
<div class="card">
|
| 329 |
+
<div class="card-header">
|
| 330 |
+
<h3 class="card-title">Recent Notifications</h3>
|
| 331 |
+
</div>
|
| 332 |
+
<div class="card-body">
|
| 333 |
+
<div class="timeline">
|
| 334 |
+
<div class="time-label">
|
| 335 |
+
<span class="bg-red">Today</span>
|
| 336 |
+
</div>
|
| 337 |
+
<div>
|
| 338 |
+
<i class="fas fa-calendar bg-blue"></i>
|
| 339 |
+
<div class="timeline-item">
|
| 340 |
+
<span class="time"><i class="fas fa-clock"></i> 12:05</span>
|
| 341 |
+
<h3 class="timeline-header">New Session Booking</h3>
|
| 342 |
+
<div class="timeline-body">
|
| 343 |
+
John Doe has booked a session for tomorrow at 10:00 AM
|
| 344 |
+
</div>
|
| 345 |
+
</div>
|
| 346 |
+
</div>
|
| 347 |
+
<div>
|
| 348 |
+
<i class="fas fa-exclamation-triangle bg-yellow"></i>
|
| 349 |
+
<div class="timeline-item">
|
| 350 |
+
<span class="time"><i class="fas fa-clock"></i> 09:30</span>
|
| 351 |
+
<h3 class="timeline-header">High Risk Alert</h3>
|
| 352 |
+
<div class="timeline-body">
|
| 353 |
+
Patient Bob Johnson has been flagged as high risk
|
| 354 |
+
</div>
|
| 355 |
+
</div>
|
| 356 |
+
</div>
|
| 357 |
+
<div>
|
| 358 |
+
<i class="fas fa-user bg-green"></i>
|
| 359 |
+
<div class="timeline-item">
|
| 360 |
+
<span class="time"><i class="fas fa-clock"></i> 08:15</span>
|
| 361 |
+
<h3 class="timeline-header">New Patient</h3>
|
| 362 |
+
<div class="timeline-body">
|
| 363 |
+
New patient Jane Smith has been assigned to you
|
| 364 |
+
</div>
|
| 365 |
+
</div>
|
| 366 |
+
</div>
|
| 367 |
+
</div>
|
| 368 |
+
</div>
|
| 369 |
+
</div>
|
| 370 |
+
</div>
|
| 371 |
+
</div>
|
| 372 |
+
</div>
|
| 373 |
+
|
| 374 |
+
<!-- Sessions Section -->
|
| 375 |
+
<div id="sessions-section" class="content-section" style="display: none;">
|
| 376 |
+
<div class="row">
|
| 377 |
+
<div class="col-12">
|
| 378 |
+
<div class="card">
|
| 379 |
+
<div class="card-header">
|
| 380 |
+
<h3 class="card-title">My Sessions</h3>
|
| 381 |
+
<div class="card-tools">
|
| 382 |
+
<button type="button" class="btn btn-primary" onclick="addNewSession()">
|
| 383 |
+
<i class="fas fa-plus"></i> New Session
|
| 384 |
+
</button>
|
| 385 |
+
</div>
|
| 386 |
+
</div>
|
| 387 |
+
<div class="card-body">
|
| 388 |
+
<div class="row mb-3">
|
| 389 |
+
<div class="col-md-4">
|
| 390 |
+
<select class="form-control" id="sessionStatusFilter">
|
| 391 |
+
<option value="">All Status</option>
|
| 392 |
+
<option value="scheduled">Scheduled</option>
|
| 393 |
+
<option value="completed">Completed</option>
|
| 394 |
+
<option value="cancelled">Cancelled</option>
|
| 395 |
+
</select>
|
| 396 |
+
</div>
|
| 397 |
+
<div class="col-md-4">
|
| 398 |
+
<input type="date" class="form-control" id="sessionDateFilter" placeholder="Filter by date">
|
| 399 |
+
</div>
|
| 400 |
+
<div class="col-md-4">
|
| 401 |
+
<button class="btn btn-success" onclick="refreshSessions()">
|
| 402 |
+
<i class="fas fa-sync"></i> Refresh
|
| 403 |
+
</button>
|
| 404 |
+
</div>
|
| 405 |
+
</div>
|
| 406 |
+
<div class="table-responsive">
|
| 407 |
+
<table id="sessionsTable" class="table table-bordered table-striped">
|
| 408 |
+
<thead>
|
| 409 |
+
<tr>
|
| 410 |
+
<th>ID</th>
|
| 411 |
+
<th>Patient</th>
|
| 412 |
+
<th>Date & Time</th>
|
| 413 |
+
<th>Type</th>
|
| 414 |
+
<th>Risk Level</th>
|
| 415 |
+
<th>Status</th>
|
| 416 |
+
<th>Actions</th>
|
| 417 |
+
</tr>
|
| 418 |
+
</thead>
|
| 419 |
+
<tbody id="sessionsTableBody">
|
| 420 |
+
<!-- Data will be loaded here -->
|
| 421 |
+
</tbody>
|
| 422 |
+
</table>
|
| 423 |
+
</div>
|
| 424 |
+
</div>
|
| 425 |
+
</div>
|
| 426 |
+
</div>
|
| 427 |
+
</div>
|
| 428 |
+
</div>
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
<!-- Notifications Section -->
|
| 432 |
+
<div id="notifications-section" class="content-section" style="display: none;">
|
| 433 |
+
<div class="row">
|
| 434 |
+
<div class="col-12">
|
| 435 |
+
<div class="card">
|
| 436 |
+
<div class="card-header">
|
| 437 |
+
<h3 class="card-title">Notifications</h3>
|
| 438 |
+
<div class="card-tools">
|
| 439 |
+
<button type="button" class="btn btn-success" onclick="markAllAsRead()">
|
| 440 |
+
<i class="fas fa-check"></i> Mark All Read
|
| 441 |
+
</button>
|
| 442 |
+
</div>
|
| 443 |
+
</div>
|
| 444 |
+
<div class="card-body">
|
| 445 |
+
<div id="notificationsList">
|
| 446 |
+
<!-- Notifications will be loaded here -->
|
| 447 |
+
</div>
|
| 448 |
+
</div>
|
| 449 |
+
</div>
|
| 450 |
+
</div>
|
| 451 |
+
</div>
|
| 452 |
+
</div>
|
| 453 |
+
|
| 454 |
+
<!-- Reports Section -->
|
| 455 |
+
<div id="reports-section" class="content-section" style="display: none;">
|
| 456 |
+
<div class="row">
|
| 457 |
+
<div class="col-12">
|
| 458 |
+
<div class="card">
|
| 459 |
+
<div class="card-header">
|
| 460 |
+
<h3 class="card-title">Professional Reports</h3>
|
| 461 |
+
<div class="card-tools">
|
| 462 |
+
<button type="button" class="btn btn-success">
|
| 463 |
+
<i class="fas fa-download"></i> Generate Report
|
| 464 |
+
</button>
|
| 465 |
+
</div>
|
| 466 |
+
</div>
|
| 467 |
+
<div class="card-body">
|
| 468 |
+
<div class="row">
|
| 469 |
+
<div class="col-md-4">
|
| 470 |
+
<div class="card card-primary">
|
| 471 |
+
<div class="card-header">
|
| 472 |
+
<h3 class="card-title">Session Summary</h3>
|
| 473 |
+
</div>
|
| 474 |
+
<div class="card-body">
|
| 475 |
+
<p>Generate monthly session summary report</p>
|
| 476 |
+
<button class="btn btn-primary">Generate</button>
|
| 477 |
+
</div>
|
| 478 |
+
</div>
|
| 479 |
+
</div>
|
| 480 |
+
<div class="col-md-4">
|
| 481 |
+
<div class="card card-success">
|
| 482 |
+
<div class="card-header">
|
| 483 |
+
<h3 class="card-title">Patient Progress</h3>
|
| 484 |
+
</div>
|
| 485 |
+
<div class="card-body">
|
| 486 |
+
<p>Track patient progress over time</p>
|
| 487 |
+
<button class="btn btn-success">Generate</button>
|
| 488 |
+
</div>
|
| 489 |
+
</div>
|
| 490 |
+
</div>
|
| 491 |
+
<div class="col-md-4">
|
| 492 |
+
<div class="card card-warning">
|
| 493 |
+
<div class="card-header">
|
| 494 |
+
<h3 class="card-title">Risk Assessment</h3>
|
| 495 |
+
</div>
|
| 496 |
+
<div class="card-body">
|
| 497 |
+
<p>Detailed risk assessment reports</p>
|
| 498 |
+
<button class="btn btn-warning">Generate</button>
|
| 499 |
+
</div>
|
| 500 |
+
</div>
|
| 501 |
+
</div>
|
| 502 |
+
</div>
|
| 503 |
+
</div>
|
| 504 |
+
</div>
|
| 505 |
+
</div>
|
| 506 |
+
</div>
|
| 507 |
+
</div>
|
| 508 |
+
|
| 509 |
+
<!-- Profile Section -->
|
| 510 |
+
<div id="profile-section" class="content-section" style="display: none;">
|
| 511 |
+
<div class="row">
|
| 512 |
+
<div class="col-md-4">
|
| 513 |
+
<div class="card card-primary card-outline">
|
| 514 |
+
<div class="card-body box-profile">
|
| 515 |
+
<div class="text-center">
|
| 516 |
+
<img class="profile-user-img img-fluid img-circle" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIiByeD0iNTAiIGZpbGw9IiMyOGE3NDUiLz4KPHRleHQgeD0iNTAiIHk9IjU4IiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNDAiIGZvbnQtd2VpZ2h0PSJib2xkIiBmaWxsPSJ3aGl0ZSIgdGV4dC1hbmNob3I9Im1pZGRsZSI+SlQ8L3RleHQ+Cjwvc3ZnPgo=" alt="User profile picture" id="profileUserImage">
|
| 517 |
+
</div>
|
| 518 |
+
<h3 class="profile-username text-center" id="profileName">Dr. John Doe</h3>
|
| 519 |
+
<p class="text-muted text-center" id="profileRole">Psychiatrist</p>
|
| 520 |
+
<ul class="list-group list-group-unbordered mb-3">
|
| 521 |
+
<li class="list-group-item">
|
| 522 |
+
<b>Specialization</b> <a class="float-right">Psychiatry</a>
|
| 523 |
+
</li>
|
| 524 |
+
<li class="list-group-item">
|
| 525 |
+
<b>Experience</b> <a class="float-right">8 years</a>
|
| 526 |
+
</li>
|
| 527 |
+
</ul>
|
| 528 |
+
</div>
|
| 529 |
+
</div>
|
| 530 |
+
</div>
|
| 531 |
+
<div class="col-md-8">
|
| 532 |
+
<div class="card">
|
| 533 |
+
<div class="card-header">
|
| 534 |
+
<h3 class="card-title">Profile Information</h3>
|
| 535 |
+
</div>
|
| 536 |
+
<div class="card-body">
|
| 537 |
+
<form>
|
| 538 |
+
<div class="row">
|
| 539 |
+
<div class="col-md-6">
|
| 540 |
+
<div class="form-group">
|
| 541 |
+
<label>First Name</label>
|
| 542 |
+
<input type="text" class="form-control" value="John">
|
| 543 |
+
</div>
|
| 544 |
+
</div>
|
| 545 |
+
<div class="col-md-6">
|
| 546 |
+
<div class="form-group">
|
| 547 |
+
<label>Last Name</label>
|
| 548 |
+
<input type="text" class="form-control" value="Doe">
|
| 549 |
+
</div>
|
| 550 |
+
</div>
|
| 551 |
+
</div>
|
| 552 |
+
<div class="row">
|
| 553 |
+
<div class="col-md-6">
|
| 554 |
+
<div class="form-group">
|
| 555 |
+
<label>Email</label>
|
| 556 |
+
<input type="email" class="form-control" value="john.doe@aimhsa.rw">
|
| 557 |
+
</div>
|
| 558 |
+
</div>
|
| 559 |
+
<div class="col-md-6">
|
| 560 |
+
<div class="form-group">
|
| 561 |
+
<label>Phone</label>
|
| 562 |
+
<input type="tel" class="form-control" value="+250 788 123 456">
|
| 563 |
+
</div>
|
| 564 |
+
</div>
|
| 565 |
+
</div>
|
| 566 |
+
<div class="form-group">
|
| 567 |
+
<label>Bio</label>
|
| 568 |
+
<textarea class="form-control" rows="3">Experienced psychiatrist specializing in trauma and anxiety disorders.</textarea>
|
| 569 |
+
</div>
|
| 570 |
+
<button type="submit" class="btn btn-primary">Update Profile</button>
|
| 571 |
+
</form>
|
| 572 |
+
</div>
|
| 573 |
+
</div>
|
| 574 |
+
</div>
|
| 575 |
+
</div>
|
| 576 |
+
</div>
|
| 577 |
+
|
| 578 |
+
<!-- Settings Section -->
|
| 579 |
+
<div id="settings-section" class="content-section" style="display: none;">
|
| 580 |
+
<div class="row">
|
| 581 |
+
<div class="col-12">
|
| 582 |
+
<div class="card">
|
| 583 |
+
<div class="card-header">
|
| 584 |
+
<h3 class="card-title">Professional Settings</h3>
|
| 585 |
+
</div>
|
| 586 |
+
<div class="card-body">
|
| 587 |
+
<form>
|
| 588 |
+
<div class="row">
|
| 589 |
+
<div class="col-md-6">
|
| 590 |
+
<div class="form-group">
|
| 591 |
+
<label>Notification Preferences</label>
|
| 592 |
+
<div class="form-check">
|
| 593 |
+
<input class="form-check-input" type="checkbox" checked>
|
| 594 |
+
<label class="form-check-label">Email Notifications</label>
|
| 595 |
+
</div>
|
| 596 |
+
<div class="form-check">
|
| 597 |
+
<input class="form-check-input" type="checkbox" checked>
|
| 598 |
+
<label class="form-check-label">SMS Notifications</label>
|
| 599 |
+
</div>
|
| 600 |
+
<div class="form-check">
|
| 601 |
+
<input class="form-check-input" type="checkbox">
|
| 602 |
+
<label class="form-check-label">Push Notifications</label>
|
| 603 |
+
</div>
|
| 604 |
+
</div>
|
| 605 |
+
</div>
|
| 606 |
+
<div class="col-md-6">
|
| 607 |
+
<div class="form-group">
|
| 608 |
+
<label>Working Hours</label>
|
| 609 |
+
<div class="row">
|
| 610 |
+
<div class="col-6">
|
| 611 |
+
<input type="time" class="form-control" value="08:00">
|
| 612 |
+
</div>
|
| 613 |
+
<div class="col-6">
|
| 614 |
+
<input type="time" class="form-control" value="17:00">
|
| 615 |
+
</div>
|
| 616 |
+
</div>
|
| 617 |
+
</div>
|
| 618 |
+
</div>
|
| 619 |
+
</div>
|
| 620 |
+
<div class="row">
|
| 621 |
+
<div class="col-md-6">
|
| 622 |
+
<div class="form-group">
|
| 623 |
+
<label>Session Duration (minutes)</label>
|
| 624 |
+
<select class="form-control">
|
| 625 |
+
<option>30</option>
|
| 626 |
+
<option selected>45</option>
|
| 627 |
+
<option>60</option>
|
| 628 |
+
<option>90</option>
|
| 629 |
+
</select>
|
| 630 |
+
</div>
|
| 631 |
+
</div>
|
| 632 |
+
<div class="col-md-6">
|
| 633 |
+
<div class="form-group">
|
| 634 |
+
<label>Auto-refresh Interval</label>
|
| 635 |
+
<select class="form-control">
|
| 636 |
+
<option>30 seconds</option>
|
| 637 |
+
<option selected>1 minute</option>
|
| 638 |
+
<option>5 minutes</option>
|
| 639 |
+
</select>
|
| 640 |
+
</div>
|
| 641 |
+
</div>
|
| 642 |
+
</div>
|
| 643 |
+
<button type="submit" class="btn btn-primary">Save Settings</button>
|
| 644 |
+
</form>
|
| 645 |
+
</div>
|
| 646 |
+
</div>
|
| 647 |
+
</div>
|
| 648 |
+
</div>
|
| 649 |
+
</div>
|
| 650 |
+
</div>
|
| 651 |
+
</section>
|
| 652 |
+
</div>
|
| 653 |
+
</div>
|
| 654 |
+
|
| 655 |
+
<!-- Session Notes Modal -->
|
| 656 |
+
<div class="modal fade" id="sessionNotesModal" tabindex="-1">
|
| 657 |
+
<div class="modal-dialog modal-lg">
|
| 658 |
+
<div class="modal-content">
|
| 659 |
+
<div class="modal-header">
|
| 660 |
+
<h4 class="modal-title">Add Session Notes</h4>
|
| 661 |
+
<button type="button" class="close" data-dismiss="modal">
|
| 662 |
+
<span>×</span>
|
| 663 |
+
</button>
|
| 664 |
+
</div>
|
| 665 |
+
<div class="modal-body">
|
| 666 |
+
<form id="sessionNotesForm">
|
| 667 |
+
<div class="row">
|
| 668 |
+
<div class="col-md-6">
|
| 669 |
+
<div class="form-group">
|
| 670 |
+
<label for="sessionId">Session ID</label>
|
| 671 |
+
<input type="text" class="form-control" id="sessionId" name="sessionId" readonly>
|
| 672 |
+
</div>
|
| 673 |
+
</div>
|
| 674 |
+
<div class="col-md-6">
|
| 675 |
+
<div class="form-group">
|
| 676 |
+
<label for="patientName">Patient Name</label>
|
| 677 |
+
<input type="text" class="form-control" id="patientName" name="patientName" readonly>
|
| 678 |
+
</div>
|
| 679 |
+
</div>
|
| 680 |
+
</div>
|
| 681 |
+
<div class="form-group">
|
| 682 |
+
<label for="sessionNotes">Session Notes *</label>
|
| 683 |
+
<textarea class="form-control" id="sessionNotes" name="sessionNotes" rows="5" required placeholder="Enter detailed session notes..."></textarea>
|
| 684 |
+
</div>
|
| 685 |
+
<div class="form-group">
|
| 686 |
+
<div class="form-check">
|
| 687 |
+
<input class="form-check-input" type="checkbox" id="followUpRequired" name="followUpRequired">
|
| 688 |
+
<label class="form-check-label" for="followUpRequired">Follow-up Required</label>
|
| 689 |
+
</div>
|
| 690 |
+
</div>
|
| 691 |
+
<div class="form-group" id="followUpDateGroup" style="display: none;">
|
| 692 |
+
<label for="followUpDate">Follow-up Date</label>
|
| 693 |
+
<input type="date" class="form-control" id="followUpDate" name="followUpDate">
|
| 694 |
+
</div>
|
| 695 |
+
</form>
|
| 696 |
+
</div>
|
| 697 |
+
<div class="modal-footer">
|
| 698 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
| 699 |
+
<button type="button" class="btn btn-primary" id="saveNotesBtn">Save Notes</button>
|
| 700 |
+
</div>
|
| 701 |
+
</div>
|
| 702 |
+
</div>
|
| 703 |
+
</div>
|
| 704 |
+
|
| 705 |
+
<!-- Emergency Contacts Modal -->
|
| 706 |
+
<div class="modal fade" id="emergencyModal" tabindex="-1">
|
| 707 |
+
<div class="modal-dialog">
|
| 708 |
+
<div class="modal-content">
|
| 709 |
+
<div class="modal-header">
|
| 710 |
+
<h4 class="modal-title">Emergency Contacts</h4>
|
| 711 |
+
<button type="button" class="close" data-dismiss="modal">
|
| 712 |
+
<span>×</span>
|
| 713 |
+
</button>
|
| 714 |
+
</div>
|
| 715 |
+
<div class="modal-body">
|
| 716 |
+
<div class="list-group">
|
| 717 |
+
<div class="list-group-item">
|
| 718 |
+
<h5 class="mb-1">Mental Health Emergency Hotline</h5>
|
| 719 |
+
<p class="mb-1">Phone: +250 788 123 456</p>
|
| 720 |
+
<small>Available 24/7</small>
|
| 721 |
+
</div>
|
| 722 |
+
<div class="list-group-item">
|
| 723 |
+
<h5 class="mb-1">Rwanda National Police</h5>
|
| 724 |
+
<p class="mb-1">Emergency: 112</p>
|
| 725 |
+
<small>General: +250 788 831 112</small>
|
| 726 |
+
</div>
|
| 727 |
+
<div class="list-group-item">
|
| 728 |
+
<h5 class="mb-1">Kigali Hospital Emergency</h5>
|
| 729 |
+
<p class="mb-1">Phone: +250 788 456 789</p>
|
| 730 |
+
<small>Address: Kigali, Rwanda</small>
|
| 731 |
+
</div>
|
| 732 |
+
</div>
|
| 733 |
+
</div>
|
| 734 |
+
<div class="modal-footer">
|
| 735 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
| 736 |
+
</div>
|
| 737 |
+
</div>
|
| 738 |
+
</div>
|
| 739 |
+
</div>
|
| 740 |
+
|
| 741 |
+
<!-- jQuery -->
|
| 742 |
+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
| 743 |
+
<!-- Bootstrap 4 -->
|
| 744 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
| 745 |
+
<!-- AdminLTE 4 -->
|
| 746 |
+
<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/js/adminlte.min.js"></script>
|
| 747 |
+
<!-- DataTables -->
|
| 748 |
+
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
| 749 |
+
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap4.min.js"></script>
|
| 750 |
+
<script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
|
| 751 |
+
<script src="https://cdn.datatables.net/responsive/2.5.0/js/responsive.bootstrap4.min.js"></script>
|
| 752 |
+
<!-- Select2 -->
|
| 753 |
+
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
| 754 |
+
<!-- SweetAlert2 -->
|
| 755 |
+
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
| 756 |
+
<!-- FullCalendar -->
|
| 757 |
+
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js"></script>
|
| 758 |
+
|
| 759 |
+
<!-- Custom JavaScript (preserves existing functionality) -->
|
| 760 |
+
<script src="professional_advanced.js"></script>
|
| 761 |
+
</body>
|
| 762 |
+
</html>
|
chatbot/professional_dashboard_clean.html
ADDED
|
@@ -0,0 +1,822 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>AIMHSA Professional Dashboard</title>
|
| 7 |
+
|
| 8 |
+
<!-- AdminLTE 4 CSS -->
|
| 9 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/css/adminlte.min.css">
|
| 10 |
+
<!-- Font Awesome -->
|
| 11 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
| 12 |
+
<!-- Google Font: Source Sans Pro -->
|
| 13 |
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700&display=fallback">
|
| 14 |
+
<!-- Chart.js -->
|
| 15 |
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 16 |
+
<!-- DataTables CSS -->
|
| 17 |
+
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap4.min.css">
|
| 18 |
+
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.5.0/css/responsive.bootstrap4.min.css">
|
| 19 |
+
<!-- Select2 CSS -->
|
| 20 |
+
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
| 21 |
+
<!-- SweetAlert2 CSS -->
|
| 22 |
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sweetalert2@11/dist/sweetalert2.min.css">
|
| 23 |
+
<!-- FullCalendar CSS -->
|
| 24 |
+
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.css" rel="stylesheet">
|
| 25 |
+
|
| 26 |
+
<!-- Custom CSS (preserves existing styles) -->
|
| 27 |
+
<link rel="stylesheet" href="professional.css">
|
| 28 |
+
</head>
|
| 29 |
+
<body class="hold-transition sidebar-mini layout-fixed">
|
| 30 |
+
<div class="wrapper">
|
| 31 |
+
<!-- Navbar -->
|
| 32 |
+
<nav class="main-header navbar navbar-expand navbar-dark navbar-success">
|
| 33 |
+
<!-- Left navbar links -->
|
| 34 |
+
<ul class="navbar-nav">
|
| 35 |
+
<li class="nav-item">
|
| 36 |
+
<a class="nav-link" data-widget="pushmenu" href="#" role="button">
|
| 37 |
+
<i class="fas fa-bars"></i>
|
| 38 |
+
</a>
|
| 39 |
+
</li>
|
| 40 |
+
<li class="nav-item d-none d-sm-inline-block">
|
| 41 |
+
<a href="#" class="nav-link">Dashboard</a>
|
| 42 |
+
</li>
|
| 43 |
+
<li class="nav-item d-none d-sm-inline-block">
|
| 44 |
+
<a href="#" class="nav-link">Sessions</a>
|
| 45 |
+
</li>
|
| 46 |
+
</ul>
|
| 47 |
+
|
| 48 |
+
<!-- Right navbar links -->
|
| 49 |
+
<ul class="navbar-nav ml-auto">
|
| 50 |
+
<!-- Notifications Dropdown Menu -->
|
| 51 |
+
<li class="nav-item dropdown">
|
| 52 |
+
<a class="nav-link" data-toggle="dropdown" href="#">
|
| 53 |
+
<i class="far fa-bell"></i>
|
| 54 |
+
<span class="badge badge-warning navbar-badge" id="notificationBadge">8</span>
|
| 55 |
+
</a>
|
| 56 |
+
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
|
| 57 |
+
<span class="dropdown-item dropdown-header">8 Notifications</span>
|
| 58 |
+
<div class="dropdown-divider"></div>
|
| 59 |
+
<a href="#" class="dropdown-item">
|
| 60 |
+
<i class="fas fa-calendar mr-2"></i> New session booking
|
| 61 |
+
<span class="float-right text-muted text-sm">3 mins</span>
|
| 62 |
+
</a>
|
| 63 |
+
<div class="dropdown-divider"></div>
|
| 64 |
+
<a href="#" class="dropdown-item">
|
| 65 |
+
<i class="fas fa-exclamation-triangle mr-2"></i> High risk assessment
|
| 66 |
+
<span class="float-right text-muted text-sm">12 hours</span>
|
| 67 |
+
</a>
|
| 68 |
+
<div class="dropdown-divider"></div>
|
| 69 |
+
<a href="#" class="dropdown-item">
|
| 70 |
+
<i class="fas fa-user mr-2"></i> New patient assigned
|
| 71 |
+
<span class="float-right text-muted text-sm">2 days</span>
|
| 72 |
+
</a>
|
| 73 |
+
<div class="dropdown-divider"></div>
|
| 74 |
+
<a href="#" class="dropdown-item dropdown-footer">See All Notifications</a>
|
| 75 |
+
</div>
|
| 76 |
+
</li>
|
| 77 |
+
|
| 78 |
+
<!-- User Account Dropdown -->
|
| 79 |
+
<li class="nav-item dropdown">
|
| 80 |
+
<a class="nav-link" data-toggle="dropdown" href="#">
|
| 81 |
+
<i class="far fa-user"></i>
|
| 82 |
+
<span class="ml-2" id="professionalName">Dr. John Doe</span>
|
| 83 |
+
</a>
|
| 84 |
+
<div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
|
| 85 |
+
<a href="#" class="dropdown-item">
|
| 86 |
+
<i class="fas fa-user mr-2"></i> Profile
|
| 87 |
+
</a>
|
| 88 |
+
<a href="#" class="dropdown-item">
|
| 89 |
+
<i class="fas fa-cog mr-2"></i> Settings
|
| 90 |
+
</a>
|
| 91 |
+
<div class="dropdown-divider"></div>
|
| 92 |
+
<a href="#" class="dropdown-item" id="logoutBtn">
|
| 93 |
+
<i class="fas fa-sign-out-alt mr-2"></i> Logout
|
| 94 |
+
</a>
|
| 95 |
+
</div>
|
| 96 |
+
</li>
|
| 97 |
+
</ul>
|
| 98 |
+
</nav>
|
| 99 |
+
|
| 100 |
+
<!-- Main Sidebar Container -->
|
| 101 |
+
<aside class="main-sidebar sidebar-dark-success elevation-4">
|
| 102 |
+
<!-- Brand Logo -->
|
| 103 |
+
<a href="#" class="brand-link">
|
| 104 |
+
<img src="https://via.placeholder.com/33x33/28a745/ffffff?text=MD" alt="AIMHSA Logo" class="brand-image img-circle elevation-3" style="opacity: .8">
|
| 105 |
+
<span class="brand-text font-weight-light">AIMHSA Professional</span>
|
| 106 |
+
</a>
|
| 107 |
+
|
| 108 |
+
<!-- Sidebar -->
|
| 109 |
+
<div class="sidebar">
|
| 110 |
+
<!-- Sidebar user panel -->
|
| 111 |
+
<div class="user-panel mt-3 pb-3 mb-3 d-flex">
|
| 112 |
+
<div class="image">
|
| 113 |
+
<img src="https://via.placeholder.com/160x160/28a745/ffffff?text=JD" class="img-circle elevation-2" alt="User Image">
|
| 114 |
+
</div>
|
| 115 |
+
<div class="info">
|
| 116 |
+
<a href="#" class="d-block" id="professionalNameSidebar">Dr. John Doe</a>
|
| 117 |
+
<small class="text-muted" id="professionalRole">Psychiatrist</small>
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
|
| 121 |
+
<!-- Sidebar Menu -->
|
| 122 |
+
<nav class="mt-2">
|
| 123 |
+
<ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
|
| 124 |
+
<li class="nav-item">
|
| 125 |
+
<a href="#dashboard" class="nav-link active" data-section="dashboard">
|
| 126 |
+
<i class="nav-icon fas fa-tachometer-alt"></i>
|
| 127 |
+
<p>Dashboard</p>
|
| 128 |
+
</a>
|
| 129 |
+
</li>
|
| 130 |
+
<li class="nav-item">
|
| 131 |
+
<a href="#sessions" class="nav-link" data-section="sessions">
|
| 132 |
+
<i class="nav-icon fas fa-calendar-check"></i>
|
| 133 |
+
<p>My Sessions</p>
|
| 134 |
+
</a>
|
| 135 |
+
</li>
|
| 136 |
+
<li class="nav-item">
|
| 137 |
+
<a href="#patients" class="nav-link" data-section="patients">
|
| 138 |
+
<i class="nav-icon fas fa-users"></i>
|
| 139 |
+
<p>My Patients</p>
|
| 140 |
+
</a>
|
| 141 |
+
</li>
|
| 142 |
+
<li class="nav-item">
|
| 143 |
+
<a href="#notifications" class="nav-link" data-section="notifications">
|
| 144 |
+
<i class="nav-icon fas fa-bell"></i>
|
| 145 |
+
<p>Notifications</p>
|
| 146 |
+
<span class="badge badge-warning right" id="notificationCount">8</span>
|
| 147 |
+
</a>
|
| 148 |
+
</li>
|
| 149 |
+
<li class="nav-item">
|
| 150 |
+
<a href="#reports" class="nav-link" data-section="reports">
|
| 151 |
+
<i class="nav-icon fas fa-chart-bar"></i>
|
| 152 |
+
<p>Reports</p>
|
| 153 |
+
</a>
|
| 154 |
+
</li>
|
| 155 |
+
<li class="nav-item">
|
| 156 |
+
<a href="#profile" class="nav-link" data-section="profile">
|
| 157 |
+
<i class="nav-icon fas fa-user"></i>
|
| 158 |
+
<p>Profile</p>
|
| 159 |
+
</a>
|
| 160 |
+
</li>
|
| 161 |
+
<li class="nav-item">
|
| 162 |
+
<a href="#settings" class="nav-link" data-section="settings">
|
| 163 |
+
<i class="nav-icon fas fa-cog"></i>
|
| 164 |
+
<p>Settings</p>
|
| 165 |
+
</a>
|
| 166 |
+
</li>
|
| 167 |
+
</ul>
|
| 168 |
+
</nav>
|
| 169 |
+
</div>
|
| 170 |
+
</aside>
|
| 171 |
+
|
| 172 |
+
<!-- Content Wrapper -->
|
| 173 |
+
<div class="content-wrapper">
|
| 174 |
+
<!-- Content Header -->
|
| 175 |
+
<div class="content-header">
|
| 176 |
+
<div class="container-fluid">
|
| 177 |
+
<div class="row mb-2">
|
| 178 |
+
<div class="col-sm-6">
|
| 179 |
+
<h1 class="m-0" id="pageTitle">Dashboard</h1>
|
| 180 |
+
</div>
|
| 181 |
+
<div class="col-sm-6">
|
| 182 |
+
<ol class="breadcrumb float-sm-right">
|
| 183 |
+
<li class="breadcrumb-item"><a href="#">Home</a></li>
|
| 184 |
+
<li class="breadcrumb-item active" id="breadcrumbActive">Dashboard</li>
|
| 185 |
+
</ol>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
|
| 191 |
+
<!-- Main content -->
|
| 192 |
+
<section class="content">
|
| 193 |
+
<div class="container-fluid">
|
| 194 |
+
<!-- Dashboard Section -->
|
| 195 |
+
<div id="dashboard-section" class="content-section">
|
| 196 |
+
<!-- Info boxes -->
|
| 197 |
+
<div class="row">
|
| 198 |
+
<div class="col-lg-3 col-6">
|
| 199 |
+
<div class="small-box bg-info">
|
| 200 |
+
<div class="inner">
|
| 201 |
+
<h3 id="totalSessions">15</h3>
|
| 202 |
+
<p>Total Sessions</p>
|
| 203 |
+
</div>
|
| 204 |
+
<div class="icon">
|
| 205 |
+
<i class="fas fa-calendar-check"></i>
|
| 206 |
+
</div>
|
| 207 |
+
<a href="#sessions" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
<div class="col-lg-3 col-6">
|
| 211 |
+
<div class="small-box bg-warning">
|
| 212 |
+
<div class="inner">
|
| 213 |
+
<h3 id="unreadNotifications">8</h3>
|
| 214 |
+
<p>Unread Notifications</p>
|
| 215 |
+
</div>
|
| 216 |
+
<div class="icon">
|
| 217 |
+
<i class="fas fa-bell"></i>
|
| 218 |
+
</div>
|
| 219 |
+
<a href="#notifications" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 220 |
+
</div>
|
| 221 |
+
</div>
|
| 222 |
+
<div class="col-lg-3 col-6">
|
| 223 |
+
<div class="small-box bg-success">
|
| 224 |
+
<div class="inner">
|
| 225 |
+
<h3 id="upcomingToday">3</h3>
|
| 226 |
+
<p>Today's Sessions</p>
|
| 227 |
+
</div>
|
| 228 |
+
<div class="icon">
|
| 229 |
+
<i class="fas fa-clock"></i>
|
| 230 |
+
</div>
|
| 231 |
+
<a href="#sessions" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 232 |
+
</div>
|
| 233 |
+
</div>
|
| 234 |
+
<div class="col-lg-3 col-6">
|
| 235 |
+
<div class="small-box bg-danger">
|
| 236 |
+
<div class="inner">
|
| 237 |
+
<h3 id="highRiskSessions">2</h3>
|
| 238 |
+
<p>High Risk Cases</p>
|
| 239 |
+
</div>
|
| 240 |
+
<div class="icon">
|
| 241 |
+
<i class="fas fa-exclamation-triangle"></i>
|
| 242 |
+
</div>
|
| 243 |
+
<a href="#patients" class="small-box-footer">More info <i class="fas fa-arrow-circle-right"></i></a>
|
| 244 |
+
</div>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
|
| 248 |
+
<!-- Charts Row -->
|
| 249 |
+
<div class="row">
|
| 250 |
+
<div class="col-lg-8">
|
| 251 |
+
<div class="card">
|
| 252 |
+
<div class="card-header">
|
| 253 |
+
<h3 class="card-title">Session Trends</h3>
|
| 254 |
+
<div class="card-tools">
|
| 255 |
+
<button type="button" class="btn btn-tool" data-card-widget="collapse">
|
| 256 |
+
<i class="fas fa-minus"></i>
|
| 257 |
+
</button>
|
| 258 |
+
<button type="button" class="btn btn-tool" data-card-widget="remove">
|
| 259 |
+
<i class="fas fa-times"></i>
|
| 260 |
+
</button>
|
| 261 |
+
</div>
|
| 262 |
+
</div>
|
| 263 |
+
<div class="card-body">
|
| 264 |
+
<canvas id="sessionTrendChart" style="height: 300px;"></canvas>
|
| 265 |
+
</div>
|
| 266 |
+
</div>
|
| 267 |
+
</div>
|
| 268 |
+
<div class="col-lg-4">
|
| 269 |
+
<div class="card">
|
| 270 |
+
<div class="card-header">
|
| 271 |
+
<h3 class="card-title">Patient Risk Distribution</h3>
|
| 272 |
+
</div>
|
| 273 |
+
<div class="card-body">
|
| 274 |
+
<canvas id="riskDistributionChart" style="height: 300px;"></canvas>
|
| 275 |
+
</div>
|
| 276 |
+
</div>
|
| 277 |
+
</div>
|
| 278 |
+
</div>
|
| 279 |
+
|
| 280 |
+
<!-- Recent Activity -->
|
| 281 |
+
<div class="row">
|
| 282 |
+
<div class="col-lg-6">
|
| 283 |
+
<div class="card">
|
| 284 |
+
<div class="card-header">
|
| 285 |
+
<h3 class="card-title">Today's Schedule</h3>
|
| 286 |
+
</div>
|
| 287 |
+
<div class="card-body">
|
| 288 |
+
<div class="table-responsive">
|
| 289 |
+
<table class="table table-striped">
|
| 290 |
+
<thead>
|
| 291 |
+
<tr>
|
| 292 |
+
<th>Time</th>
|
| 293 |
+
<th>Patient</th>
|
| 294 |
+
<th>Type</th>
|
| 295 |
+
<th>Status</th>
|
| 296 |
+
</tr>
|
| 297 |
+
</thead>
|
| 298 |
+
<tbody id="todayScheduleTable">
|
| 299 |
+
<tr>
|
| 300 |
+
<td>09:00</td>
|
| 301 |
+
<td>John Doe</td>
|
| 302 |
+
<td>Initial</td>
|
| 303 |
+
<td><span class="badge badge-success">Confirmed</span></td>
|
| 304 |
+
</tr>
|
| 305 |
+
<tr>
|
| 306 |
+
<td>11:00</td>
|
| 307 |
+
<td>Jane Smith</td>
|
| 308 |
+
<td>Follow-up</td>
|
| 309 |
+
<td><span class="badge badge-warning">Pending</span></td>
|
| 310 |
+
</tr>
|
| 311 |
+
<tr>
|
| 312 |
+
<td>14:00</td>
|
| 313 |
+
<td>Bob Johnson</td>
|
| 314 |
+
<td>Emergency</td>
|
| 315 |
+
<td><span class="badge badge-danger">High Risk</span></td>
|
| 316 |
+
</tr>
|
| 317 |
+
</tbody>
|
| 318 |
+
</table>
|
| 319 |
+
</div>
|
| 320 |
+
</div>
|
| 321 |
+
</div>
|
| 322 |
+
</div>
|
| 323 |
+
<div class="col-lg-6">
|
| 324 |
+
<div class="card">
|
| 325 |
+
<div class="card-header">
|
| 326 |
+
<h3 class="card-title">Recent Notifications</h3>
|
| 327 |
+
</div>
|
| 328 |
+
<div class="card-body">
|
| 329 |
+
<div class="timeline">
|
| 330 |
+
<div class="time-label">
|
| 331 |
+
<span class="bg-red">Today</span>
|
| 332 |
+
</div>
|
| 333 |
+
<div>
|
| 334 |
+
<i class="fas fa-calendar bg-blue"></i>
|
| 335 |
+
<div class="timeline-item">
|
| 336 |
+
<span class="time"><i class="fas fa-clock"></i> 12:05</span>
|
| 337 |
+
<h3 class="timeline-header">New Session Booking</h3>
|
| 338 |
+
<div class="timeline-body">
|
| 339 |
+
John Doe has booked a session for tomorrow at 10:00 AM
|
| 340 |
+
</div>
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
<div>
|
| 344 |
+
<i class="fas fa-exclamation-triangle bg-yellow"></i>
|
| 345 |
+
<div class="timeline-item">
|
| 346 |
+
<span class="time"><i class="fas fa-clock"></i> 09:30</span>
|
| 347 |
+
<h3 class="timeline-header">High Risk Alert</h3>
|
| 348 |
+
<div class="timeline-body">
|
| 349 |
+
Patient Bob Johnson has been flagged as high risk
|
| 350 |
+
</div>
|
| 351 |
+
</div>
|
| 352 |
+
</div>
|
| 353 |
+
<div>
|
| 354 |
+
<i class="fas fa-user bg-green"></i>
|
| 355 |
+
<div class="timeline-item">
|
| 356 |
+
<span class="time"><i class="fas fa-clock"></i> 08:15</span>
|
| 357 |
+
<h3 class="timeline-header">New Patient</h3>
|
| 358 |
+
<div class="timeline-body">
|
| 359 |
+
New patient Jane Smith has been assigned to you
|
| 360 |
+
</div>
|
| 361 |
+
</div>
|
| 362 |
+
</div>
|
| 363 |
+
</div>
|
| 364 |
+
</div>
|
| 365 |
+
</div>
|
| 366 |
+
</div>
|
| 367 |
+
</div>
|
| 368 |
+
</div>
|
| 369 |
+
|
| 370 |
+
<!-- Sessions Section -->
|
| 371 |
+
<div id="sessions-section" class="content-section" style="display: none;">
|
| 372 |
+
<div class="row">
|
| 373 |
+
<div class="col-12">
|
| 374 |
+
<div class="card">
|
| 375 |
+
<div class="card-header">
|
| 376 |
+
<h3 class="card-title">My Sessions</h3>
|
| 377 |
+
<div class="card-tools">
|
| 378 |
+
<button type="button" class="btn btn-primary" onclick="addNewSession()">
|
| 379 |
+
<i class="fas fa-plus"></i> New Session
|
| 380 |
+
</button>
|
| 381 |
+
</div>
|
| 382 |
+
</div>
|
| 383 |
+
<div class="card-body">
|
| 384 |
+
<div class="row mb-3">
|
| 385 |
+
<div class="col-md-4">
|
| 386 |
+
<select class="form-control" id="sessionStatusFilter">
|
| 387 |
+
<option value="">All Status</option>
|
| 388 |
+
<option value="scheduled">Scheduled</option>
|
| 389 |
+
<option value="completed">Completed</option>
|
| 390 |
+
<option value="cancelled">Cancelled</option>
|
| 391 |
+
</select>
|
| 392 |
+
</div>
|
| 393 |
+
<div class="col-md-4">
|
| 394 |
+
<input type="date" class="form-control" id="sessionDateFilter" placeholder="Filter by date">
|
| 395 |
+
</div>
|
| 396 |
+
<div class="col-md-4">
|
| 397 |
+
<button class="btn btn-success" onclick="refreshSessions()">
|
| 398 |
+
<i class="fas fa-sync"></i> Refresh
|
| 399 |
+
</button>
|
| 400 |
+
</div>
|
| 401 |
+
</div>
|
| 402 |
+
<div class="table-responsive">
|
| 403 |
+
<table id="sessionsTable" class="table table-bordered table-striped">
|
| 404 |
+
<thead>
|
| 405 |
+
<tr>
|
| 406 |
+
<th>ID</th>
|
| 407 |
+
<th>Patient</th>
|
| 408 |
+
<th>Date & Time</th>
|
| 409 |
+
<th>Type</th>
|
| 410 |
+
<th>Risk Level</th>
|
| 411 |
+
<th>Status</th>
|
| 412 |
+
<th>Actions</th>
|
| 413 |
+
</tr>
|
| 414 |
+
</thead>
|
| 415 |
+
<tbody id="sessionsTableBody">
|
| 416 |
+
<!-- Data will be loaded here -->
|
| 417 |
+
</tbody>
|
| 418 |
+
</table>
|
| 419 |
+
</div>
|
| 420 |
+
</div>
|
| 421 |
+
</div>
|
| 422 |
+
</div>
|
| 423 |
+
</div>
|
| 424 |
+
</div>
|
| 425 |
+
|
| 426 |
+
<!-- Patients Section -->
|
| 427 |
+
<div id="patients-section" class="content-section" style="display: none;">
|
| 428 |
+
<div class="row">
|
| 429 |
+
<div class="col-12">
|
| 430 |
+
<div class="card">
|
| 431 |
+
<div class="card-header">
|
| 432 |
+
<h3 class="card-title">My Patients</h3>
|
| 433 |
+
<div class="card-tools">
|
| 434 |
+
<button type="button" class="btn btn-primary">
|
| 435 |
+
<i class="fas fa-plus"></i> Add Patient
|
| 436 |
+
</button>
|
| 437 |
+
</div>
|
| 438 |
+
</div>
|
| 439 |
+
<div class="card-body">
|
| 440 |
+
<div class="row mb-3">
|
| 441 |
+
<div class="col-md-6">
|
| 442 |
+
<div class="input-group">
|
| 443 |
+
<input type="text" class="form-control" id="patientSearch" placeholder="Search patients...">
|
| 444 |
+
<div class="input-group-append">
|
| 445 |
+
<button class="btn btn-outline-secondary" type="button">
|
| 446 |
+
<i class="fas fa-search"></i>
|
| 447 |
+
</button>
|
| 448 |
+
</div>
|
| 449 |
+
</div>
|
| 450 |
+
</div>
|
| 451 |
+
<div class="col-md-6">
|
| 452 |
+
<select class="form-control" id="riskLevelFilter">
|
| 453 |
+
<option value="">All Risk Levels</option>
|
| 454 |
+
<option value="low">Low</option>
|
| 455 |
+
<option value="medium">Medium</option>
|
| 456 |
+
<option value="high">High</option>
|
| 457 |
+
<option value="critical">Critical</option>
|
| 458 |
+
</select>
|
| 459 |
+
</div>
|
| 460 |
+
</div>
|
| 461 |
+
<div class="table-responsive">
|
| 462 |
+
<table id="patientsTable" class="table table-bordered table-striped">
|
| 463 |
+
<thead>
|
| 464 |
+
<tr>
|
| 465 |
+
<th>ID</th>
|
| 466 |
+
<th>Name</th>
|
| 467 |
+
<th>Age</th>
|
| 468 |
+
<th>Risk Level</th>
|
| 469 |
+
<th>Last Session</th>
|
| 470 |
+
<th>Next Session</th>
|
| 471 |
+
<th>Actions</th>
|
| 472 |
+
</tr>
|
| 473 |
+
</thead>
|
| 474 |
+
<tbody id="patientsTableBody">
|
| 475 |
+
<!-- Data will be loaded here -->
|
| 476 |
+
</tbody>
|
| 477 |
+
</table>
|
| 478 |
+
</div>
|
| 479 |
+
</div>
|
| 480 |
+
</div>
|
| 481 |
+
</div>
|
| 482 |
+
</div>
|
| 483 |
+
</div>
|
| 484 |
+
|
| 485 |
+
<!-- Notifications Section -->
|
| 486 |
+
<div id="notifications-section" class="content-section" style="display: none;">
|
| 487 |
+
<div class="row">
|
| 488 |
+
<div class="col-12">
|
| 489 |
+
<div class="card">
|
| 490 |
+
<div class="card-header">
|
| 491 |
+
<h3 class="card-title">Notifications</h3>
|
| 492 |
+
<div class="card-tools">
|
| 493 |
+
<button type="button" class="btn btn-success" onclick="markAllAsRead()">
|
| 494 |
+
<i class="fas fa-check"></i> Mark All Read
|
| 495 |
+
</button>
|
| 496 |
+
</div>
|
| 497 |
+
</div>
|
| 498 |
+
<div class="card-body">
|
| 499 |
+
<div id="notificationsList">
|
| 500 |
+
<!-- Notifications will be loaded here -->
|
| 501 |
+
</div>
|
| 502 |
+
</div>
|
| 503 |
+
</div>
|
| 504 |
+
</div>
|
| 505 |
+
</div>
|
| 506 |
+
</div>
|
| 507 |
+
|
| 508 |
+
<!-- Reports Section -->
|
| 509 |
+
<div id="reports-section" class="content-section" style="display: none;">
|
| 510 |
+
<div class="row">
|
| 511 |
+
<div class="col-12">
|
| 512 |
+
<div class="card">
|
| 513 |
+
<div class="card-header">
|
| 514 |
+
<h3 class="card-title">Professional Reports</h3>
|
| 515 |
+
<div class="card-tools">
|
| 516 |
+
<button type="button" class="btn btn-success">
|
| 517 |
+
<i class="fas fa-download"></i> Generate Report
|
| 518 |
+
</button>
|
| 519 |
+
</div>
|
| 520 |
+
</div>
|
| 521 |
+
<div class="card-body">
|
| 522 |
+
<div class="row">
|
| 523 |
+
<div class="col-md-4">
|
| 524 |
+
<div class="card card-primary">
|
| 525 |
+
<div class="card-header">
|
| 526 |
+
<h3 class="card-title">Session Summary</h3>
|
| 527 |
+
</div>
|
| 528 |
+
<div class="card-body">
|
| 529 |
+
<p>Generate monthly session summary report</p>
|
| 530 |
+
<button class="btn btn-primary">Generate</button>
|
| 531 |
+
</div>
|
| 532 |
+
</div>
|
| 533 |
+
</div>
|
| 534 |
+
<div class="col-md-4">
|
| 535 |
+
<div class="card card-success">
|
| 536 |
+
<div class="card-header">
|
| 537 |
+
<h3 class="card-title">Patient Progress</h3>
|
| 538 |
+
</div>
|
| 539 |
+
<div class="card-body">
|
| 540 |
+
<p>Track patient progress over time</p>
|
| 541 |
+
<button class="btn btn-success">Generate</button>
|
| 542 |
+
</div>
|
| 543 |
+
</div>
|
| 544 |
+
</div>
|
| 545 |
+
<div class="col-md-4">
|
| 546 |
+
<div class="card card-warning">
|
| 547 |
+
<div class="card-header">
|
| 548 |
+
<h3 class="card-title">Risk Assessment</h3>
|
| 549 |
+
</div>
|
| 550 |
+
<div class="card-body">
|
| 551 |
+
<p>Detailed risk assessment reports</p>
|
| 552 |
+
<button class="btn btn-warning">Generate</button>
|
| 553 |
+
</div>
|
| 554 |
+
</div>
|
| 555 |
+
</div>
|
| 556 |
+
</div>
|
| 557 |
+
</div>
|
| 558 |
+
</div>
|
| 559 |
+
</div>
|
| 560 |
+
</div>
|
| 561 |
+
</div>
|
| 562 |
+
|
| 563 |
+
<!-- Profile Section -->
|
| 564 |
+
<div id="profile-section" class="content-section" style="display: none;">
|
| 565 |
+
<div class="row">
|
| 566 |
+
<div class="col-md-4">
|
| 567 |
+
<div class="card card-primary card-outline">
|
| 568 |
+
<div class="card-body box-profile">
|
| 569 |
+
<div class="text-center">
|
| 570 |
+
<img class="profile-user-img img-fluid img-circle" src="https://via.placeholder.com/100x100/28a745/ffffff?text=JD" alt="User profile picture">
|
| 571 |
+
</div>
|
| 572 |
+
<h3 class="profile-username text-center" id="profileName">Dr. John Doe</h3>
|
| 573 |
+
<p class="text-muted text-center" id="profileRole">Psychiatrist</p>
|
| 574 |
+
<ul class="list-group list-group-unbordered mb-3">
|
| 575 |
+
<li class="list-group-item">
|
| 576 |
+
<b>Specialization</b> <a class="float-right">Psychiatry</a>
|
| 577 |
+
</li>
|
| 578 |
+
<li class="list-group-item">
|
| 579 |
+
<b>Experience</b> <a class="float-right">8 years</a>
|
| 580 |
+
</li>
|
| 581 |
+
<li class="list-group-item">
|
| 582 |
+
<b>Patients</b> <a class="float-right">25</a>
|
| 583 |
+
</li>
|
| 584 |
+
</ul>
|
| 585 |
+
</div>
|
| 586 |
+
</div>
|
| 587 |
+
</div>
|
| 588 |
+
<div class="col-md-8">
|
| 589 |
+
<div class="card">
|
| 590 |
+
<div class="card-header">
|
| 591 |
+
<h3 class="card-title">Profile Information</h3>
|
| 592 |
+
</div>
|
| 593 |
+
<div class="card-body">
|
| 594 |
+
<form>
|
| 595 |
+
<div class="row">
|
| 596 |
+
<div class="col-md-6">
|
| 597 |
+
<div class="form-group">
|
| 598 |
+
<label>First Name</label>
|
| 599 |
+
<input type="text" class="form-control" value="John">
|
| 600 |
+
</div>
|
| 601 |
+
</div>
|
| 602 |
+
<div class="col-md-6">
|
| 603 |
+
<div class="form-group">
|
| 604 |
+
<label>Last Name</label>
|
| 605 |
+
<input type="text" class="form-control" value="Doe">
|
| 606 |
+
</div>
|
| 607 |
+
</div>
|
| 608 |
+
</div>
|
| 609 |
+
<div class="row">
|
| 610 |
+
<div class="col-md-6">
|
| 611 |
+
<div class="form-group">
|
| 612 |
+
<label>Email</label>
|
| 613 |
+
<input type="email" class="form-control" value="john.doe@aimhsa.rw">
|
| 614 |
+
</div>
|
| 615 |
+
</div>
|
| 616 |
+
<div class="col-md-6">
|
| 617 |
+
<div class="form-group">
|
| 618 |
+
<label>Phone</label>
|
| 619 |
+
<input type="tel" class="form-control" value="+250 788 123 456">
|
| 620 |
+
</div>
|
| 621 |
+
</div>
|
| 622 |
+
</div>
|
| 623 |
+
<div class="form-group">
|
| 624 |
+
<label>Bio</label>
|
| 625 |
+
<textarea class="form-control" rows="3">Experienced psychiatrist specializing in trauma and anxiety disorders.</textarea>
|
| 626 |
+
</div>
|
| 627 |
+
<button type="submit" class="btn btn-primary">Update Profile</button>
|
| 628 |
+
</form>
|
| 629 |
+
</div>
|
| 630 |
+
</div>
|
| 631 |
+
</div>
|
| 632 |
+
</div>
|
| 633 |
+
</div>
|
| 634 |
+
|
| 635 |
+
<!-- Settings Section -->
|
| 636 |
+
<div id="settings-section" class="content-section" style="display: none;">
|
| 637 |
+
<div class="row">
|
| 638 |
+
<div class="col-12">
|
| 639 |
+
<div class="card">
|
| 640 |
+
<div class="card-header">
|
| 641 |
+
<h3 class="card-title">Professional Settings</h3>
|
| 642 |
+
</div>
|
| 643 |
+
<div class="card-body">
|
| 644 |
+
<form>
|
| 645 |
+
<div class="row">
|
| 646 |
+
<div class="col-md-6">
|
| 647 |
+
<div class="form-group">
|
| 648 |
+
<label>Notification Preferences</label>
|
| 649 |
+
<div class="form-check">
|
| 650 |
+
<input class="form-check-input" type="checkbox" checked>
|
| 651 |
+
<label class="form-check-label">Email Notifications</label>
|
| 652 |
+
</div>
|
| 653 |
+
<div class="form-check">
|
| 654 |
+
<input class="form-check-input" type="checkbox" checked>
|
| 655 |
+
<label class="form-check-label">SMS Notifications</label>
|
| 656 |
+
</div>
|
| 657 |
+
<div class="form-check">
|
| 658 |
+
<input class="form-check-input" type="checkbox">
|
| 659 |
+
<label class="form-check-label">Push Notifications</label>
|
| 660 |
+
</div>
|
| 661 |
+
</div>
|
| 662 |
+
</div>
|
| 663 |
+
<div class="col-md-6">
|
| 664 |
+
<div class="form-group">
|
| 665 |
+
<label>Working Hours</label>
|
| 666 |
+
<div class="row">
|
| 667 |
+
<div class="col-6">
|
| 668 |
+
<input type="time" class="form-control" value="08:00">
|
| 669 |
+
</div>
|
| 670 |
+
<div class="col-6">
|
| 671 |
+
<input type="time" class="form-control" value="17:00">
|
| 672 |
+
</div>
|
| 673 |
+
</div>
|
| 674 |
+
</div>
|
| 675 |
+
</div>
|
| 676 |
+
</div>
|
| 677 |
+
<div class="row">
|
| 678 |
+
<div class="col-md-6">
|
| 679 |
+
<div class="form-group">
|
| 680 |
+
<label>Session Duration (minutes)</label>
|
| 681 |
+
<select class="form-control">
|
| 682 |
+
<option>30</option>
|
| 683 |
+
<option selected>45</option>
|
| 684 |
+
<option>60</option>
|
| 685 |
+
<option>90</option>
|
| 686 |
+
</select>
|
| 687 |
+
</div>
|
| 688 |
+
</div>
|
| 689 |
+
<div class="col-md-6">
|
| 690 |
+
<div class="form-group">
|
| 691 |
+
<label>Auto-refresh Interval</label>
|
| 692 |
+
<select class="form-control">
|
| 693 |
+
<option>30 seconds</option>
|
| 694 |
+
<option selected>1 minute</option>
|
| 695 |
+
<option>5 minutes</option>
|
| 696 |
+
</select>
|
| 697 |
+
</div>
|
| 698 |
+
</div>
|
| 699 |
+
</div>
|
| 700 |
+
<button type="submit" class="btn btn-primary">Save Settings</button>
|
| 701 |
+
</form>
|
| 702 |
+
</div>
|
| 703 |
+
</div>
|
| 704 |
+
</div>
|
| 705 |
+
</div>
|
| 706 |
+
</div>
|
| 707 |
+
</div>
|
| 708 |
+
</section>
|
| 709 |
+
</div>
|
| 710 |
+
</div>
|
| 711 |
+
|
| 712 |
+
<!-- Session Notes Modal -->
|
| 713 |
+
<div class="modal fade" id="sessionNotesModal" tabindex="-1">
|
| 714 |
+
<div class="modal-dialog modal-lg">
|
| 715 |
+
<div class="modal-content">
|
| 716 |
+
<div class="modal-header">
|
| 717 |
+
<h4 class="modal-title">Add Session Notes</h4>
|
| 718 |
+
<button type="button" class="close" data-dismiss="modal">
|
| 719 |
+
<span>×</span>
|
| 720 |
+
</button>
|
| 721 |
+
</div>
|
| 722 |
+
<div class="modal-body">
|
| 723 |
+
<form id="sessionNotesForm">
|
| 724 |
+
<div class="row">
|
| 725 |
+
<div class="col-md-6">
|
| 726 |
+
<div class="form-group">
|
| 727 |
+
<label for="sessionId">Session ID</label>
|
| 728 |
+
<input type="text" class="form-control" id="sessionId" name="sessionId" readonly>
|
| 729 |
+
</div>
|
| 730 |
+
</div>
|
| 731 |
+
<div class="col-md-6">
|
| 732 |
+
<div class="form-group">
|
| 733 |
+
<label for="patientName">Patient Name</label>
|
| 734 |
+
<input type="text" class="form-control" id="patientName" name="patientName" readonly>
|
| 735 |
+
</div>
|
| 736 |
+
</div>
|
| 737 |
+
</div>
|
| 738 |
+
<div class="form-group">
|
| 739 |
+
<label for="sessionNotes">Session Notes *</label>
|
| 740 |
+
<textarea class="form-control" id="sessionNotes" name="sessionNotes" rows="5" required placeholder="Enter detailed session notes..."></textarea>
|
| 741 |
+
</div>
|
| 742 |
+
<div class="form-group">
|
| 743 |
+
<div class="form-check">
|
| 744 |
+
<input class="form-check-input" type="checkbox" id="followUpRequired" name="followUpRequired">
|
| 745 |
+
<label class="form-check-label" for="followUpRequired">Follow-up Required</label>
|
| 746 |
+
</div>
|
| 747 |
+
</div>
|
| 748 |
+
<div class="form-group" id="followUpDateGroup" style="display: none;">
|
| 749 |
+
<label for="followUpDate">Follow-up Date</label>
|
| 750 |
+
<input type="date" class="form-control" id="followUpDate" name="followUpDate">
|
| 751 |
+
</div>
|
| 752 |
+
</form>
|
| 753 |
+
</div>
|
| 754 |
+
<div class="modal-footer">
|
| 755 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
|
| 756 |
+
<button type="button" class="btn btn-primary" id="saveNotesBtn">Save Notes</button>
|
| 757 |
+
</div>
|
| 758 |
+
</div>
|
| 759 |
+
</div>
|
| 760 |
+
</div>
|
| 761 |
+
|
| 762 |
+
<!-- Emergency Contacts Modal -->
|
| 763 |
+
<div class="modal fade" id="emergencyModal" tabindex="-1">
|
| 764 |
+
<div class="modal-dialog">
|
| 765 |
+
<div class="modal-content">
|
| 766 |
+
<div class="modal-header">
|
| 767 |
+
<h4 class="modal-title">Emergency Contacts</h4>
|
| 768 |
+
<button type="button" class="close" data-dismiss="modal">
|
| 769 |
+
<span>×</span>
|
| 770 |
+
</button>
|
| 771 |
+
</div>
|
| 772 |
+
<div class="modal-body">
|
| 773 |
+
<div class="list-group">
|
| 774 |
+
<div class="list-group-item">
|
| 775 |
+
<h5 class="mb-1">Mental Health Emergency Hotline</h5>
|
| 776 |
+
<p class="mb-1">Phone: +250 788 123 456</p>
|
| 777 |
+
<small>Available 24/7</small>
|
| 778 |
+
</div>
|
| 779 |
+
<div class="list-group-item">
|
| 780 |
+
<h5 class="mb-1">Rwanda National Police</h5>
|
| 781 |
+
<p class="mb-1">Emergency: 112</p>
|
| 782 |
+
<small>General: +250 788 831 112</small>
|
| 783 |
+
</div>
|
| 784 |
+
<div class="list-group-item">
|
| 785 |
+
<h5 class="mb-1">Kigali Hospital Emergency</h5>
|
| 786 |
+
<p class="mb-1">Phone: +250 788 456 789</p>
|
| 787 |
+
<small>Address: Kigali, Rwanda</small>
|
| 788 |
+
</div>
|
| 789 |
+
</div>
|
| 790 |
+
</div>
|
| 791 |
+
<div class="modal-footer">
|
| 792 |
+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
| 793 |
+
</div>
|
| 794 |
+
</div>
|
| 795 |
+
</div>
|
| 796 |
+
</div>
|
| 797 |
+
|
| 798 |
+
<!-- jQuery -->
|
| 799 |
+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
| 800 |
+
<!-- Bootstrap 4 -->
|
| 801 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script>
|
| 802 |
+
<!-- AdminLTE 4 -->
|
| 803 |
+
<script src="https://cdn.jsdelivr.net/npm/admin-lte@3.2/dist/js/adminlte.min.js"></script>
|
| 804 |
+
<!-- DataTables -->
|
| 805 |
+
<script src="https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js"></script>
|
| 806 |
+
<script src="https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap4.min.js"></script>
|
| 807 |
+
<script src="https://cdn.datatables.net/responsive/2.5.0/js/dataTables.responsive.min.js"></script>
|
| 808 |
+
<script src="https://cdn.datatables.net/responsive/2.5.0/js/responsive.bootstrap4.min.js"></script>
|
| 809 |
+
<!-- Select2 -->
|
| 810 |
+
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
| 811 |
+
<!-- SweetAlert2 -->
|
| 812 |
+
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
| 813 |
+
<!-- FullCalendar -->
|
| 814 |
+
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js"></script>
|
| 815 |
+
|
| 816 |
+
<!-- Custom JavaScript (preserves existing functionality) -->
|
| 817 |
+
<script src="professional_advanced.js"></script>
|
| 818 |
+
</body>
|
| 819 |
+
</html>
|
| 820 |
+
|
| 821 |
+
|
| 822 |
+
|
chatbot/register.html
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
| 6 |
+
<title>AIMHSA - Create Account</title>
|
| 7 |
+
<link rel="stylesheet" href="auth.css" />
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="auth-container">
|
| 11 |
+
<div class="auth-card">
|
| 12 |
+
<div class="auth-header">
|
| 13 |
+
<h1 class="brand">AIMHSA</h1>
|
| 14 |
+
<p class="subtitle">Mental Health Companion for Rwanda</p>
|
| 15 |
+
</div>
|
| 16 |
+
|
| 17 |
+
<form class="auth-form" id="registerForm" novalidate>
|
| 18 |
+
<div class="form-group">
|
| 19 |
+
<label for="regUsername">Username *</label>
|
| 20 |
+
<input type="text" id="regUsername" name="regUsername"
|
| 21 |
+
placeholder="Enter your username"
|
| 22 |
+
minlength="3" maxlength="50"
|
| 23 |
+
pattern="^[a-zA-Z0-9_]+$"
|
| 24 |
+
title="Username can only contain letters, numbers, and underscores"
|
| 25 |
+
required />
|
| 26 |
+
<div class="field-error" id="regUsernameError"></div>
|
| 27 |
+
<div class="field-help">3-50 characters, letters, numbers, and underscores only</div>
|
| 28 |
+
</div>
|
| 29 |
+
|
| 30 |
+
<div class="form-group">
|
| 31 |
+
<label for="regEmail">Email Address *</label>
|
| 32 |
+
<input type="email" id="regEmail" name="regEmail"
|
| 33 |
+
placeholder="Enter your email address"
|
| 34 |
+
maxlength="100"
|
| 35 |
+
title="Enter a valid email address"
|
| 36 |
+
required />
|
| 37 |
+
<div class="field-error" id="regEmailError"></div>
|
| 38 |
+
<div class="field-help">We'll use this to contact you about your account</div>
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<div class="form-group">
|
| 42 |
+
<label for="regFullname">Full Name *</label>
|
| 43 |
+
<input type="text" id="regFullname" name="regFullname"
|
| 44 |
+
placeholder="Enter your full name"
|
| 45 |
+
minlength="2" maxlength="100"
|
| 46 |
+
pattern="^[a-zA-Z\s\-'\.]+$"
|
| 47 |
+
title="Enter your full name (letters, spaces, hyphens, apostrophes, and periods only)"
|
| 48 |
+
required />
|
| 49 |
+
<div class="field-error" id="regFullnameError"></div>
|
| 50 |
+
<div class="field-help">Enter your complete name as it appears on official documents</div>
|
| 51 |
+
</div>
|
| 52 |
+
|
| 53 |
+
<div class="form-group">
|
| 54 |
+
<label for="regTelephone">Phone Number *</label>
|
| 55 |
+
<input type="tel" id="regTelephone" name="regTelephone"
|
| 56 |
+
placeholder="+250XXXXXXXXX or 07XXXXXXXX"
|
| 57 |
+
pattern="^(\+250|0)[0-9]{9}$"
|
| 58 |
+
title="Enter a valid Rwanda phone number"
|
| 59 |
+
required />
|
| 60 |
+
<div class="field-error" id="regTelephoneError"></div>
|
| 61 |
+
<div class="field-help">Rwanda format: +250XXXXXXXXX or 07XXXXXXXX</div>
|
| 62 |
+
</div>
|
| 63 |
+
|
| 64 |
+
<div class="form-group">
|
| 65 |
+
<label for="regProvince">Province *</label>
|
| 66 |
+
<select id="regProvince" name="regProvince" required>
|
| 67 |
+
<option value="">Select Province</option>
|
| 68 |
+
<option value="Kigali">Kigali</option>
|
| 69 |
+
<option value="Eastern">Eastern</option>
|
| 70 |
+
<option value="Northern">Northern</option>
|
| 71 |
+
<option value="Southern">Southern</option>
|
| 72 |
+
<option value="Western">Western</option>
|
| 73 |
+
</select>
|
| 74 |
+
<div class="field-error" id="regProvinceError"></div>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<div class="form-group">
|
| 78 |
+
<label for="regDistrict">District *</label>
|
| 79 |
+
<select id="regDistrict" name="regDistrict" required>
|
| 80 |
+
<option value="">Select District</option>
|
| 81 |
+
</select>
|
| 82 |
+
<div class="field-error" id="regDistrictError"></div>
|
| 83 |
+
</div>
|
| 84 |
+
|
| 85 |
+
<div class="form-group">
|
| 86 |
+
<label for="regPassword">Password *</label>
|
| 87 |
+
<input type="password" id="regPassword" name="regPassword"
|
| 88 |
+
placeholder="Create a strong password"
|
| 89 |
+
minlength="8" maxlength="128"
|
| 90 |
+
title="Password must be at least 8 characters long"
|
| 91 |
+
required />
|
| 92 |
+
<div class="field-error" id="regPasswordError"></div>
|
| 93 |
+
<div class="field-help">At least 8 characters, include letters and numbers</div>
|
| 94 |
+
<div class="password-strength-indicator">
|
| 95 |
+
<div class="password-strength-bar"></div>
|
| 96 |
+
</div>
|
| 97 |
+
<div class="password-strength"></div>
|
| 98 |
+
</div>
|
| 99 |
+
|
| 100 |
+
<div class="form-group">
|
| 101 |
+
<label for="regConfirmPassword">Confirm Password *</label>
|
| 102 |
+
<input type="password" id="regConfirmPassword" name="regConfirmPassword"
|
| 103 |
+
placeholder="Confirm your password"
|
| 104 |
+
required />
|
| 105 |
+
<div class="field-error" id="regConfirmPasswordError"></div>
|
| 106 |
+
<div class="field-help">Must match the password above</div>
|
| 107 |
+
</div>
|
| 108 |
+
|
| 109 |
+
<div class="form-group">
|
| 110 |
+
<label class="checkbox-label">
|
| 111 |
+
<input type="checkbox" id="agreeTerms" name="agreeTerms" required />
|
| 112 |
+
<span class="checkmark"></span>
|
| 113 |
+
I agree to the <a href="#" target="_blank">Terms of Service</a> and <a href="#" target="_blank">Privacy Policy</a> *
|
| 114 |
+
</label>
|
| 115 |
+
<div class="field-error" id="agreeTermsError"></div>
|
| 116 |
+
</div>
|
| 117 |
+
|
| 118 |
+
<button type="submit" class="auth-btn" id="registerBtn">Create Account</button>
|
| 119 |
+
</form>
|
| 120 |
+
|
| 121 |
+
<div class="auth-links">
|
| 122 |
+
<p>Already have an account? <a href="/login">Sign In</a></p>
|
| 123 |
+
</div>
|
| 124 |
+
|
| 125 |
+
<div class="auth-footer">
|
| 126 |
+
<p>By continuing, you agree to our Terms of Service and Privacy Policy</p>
|
| 127 |
+
</div>
|
| 128 |
+
</div>
|
| 129 |
+
</div>
|
| 130 |
+
|
| 131 |
+
<script src="register.js"></script>
|
| 132 |
+
</body>
|
| 133 |
+
</html>
|
chatbot/register.js
ADDED
|
@@ -0,0 +1,647 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
(() => {
|
| 2 |
+
const API_BASE_URL = `http://${window.location.hostname}:${window.location.port || '5057'}`;
|
| 3 |
+
|
| 4 |
+
// Elements
|
| 5 |
+
const registerForm = document.getElementById('registerForm');
|
| 6 |
+
const registerBtn = document.getElementById('registerBtn');
|
| 7 |
+
|
| 8 |
+
// Validation state
|
| 9 |
+
let validationErrors = {};
|
| 10 |
+
let isSubmitting = false;
|
| 11 |
+
|
| 12 |
+
// API helper
|
| 13 |
+
async function api(path, opts) {
|
| 14 |
+
const url = API_BASE_URL + path;
|
| 15 |
+
const res = await fetch(url, opts);
|
| 16 |
+
if (!res.ok) {
|
| 17 |
+
let errorData;
|
| 18 |
+
try {
|
| 19 |
+
errorData = await res.json();
|
| 20 |
+
} catch (e) {
|
| 21 |
+
const txt = await res.text();
|
| 22 |
+
errorData = { error: txt || res.statusText };
|
| 23 |
+
}
|
| 24 |
+
throw new Error(JSON.stringify(errorData));
|
| 25 |
+
}
|
| 26 |
+
return res.json();
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Show message
|
| 30 |
+
function showMessage(text, type = 'error') {
|
| 31 |
+
const existing = document.querySelector('.error-message, .success-message');
|
| 32 |
+
if (existing) existing.remove();
|
| 33 |
+
|
| 34 |
+
const message = document.createElement('div');
|
| 35 |
+
message.className = type === 'error' ? 'error-message' : 'success-message';
|
| 36 |
+
message.textContent = text;
|
| 37 |
+
|
| 38 |
+
registerForm.insertBefore(message, registerForm.firstChild);
|
| 39 |
+
|
| 40 |
+
setTimeout(() => message.remove(), 5000);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
// Show field error
|
| 44 |
+
function showFieldError(fieldId, message) {
|
| 45 |
+
const errorElement = document.getElementById(fieldId + 'Error');
|
| 46 |
+
if (errorElement) {
|
| 47 |
+
errorElement.textContent = message;
|
| 48 |
+
errorElement.classList.add('show');
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
const inputElement = document.getElementById(fieldId);
|
| 52 |
+
const formGroup = inputElement ? inputElement.closest('.form-group') : null;
|
| 53 |
+
|
| 54 |
+
if (formGroup) {
|
| 55 |
+
formGroup.classList.add('error');
|
| 56 |
+
formGroup.classList.remove('success');
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
// Show server validation errors for specific fields
|
| 61 |
+
function showServerFieldErrors(serverErrors) {
|
| 62 |
+
const fieldMapping = {
|
| 63 |
+
'username': 'regUsername',
|
| 64 |
+
'email': 'regEmail',
|
| 65 |
+
'fullname': 'regFullname',
|
| 66 |
+
'telephone': 'regTelephone',
|
| 67 |
+
'province': 'regProvince',
|
| 68 |
+
'district': 'regDistrict',
|
| 69 |
+
'password': 'regPassword',
|
| 70 |
+
'confirmPassword': 'regConfirmPassword'
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
Object.keys(serverErrors).forEach(field => {
|
| 74 |
+
const fieldId = fieldMapping[field] || 'reg' + field.charAt(0).toUpperCase() + field.slice(1);
|
| 75 |
+
showFieldError(fieldId, serverErrors[field]);
|
| 76 |
+
});
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
// Clear field error
|
| 80 |
+
function clearFieldError(fieldId) {
|
| 81 |
+
const errorElement = document.getElementById(fieldId + 'Error');
|
| 82 |
+
if (errorElement) {
|
| 83 |
+
errorElement.textContent = '';
|
| 84 |
+
errorElement.classList.remove('show');
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
const formGroup = document.getElementById(fieldId).closest('.form-group');
|
| 88 |
+
if (formGroup) {
|
| 89 |
+
formGroup.classList.remove('error');
|
| 90 |
+
formGroup.classList.add('success');
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
// Clear all field errors
|
| 95 |
+
function clearAllFieldErrors() {
|
| 96 |
+
const fieldIds = ['regUsername', 'regEmail', 'regFullname', 'regTelephone', 'regProvince', 'regDistrict', 'regPassword', 'regConfirmPassword', 'agreeTerms'];
|
| 97 |
+
fieldIds.forEach(fieldId => clearFieldError(fieldId));
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
// Clear all generic error messages
|
| 101 |
+
function clearAllGenericMessages() {
|
| 102 |
+
const existing = document.querySelector('.error-message, .success-message');
|
| 103 |
+
if (existing) existing.remove();
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
// Validate username
|
| 107 |
+
function validateUsername(username) {
|
| 108 |
+
if (!username || username.trim() === '') {
|
| 109 |
+
return 'Username is required';
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
if (username.length < 3) {
|
| 113 |
+
return 'Username must be at least 3 characters';
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
if (username.length > 50) {
|
| 117 |
+
return 'Username must be less than 50 characters';
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
|
| 121 |
+
return 'Username can only contain letters, numbers, and underscores';
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
// Check for reserved usernames
|
| 125 |
+
const reservedUsernames = ['admin', 'administrator', 'root', 'system', 'api', 'test', 'user', 'guest', 'null', 'undefined'];
|
| 126 |
+
if (reservedUsernames.includes(username.toLowerCase())) {
|
| 127 |
+
return 'This username is reserved and cannot be used';
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
return null;
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
// Validate email
|
| 134 |
+
function validateEmail(email) {
|
| 135 |
+
if (!email || email.trim() === '') {
|
| 136 |
+
return 'Email address is required';
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
if (email.length > 100) {
|
| 140 |
+
return 'Email address must be less than 100 characters';
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
| 144 |
+
if (!emailPattern.test(email)) {
|
| 145 |
+
return 'Please enter a valid email address';
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// Check for common email providers
|
| 149 |
+
const commonDomains = ['gmail.com', 'yahoo.com', 'hotmail.com', 'outlook.com', 'icloud.com'];
|
| 150 |
+
const domain = email.split('@')[1]?.toLowerCase();
|
| 151 |
+
if (domain && !commonDomains.includes(domain) && !domain.includes('.')) {
|
| 152 |
+
return 'Please enter a valid email address';
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
return null;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
// Validate full name
|
| 159 |
+
function validateFullName(fullname) {
|
| 160 |
+
if (!fullname || fullname.trim() === '') {
|
| 161 |
+
return 'Full name is required';
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
if (fullname.length < 2) {
|
| 165 |
+
return 'Full name must be at least 2 characters';
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
if (fullname.length > 100) {
|
| 169 |
+
return 'Full name must be less than 100 characters';
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
if (!/^[a-zA-Z\s\-'\.]+$/.test(fullname)) {
|
| 173 |
+
return 'Full name can only contain letters, spaces, hyphens, apostrophes, and periods';
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
// Check for minimum words
|
| 177 |
+
const words = fullname.trim().split(/\s+/);
|
| 178 |
+
if (words.length < 2) {
|
| 179 |
+
return 'Please enter your complete name (first and last name)';
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
return null;
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// Validate phone number
|
| 186 |
+
function validatePhone(telephone) {
|
| 187 |
+
if (!telephone || telephone.trim() === '') {
|
| 188 |
+
return 'Phone number is required';
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
// Remove all spaces and special characters except + and digits
|
| 192 |
+
const cleanPhone = telephone.replace(/[^\d+]/g, '');
|
| 193 |
+
|
| 194 |
+
// Check Rwanda phone number format
|
| 195 |
+
const phonePattern = /^(\+250|0)[0-9]{9}$/;
|
| 196 |
+
if (!phonePattern.test(cleanPhone)) {
|
| 197 |
+
return 'Please enter a valid Rwanda phone number (+250XXXXXXXXX or 07XXXXXXXX)';
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
// Additional validation for specific prefixes
|
| 201 |
+
if (cleanPhone.startsWith('0')) {
|
| 202 |
+
const prefix = cleanPhone.substring(0, 3);
|
| 203 |
+
const validPrefixes = ['078', '079', '072', '073', '074', '075', '076', '077'];
|
| 204 |
+
if (!validPrefixes.includes(prefix)) {
|
| 205 |
+
return 'Please enter a valid Rwanda mobile number';
|
| 206 |
+
}
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
return null;
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
// Validate password
|
| 213 |
+
function validatePassword(password) {
|
| 214 |
+
if (!password || password === '') {
|
| 215 |
+
return 'Password is required';
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
if (password.length < 8) {
|
| 219 |
+
return 'Password must be at least 8 characters long';
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
if (password.length > 128) {
|
| 223 |
+
return 'Password must be less than 128 characters';
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
// Check for at least one letter and one number
|
| 227 |
+
if (!/[a-zA-Z]/.test(password)) {
|
| 228 |
+
return 'Password must contain at least one letter';
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
if (!/[0-9]/.test(password)) {
|
| 232 |
+
return 'Password must contain at least one number';
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
// Check for common weak passwords
|
| 236 |
+
const weakPasswords = ['password', '123456', '12345678', 'qwerty', 'abc123', 'password123', 'admin', 'letmein'];
|
| 237 |
+
if (weakPasswords.includes(password.toLowerCase())) {
|
| 238 |
+
return 'This password is too common. Please choose a stronger password';
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
return null;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
// Validate password confirmation
|
| 245 |
+
function validatePasswordConfirmation(password, confirmPassword) {
|
| 246 |
+
if (!confirmPassword || confirmPassword === '') {
|
| 247 |
+
return 'Please confirm your password';
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
if (password !== confirmPassword) {
|
| 251 |
+
return 'Passwords do not match';
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
return null;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
// Validate province
|
| 258 |
+
function validateProvince(province) {
|
| 259 |
+
if (!province || province === '') {
|
| 260 |
+
return 'Please select a province';
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
const validProvinces = ['Kigali', 'Eastern', 'Northern', 'Southern', 'Western'];
|
| 264 |
+
if (!validProvinces.includes(province)) {
|
| 265 |
+
return 'Please select a valid province';
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
return null;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
// Validate district
|
| 272 |
+
function validateDistrict(district, province) {
|
| 273 |
+
if (!district || district === '') {
|
| 274 |
+
return 'Please select a district';
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
if (!province || province === '') {
|
| 278 |
+
return 'Please select a province first';
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
const validDistricts = getDistrictsForProvince(province);
|
| 282 |
+
if (!validDistricts.includes(district)) {
|
| 283 |
+
return 'Please select a valid district for the selected province';
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
return null;
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
// Validate terms agreement
|
| 290 |
+
function validateTerms(agreeTerms) {
|
| 291 |
+
if (!agreeTerms) {
|
| 292 |
+
return 'You must agree to the Terms of Service and Privacy Policy';
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
return null;
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
+
// Get districts for province
|
| 299 |
+
function getDistrictsForProvince(province) {
|
| 300 |
+
const provinceDistricts = {
|
| 301 |
+
'Kigali': ['Gasabo', 'Kicukiro', 'Nyarugenge'],
|
| 302 |
+
'Eastern': ['Bugesera', 'Gatsibo', 'Kayonza', 'Kirehe', 'Ngoma', 'Nyagatare', 'Rwamagana'],
|
| 303 |
+
'Northern': ['Burera', 'Gakenke', 'Gicumbi', 'Musanze', 'Rulindo'],
|
| 304 |
+
'Southern': ['Gisagara', 'Huye', 'Kamonyi', 'Muhanga', 'Nyamagabe', 'Nyanza', 'Nyaruguru', 'Ruhango'],
|
| 305 |
+
'Western': ['Karongi', 'Ngororero', 'Nyabihu', 'Nyamasheke', 'Rubavu', 'Rusizi', 'Rutsiro']
|
| 306 |
+
};
|
| 307 |
+
return provinceDistricts[province] || [];
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
// Calculate password strength
|
| 311 |
+
function calculatePasswordStrength(password) {
|
| 312 |
+
let score = 0;
|
| 313 |
+
|
| 314 |
+
if (password.length >= 8) score += 1;
|
| 315 |
+
if (password.length >= 12) score += 1;
|
| 316 |
+
if (/[a-z]/.test(password)) score += 1;
|
| 317 |
+
if (/[A-Z]/.test(password)) score += 1;
|
| 318 |
+
if (/[0-9]/.test(password)) score += 1;
|
| 319 |
+
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
|
| 320 |
+
|
| 321 |
+
if (score <= 2) return 'weak';
|
| 322 |
+
if (score <= 4) return 'medium';
|
| 323 |
+
return 'strong';
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
// Update password strength indicator
|
| 327 |
+
function updatePasswordStrength(password) {
|
| 328 |
+
const strength = calculatePasswordStrength(password);
|
| 329 |
+
const strengthElement = document.querySelector('.password-strength');
|
| 330 |
+
const strengthBar = document.querySelector('.password-strength-bar');
|
| 331 |
+
|
| 332 |
+
if (strengthElement && strengthBar) {
|
| 333 |
+
strengthElement.textContent = `Password strength: ${strength}`;
|
| 334 |
+
strengthElement.className = `password-strength ${strength}`;
|
| 335 |
+
strengthBar.className = `password-strength-bar ${strength}`;
|
| 336 |
+
}
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
// Real-time validation
|
| 340 |
+
function setupRealTimeValidation() {
|
| 341 |
+
// Username validation
|
| 342 |
+
document.getElementById('regUsername').addEventListener('blur', function() {
|
| 343 |
+
const error = validateUsername(this.value);
|
| 344 |
+
if (error) {
|
| 345 |
+
showFieldError('regUsername', error);
|
| 346 |
+
} else {
|
| 347 |
+
clearFieldError('regUsername');
|
| 348 |
+
}
|
| 349 |
+
});
|
| 350 |
+
|
| 351 |
+
// Email validation
|
| 352 |
+
document.getElementById('regEmail').addEventListener('blur', function() {
|
| 353 |
+
const error = validateEmail(this.value);
|
| 354 |
+
if (error) {
|
| 355 |
+
showFieldError('regEmail', error);
|
| 356 |
+
} else {
|
| 357 |
+
clearFieldError('regEmail');
|
| 358 |
+
}
|
| 359 |
+
});
|
| 360 |
+
|
| 361 |
+
// Full name validation
|
| 362 |
+
document.getElementById('regFullname').addEventListener('blur', function() {
|
| 363 |
+
const error = validateFullName(this.value);
|
| 364 |
+
if (error) {
|
| 365 |
+
showFieldError('regFullname', error);
|
| 366 |
+
} else {
|
| 367 |
+
clearFieldError('regFullname');
|
| 368 |
+
}
|
| 369 |
+
});
|
| 370 |
+
|
| 371 |
+
// Phone validation
|
| 372 |
+
document.getElementById('regTelephone').addEventListener('blur', function() {
|
| 373 |
+
const error = validatePhone(this.value);
|
| 374 |
+
if (error) {
|
| 375 |
+
showFieldError('regTelephone', error);
|
| 376 |
+
} else {
|
| 377 |
+
clearFieldError('regTelephone');
|
| 378 |
+
}
|
| 379 |
+
});
|
| 380 |
+
|
| 381 |
+
// Province validation
|
| 382 |
+
document.getElementById('regProvince').addEventListener('change', function() {
|
| 383 |
+
const error = validateProvince(this.value);
|
| 384 |
+
if (error) {
|
| 385 |
+
showFieldError('regProvince', error);
|
| 386 |
+
} else {
|
| 387 |
+
clearFieldError('regProvince');
|
| 388 |
+
}
|
| 389 |
+
});
|
| 390 |
+
|
| 391 |
+
// District validation
|
| 392 |
+
document.getElementById('regDistrict').addEventListener('change', function() {
|
| 393 |
+
const province = document.getElementById('regProvince').value;
|
| 394 |
+
const error = validateDistrict(this.value, province);
|
| 395 |
+
if (error) {
|
| 396 |
+
showFieldError('regDistrict', error);
|
| 397 |
+
} else {
|
| 398 |
+
clearFieldError('regDistrict');
|
| 399 |
+
}
|
| 400 |
+
});
|
| 401 |
+
|
| 402 |
+
// Password validation
|
| 403 |
+
document.getElementById('regPassword').addEventListener('input', function() {
|
| 404 |
+
updatePasswordStrength(this.value);
|
| 405 |
+
const error = validatePassword(this.value);
|
| 406 |
+
if (error) {
|
| 407 |
+
showFieldError('regPassword', error);
|
| 408 |
+
} else {
|
| 409 |
+
clearFieldError('regPassword');
|
| 410 |
+
}
|
| 411 |
+
});
|
| 412 |
+
|
| 413 |
+
// Password confirmation validation
|
| 414 |
+
document.getElementById('regConfirmPassword').addEventListener('blur', function() {
|
| 415 |
+
const password = document.getElementById('regPassword').value;
|
| 416 |
+
const error = validatePasswordConfirmation(password, this.value);
|
| 417 |
+
if (error) {
|
| 418 |
+
showFieldError('regConfirmPassword', error);
|
| 419 |
+
} else {
|
| 420 |
+
clearFieldError('regConfirmPassword');
|
| 421 |
+
}
|
| 422 |
+
});
|
| 423 |
+
|
| 424 |
+
// Terms validation
|
| 425 |
+
document.getElementById('agreeTerms').addEventListener('change', function() {
|
| 426 |
+
const error = validateTerms(this.checked);
|
| 427 |
+
if (error) {
|
| 428 |
+
showFieldError('agreeTerms', error);
|
| 429 |
+
} else {
|
| 430 |
+
clearFieldError('agreeTerms');
|
| 431 |
+
}
|
| 432 |
+
});
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
// Validate all fields
|
| 436 |
+
function validateAllFields() {
|
| 437 |
+
const username = document.getElementById('regUsername').value.trim();
|
| 438 |
+
const email = document.getElementById('regEmail').value.trim();
|
| 439 |
+
const fullname = document.getElementById('regFullname').value.trim();
|
| 440 |
+
const telephone = document.getElementById('regTelephone').value.trim();
|
| 441 |
+
const province = document.getElementById('regProvince').value;
|
| 442 |
+
const district = document.getElementById('regDistrict').value;
|
| 443 |
+
const password = document.getElementById('regPassword').value;
|
| 444 |
+
const confirmPassword = document.getElementById('regConfirmPassword').value;
|
| 445 |
+
const agreeTerms = document.getElementById('agreeTerms').checked;
|
| 446 |
+
|
| 447 |
+
validationErrors = {};
|
| 448 |
+
|
| 449 |
+
// Validate each field
|
| 450 |
+
const usernameError = validateUsername(username);
|
| 451 |
+
if (usernameError) validationErrors.username = usernameError;
|
| 452 |
+
|
| 453 |
+
const emailError = validateEmail(email);
|
| 454 |
+
if (emailError) validationErrors.email = emailError;
|
| 455 |
+
|
| 456 |
+
const fullnameError = validateFullName(fullname);
|
| 457 |
+
if (fullnameError) validationErrors.fullname = fullnameError;
|
| 458 |
+
|
| 459 |
+
const telephoneError = validatePhone(telephone);
|
| 460 |
+
if (telephoneError) validationErrors.telephone = telephoneError;
|
| 461 |
+
|
| 462 |
+
const provinceError = validateProvince(province);
|
| 463 |
+
if (provinceError) validationErrors.province = provinceError;
|
| 464 |
+
|
| 465 |
+
const districtError = validateDistrict(district, province);
|
| 466 |
+
if (districtError) validationErrors.district = districtError;
|
| 467 |
+
|
| 468 |
+
const passwordError = validatePassword(password);
|
| 469 |
+
if (passwordError) validationErrors.password = passwordError;
|
| 470 |
+
|
| 471 |
+
const confirmPasswordError = validatePasswordConfirmation(password, confirmPassword);
|
| 472 |
+
if (confirmPasswordError) validationErrors.confirmPassword = confirmPasswordError;
|
| 473 |
+
|
| 474 |
+
const termsError = validateTerms(agreeTerms);
|
| 475 |
+
if (termsError) validationErrors.terms = termsError;
|
| 476 |
+
|
| 477 |
+
// Show all errors
|
| 478 |
+
Object.keys(validationErrors).forEach(field => {
|
| 479 |
+
showFieldError('reg' + field.charAt(0).toUpperCase() + field.slice(1), validationErrors[field]);
|
| 480 |
+
});
|
| 481 |
+
|
| 482 |
+
return Object.keys(validationErrors).length === 0;
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
// Redirect to main app
|
| 486 |
+
function redirectToApp(account = null) {
|
| 487 |
+
if (account) {
|
| 488 |
+
localStorage.setItem('aimhsa_account', account);
|
| 489 |
+
}
|
| 490 |
+
window.location.href = '/index.html';
|
| 491 |
+
}
|
| 492 |
+
|
| 493 |
+
// Registration form submission
|
| 494 |
+
registerForm.addEventListener('submit', async (e) => {
|
| 495 |
+
e.preventDefault();
|
| 496 |
+
|
| 497 |
+
if (isSubmitting) return;
|
| 498 |
+
|
| 499 |
+
// Clear previous errors
|
| 500 |
+
clearAllFieldErrors();
|
| 501 |
+
clearAllGenericMessages();
|
| 502 |
+
|
| 503 |
+
// Validate all fields
|
| 504 |
+
if (!validateAllFields()) {
|
| 505 |
+
showMessage('Please correct the errors below');
|
| 506 |
+
return;
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
const username = document.getElementById('regUsername').value.trim();
|
| 510 |
+
const email = document.getElementById('regEmail').value.trim();
|
| 511 |
+
const fullname = document.getElementById('regFullname').value.trim();
|
| 512 |
+
const telephone = document.getElementById('regTelephone').value.trim();
|
| 513 |
+
const province = document.getElementById('regProvince').value;
|
| 514 |
+
const district = document.getElementById('regDistrict').value;
|
| 515 |
+
const password = document.getElementById('regPassword').value;
|
| 516 |
+
|
| 517 |
+
isSubmitting = true;
|
| 518 |
+
registerBtn.disabled = true;
|
| 519 |
+
registerBtn.textContent = 'Creating account...';
|
| 520 |
+
|
| 521 |
+
try {
|
| 522 |
+
const response = await api('/api/register', {
|
| 523 |
+
method: 'POST',
|
| 524 |
+
headers: { 'Content-Type': 'application/json' },
|
| 525 |
+
body: JSON.stringify({
|
| 526 |
+
username,
|
| 527 |
+
email,
|
| 528 |
+
fullname,
|
| 529 |
+
telephone,
|
| 530 |
+
province,
|
| 531 |
+
district,
|
| 532 |
+
password
|
| 533 |
+
})
|
| 534 |
+
});
|
| 535 |
+
|
| 536 |
+
showMessage('Account created successfully! Redirecting...', 'success');
|
| 537 |
+
setTimeout(() => redirectToApp(username), 1500);
|
| 538 |
+
} catch (err) {
|
| 539 |
+
console.log('Registration error:', err);
|
| 540 |
+
|
| 541 |
+
// Parse server error response for specific field errors
|
| 542 |
+
let serverErrors = {};
|
| 543 |
+
let genericError = 'Registration failed. Please check the errors below.';
|
| 544 |
+
|
| 545 |
+
try {
|
| 546 |
+
// Try to parse JSON error response
|
| 547 |
+
const errorData = JSON.parse(err.message);
|
| 548 |
+
console.log('Parsed error data:', errorData);
|
| 549 |
+
|
| 550 |
+
if (errorData.errors) {
|
| 551 |
+
serverErrors = errorData.errors;
|
| 552 |
+
console.log('Server errors:', serverErrors);
|
| 553 |
+
} else if (errorData.error) {
|
| 554 |
+
genericError = errorData.error;
|
| 555 |
+
}
|
| 556 |
+
} catch (parseError) {
|
| 557 |
+
console.log('Could not parse error as JSON:', parseError);
|
| 558 |
+
// If not JSON, check for specific error patterns
|
| 559 |
+
const errorText = err.message.toLowerCase();
|
| 560 |
+
|
| 561 |
+
if (errorText.includes('username')) {
|
| 562 |
+
if (errorText.includes('already exists') || errorText.includes('taken')) {
|
| 563 |
+
serverErrors.username = 'This username is already taken. Please choose another.';
|
| 564 |
+
} else if (errorText.includes('invalid')) {
|
| 565 |
+
serverErrors.username = 'Invalid username format.';
|
| 566 |
+
}
|
| 567 |
+
} else if (errorText.includes('email')) {
|
| 568 |
+
if (errorText.includes('already exists') || errorText.includes('taken')) {
|
| 569 |
+
serverErrors.email = 'This email is already registered. Please use a different email.';
|
| 570 |
+
} else if (errorText.includes('invalid')) {
|
| 571 |
+
serverErrors.email = 'Invalid email format.';
|
| 572 |
+
}
|
| 573 |
+
} else if (errorText.includes('phone') || errorText.includes('telephone')) {
|
| 574 |
+
serverErrors.telephone = 'Invalid phone number format.';
|
| 575 |
+
} else if (errorText.includes('password')) {
|
| 576 |
+
serverErrors.password = 'Password does not meet requirements.';
|
| 577 |
+
} else if (errorText.includes('province')) {
|
| 578 |
+
serverErrors.province = 'Please select a valid province.';
|
| 579 |
+
} else if (errorText.includes('district')) {
|
| 580 |
+
serverErrors.district = 'Please select a valid district.';
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
// Show specific field errors
|
| 585 |
+
if (Object.keys(serverErrors).length > 0) {
|
| 586 |
+
console.log('Showing field errors:', serverErrors);
|
| 587 |
+
|
| 588 |
+
// Clear any existing generic error messages
|
| 589 |
+
clearAllGenericMessages();
|
| 590 |
+
|
| 591 |
+
// Show server validation errors for each field
|
| 592 |
+
showServerFieldErrors(serverErrors);
|
| 593 |
+
|
| 594 |
+
// Show generic message if there are field errors
|
| 595 |
+
showMessage('Please correct the errors below');
|
| 596 |
+
return; // Exit after showing field errors
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
// Only show generic message if no specific field errors
|
| 600 |
+
console.log('Showing generic error:', genericError);
|
| 601 |
+
showMessage(genericError);
|
| 602 |
+
} finally {
|
| 603 |
+
isSubmitting = false;
|
| 604 |
+
registerBtn.disabled = false;
|
| 605 |
+
registerBtn.textContent = 'Create Account';
|
| 606 |
+
}
|
| 607 |
+
});
|
| 608 |
+
|
| 609 |
+
// Province/District mapping for Rwanda
|
| 610 |
+
const provinceDistricts = {
|
| 611 |
+
'Kigali': ['Gasabo', 'Kicukiro', 'Nyarugenge'],
|
| 612 |
+
'Eastern': ['Bugesera', 'Gatsibo', 'Kayonza', 'Kirehe', 'Ngoma', 'Nyagatare', 'Rwamagana'],
|
| 613 |
+
'Northern': ['Burera', 'Gakenke', 'Gicumbi', 'Musanze', 'Rulindo'],
|
| 614 |
+
'Southern': ['Gisagara', 'Huye', 'Kamonyi', 'Muhanga', 'Nyamagabe', 'Nyanza', 'Nyaruguru', 'Ruhango'],
|
| 615 |
+
'Western': ['Karongi', 'Ngororero', 'Nyabihu', 'Nyamasheke', 'Rubavu', 'Rusizi', 'Rutsiro']
|
| 616 |
+
};
|
| 617 |
+
|
| 618 |
+
// Handle province change to filter districts
|
| 619 |
+
document.getElementById('regProvince').addEventListener('change', function() {
|
| 620 |
+
const province = this.value;
|
| 621 |
+
const districtSelect = document.getElementById('regDistrict');
|
| 622 |
+
|
| 623 |
+
// Clear existing options except the first one
|
| 624 |
+
districtSelect.innerHTML = '<option value="">Select District</option>';
|
| 625 |
+
|
| 626 |
+
if (province && provinceDistricts[province]) {
|
| 627 |
+
provinceDistricts[province].forEach(district => {
|
| 628 |
+
const option = document.createElement('option');
|
| 629 |
+
option.value = district;
|
| 630 |
+
option.textContent = district;
|
| 631 |
+
districtSelect.appendChild(option);
|
| 632 |
+
});
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
// Clear district error when province changes
|
| 636 |
+
clearFieldError('regDistrict');
|
| 637 |
+
});
|
| 638 |
+
|
| 639 |
+
// Initialize real-time validation
|
| 640 |
+
setupRealTimeValidation();
|
| 641 |
+
|
| 642 |
+
// Check if already logged in
|
| 643 |
+
const account = localStorage.getItem('aimhsa_account');
|
| 644 |
+
if (account && account !== 'null') {
|
| 645 |
+
redirectToApp(account);
|
| 646 |
+
}
|
| 647 |
+
})();
|
chatbot/style.css
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--primary: #7c3aed;
|
| 3 |
+
--primary-light: #a855f7;
|
| 4 |
+
--primary-dark: #5b21b6;
|
| 5 |
+
--background: #0f172a;
|
| 6 |
+
--surface: #1e293b;
|
| 7 |
+
--card: #334155;
|
| 8 |
+
--text: #f8fafc;
|
| 9 |
+
--text-secondary: #cbd5e1;
|
| 10 |
+
--text-muted: #94a3b8;
|
| 11 |
+
--border: #334155;
|
| 12 |
+
--border-light: #475569;
|
| 13 |
+
--success: #10b981;
|
| 14 |
+
--warning: #f59e0b;
|
| 15 |
+
--danger: #ef4444;
|
| 16 |
+
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3), 0 1px 2px 0 rgba(0, 0, 0, 0.2);
|
| 17 |
+
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
|
| 18 |
+
--composer-height: 80px;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
* {
|
| 22 |
+
box-sizing: border-box;
|
| 23 |
+
margin: 0;
|
| 24 |
+
padding: 0;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
body {
|
| 28 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 29 |
+
background: var(--background);
|
| 30 |
+
color: var(--text);
|
| 31 |
+
height: 100vh;
|
| 32 |
+
overflow: hidden;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.app {
|
| 36 |
+
display: flex;
|
| 37 |
+
height: 100vh;
|
| 38 |
+
background: var(--background);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
/* Sidebar */
|
| 42 |
+
.sidebar {
|
| 43 |
+
width: 320px;
|
| 44 |
+
background: linear-gradient(180deg, #111827 0%, #0f172a 60%);
|
| 45 |
+
border-right: 1px solid var(--border);
|
| 46 |
+
display: flex;
|
| 47 |
+
flex-direction: column;
|
| 48 |
+
flex-shrink: 0;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.sidebar-header {
|
| 52 |
+
padding: 24px;
|
| 53 |
+
border-bottom: 1px solid var(--border);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
.user-info {
|
| 57 |
+
display: flex;
|
| 58 |
+
align-items: center;
|
| 59 |
+
gap: 12px;
|
| 60 |
+
margin-bottom: 20px;
|
| 61 |
+
background: rgba(255,255,255,0.02);
|
| 62 |
+
padding: 10px;
|
| 63 |
+
border-radius: 12px;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.avatar {
|
| 67 |
+
width: 40px;
|
| 68 |
+
height: 40px;
|
| 69 |
+
background: var(--primary);
|
| 70 |
+
border-radius: 50%;
|
| 71 |
+
display: flex;
|
| 72 |
+
align-items: center;
|
| 73 |
+
justify-content: center;
|
| 74 |
+
font-size: 18px;
|
| 75 |
+
color: white;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
.user-details .username {
|
| 79 |
+
font-weight: 600;
|
| 80 |
+
color: var(--text);
|
| 81 |
+
font-size: 16px;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.user-details .status {
|
| 85 |
+
font-size: 12px;
|
| 86 |
+
color: var(--success);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.new-chat-btn {
|
| 90 |
+
width: 100%;
|
| 91 |
+
padding: 12px 16px;
|
| 92 |
+
background: linear-gradient(90deg, var(--primary) 0%, var(--primary-dark) 100%);
|
| 93 |
+
color: white;
|
| 94 |
+
border: none;
|
| 95 |
+
border-radius: 10px;
|
| 96 |
+
font-weight: 500;
|
| 97 |
+
cursor: pointer;
|
| 98 |
+
transition: all 0.2s ease;
|
| 99 |
+
display: flex;
|
| 100 |
+
align-items: center;
|
| 101 |
+
justify-content: center;
|
| 102 |
+
gap: 8px;
|
| 103 |
+
box-shadow: 0 8px 20px rgba(124, 58, 237, 0.25);
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
.new-chat-btn:hover {
|
| 107 |
+
background: var(--primary-dark);
|
| 108 |
+
transform: translateY(-1px);
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.history-section {
|
| 112 |
+
flex: 1;
|
| 113 |
+
padding: 20px 24px;
|
| 114 |
+
overflow-y: auto;
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
.history-section h3 {
|
| 118 |
+
font-size: 14px;
|
| 119 |
+
font-weight: 600;
|
| 120 |
+
color: #a5b4fc;
|
| 121 |
+
margin-bottom: 16px;
|
| 122 |
+
text-transform: uppercase;
|
| 123 |
+
letter-spacing: 0.5px;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.history-list {
|
| 127 |
+
display: flex;
|
| 128 |
+
flex-direction: column;
|
| 129 |
+
gap: 4px;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.history-item {
|
| 133 |
+
padding: 12px 16px;
|
| 134 |
+
border-radius: 8px;
|
| 135 |
+
cursor: pointer;
|
| 136 |
+
color: var(--text-secondary);
|
| 137 |
+
transition: all 0.2s ease;
|
| 138 |
+
font-size: 14px;
|
| 139 |
+
border: 1px solid transparent;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.history-item:hover {
|
| 143 |
+
background: rgba(124,58,237,0.12);
|
| 144 |
+
border-color: rgba(124,58,237,0.25);
|
| 145 |
+
color: var(--text);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.history-item.active {
|
| 149 |
+
background: linear-gradient(90deg, var(--primary) 0%, var(--primary-dark) 100%);
|
| 150 |
+
color: white;
|
| 151 |
+
border-color: transparent;
|
| 152 |
+
box-shadow: 0 6px 16px rgba(124,58,237,0.35);
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.history-preview {
|
| 156 |
+
white-space: nowrap;
|
| 157 |
+
overflow: hidden;
|
| 158 |
+
text-overflow: ellipsis;
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
/* three-dot menu */
|
| 162 |
+
.history-menu-btn {
|
| 163 |
+
position: absolute;
|
| 164 |
+
right: 8px;
|
| 165 |
+
top: 50%;
|
| 166 |
+
transform: translateY(-50%);
|
| 167 |
+
background: transparent;
|
| 168 |
+
border: none;
|
| 169 |
+
color: var(--text-secondary);
|
| 170 |
+
display: none;
|
| 171 |
+
cursor: pointer;
|
| 172 |
+
padding: 4px 6px;
|
| 173 |
+
border-radius: 6px;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.history-item:hover .history-menu-btn,
|
| 177 |
+
.history-item.active .history-menu-btn { display: inline-flex; }
|
| 178 |
+
|
| 179 |
+
.history-menu-btn:hover { background: var(--card); }
|
| 180 |
+
|
| 181 |
+
.history-menu {
|
| 182 |
+
position: absolute;
|
| 183 |
+
right: 8px;
|
| 184 |
+
top: calc(100% - 6px);
|
| 185 |
+
min-width: 140px;
|
| 186 |
+
background: linear-gradient(180deg, #1f2937 0%, #111827 100%);
|
| 187 |
+
border: 1px solid var(--border);
|
| 188 |
+
border-radius: 8px;
|
| 189 |
+
box-shadow: var(--shadow-lg);
|
| 190 |
+
display: none;
|
| 191 |
+
z-index: 10;
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.history-menu.open { display: block; }
|
| 195 |
+
|
| 196 |
+
.history-menu button {
|
| 197 |
+
width: 100%;
|
| 198 |
+
text-align: left;
|
| 199 |
+
background: transparent;
|
| 200 |
+
border: none;
|
| 201 |
+
color: var(--text-secondary);
|
| 202 |
+
padding: 10px 12px;
|
| 203 |
+
cursor: pointer;
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
.history-menu button:hover { background: #374151; color: var(--text); }
|
| 207 |
+
.history-menu .danger { color: #f87171; }
|
| 208 |
+
.history-menu .danger:hover { background: rgba(248, 113, 113, 0.15); }
|
| 209 |
+
|
| 210 |
+
/* inline delete button for history items */
|
| 211 |
+
.history-item {
|
| 212 |
+
display: flex;
|
| 213 |
+
align-items: center;
|
| 214 |
+
gap: 8px;
|
| 215 |
+
position: relative;
|
| 216 |
+
padding-right: 36px; /* reserve space for delete icon */
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
.history-delete {
|
| 220 |
+
display: none;
|
| 221 |
+
color: var(--text-muted);
|
| 222 |
+
background: transparent;
|
| 223 |
+
border: none;
|
| 224 |
+
cursor: pointer;
|
| 225 |
+
padding: 4px;
|
| 226 |
+
border-radius: 6px;
|
| 227 |
+
position: absolute;
|
| 228 |
+
right: 8px;
|
| 229 |
+
top: 50%;
|
| 230 |
+
transform: translateY(-50%);
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.history-item:hover .history-delete {
|
| 234 |
+
display: inline-flex;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
/* Always show delete on the active item */
|
| 238 |
+
.history-item.active .history-delete {
|
| 239 |
+
display: inline-flex;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
.history-delete:hover {
|
| 243 |
+
color: #fff;
|
| 244 |
+
background: rgba(239, 68, 68, 0.18);
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
.sidebar-footer {
|
| 248 |
+
padding: 24px;
|
| 249 |
+
border-top: 1px solid var(--border);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
.logout-btn {
|
| 253 |
+
width: 100%;
|
| 254 |
+
padding: 10px;
|
| 255 |
+
background: transparent;
|
| 256 |
+
color: var(--text-secondary);
|
| 257 |
+
border: 1px solid var(--border);
|
| 258 |
+
border-radius: 8px;
|
| 259 |
+
cursor: pointer;
|
| 260 |
+
transition: all 0.2s ease;
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
.logout-btn:hover {
|
| 264 |
+
background: var(--danger);
|
| 265 |
+
color: white;
|
| 266 |
+
border-color: var(--danger);
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
/* Chat Area */
|
| 270 |
+
.chat-area {
|
| 271 |
+
flex: 1;
|
| 272 |
+
display: flex;
|
| 273 |
+
flex-direction: column;
|
| 274 |
+
background: var(--background);
|
| 275 |
+
height: 100vh;
|
| 276 |
+
position: relative;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
.chat-header {
|
| 280 |
+
padding: 20px 24px;
|
| 281 |
+
border-bottom: 1px solid var(--border);
|
| 282 |
+
display: flex;
|
| 283 |
+
justify-content: space-between;
|
| 284 |
+
align-items: center;
|
| 285 |
+
background: var(--surface);
|
| 286 |
+
flex-shrink: 0;
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
.chat-title h2 {
|
| 290 |
+
font-size: 24px;
|
| 291 |
+
font-weight: 700;
|
| 292 |
+
color: var(--primary);
|
| 293 |
+
margin-bottom: 4px;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.chat-subtitle {
|
| 297 |
+
font-size: 14px;
|
| 298 |
+
color: var(--text-secondary);
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.chat-actions {
|
| 302 |
+
display: flex;
|
| 303 |
+
gap: 8px;
|
| 304 |
+
align-items: center;
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
.action-btn {
|
| 308 |
+
padding: 8px 16px;
|
| 309 |
+
border: 1px solid var(--border);
|
| 310 |
+
background: var(--surface);
|
| 311 |
+
color: var(--text-secondary);
|
| 312 |
+
border-radius: 8px;
|
| 313 |
+
cursor: pointer;
|
| 314 |
+
font-size: 14px;
|
| 315 |
+
transition: all 0.2s ease;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
.action-btn:hover {
|
| 319 |
+
background: var(--card);
|
| 320 |
+
color: var(--text);
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
.action-btn.danger:hover {
|
| 324 |
+
background: var(--danger);
|
| 325 |
+
color: white;
|
| 326 |
+
border-color: var(--danger);
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
/* Messages Container - Fixed height calculation */
|
| 330 |
+
.messages-container {
|
| 331 |
+
flex: 1;
|
| 332 |
+
overflow-y: auto;
|
| 333 |
+
padding: 24px;
|
| 334 |
+
padding-bottom: calc(var(--composer-height) + 24px);
|
| 335 |
+
background: var(--background);
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
.messages {
|
| 339 |
+
max-width: 800px;
|
| 340 |
+
margin: 0 auto;
|
| 341 |
+
display: flex;
|
| 342 |
+
flex-direction: column;
|
| 343 |
+
gap: 16px;
|
| 344 |
+
width: 100%;
|
| 345 |
+
min-height: 0;
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
.msg {
|
| 349 |
+
display: flex;
|
| 350 |
+
max-width: 80%;
|
| 351 |
+
animation: fadeIn 0.3s ease;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
.msg.user {
|
| 355 |
+
align-self: flex-end;
|
| 356 |
+
flex-direction: row-reverse;
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
.msg.bot {
|
| 360 |
+
align-self: flex-start;
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
.msg-content {
|
| 364 |
+
background: var(--surface);
|
| 365 |
+
padding: 16px 20px;
|
| 366 |
+
border-radius: 18px;
|
| 367 |
+
box-shadow: var(--shadow);
|
| 368 |
+
position: relative;
|
| 369 |
+
border: 1px solid var(--border);
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
.msg.user .msg-content {
|
| 373 |
+
background: var(--primary);
|
| 374 |
+
color: white;
|
| 375 |
+
border-bottom-right-radius: 6px;
|
| 376 |
+
border-color: var(--primary-dark);
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.msg.bot .msg-content {
|
| 380 |
+
border-bottom-left-radius: 6px;
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
.msg-meta {
|
| 384 |
+
font-size: 12px;
|
| 385 |
+
color: var(--text-muted);
|
| 386 |
+
margin-bottom: 8px;
|
| 387 |
+
font-weight: 500;
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
.msg.user .msg-meta {
|
| 391 |
+
color: rgba(255, 255, 255, 0.8);
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.msg-text {
|
| 395 |
+
line-height: 1.5;
|
| 396 |
+
white-space: pre-wrap;
|
| 397 |
+
word-break: break-word;
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
/* Message Composer - Fixed positioning */
|
| 401 |
+
.message-composer {
|
| 402 |
+
position: absolute;
|
| 403 |
+
bottom: 0;
|
| 404 |
+
left: 0;
|
| 405 |
+
right: 0;
|
| 406 |
+
padding: 20px 24px;
|
| 407 |
+
background: var(--surface);
|
| 408 |
+
border-top: 1px solid var(--border);
|
| 409 |
+
height: var(--composer-height);
|
| 410 |
+
display: flex;
|
| 411 |
+
align-items: center;
|
| 412 |
+
z-index: 100;
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
.composer-input {
|
| 416 |
+
max-width: 800px;
|
| 417 |
+
margin: 0 auto;
|
| 418 |
+
display: flex;
|
| 419 |
+
align-items: flex-end;
|
| 420 |
+
gap: 12px;
|
| 421 |
+
background: var(--background);
|
| 422 |
+
border: 2px solid var(--border);
|
| 423 |
+
border-radius: 24px;
|
| 424 |
+
padding: 12px 8px 12px 20px;
|
| 425 |
+
transition: all 0.2s ease;
|
| 426 |
+
width: 100%;
|
| 427 |
+
min-height: 56px;
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
.composer-input:focus-within {
|
| 431 |
+
border-color: var(--primary);
|
| 432 |
+
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.2);
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
.composer-input textarea,
|
| 436 |
+
.composer-input input[type="text"] {
|
| 437 |
+
flex: 1;
|
| 438 |
+
border: none;
|
| 439 |
+
background: transparent;
|
| 440 |
+
font-size: 16px;
|
| 441 |
+
color: var(--text);
|
| 442 |
+
outline: none;
|
| 443 |
+
resize: none;
|
| 444 |
+
min-height: 24px;
|
| 445 |
+
max-height: 120px;
|
| 446 |
+
line-height: 1.5;
|
| 447 |
+
font-family: inherit;
|
| 448 |
+
padding: 8px 0;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
.composer-input textarea::placeholder,
|
| 452 |
+
.composer-input input[type="text"]::placeholder {
|
| 453 |
+
color: var(--text-muted);
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
.composer-input textarea {
|
| 457 |
+
overflow-y: auto;
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
.file-upload-btn {
|
| 461 |
+
width: 40px;
|
| 462 |
+
height: 40px;
|
| 463 |
+
border-radius: 50%;
|
| 464 |
+
background: var(--card);
|
| 465 |
+
display: flex;
|
| 466 |
+
align-items: center;
|
| 467 |
+
justify-content: center;
|
| 468 |
+
cursor: pointer;
|
| 469 |
+
transition: all 0.2s ease;
|
| 470 |
+
font-size: 18px;
|
| 471 |
+
color: var(--text-secondary);
|
| 472 |
+
border: 1px solid var(--border);
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
.file-upload-btn:hover {
|
| 476 |
+
background: var(--primary);
|
| 477 |
+
color: white;
|
| 478 |
+
border-color: var(--primary);
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
.file-upload-btn input {
|
| 482 |
+
display: none;
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
.send-btn {
|
| 486 |
+
width: 40px;
|
| 487 |
+
height: 40px;
|
| 488 |
+
border-radius: 50%;
|
| 489 |
+
background: var(--primary);
|
| 490 |
+
color: white;
|
| 491 |
+
border: none;
|
| 492 |
+
cursor: pointer;
|
| 493 |
+
transition: all 0.2s ease;
|
| 494 |
+
display: flex;
|
| 495 |
+
align-items: center;
|
| 496 |
+
justify-content: center;
|
| 497 |
+
font-size: 18px;
|
| 498 |
+
font-weight: bold;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
.send-btn:hover {
|
| 502 |
+
background: var(--primary-dark);
|
| 503 |
+
transform: scale(1.05);
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
.send-btn:disabled {
|
| 507 |
+
opacity: 0.5;
|
| 508 |
+
cursor: not-allowed;
|
| 509 |
+
transform: none;
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
/* Typing Indicator */
|
| 513 |
+
.typing {
|
| 514 |
+
display: flex;
|
| 515 |
+
align-items: center;
|
| 516 |
+
gap: 8px;
|
| 517 |
+
padding: 16px 20px;
|
| 518 |
+
background: var(--surface);
|
| 519 |
+
border-radius: 18px;
|
| 520 |
+
border-bottom-left-radius: 6px;
|
| 521 |
+
box-shadow: var(--shadow);
|
| 522 |
+
max-width: 80px;
|
| 523 |
+
border: 1px solid var(--border);
|
| 524 |
+
}
|
| 525 |
+
|
| 526 |
+
.typing-dots {
|
| 527 |
+
display: flex;
|
| 528 |
+
gap: 4px;
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
.typing-dot {
|
| 532 |
+
width: 8px;
|
| 533 |
+
height: 8px;
|
| 534 |
+
background: var(--text-muted);
|
| 535 |
+
border-radius: 50%;
|
| 536 |
+
animation: typing 1.5s infinite;
|
| 537 |
+
}
|
| 538 |
+
|
| 539 |
+
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
| 540 |
+
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
| 541 |
+
|
| 542 |
+
/* Drag and Drop */
|
| 543 |
+
.app.dragging::after {
|
| 544 |
+
content: "Drop PDF file here";
|
| 545 |
+
position: absolute;
|
| 546 |
+
inset: 0;
|
| 547 |
+
display: flex;
|
| 548 |
+
align-items: center;
|
| 549 |
+
justify-content: center;
|
| 550 |
+
font-size: 24px;
|
| 551 |
+
font-weight: 600;
|
| 552 |
+
color: var(--primary);
|
| 553 |
+
background: rgba(124, 58, 237, 0.1);
|
| 554 |
+
border: 3px dashed var(--primary);
|
| 555 |
+
z-index: 1000;
|
| 556 |
+
pointer-events: none;
|
| 557 |
+
animation: pulse 2s infinite;
|
| 558 |
+
}
|
| 559 |
+
|
| 560 |
+
/* Animations */
|
| 561 |
+
@keyframes fadeIn {
|
| 562 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 563 |
+
to { opacity: 1; transform: translateY(0); }
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
@keyframes typing {
|
| 567 |
+
0%, 60%, 100% { transform: translateY(0); }
|
| 568 |
+
30% { transform: translateY(-10px); }
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
@keyframes pulse {
|
| 572 |
+
0%, 100% { background: rgba(124, 58, 237, 0.05); }
|
| 573 |
+
50% { background: rgba(124, 58, 237, 0.1); }
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
/* Scrollbar */
|
| 577 |
+
::-webkit-scrollbar {
|
| 578 |
+
width: 6px;
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
::-webkit-scrollbar-track {
|
| 582 |
+
background: transparent;
|
| 583 |
+
}
|
| 584 |
+
|
| 585 |
+
::-webkit-scrollbar-thumb {
|
| 586 |
+
background: var(--border);
|
| 587 |
+
border-radius: 3px;
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
::-webkit-scrollbar-thumb:hover {
|
| 591 |
+
background: var(--text-muted);
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
/* Responsive */
|
| 595 |
+
@media (max-width: 768px) {
|
| 596 |
+
.sidebar {
|
| 597 |
+
width: 280px;
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
.chat-header {
|
| 601 |
+
padding: 16px;
|
| 602 |
+
}
|
| 603 |
+
|
| 604 |
+
.messages-container {
|
| 605 |
+
padding: 16px;
|
| 606 |
+
padding-bottom: calc(var(--composer-height) + 16px);
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
.message-composer {
|
| 610 |
+
padding: 16px;
|
| 611 |
+
}
|
| 612 |
+
}
|
check_professional_data.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Check professional ID and fix the API query
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sqlite3
|
| 7 |
+
|
| 8 |
+
def check_professional_data():
|
| 9 |
+
"""Check professional data and fix the query"""
|
| 10 |
+
|
| 11 |
+
DB_FILE = "aimhsa.db"
|
| 12 |
+
|
| 13 |
+
try:
|
| 14 |
+
conn = sqlite3.connect(DB_FILE)
|
| 15 |
+
|
| 16 |
+
print("="*60)
|
| 17 |
+
print("CHECKING PROFESSIONAL DATA")
|
| 18 |
+
print("="*60)
|
| 19 |
+
|
| 20 |
+
# Check professionals
|
| 21 |
+
professionals = conn.execute("""
|
| 22 |
+
SELECT id, username, first_name, last_name, email
|
| 23 |
+
FROM professionals
|
| 24 |
+
ORDER BY id
|
| 25 |
+
""").fetchall()
|
| 26 |
+
|
| 27 |
+
print(f"Found {len(professionals)} professionals:")
|
| 28 |
+
for p in professionals:
|
| 29 |
+
print(f" ID: {p[0]} | Username: {p[1]} | Name: {p[2]} {p[3]} | Email: {p[4]}")
|
| 30 |
+
|
| 31 |
+
# Check the specific booking
|
| 32 |
+
booking = conn.execute("""
|
| 33 |
+
SELECT booking_id, user_account, professional_id, risk_level, risk_score
|
| 34 |
+
FROM automated_bookings
|
| 35 |
+
WHERE booking_id = 'd63a7794-a89c-452c-80a6-24691e3cb848'
|
| 36 |
+
""").fetchone()
|
| 37 |
+
|
| 38 |
+
if booking:
|
| 39 |
+
print(f"\nBooking found:")
|
| 40 |
+
print(f" Booking ID: {booking[0]}")
|
| 41 |
+
print(f" User Account: {booking[1]}")
|
| 42 |
+
print(f" Professional ID: {booking[2]}")
|
| 43 |
+
print(f" Risk Level: {booking[3]}")
|
| 44 |
+
print(f" Risk Score: {booking[4]}")
|
| 45 |
+
else:
|
| 46 |
+
print("\n❌ Booking not found")
|
| 47 |
+
|
| 48 |
+
# Test the exact query from the API with the correct professional ID
|
| 49 |
+
if booking:
|
| 50 |
+
professional_id = booking[2]
|
| 51 |
+
print(f"\nTesting API query with professional ID: {professional_id}")
|
| 52 |
+
|
| 53 |
+
test_query = conn.execute("""
|
| 54 |
+
SELECT ab.booking_id, ab.conv_id, ab.user_account, ab.user_ip, ab.risk_level, ab.risk_score,
|
| 55 |
+
ab.detected_indicators, ab.conversation_summary, ab.booking_status,
|
| 56 |
+
ab.scheduled_datetime, ab.session_type, ab.created_ts, ab.updated_ts,
|
| 57 |
+
u.fullname, u.email, u.telephone, u.province, u.district, u.created_at
|
| 58 |
+
FROM automated_bookings ab
|
| 59 |
+
LEFT JOIN users u ON ab.user_account = u.username
|
| 60 |
+
WHERE ab.booking_id = ? AND ab.professional_id = ?
|
| 61 |
+
""", ('d63a7794-a89c-452c-80a6-24691e3cb848', professional_id)).fetchone()
|
| 62 |
+
|
| 63 |
+
if test_query:
|
| 64 |
+
print("✅ API query successful!")
|
| 65 |
+
print(f" Booking ID: {test_query[0]}")
|
| 66 |
+
print(f" User Account: {test_query[2]}")
|
| 67 |
+
print(f" Full Name: {test_query[13]}")
|
| 68 |
+
print(f" Email: {test_query[14]}")
|
| 69 |
+
print(f" Phone: {test_query[15]}")
|
| 70 |
+
print(f" Province: {test_query[16]}")
|
| 71 |
+
print(f" District: {test_query[17]}")
|
| 72 |
+
print(f" Created At: {test_query[18]}")
|
| 73 |
+
else:
|
| 74 |
+
print("❌ API query failed - no results")
|
| 75 |
+
|
| 76 |
+
conn.close()
|
| 77 |
+
|
| 78 |
+
except Exception as e:
|
| 79 |
+
print(f"❌ Database Error: {e}")
|
| 80 |
+
|
| 81 |
+
if __name__ == "__main__":
|
| 82 |
+
check_professional_data()
|
| 83 |
+
|
config.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
AIMHSA Configuration Management
|
| 3 |
+
Handles environment-specific configuration for hosting
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
|
| 9 |
+
# Load environment variables
|
| 10 |
+
load_dotenv()
|
| 11 |
+
|
| 12 |
+
class Config:
|
| 13 |
+
"""Base configuration class"""
|
| 14 |
+
|
| 15 |
+
# Server Configuration
|
| 16 |
+
HOST = os.getenv('SERVER_HOST', '0.0.0.0')
|
| 17 |
+
PORT = int(os.getenv('SERVER_PORT', '5057'))
|
| 18 |
+
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
|
| 19 |
+
|
| 20 |
+
# API Configuration
|
| 21 |
+
API_BASE_URL = os.getenv('API_BASE_URL', '') # Empty means relative URLs
|
| 22 |
+
FRONTEND_URL = os.getenv('FRONTEND_URL', '') # Empty means same origin
|
| 23 |
+
|
| 24 |
+
# Database Configuration
|
| 25 |
+
DB_FILE = os.getenv('DB_FILE', 'storage/conversations.db')
|
| 26 |
+
|
| 27 |
+
# Ollama Configuration
|
| 28 |
+
OLLAMA_BASE_URL = os.getenv('OLLAMA_BASE_URL', 'http://localhost:11434/v1')
|
| 29 |
+
OLLAMA_API_KEY = os.getenv('OLLAMA_API_KEY', 'ollama')
|
| 30 |
+
|
| 31 |
+
# AI Models
|
| 32 |
+
CHAT_MODEL = os.getenv('CHAT_MODEL', 'llama3.2:3b')
|
| 33 |
+
EMBED_MODEL = os.getenv('EMBED_MODEL', 'nomic-embed-text')
|
| 34 |
+
SENT_EMBED_MODEL = os.getenv('SENT_EMBED_MODEL', 'nomic-embed-text')
|
| 35 |
+
|
| 36 |
+
# Email Configuration
|
| 37 |
+
SMTP_SERVER = os.getenv('SMTP_SERVER', 'smtp.gmail.com')
|
| 38 |
+
SMTP_PORT = int(os.getenv('SMTP_PORT', '587'))
|
| 39 |
+
SMTP_USERNAME = os.getenv('SMTP_USERNAME', '')
|
| 40 |
+
SMTP_PASSWORD = os.getenv('SMTP_PASSWORD', '')
|
| 41 |
+
FROM_EMAIL = os.getenv('FROM_EMAIL', 'noreply@aimhsa.rw')
|
| 42 |
+
|
| 43 |
+
# SMS Configuration
|
| 44 |
+
HDEV_SMS_API_ID = os.getenv('HDEV_SMS_API_ID', '')
|
| 45 |
+
HDEV_SMS_API_KEY = os.getenv('HDEV_SMS_API_KEY', '')
|
| 46 |
+
|
| 47 |
+
# Storage Configuration
|
| 48 |
+
STORAGE_DIR = os.getenv('STORAGE_DIR', 'storage')
|
| 49 |
+
DATA_DIR = os.getenv('DATA_DIR', 'data')
|
| 50 |
+
EMBED_FILE = os.path.join(STORAGE_DIR, 'embeddings.json')
|
| 51 |
+
|
| 52 |
+
class DevelopmentConfig(Config):
|
| 53 |
+
"""Development environment configuration"""
|
| 54 |
+
DEBUG = True
|
| 55 |
+
HOST = '0.0.0.0'
|
| 56 |
+
PORT = 5057
|
| 57 |
+
|
| 58 |
+
class ProductionConfig(Config):
|
| 59 |
+
"""Production environment configuration"""
|
| 60 |
+
DEBUG = False
|
| 61 |
+
HOST = '0.0.0.0'
|
| 62 |
+
PORT = int(os.getenv('PORT', '8000')) # Common for hosting services
|
| 63 |
+
|
| 64 |
+
class TestingConfig(Config):
|
| 65 |
+
"""Testing environment configuration"""
|
| 66 |
+
DEBUG = True
|
| 67 |
+
HOST = '127.0.0.1'
|
| 68 |
+
PORT = 5058
|
| 69 |
+
DB_FILE = 'test_conversations.db'
|
| 70 |
+
|
| 71 |
+
# Configuration mapping
|
| 72 |
+
config_map = {
|
| 73 |
+
'development': DevelopmentConfig,
|
| 74 |
+
'production': ProductionConfig,
|
| 75 |
+
'testing': TestingConfig
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
def get_config():
|
| 79 |
+
"""Get configuration based on environment"""
|
| 80 |
+
env = os.getenv('FLASK_ENV', 'development')
|
| 81 |
+
return config_map.get(env, DevelopmentConfig)
|
| 82 |
+
|
| 83 |
+
# Export current config
|
| 84 |
+
current_config = get_config()
|
data/mental-health-overview.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Mental health refers to emotional, psychological, and social well-being. In Rwanda, depression, anxiety, and trauma-related disorders are common, especially after the 1994 genocide against the Tutsi.
|
| 2 |
+
|
| 3 |
+
Common symptoms:
|
| 4 |
+
- Persistent sadness or hopelessness
|
| 5 |
+
- Difficulty sleeping or concentrating
|
| 6 |
+
- Feeling anxious, panicked, or restless
|
| 7 |
+
- Loss of interest in daily activities
|
| 8 |
+
|
| 9 |
+
Healthy coping strategies:
|
| 10 |
+
1. Talk to someone you trust — friend, family, or counselor.
|
| 11 |
+
2. Practice relaxation techniques: deep breathing, meditation, prayer.
|
| 12 |
+
3. Stay physically active: exercise improves mood.
|
| 13 |
+
4. Avoid alcohol or drug abuse.
|
| 14 |
+
5. Seek professional help when needed.
|
data/ptsd-trauma-guide.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Post-Traumatic Stress Disorder (PTSD) is common in Rwanda due to the 1994 genocide, domestic violence, and conflict-related trauma.
|
| 2 |
+
|
| 3 |
+
Symptoms:
|
| 4 |
+
- Flashbacks or nightmares
|
| 5 |
+
- Emotional numbness
|
| 6 |
+
- Sudden anger or irritability
|
| 7 |
+
- Fear of crowds or loud sounds
|
| 8 |
+
|
| 9 |
+
Healing strategies:
|
| 10 |
+
- Trauma counseling (available at CARAES Ndera & ARCT Ruhuka)
|
| 11 |
+
- Group therapy for survivors
|
| 12 |
+
- Writing therapy: journaling personal experiences
|
| 13 |
+
- Breathing exercises for grounding
|
| 14 |
+
- Social support networks in local communities
|
data/rwanda-helplines.txt
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
🇷🇼 Rwanda Emergency Mental Health Contacts:
|
| 2 |
+
|
| 3 |
+
- CARAES Ndera Hospital: +250 788 305 703
|
| 4 |
+
- Mental Health Hotline: 105
|
| 5 |
+
- HDI Rwanda Counseling: +250 782 290 352
|
| 6 |
+
- ARCT Ruhuka Trauma Counseling: +250 788 304 454
|
| 7 |
+
- Youth Helpline: 116
|
| 8 |
+
|
| 9 |
+
Mental Health Facilities in Rwanda — Full District Coverage
|
| 10 |
+
|
| 11 |
+
1. CARAES Ndera Neuropsychiatric Teaching Hospital located in Gasabo District, Kigali City
|
| 12 |
+
2. CARAES Butare (Ndera branch) located in – Huye District, Southern Province
|
| 13 |
+
3. Icyizere Psychotherapeutic Center (Ndera branch)located in – Kicukiro District, Kigali City
|
| 14 |
+
4. Kigali Mental Health Referral Centre located in – Gasabo District, Kigali City
|
| 15 |
+
5. Service de Consultation Psycho-Sociale (SCPS), CHUK located in – Nyarugenge District, Kigali City
|
| 16 |
+
6. Mental health departments at referral hospitals:
|
| 17 |
+
- University Teaching Hospital of Kigali (CHUK) located in – Nyarugenge District, Kigali City
|
| 18 |
+
- University Teaching Hospital of Butare (CHUB) located in – Huye District, Southern Province
|
| 19 |
+
- Rwanda Military Hospital located in – Kicukiro District, Kigali City
|
| 20 |
+
- King Faisal Hospital located in – Gasabo District, Kigali City
|
| 21 |
+
7. Health facilities in all **district hospitals**—mental health departments present across every district:contentReference[oaicite:0]{index=0}
|
| 22 |
+
|
| 23 |
+
Major District-Level Facilities by Province:
|
| 24 |
+
**Northern Province**:
|
| 25 |
+
- Ruhengeri Hospital located in – Musanze District
|
| 26 |
+
- Butaro Hospital located in – Burera District (Butaro Sector):contentReference[oaicite:1]{index=1}
|
| 27 |
+
|
| 28 |
+
**Eastern Province**:
|
| 29 |
+
- Kibungo Referral Hospital located in – Ngoma District
|
| 30 |
+
- Kiziguro District Hospital located in – Gatsibo District
|
| 31 |
+
- Rwinkwavu District Hospital located in – Kayonza District:contentReference[oaicite:2]{index=2}
|
| 32 |
+
|
| 33 |
+
**Western Province**:
|
| 34 |
+
- Gisenyi District Hospital located in – Rubavu District
|
| 35 |
+
- Kibuye Referral Hospital located in – Karongi District:contentReference[oaicite:3]{index=3}
|
| 36 |
+
|
| 37 |
+
**Southern Province**:
|
| 38 |
+
- Kabutare Hospital located in– Huye District
|
| 39 |
+
- Kabgayi Hospital located in– Muhanga District
|
| 40 |
+
- Byumba Hospital located in– Gicumbi District
|
| 41 |
+
- Nyanza Hospital located in– Nyanza District
|
| 42 |
+
- Nyamata Hospital located in– Bugesera District
|
| 43 |
+
- Others (Kigeme, Gitwe, etc.) offer mental health services:contentReference[oaicite:4]{index=4}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
In case of immediate danger or suicidal thoughts:
|
| 47 |
+
Call 112 for the Rwanda National Police.
|
| 48 |
+
|
data/rwanda-mental-health-policy.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Rwanda's National Mental Health Policy (2022) aims to integrate mental health care into all levels of the health system.
|
| 2 |
+
|
| 3 |
+
Key objectives:
|
| 4 |
+
- Strengthen mental health services at district hospitals and health centers.
|
| 5 |
+
- Train community health workers to detect and refer mental health cases.
|
| 6 |
+
- Increase awareness campaigns in schools, workplaces, and communities.
|
| 7 |
+
- Reduce stigma associated with seeking help.
|
| 8 |
+
- Collaborate with NGOs like CARAES Ndera Hospital, HDI Rwanda, and ARCT Ruhuka.
|
| 9 |
+
|
| 10 |
+
Government initiatives include:
|
| 11 |
+
- Free counseling services in public hospitals.
|
| 12 |
+
- Helplines for trauma survivors.
|
| 13 |
+
- Mental health education in schools.
|
data/rwanda_mental_health_hospitals.txt
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Mental Health Facilities in Rwanda — Full District Coverage
|
| 2 |
+
|
| 3 |
+
1. CARAES Ndera Neuropsychiatric Teaching Hospital located in Gasabo District, Kigali City
|
| 4 |
+
2. CARAES Butare (Ndera branch) located in – Huye District, Southern Province
|
| 5 |
+
3. Icyizere Psychotherapeutic Center (Ndera branch)located in – Kicukiro District, Kigali City
|
| 6 |
+
4. Kigali Mental Health Referral Centre located in – Gasabo District, Kigali City
|
| 7 |
+
5. Service de Consultation Psycho-Sociale (SCPS), CHUK located in – Nyarugenge District, Kigali City
|
| 8 |
+
6. Mental health departments at referral hospitals:
|
| 9 |
+
- University Teaching Hospital of Kigali (CHUK) located in – Nyarugenge District, Kigali City
|
| 10 |
+
- University Teaching Hospital of Butare (CHUB) located in – Huye District, Southern Province
|
| 11 |
+
- Rwanda Military Hospital located in – Kicukiro District, Kigali City
|
| 12 |
+
- King Faisal Hospital located in – Gasabo District, Kigali City
|
| 13 |
+
7. Health facilities in all **district hospitals**—mental health departments present across every district:contentReference[oaicite:0]{index=0}
|
| 14 |
+
|
| 15 |
+
Major District-Level Facilities by Province:
|
| 16 |
+
**Northern Province**:
|
| 17 |
+
- Ruhengeri Hospital located in – Musanze District
|
| 18 |
+
- Butaro Hospital located in – Burera District (Butaro Sector):contentReference[oaicite:1]{index=1}
|
| 19 |
+
|
| 20 |
+
**Eastern Province**:
|
| 21 |
+
- Kibungo Referral Hospital located in – Ngoma District
|
| 22 |
+
- Kiziguro District Hospital located in – Gatsibo District
|
| 23 |
+
- Rwinkwavu District Hospital located in – Kayonza District:contentReference[oaicite:2]{index=2}
|
| 24 |
+
|
| 25 |
+
**Western Province**:
|
| 26 |
+
- Gisenyi District Hospital located in – Rubavu District
|
| 27 |
+
- Kibuye Referral Hospital located in – Karongi District:contentReference[oaicite:3]{index=3}
|
| 28 |
+
|
| 29 |
+
**Southern Province**:
|
| 30 |
+
- Kabutare Hospital located in– Huye District
|
| 31 |
+
- Kabgayi Hospital located in– Muhanga District
|
| 32 |
+
- Byumba Hospital located in– Gicumbi District
|
| 33 |
+
- Nyanza Hospital located in– Nyanza District
|
| 34 |
+
- Nyamata Hospital located in– Bugesera District
|
| 35 |
+
- Others (Kigeme, Gitwe, etc.) offer mental health services:contentReference[oaicite:4]{index=4}
|
data/self-help-coping.txt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Effective self-help strategies for managing stress and anxiety:
|
| 2 |
+
|
| 3 |
+
1. Deep Breathing Exercise:
|
| 4 |
+
- Inhale slowly for 4 seconds.
|
| 5 |
+
- Hold for 4 seconds.
|
| 6 |
+
- Exhale for 6 seconds.
|
| 7 |
+
- Repeat 5–10 times.
|
| 8 |
+
|
| 9 |
+
2. Journaling:
|
| 10 |
+
Write your thoughts and emotions daily to release stress.
|
| 11 |
+
|
| 12 |
+
3. Grounding Exercise:
|
| 13 |
+
- Name 5 things you see.
|
| 14 |
+
- Name 4 things you can touch.
|
| 15 |
+
- Name 3 things you hear.
|
| 16 |
+
- Name 2 things you smell.
|
| 17 |
+
- Name 1 thing you taste.
|
| 18 |
+
|
| 19 |
+
4. Limit social media and avoid negative news cycles.
|
| 20 |
+
5. Prioritize sleep and a balanced diet.
|
data/youth-mental-health.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
In Rwanda, 1 in 4 young people experience depression or anxiety symptoms. Social pressures, unemployment, and academic stress are major causes.
|
| 2 |
+
|
| 3 |
+
Tips for youth mental well-being:
|
| 4 |
+
- Talk to mentors, teachers, or counselors.
|
| 5 |
+
- Join youth support groups.
|
| 6 |
+
- Avoid isolation — spend time with friends and family.
|
| 7 |
+
- Learn stress management techniques like mindfulness.
|
| 8 |
+
- Know where to seek professional help.
|
| 9 |
+
|
| 10 |
+
Organizations supporting youth:
|
| 11 |
+
- Imbuto Foundation Youth Wellness Program
|
| 12 |
+
- Youth Helpline: 116
|
| 13 |
+
- Ndera Hospital Youth Counseling Unit
|
email_config.env
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Email Configuration for AI Mental Health Chatbot
|
| 2 |
+
# Copy this file to .env and update with your actual email credentials
|
| 3 |
+
|
| 4 |
+
# SMTP Server Configuration
|
| 5 |
+
SMTP_SERVER=smtp.gmail.com
|
| 6 |
+
SMTP_PORT=587
|
| 7 |
+
SMTP_USERNAME=it.elias38@gmail.com
|
| 8 |
+
SMTP_PASSWORD=your-app-password-here
|
| 9 |
+
FROM_EMAIL=noreply@aimhsa.rw
|
| 10 |
+
|
| 11 |
+
# Security Note: Never commit this file with real credentials to version control
|
ingest.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, uuid, json
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
# Replace ollama import with OpenAI client
|
| 4 |
+
from openai import OpenAI
|
| 5 |
+
from pypdf import PdfReader
|
| 6 |
+
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
|
| 9 |
+
load_dotenv()
|
| 10 |
+
|
| 11 |
+
DATA_DIR = Path("data")
|
| 12 |
+
EMBED_FILE = Path("storage/embeddings.json")
|
| 13 |
+
EMBED_MODEL = os.getenv("EMBED_MODEL", "nomic-embed-text")
|
| 14 |
+
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://localhost:11434/v1")
|
| 15 |
+
OLLAMA_API_KEY = os.getenv("OLLAMA_API_KEY", "ollama")
|
| 16 |
+
|
| 17 |
+
# Initialize OpenAI client for Ollama
|
| 18 |
+
openai_client = OpenAI(
|
| 19 |
+
base_url=OLLAMA_BASE_URL,
|
| 20 |
+
api_key=OLLAMA_API_KEY
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
# --- Load or initialize embeddings ---
|
| 24 |
+
if EMBED_FILE.exists():
|
| 25 |
+
with open(EMBED_FILE, "r", encoding="utf-8") as f:
|
| 26 |
+
chunks_data = json.load(f)
|
| 27 |
+
else:
|
| 28 |
+
chunks_data = []
|
| 29 |
+
|
| 30 |
+
# --- Helper functions ---
|
| 31 |
+
def load_text_from_file(path: Path) -> str:
|
| 32 |
+
if path.suffix.lower() in [".txt", ".md"]:
|
| 33 |
+
return path.read_text(encoding="utf-8", errors="ignore")
|
| 34 |
+
if path.suffix.lower() == ".pdf":
|
| 35 |
+
pdf = PdfReader(str(path))
|
| 36 |
+
return "\n".join((page.extract_text() or "") for page in pdf.pages)
|
| 37 |
+
return ""
|
| 38 |
+
|
| 39 |
+
def chunk_text(text: str):
|
| 40 |
+
splitter = RecursiveCharacterTextSplitter(
|
| 41 |
+
chunk_size=900, chunk_overlap=150,
|
| 42 |
+
separators=["\n\n", "\n", " ", ""]
|
| 43 |
+
)
|
| 44 |
+
return splitter.split_text(text)
|
| 45 |
+
|
| 46 |
+
# --- Track existing sources ---
|
| 47 |
+
existing_files = {c["source"] for c in chunks_data}
|
| 48 |
+
|
| 49 |
+
new_chunks = []
|
| 50 |
+
for fp in DATA_DIR.glob("**/*"):
|
| 51 |
+
if fp.suffix.lower() not in [".pdf", ".txt", ".md"]:
|
| 52 |
+
continue
|
| 53 |
+
if fp.name in existing_files:
|
| 54 |
+
continue # skip already processed files
|
| 55 |
+
|
| 56 |
+
raw = load_text_from_file(fp)
|
| 57 |
+
if not raw.strip():
|
| 58 |
+
continue
|
| 59 |
+
|
| 60 |
+
for idx, piece in enumerate(chunk_text(raw)):
|
| 61 |
+
new_chunks.append({
|
| 62 |
+
"id": str(uuid.uuid4()),
|
| 63 |
+
"text": piece,
|
| 64 |
+
"source": fp.name,
|
| 65 |
+
"chunk": idx,
|
| 66 |
+
"embedding": None # to fill below
|
| 67 |
+
})
|
| 68 |
+
|
| 69 |
+
# --- Generate embeddings with OpenAI client ---
|
| 70 |
+
if new_chunks:
|
| 71 |
+
texts = [c["text"] for c in new_chunks]
|
| 72 |
+
|
| 73 |
+
# Generate embeddings using OpenAI client
|
| 74 |
+
embeddings = []
|
| 75 |
+
batch_size = 32 # Process in batches for better performance
|
| 76 |
+
|
| 77 |
+
for i in range(0, len(texts), batch_size):
|
| 78 |
+
batch = texts[i:i + batch_size]
|
| 79 |
+
try:
|
| 80 |
+
# OpenAI client supports batch processing
|
| 81 |
+
response = openai_client.embeddings.create(
|
| 82 |
+
model=EMBED_MODEL,
|
| 83 |
+
input=batch
|
| 84 |
+
)
|
| 85 |
+
batch_embeddings = [item.embedding for item in response.data]
|
| 86 |
+
embeddings.extend(batch_embeddings)
|
| 87 |
+
print(f"Processed batch {i//batch_size + 1}/{(len(texts) + batch_size - 1)//batch_size}")
|
| 88 |
+
except Exception as e:
|
| 89 |
+
print(f"Error embedding batch: {e}")
|
| 90 |
+
# Fallback: process individually
|
| 91 |
+
for text in batch:
|
| 92 |
+
try:
|
| 93 |
+
response = openai_client.embeddings.create(
|
| 94 |
+
model=EMBED_MODEL,
|
| 95 |
+
input=text
|
| 96 |
+
)
|
| 97 |
+
embeddings.append(response.data[0].embedding)
|
| 98 |
+
except Exception as e2:
|
| 99 |
+
print(f"Error embedding individual text: {e2}")
|
| 100 |
+
embeddings.append([0.0] * 384) # fallback with correct dimension
|
| 101 |
+
|
| 102 |
+
for c, e in zip(new_chunks, embeddings):
|
| 103 |
+
c["embedding"] = e
|
| 104 |
+
|
| 105 |
+
chunks_data.extend(new_chunks)
|
| 106 |
+
|
| 107 |
+
# Save updated embeddings
|
| 108 |
+
EMBED_FILE.parent.mkdir(parents=True, exist_ok=True)
|
| 109 |
+
with open(EMBED_FILE, "w", encoding="utf-8") as f:
|
| 110 |
+
json.dump(chunks_data, f, ensure_ascii=False, indent=2)
|
| 111 |
+
|
| 112 |
+
print(f"Added {len(new_chunks)} new chunks to {EMBED_FILE}")
|
| 113 |
+
else:
|
| 114 |
+
print("No new documents found.")
|
init_database.py
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Initialize the database with all required tables
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import sqlite3
|
| 7 |
+
import os
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
# Database file path
|
| 11 |
+
DB_FILE = "aimhsa.db"
|
| 12 |
+
|
| 13 |
+
def init_database():
|
| 14 |
+
"""Initialize the database with all required tables"""
|
| 15 |
+
|
| 16 |
+
print("="*60)
|
| 17 |
+
print("INITIALIZING DATABASE")
|
| 18 |
+
print("="*60)
|
| 19 |
+
|
| 20 |
+
# Create directory if it doesn't exist (only if there's a directory)
|
| 21 |
+
db_dir = os.path.dirname(DB_FILE)
|
| 22 |
+
if db_dir:
|
| 23 |
+
os.makedirs(db_dir, exist_ok=True)
|
| 24 |
+
|
| 25 |
+
conn = sqlite3.connect(DB_FILE)
|
| 26 |
+
|
| 27 |
+
try:
|
| 28 |
+
print("Creating tables...")
|
| 29 |
+
|
| 30 |
+
# Messages table
|
| 31 |
+
conn.execute("""
|
| 32 |
+
CREATE TABLE IF NOT EXISTS messages (
|
| 33 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 34 |
+
conv_id TEXT NOT NULL,
|
| 35 |
+
role TEXT NOT NULL,
|
| 36 |
+
content TEXT NOT NULL,
|
| 37 |
+
ts REAL NOT NULL
|
| 38 |
+
)
|
| 39 |
+
""")
|
| 40 |
+
print("✅ messages table created")
|
| 41 |
+
|
| 42 |
+
# Attachments table
|
| 43 |
+
conn.execute("""
|
| 44 |
+
CREATE TABLE IF NOT EXISTS attachments (
|
| 45 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 46 |
+
conv_id TEXT NOT NULL,
|
| 47 |
+
filename TEXT NOT NULL,
|
| 48 |
+
text TEXT NOT NULL,
|
| 49 |
+
ts REAL NOT NULL
|
| 50 |
+
)
|
| 51 |
+
""")
|
| 52 |
+
print("✅ attachments table created")
|
| 53 |
+
|
| 54 |
+
# Sessions table
|
| 55 |
+
conn.execute("""
|
| 56 |
+
CREATE TABLE IF NOT EXISTS sessions (
|
| 57 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 58 |
+
key TEXT UNIQUE NOT NULL,
|
| 59 |
+
conv_id TEXT NOT NULL,
|
| 60 |
+
ts REAL NOT NULL
|
| 61 |
+
)
|
| 62 |
+
""")
|
| 63 |
+
print("✅ sessions table created")
|
| 64 |
+
|
| 65 |
+
# Users table
|
| 66 |
+
conn.execute("""
|
| 67 |
+
CREATE TABLE IF NOT EXISTS users (
|
| 68 |
+
username TEXT PRIMARY KEY,
|
| 69 |
+
password_hash TEXT NOT NULL,
|
| 70 |
+
created_ts REAL NOT NULL
|
| 71 |
+
)
|
| 72 |
+
""")
|
| 73 |
+
print("✅ users table created")
|
| 74 |
+
|
| 75 |
+
# Add additional columns to users table
|
| 76 |
+
cursor = conn.execute("PRAGMA table_info(users)")
|
| 77 |
+
columns = [column[1] for column in cursor.fetchall()]
|
| 78 |
+
|
| 79 |
+
if "email" not in columns:
|
| 80 |
+
conn.execute("ALTER TABLE users ADD COLUMN email TEXT")
|
| 81 |
+
if "fullname" not in columns:
|
| 82 |
+
conn.execute("ALTER TABLE users ADD COLUMN fullname TEXT")
|
| 83 |
+
if "telephone" not in columns:
|
| 84 |
+
conn.execute("ALTER TABLE users ADD COLUMN telephone TEXT")
|
| 85 |
+
if "province" not in columns:
|
| 86 |
+
conn.execute("ALTER TABLE users ADD COLUMN province TEXT")
|
| 87 |
+
if "district" not in columns:
|
| 88 |
+
conn.execute("ALTER TABLE users ADD COLUMN district TEXT")
|
| 89 |
+
if "created_at" not in columns:
|
| 90 |
+
conn.execute("ALTER TABLE users ADD COLUMN created_at REAL")
|
| 91 |
+
|
| 92 |
+
print("✅ users table columns updated")
|
| 93 |
+
|
| 94 |
+
# Password resets table
|
| 95 |
+
conn.execute("""
|
| 96 |
+
CREATE TABLE IF NOT EXISTS password_resets (
|
| 97 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 98 |
+
username TEXT NOT NULL,
|
| 99 |
+
token TEXT UNIQUE NOT NULL,
|
| 100 |
+
expires_ts REAL NOT NULL,
|
| 101 |
+
used INTEGER DEFAULT 0
|
| 102 |
+
)
|
| 103 |
+
""")
|
| 104 |
+
print("✅ password_resets table created")
|
| 105 |
+
|
| 106 |
+
# Conversations table
|
| 107 |
+
conn.execute("""
|
| 108 |
+
CREATE TABLE IF NOT EXISTS conversations (
|
| 109 |
+
conv_id TEXT PRIMARY KEY,
|
| 110 |
+
owner_key TEXT,
|
| 111 |
+
preview TEXT,
|
| 112 |
+
ts REAL
|
| 113 |
+
)
|
| 114 |
+
""")
|
| 115 |
+
print("✅ conversations table created")
|
| 116 |
+
|
| 117 |
+
# Add additional columns to conversations table
|
| 118 |
+
try:
|
| 119 |
+
cur = conn.execute("PRAGMA table_info(conversations)")
|
| 120 |
+
cols = [r[1] for r in cur.fetchall()]
|
| 121 |
+
if "archive_pw_hash" not in cols:
|
| 122 |
+
conn.execute("ALTER TABLE conversations ADD COLUMN archive_pw_hash TEXT")
|
| 123 |
+
if "booking_prompt_shown" not in cols:
|
| 124 |
+
conn.execute("ALTER TABLE conversations ADD COLUMN booking_prompt_shown INTEGER DEFAULT 0")
|
| 125 |
+
except Exception:
|
| 126 |
+
pass
|
| 127 |
+
|
| 128 |
+
print("✅ conversations table columns updated")
|
| 129 |
+
|
| 130 |
+
# Professionals table
|
| 131 |
+
conn.execute("""
|
| 132 |
+
CREATE TABLE IF NOT EXISTS professionals (
|
| 133 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 134 |
+
username TEXT UNIQUE NOT NULL,
|
| 135 |
+
password_hash TEXT NOT NULL,
|
| 136 |
+
first_name TEXT NOT NULL,
|
| 137 |
+
last_name TEXT NOT NULL,
|
| 138 |
+
email TEXT NOT NULL,
|
| 139 |
+
phone TEXT,
|
| 140 |
+
license_number TEXT,
|
| 141 |
+
specialization TEXT NOT NULL,
|
| 142 |
+
expertise_areas TEXT NOT NULL,
|
| 143 |
+
languages TEXT NOT NULL,
|
| 144 |
+
qualifications TEXT NOT NULL,
|
| 145 |
+
availability_schedule TEXT NOT NULL,
|
| 146 |
+
location_latitude REAL,
|
| 147 |
+
location_longitude REAL,
|
| 148 |
+
location_address TEXT,
|
| 149 |
+
district TEXT,
|
| 150 |
+
max_patients_per_day INTEGER DEFAULT 10,
|
| 151 |
+
consultation_fee REAL,
|
| 152 |
+
experience_years INTEGER,
|
| 153 |
+
bio TEXT,
|
| 154 |
+
profile_picture TEXT,
|
| 155 |
+
is_active BOOLEAN DEFAULT 1,
|
| 156 |
+
created_ts REAL NOT NULL,
|
| 157 |
+
updated_ts REAL NOT NULL
|
| 158 |
+
)
|
| 159 |
+
""")
|
| 160 |
+
print("✅ professionals table created")
|
| 161 |
+
|
| 162 |
+
# Risk assessments table
|
| 163 |
+
conn.execute("""
|
| 164 |
+
CREATE TABLE IF NOT EXISTS risk_assessments (
|
| 165 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 166 |
+
conv_id TEXT NOT NULL,
|
| 167 |
+
user_query TEXT NOT NULL,
|
| 168 |
+
risk_score REAL NOT NULL,
|
| 169 |
+
risk_level TEXT NOT NULL,
|
| 170 |
+
detected_indicators TEXT,
|
| 171 |
+
assessment_timestamp REAL NOT NULL,
|
| 172 |
+
processed BOOLEAN DEFAULT 0,
|
| 173 |
+
booking_created BOOLEAN DEFAULT 0
|
| 174 |
+
)
|
| 175 |
+
""")
|
| 176 |
+
print("✅ risk_assessments table created")
|
| 177 |
+
|
| 178 |
+
# Automated bookings table
|
| 179 |
+
conn.execute("""
|
| 180 |
+
CREATE TABLE IF NOT EXISTS automated_bookings (
|
| 181 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 182 |
+
booking_id TEXT UNIQUE NOT NULL,
|
| 183 |
+
conv_id TEXT NOT NULL,
|
| 184 |
+
user_account TEXT,
|
| 185 |
+
user_ip TEXT,
|
| 186 |
+
professional_id INTEGER NOT NULL,
|
| 187 |
+
risk_level TEXT NOT NULL,
|
| 188 |
+
risk_score REAL NOT NULL,
|
| 189 |
+
detected_indicators TEXT,
|
| 190 |
+
conversation_summary TEXT,
|
| 191 |
+
booking_status TEXT DEFAULT 'pending',
|
| 192 |
+
scheduled_datetime REAL,
|
| 193 |
+
session_type TEXT DEFAULT 'routine',
|
| 194 |
+
location_preference TEXT,
|
| 195 |
+
notes TEXT,
|
| 196 |
+
created_ts REAL NOT NULL,
|
| 197 |
+
updated_ts REAL NOT NULL,
|
| 198 |
+
FOREIGN KEY (professional_id) REFERENCES professionals (id)
|
| 199 |
+
)
|
| 200 |
+
""")
|
| 201 |
+
print("✅ automated_bookings table created")
|
| 202 |
+
|
| 203 |
+
# Professional notifications table
|
| 204 |
+
conn.execute("""
|
| 205 |
+
CREATE TABLE IF NOT EXISTS professional_notifications (
|
| 206 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 207 |
+
professional_id INTEGER NOT NULL,
|
| 208 |
+
booking_id TEXT NOT NULL,
|
| 209 |
+
notification_type TEXT NOT NULL,
|
| 210 |
+
title TEXT NOT NULL,
|
| 211 |
+
message TEXT NOT NULL,
|
| 212 |
+
is_read BOOLEAN DEFAULT 0,
|
| 213 |
+
priority TEXT DEFAULT 'normal',
|
| 214 |
+
created_ts REAL NOT NULL,
|
| 215 |
+
FOREIGN KEY (professional_id) REFERENCES professionals (id)
|
| 216 |
+
)
|
| 217 |
+
""")
|
| 218 |
+
print("✅ professional_notifications table created")
|
| 219 |
+
|
| 220 |
+
# Therapy sessions table
|
| 221 |
+
conn.execute("""
|
| 222 |
+
CREATE TABLE IF NOT EXISTS therapy_sessions (
|
| 223 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 224 |
+
booking_id TEXT NOT NULL,
|
| 225 |
+
professional_id INTEGER NOT NULL,
|
| 226 |
+
conv_id TEXT NOT NULL,
|
| 227 |
+
session_start REAL,
|
| 228 |
+
session_end REAL,
|
| 229 |
+
session_notes TEXT,
|
| 230 |
+
treatment_plan TEXT,
|
| 231 |
+
follow_up_required BOOLEAN DEFAULT 0,
|
| 232 |
+
follow_up_date REAL,
|
| 233 |
+
session_rating INTEGER,
|
| 234 |
+
session_feedback TEXT,
|
| 235 |
+
created_ts REAL NOT NULL,
|
| 236 |
+
FOREIGN KEY (professional_id) REFERENCES professionals (id)
|
| 237 |
+
)
|
| 238 |
+
""")
|
| 239 |
+
print("✅ therapy_sessions table created")
|
| 240 |
+
|
| 241 |
+
# Session notes table
|
| 242 |
+
conn.execute("""
|
| 243 |
+
CREATE TABLE IF NOT EXISTS session_notes (
|
| 244 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 245 |
+
booking_id TEXT NOT NULL,
|
| 246 |
+
professional_id INTEGER NOT NULL,
|
| 247 |
+
notes TEXT,
|
| 248 |
+
treatment_plan TEXT,
|
| 249 |
+
follow_up_required BOOLEAN DEFAULT 0,
|
| 250 |
+
follow_up_date REAL,
|
| 251 |
+
created_ts REAL NOT NULL,
|
| 252 |
+
FOREIGN KEY (professional_id) REFERENCES professionals (id)
|
| 253 |
+
)
|
| 254 |
+
""")
|
| 255 |
+
print("✅ session_notes table created")
|
| 256 |
+
|
| 257 |
+
# Conversation messages table
|
| 258 |
+
conn.execute("""
|
| 259 |
+
CREATE TABLE IF NOT EXISTS conversation_messages (
|
| 260 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 261 |
+
conv_id TEXT NOT NULL,
|
| 262 |
+
sender TEXT NOT NULL,
|
| 263 |
+
content TEXT NOT NULL,
|
| 264 |
+
timestamp REAL NOT NULL
|
| 265 |
+
)
|
| 266 |
+
""")
|
| 267 |
+
print("✅ conversation_messages table created")
|
| 268 |
+
|
| 269 |
+
# Admin users table
|
| 270 |
+
conn.execute("""
|
| 271 |
+
CREATE TABLE IF NOT EXISTS admin_users (
|
| 272 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 273 |
+
username TEXT UNIQUE NOT NULL,
|
| 274 |
+
password_hash TEXT NOT NULL,
|
| 275 |
+
email TEXT NOT NULL,
|
| 276 |
+
role TEXT DEFAULT 'admin',
|
| 277 |
+
permissions TEXT,
|
| 278 |
+
created_ts REAL NOT NULL
|
| 279 |
+
)
|
| 280 |
+
""")
|
| 281 |
+
print("✅ admin_users table created")
|
| 282 |
+
|
| 283 |
+
# Commit all changes
|
| 284 |
+
conn.commit()
|
| 285 |
+
|
| 286 |
+
print("\n" + "="*60)
|
| 287 |
+
print("DATABASE INITIALIZATION COMPLETE!")
|
| 288 |
+
print("="*60)
|
| 289 |
+
|
| 290 |
+
# Verify tables were created
|
| 291 |
+
tables = conn.execute("""
|
| 292 |
+
SELECT name FROM sqlite_master WHERE type='table' ORDER BY name
|
| 293 |
+
""").fetchall()
|
| 294 |
+
|
| 295 |
+
print(f"\nCreated {len(tables)} tables:")
|
| 296 |
+
for table in tables:
|
| 297 |
+
print(f" ✅ {table[0]}")
|
| 298 |
+
|
| 299 |
+
conn.close()
|
| 300 |
+
|
| 301 |
+
except Exception as e:
|
| 302 |
+
print(f"❌ Error initializing database: {e}")
|
| 303 |
+
conn.close()
|
| 304 |
+
raise
|
| 305 |
+
|
| 306 |
+
if __name__ == "__main__":
|
| 307 |
+
init_database()
|
install.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
One-click installer for AIMHSA without Ollama
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
import os
|
| 9 |
+
|
| 10 |
+
def install_requirements():
|
| 11 |
+
"""Install required packages"""
|
| 12 |
+
print("📦 Installing required packages...")
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
# Upgrade pip first
|
| 16 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "pip"])
|
| 17 |
+
|
| 18 |
+
# Install requirements
|
| 19 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
|
| 20 |
+
|
| 21 |
+
print("✅ All packages installed successfully!")
|
| 22 |
+
return True
|
| 23 |
+
|
| 24 |
+
except subprocess.CalledProcessError as e:
|
| 25 |
+
print(f"❌ Error installing packages: {e}")
|
| 26 |
+
print("\n🔧 Try installing minimal requirements instead:")
|
| 27 |
+
print("pip install -r requirements-minimal.txt")
|
| 28 |
+
return False
|
| 29 |
+
|
| 30 |
+
def setup_directories():
|
| 31 |
+
"""Create necessary directories"""
|
| 32 |
+
print("📁 Creating directories...")
|
| 33 |
+
|
| 34 |
+
directories = ['storage', 'data', 'logs']
|
| 35 |
+
for directory in directories:
|
| 36 |
+
os.makedirs(directory, exist_ok=True)
|
| 37 |
+
print(f" ✅ {directory}/")
|
| 38 |
+
|
| 39 |
+
def initialize_database():
|
| 40 |
+
"""Initialize the database"""
|
| 41 |
+
print("🗄️ Initializing database...")
|
| 42 |
+
|
| 43 |
+
try:
|
| 44 |
+
subprocess.check_call([sys.executable, "init_database.py"])
|
| 45 |
+
print("✅ Database initialized!")
|
| 46 |
+
return True
|
| 47 |
+
except subprocess.CalledProcessError as e:
|
| 48 |
+
print(f"❌ Error initializing database: {e}")
|
| 49 |
+
return False
|
| 50 |
+
|
| 51 |
+
def main():
|
| 52 |
+
"""Main installation process"""
|
| 53 |
+
print("🚀 AIMHSA Installation (No Ollama Required)")
|
| 54 |
+
print("=" * 50)
|
| 55 |
+
|
| 56 |
+
# Step 1: Setup directories
|
| 57 |
+
setup_directories()
|
| 58 |
+
|
| 59 |
+
# Step 2: Install requirements
|
| 60 |
+
if not install_requirements():
|
| 61 |
+
return False
|
| 62 |
+
|
| 63 |
+
# Step 3: Setup configuration
|
| 64 |
+
try:
|
| 65 |
+
from setup_without_ollama import setup_openai_compatible
|
| 66 |
+
setup_openai_compatible()
|
| 67 |
+
except ImportError:
|
| 68 |
+
print("⚠️ Configuration setup not available. Please run setup_without_ollama.py manually.")
|
| 69 |
+
|
| 70 |
+
# Step 4: Initialize database
|
| 71 |
+
if not initialize_database():
|
| 72 |
+
return False
|
| 73 |
+
|
| 74 |
+
print("\n🎉 Installation complete!")
|
| 75 |
+
print("\n🔑 Next steps:")
|
| 76 |
+
print("1. Update your API keys in .env file")
|
| 77 |
+
print("2. Run: python app.py")
|
| 78 |
+
print("3. Open: http://localhost:5057")
|
| 79 |
+
|
| 80 |
+
return True
|
| 81 |
+
|
| 82 |
+
if __name__ == "__main__":
|
| 83 |
+
main()
|
production.env
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Production Environment Configuration Template
|
| 2 |
+
# Copy this to .env for production deployment
|
| 3 |
+
|
| 4 |
+
# Environment
|
| 5 |
+
FLASK_ENV=production
|
| 6 |
+
DEBUG=False
|
| 7 |
+
|
| 8 |
+
# Server Configuration (will be overridden by hosting platform)
|
| 9 |
+
SERVER_HOST=0.0.0.0
|
| 10 |
+
PORT=8000
|
| 11 |
+
|
| 12 |
+
# API Configuration (use relative URLs in production)
|
| 13 |
+
API_BASE_URL=
|
| 14 |
+
FRONTEND_URL=
|
| 15 |
+
|
| 16 |
+
# Database Configuration
|
| 17 |
+
DB_FILE=aimhsa_production.db
|
| 18 |
+
STORAGE_DIR=storage
|
| 19 |
+
DATA_DIR=data
|
| 20 |
+
|
| 21 |
+
# Email Configuration (UPDATE WITH REAL VALUES)
|
| 22 |
+
SMTP_SERVER=smtp.gmail.com
|
| 23 |
+
SMTP_PORT=587
|
| 24 |
+
SMTP_USERNAME=your-production-email@gmail.com
|
| 25 |
+
SMTP_PASSWORD=your-app-password
|
| 26 |
+
FROM_EMAIL=noreply@yourdomain.com
|
| 27 |
+
|
| 28 |
+
# SMS Configuration (UPDATE WITH REAL VALUES)
|
| 29 |
+
HDEV_SMS_API_ID=your-real-api-id
|
| 30 |
+
HDEV_SMS_API_KEY=your-real-api-key
|
| 31 |
+
|
| 32 |
+
# AI Configuration
|
| 33 |
+
CHAT_MODEL=llama3.2:3b
|
| 34 |
+
EMBED_MODEL=nomic-embed-text
|
| 35 |
+
SENT_EMBED_MODEL=nomic-embed-text
|
| 36 |
+
OLLAMA_BASE_URL=http://localhost:11434/v1
|
| 37 |
+
OLLAMA_API_KEY=ollama
|
requirements-cloud.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Requirements for cloud deployment with external AI APIs
|
| 2 |
+
flask>=2.3.0
|
| 3 |
+
flask-cors>=4.0.0
|
| 4 |
+
openai>=1.3.0
|
| 5 |
+
python-dotenv>=1.0.0
|
| 6 |
+
numpy>=1.24.0
|
| 7 |
+
werkzeug>=2.3.0
|
| 8 |
+
gunicorn>=21.2.0
|
| 9 |
+
requests>=2.31.0
|
| 10 |
+
langdetect>=1.0.9
|
| 11 |
+
deep-translator>=1.11.4
|
| 12 |
+
PyPDF2>=3.0.0
|
| 13 |
+
Pillow>=10.0.0
|
| 14 |
+
pytesseract>=0.3.10
|
| 15 |
+
langchain-text-splitters>=0.0.1
|
| 16 |
+
anthropic>=0.7.0
|
| 17 |
+
google-generativeai>=0.3.0
|
| 18 |
+
cohere>=4.37.0
|
| 19 |
+
groq>=0.4.0
|
| 20 |
+
redis>=5.0.0
|
| 21 |
+
sentry-sdk[flask]>=1.38.0
|
| 22 |
+
waitress>=2.1.2
|
requirements-minimal.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Minimal requirements for basic functionality without Ollama
|
| 2 |
+
flask>=2.3.0
|
| 3 |
+
flask-cors>=4.0.0
|
| 4 |
+
openai>=1.3.0
|
| 5 |
+
python-dotenv>=1.0.0
|
| 6 |
+
numpy>=1.24.0
|
| 7 |
+
werkzeug>=2.3.0
|
| 8 |
+
requests>=2.31.0
|
| 9 |
+
langdetect>=1.0.9
|
| 10 |
+
deep-translator>=1.11.4
|
| 11 |
+
PyPDF2>=3.0.0
|
| 12 |
+
Pillow>=10.0.0
|
| 13 |
+
pytesseract>=0.3.10
|
| 14 |
+
langchain-text-splitters>=0.0.1
|
requirements.txt
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core Framework
|
| 2 |
+
flask>=2.3.0
|
| 3 |
+
flask-cors>=4.0.0
|
| 4 |
+
|
| 5 |
+
# OpenAI Client (for Ollama alternative)
|
| 6 |
+
openai>=1.3.0
|
| 7 |
+
|
| 8 |
+
# Environment and Configuration
|
| 9 |
+
python-dotenv>=1.0.0
|
| 10 |
+
|
| 11 |
+
# Data Processing and Storage
|
| 12 |
+
numpy>=1.24.0
|
| 13 |
+
sqlite3
|
| 14 |
+
|
| 15 |
+
# Web Server
|
| 16 |
+
werkzeug>=2.3.0
|
| 17 |
+
gunicorn>=21.2.0
|
| 18 |
+
|
| 19 |
+
# Document Processing and OCR
|
| 20 |
+
PyPDF2>=3.0.0
|
| 21 |
+
pdf2image>=1.16.0
|
| 22 |
+
pytesseract>=0.3.10
|
| 23 |
+
Pillow>=10.0.0
|
| 24 |
+
PyMuPDF>=1.23.0
|
| 25 |
+
|
| 26 |
+
# Text Processing and NLP
|
| 27 |
+
langdetect>=1.0.9
|
| 28 |
+
deep-translator>=1.11.4
|
| 29 |
+
|
| 30 |
+
# Language Detection (Alternative options)
|
| 31 |
+
langid.py>=1.1.6
|
| 32 |
+
pycld3>=0.22
|
| 33 |
+
|
| 34 |
+
# Text Splitting for RAG
|
| 35 |
+
langchain-text-splitters>=0.0.1
|
| 36 |
+
|
| 37 |
+
# HTTP Requests
|
| 38 |
+
requests>=2.31.0
|
| 39 |
+
|
| 40 |
+
# Email Support
|
| 41 |
+
email-validator>=2.0.0
|
| 42 |
+
|
| 43 |
+
# Password Security
|
| 44 |
+
bcrypt>=4.0.1
|
| 45 |
+
|
| 46 |
+
# Date and Time Utilities
|
| 47 |
+
python-dateutil>=2.8.2
|
| 48 |
+
|
| 49 |
+
# JSON Web Tokens (if needed for auth)
|
| 50 |
+
PyJWT>=2.8.0
|
| 51 |
+
|
| 52 |
+
# Database Migrations (optional)
|
| 53 |
+
alembic>=1.12.0
|
| 54 |
+
|
| 55 |
+
# Testing (optional)
|
| 56 |
+
pytest>=7.4.0
|
| 57 |
+
pytest-flask>=1.2.0
|
| 58 |
+
|
| 59 |
+
# Logging and Monitoring
|
| 60 |
+
structlog>=23.1.0
|
| 61 |
+
|
| 62 |
+
# Alternative AI Providers (choose one or more)
|
| 63 |
+
# Anthropic Claude
|
| 64 |
+
anthropic>=0.7.0
|
| 65 |
+
|
| 66 |
+
# Google AI
|
| 67 |
+
google-generativeai>=0.3.0
|
| 68 |
+
|
| 69 |
+
# Hugging Face Transformers
|
| 70 |
+
transformers>=4.35.0
|
| 71 |
+
torch>=2.1.0
|
| 72 |
+
sentence-transformers>=2.2.2
|
| 73 |
+
|
| 74 |
+
# Cohere
|
| 75 |
+
cohere>=4.37.0
|
| 76 |
+
|
| 77 |
+
# Together AI
|
| 78 |
+
together>=0.2.0
|
| 79 |
+
|
| 80 |
+
# Groq
|
| 81 |
+
groq>=0.4.0
|
| 82 |
+
|
| 83 |
+
# Azure OpenAI
|
| 84 |
+
azure-openai>=1.0.0
|
| 85 |
+
|
| 86 |
+
# AWS Bedrock
|
| 87 |
+
boto3>=1.29.0
|
| 88 |
+
|
| 89 |
+
# Production WSGI Server
|
| 90 |
+
waitress>=2.1.2
|
| 91 |
+
|
| 92 |
+
# Security
|
| 93 |
+
cryptography>=41.0.0
|
| 94 |
+
|
| 95 |
+
# Performance
|
| 96 |
+
redis>=5.0.0
|
| 97 |
+
celery>=5.3.0
|
| 98 |
+
|
| 99 |
+
# Monitoring
|
| 100 |
+
sentry-sdk[flask]>=1.38.0
|
| 101 |
+
|
| 102 |
+
# File Upload Handling
|
| 103 |
+
secure-filename>=0.1
|
| 104 |
+
|
| 105 |
+
# Timezone Support
|
| 106 |
+
pytz>=2023.3
|
| 107 |
+
|
| 108 |
+
# UUID Generation
|
| 109 |
+
uuid>=1.30
|
| 110 |
+
|
| 111 |
+
# Math and Statistics
|
| 112 |
+
scipy>=1.11.0
|
| 113 |
+
|
| 114 |
+
# HTTP Client with better error handling
|
| 115 |
+
httpx>=0.25.0
|
| 116 |
+
|
| 117 |
+
# Configuration Management
|
| 118 |
+
pydantic>=2.4.0
|
| 119 |
+
pydantic-settings>=2.0.0
|
| 120 |
+
|
| 121 |
+
# Async Support
|
| 122 |
+
asyncio
|
| 123 |
+
aiohttp>=3.9.0
|
| 124 |
+
|
| 125 |
+
# Alternative Embeddings
|
| 126 |
+
chromadb>=0.4.0
|
| 127 |
+
faiss-cpu>=1.7.4
|
| 128 |
+
|
| 129 |
+
# Alternative to Ollama - Local LLM serving
|
| 130 |
+
llama-cpp-python>=0.2.0
|
| 131 |
+
ctransformers>=0.2.0
|
| 132 |
+
|
| 133 |
+
# OpenAI Alternative APIs
|
| 134 |
+
replicate>=0.15.0
|
requirements_ollama.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AIMHSA Requirements for OpenAI Client with Ollama
|
| 2 |
+
flask>=2.0.0
|
| 3 |
+
flask-cors>=3.0.0
|
| 4 |
+
openai>=1.0.0
|
| 5 |
+
python-dotenv>=0.19.0
|
| 6 |
+
numpy>=1.21.0
|
| 7 |
+
werkzeug>=2.0.0
|
| 8 |
+
pytesseract>=0.3.8
|
| 9 |
+
Pillow>=8.0.0
|
| 10 |
+
PyPDF2>=1.26.0
|
| 11 |
+
pdf2image>=1.16.0
|
| 12 |
+
PyMuPDF>=1.20.0
|