IZERE HIRWA Roger commited on
Commit
c024705
·
1 Parent(s): 2873069
This view is limited to 50 files because it contains too many changes.   See raw diff
.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">&times;</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">&times;</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>&times;</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>&times;</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>&copy; 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">&times;</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>&times;</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})">&times;</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>&times;</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>&times;</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>&times;</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>&times;</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