parinazAkef commited on
Commit
2fe573b
·
0 Parent(s):

Fresh start: TalimBot project without binary files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +53 -0
  2. .gitattributes +35 -0
  3. .gitignore +0 -0
  4. Dockerfile +35 -0
  5. HUGGINGFACE_DEPLOYMENT.md +232 -0
  6. MANUAL_UPDATE_INSTRUCTIONS.md +150 -0
  7. Procfile +1 -0
  8. README.md +278 -0
  9. README_HF.md +54 -0
  10. backend/README.md +53 -0
  11. backend/__pycache__/grouping_logic.cpython-310.pyc +0 -0
  12. backend/__pycache__/main.cpython-310.pyc +0 -0
  13. backend/data/students.json +388 -0
  14. backend/data/students.json.backup +388 -0
  15. backend/grouping_logic.py +334 -0
  16. backend/main.py +428 -0
  17. backend/requirements.txt +6 -0
  18. backend/static/Icons/Additional/teacherIcon.png +0 -0
  19. backend/static/Icons/Additional/teacherIcon2.jpg +0 -0
  20. backend/static/Icons/logo/Icon.png +0 -0
  21. backend/static/Icons/logo/Logo_blueBackground.jpg +0 -0
  22. backend/static/Icons/logo/Logo_noBackground.jpg +0 -0
  23. backend/static/Icons/logo/logo.png +0 -0
  24. backend/static/Icons/studentIcon.png +0 -0
  25. backend/static/Icons/teacherIcon3.png +0 -0
  26. backend/static/assets/css/styles.css +727 -0
  27. backend/static/assets/js/data.js +244 -0
  28. backend/static/assets/js/grouping.js +159 -0
  29. backend/static/index.html +13 -0
  30. backend/static/pages/ams-questionnaire.html +436 -0
  31. backend/static/pages/cooperative-questionnaire.html +401 -0
  32. backend/static/pages/group-view.html +405 -0
  33. backend/static/pages/login.html +274 -0
  34. backend/static/pages/student-dashboard.html +580 -0
  35. backend/static/pages/student-data.html +388 -0
  36. backend/static/pages/teacher-dashboard.html +1017 -0
  37. render.yaml +7 -0
  38. requirements.txt +6 -0
  39. resources_references/DEPLOYMENT_CHECKLIST.md +207 -0
  40. resources_references/Icons/Additional/teacherIcon.png +0 -0
  41. resources_references/Icons/Additional/teacherIcon2.jpg +0 -0
  42. resources_references/Icons/logo/Logo_blueBackground.jpg +0 -0
  43. resources_references/Icons/logo/Logo_noBackground.jpg +0 -0
  44. resources_references/Icons/logo/logo.png +0 -0
  45. resources_references/Icons/studentIcon.png +0 -0
  46. resources_references/Icons/teacherIcon3.png +0 -0
  47. resources_references/RAILWAY_DEPLOYMENT.md +126 -0
  48. resources_references/RAILWAY_SETUP_GUIDE.md +300 -0
  49. resources_references/README.md +407 -0
  50. resources_references/TEST_RESULTS_AND_SOLUTION.md +225 -0
.dockerignore ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python cache
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+
8
+ # Virtual environments
9
+ venv/
10
+ env/
11
+ ENV/
12
+
13
+ # IDE
14
+ .vscode/
15
+ .idea/
16
+ *.swp
17
+ *.swo
18
+
19
+ # Git
20
+ .git/
21
+ .gitignore
22
+
23
+ # Data files (will be regenerated)
24
+ backend/data/students.json
25
+
26
+ # Documentation (not needed in runtime)
27
+ *.md
28
+ !README.md
29
+
30
+ # Local development files
31
+ .env
32
+ .env.local
33
+
34
+ # OS files
35
+ .DS_Store
36
+ Thumbs.db
37
+
38
+ # Logs
39
+ *.log
40
+
41
+ # PowerShell scripts (local dev only)
42
+ *.ps1
43
+
44
+ # Backup files
45
+ *.backup
46
+ *.bak
47
+
48
+ # Render config (not needed for HuggingFace)
49
+ render.yaml
50
+
51
+ # Railway config (not needed for HuggingFace)
52
+ Procfile
53
+ runtime.txt
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
Binary file (426 Bytes). View file
 
Dockerfile ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # Dockerfile for TalimBot - AI-Powered Student Grouping System
3
+
4
+ FROM python:3.11
5
+
6
+ # Create non-root user (Hugging Face security requirement)
7
+ RUN useradd -m -u 1000 user
8
+ USER user
9
+
10
+ # Set environment variables
11
+ ENV HOME=/home/user \
12
+ PATH=/home/user/.local/bin:$PATH \
13
+ PYTHONUNBUFFERED=1
14
+
15
+ # Set working directory
16
+ WORKDIR /app
17
+
18
+ # Copy requirements and install dependencies
19
+ COPY --chown=user ./requirements.txt /app/requirements.txt
20
+ RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt
21
+
22
+ # Copy the entire backend folder (contains main.py, static files, etc.)
23
+ COPY --chown=user ./backend /app/backend
24
+
25
+ # Copy resources_references folder (optional, for documentation)
26
+ COPY --chown=user ./resources_references /app/resources_references
27
+
28
+ # Expose port 7860 (Hugging Face Spaces requirement)
29
+ EXPOSE 7860
30
+
31
+ # Change to backend directory where main.py is located
32
+ WORKDIR /app/backend
33
+
34
+ # Run the FastAPI application on port 7860
35
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
HUGGINGFACE_DEPLOYMENT.md ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces Deployment Guide for TalimBot
2
+
3
+ ## Status: Ready to Deploy ✅
4
+
5
+ All necessary files have been created and committed. Follow the steps below to complete the deployment.
6
+
7
+ ---
8
+
9
+ ## Files Created
10
+
11
+ 1. **Dockerfile** - Docker configuration for HF Spaces (port 7860)
12
+ 2. **.dockerignore** - Excludes unnecessary files from Docker build
13
+ 3. **README_HF.md** - Hugging Face Space description with metadata
14
+
15
+ ---
16
+
17
+ ## Step 1: Get Your Hugging Face Access Token
18
+
19
+ 1. Go to: https://huggingface.co/settings/tokens
20
+ 2. Click **"New token"**
21
+ 3. Name it: `TalimBot Deploy`
22
+ 4. Type: **Write** (must have write permissions)
23
+ 5. Click **"Generate a token"**
24
+ 6. **COPY THE TOKEN** (you won't see it again!)
25
+
26
+ ---
27
+
28
+ ## Step 2: Push to Hugging Face
29
+
30
+ The git remote is already configured. Now push your code:
31
+
32
+ ```powershell
33
+ git push huggingface main
34
+ ```
35
+
36
+ When prompted:
37
+ - **Username**: `parinazAkef` (your HF username)
38
+ - **Password**: Paste your **access token** (not your actual password!)
39
+
40
+ ---
41
+
42
+ ## Step 3: Configure Environment Secret
43
+
44
+ 1. Go to: https://huggingface.co/spaces/parinazAkef/talimbot
45
+ 2. Click the **Settings** tab (top of page)
46
+ 3. Scroll to **"Variables and secrets"**
47
+ 4. Click **"New secret"**
48
+ 5. Name: `OPENROUTER_API_KEY`
49
+ 6. Value: Your OpenRouter API key (starts with `sk-or-v1-`)
50
+ 7. Click **"Save"**
51
+
52
+ **✅ You've already done this step!**
53
+
54
+ ---
55
+
56
+ ## Step 4: Wait for Build
57
+
58
+ After pushing, Hugging Face will:
59
+ 1. Show "Building" status (3-5 minutes)
60
+ 2. Pull Docker image, install dependencies
61
+ 3. Start your FastAPI app on port 7860
62
+ 4. Show "Running" status when ready
63
+
64
+ ---
65
+
66
+ ## Step 5: Access Your Live App
67
+
68
+ Once status shows **"Running"** (green), your app will be available at:
69
+
70
+ **Primary URL:**
71
+ ```
72
+ https://huggingface.co/spaces/parinazAkef/talimbot
73
+ ```
74
+
75
+ **Direct URL (no HF frame):**
76
+ ```
77
+ https://parinazakef-talimbot.hf.space
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Step 6: Test the Application
83
+
84
+ ### Teacher Login
85
+ 1. Go to your HF Space URL
86
+ 2. Click on **"معلم"** (Teacher)
87
+ 3. Password: `teacher123`
88
+ 4. You should see the teacher dashboard
89
+
90
+ ### Student Login
91
+ 1. Click on **"دانش آموز"** (Student)
92
+ 2. Enter national code: `0921111111` (demo account)
93
+ 3. You should see: **پریناز عاکف** dashboard
94
+
95
+ ### Test Grouping
96
+ 1. Login as teacher
97
+ 2. Use **"پر کردن داده‌های تست"** to generate 10 sample students
98
+ 3. Click **"شروع گروه‌بندی"**
99
+ 4. Enter course name (e.g., "ریاضی")
100
+ 5. Wait 30-60 seconds for AI grouping
101
+ 6. Check results!
102
+
103
+ ---
104
+
105
+ ## Troubleshooting
106
+
107
+ ### If Build Fails
108
+
109
+ **Error: "Port 7860 not listening"**
110
+ - Fixed! Our Dockerfile uses `--port 7860`
111
+
112
+ **Error: "OPENROUTER_API_KEY not found"**
113
+ - Go to Space Settings → Add secret `OPENROUTER_API_KEY`
114
+
115
+ **Error: "File not found: main.py"**
116
+ - Fixed! Dockerfile copies `backend/` folder and sets working directory
117
+
118
+ ### If Push Fails
119
+
120
+ **Error: "Authentication failed"**
121
+ - Make sure you're using an **access token**, not your password
122
+ - Generate new token with **Write** permissions
123
+
124
+ **Error: "Repository not found"**
125
+ - Check space URL: https://huggingface.co/spaces/parinazAkef/talimbot
126
+ - Make sure space exists and is public
127
+
128
+ ---
129
+
130
+ ## File Structure (Docker Container)
131
+
132
+ ```
133
+ /app/
134
+ ├── requirements.txt
135
+ ├── backend/
136
+ │ ├── main.py # FastAPI app (runs on port 7860)
137
+ │ ├── grouping_logic.py # AI grouping algorithm
138
+ │ ├── data/
139
+ │ │ └── students.json.backup # Initial data template
140
+ │ └── static/
141
+ │ ├── pages/ # HTML pages
142
+ │ ├── assets/ # CSS, JS files
143
+ │ └── Icons/ # Images
144
+ └── resources_references/ # Documentation
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Environment Variables
150
+
151
+ The Space automatically provides:
152
+ - `OPENROUTER_API_KEY` (from Secrets)
153
+ - `PORT=7860` (HF Spaces requirement)
154
+
155
+ Your FastAPI app reads `OPENROUTER_API_KEY` via:
156
+ ```python
157
+ api_key = os.getenv("OPENROUTER_API_KEY")
158
+ ```
159
+
160
+ ---
161
+
162
+ ## Next Steps After Deployment
163
+
164
+ 1. **Update GitHub README** - Add HF Space badge and link
165
+ 2. **Share URL** - Send to users/colleagues
166
+ 3. **Monitor Usage** - Check Logs tab in HF Space
167
+ 4. **Add to Profile** - Pin Space to your HF profile
168
+
169
+ ---
170
+
171
+ ## Updating Your App
172
+
173
+ To push changes:
174
+
175
+ ```powershell
176
+ # Make your code changes
177
+ git add .
178
+ git commit -m "Your update description"
179
+
180
+ # Push to GitHub (optional)
181
+ git push fork main
182
+
183
+ # Push to Hugging Face (triggers rebuild)
184
+ git push huggingface main
185
+ ```
186
+
187
+ HF will automatically rebuild and redeploy!
188
+
189
+ ---
190
+
191
+ ## Comparison: Railway vs Hugging Face
192
+
193
+ | Feature | Railway | Hugging Face |
194
+ |---------|---------|--------------|
195
+ | Free Tier | 500 hours/month | Unlimited (24/7) |
196
+ | Sleep Mode | No | No |
197
+ | Cold Start | No | No |
198
+ | Iran Access | Sometimes blocked | More accessible |
199
+ | Custom Domain | Yes (paid) | Yes (subdomain free) |
200
+ | Secrets | Environment variables | Secrets (encrypted) |
201
+ | Logs | Real-time | Real-time |
202
+ | Auto-deploy | Git push | Git push |
203
+
204
+ ---
205
+
206
+ ## Contact & Support
207
+
208
+ - **Hugging Face Docs**: https://huggingface.co/docs/hub/spaces-sdks-docker
209
+ - **Your Space**: https://huggingface.co/spaces/parinazAkef/talimbot
210
+ - **OpenRouter Docs**: https://openrouter.ai/docs
211
+
212
+ ---
213
+
214
+ ## Summary
215
+
216
+ ✅ **Completed:**
217
+ - Created Dockerfile (port 7860)
218
+ - Created .dockerignore
219
+ - Created README_HF.md
220
+ - Committed files to git
221
+ - Added HF remote
222
+
223
+ 🔄 **Your Action Required:**
224
+ 1. Get HF access token from https://huggingface.co/settings/tokens
225
+ 2. Run: `git push huggingface main`
226
+ 3. Enter username + token when prompted
227
+ 4. Wait for "Running" status
228
+ 5. Test at: https://parinazakef-talimbot.hf.space
229
+
230
+ ---
231
+
232
+ Good luck! 🚀
MANUAL_UPDATE_INSTRUCTIONS.md ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Manual Update Instructions for HuggingFace Space
2
+
3
+ Since git push is timing out, you can update the files manually on HuggingFace.
4
+
5
+ ## Files to Update
6
+
7
+ ### 1. backend/static/pages/login.html
8
+
9
+ **Go to:** https://huggingface.co/spaces/TalimBot/talimbot/blob/main/backend/static/pages/login.html
10
+
11
+ **Click "Edit" button, then find and replace these sections:**
12
+
13
+ **CHANGE 1 (around line 90):**
14
+
15
+ Find:
16
+ ```html
17
+ <input
18
+ type="text"
19
+ id="nationalCode"
20
+ placeholder="کد ملی ۱۰ رقمی "
21
+ maxlength="10"
22
+ class="w-full p-4 border-2 border-gray-300 rounded-xl text-center text-lg focus:border-teal-500 focus:ring-2 focus:ring-teal-200 outline-none transition-all"
23
+ dir="ltr"
24
+ >
25
+ <p class="text-xs text-gray-500 mt-2 text-center">کد ملی ۱۰ رقمی خود را وارد کنید</p>
26
+ ```
27
+
28
+ Replace with:
29
+ ```html
30
+ <input
31
+ type="text"
32
+ id="nationalCode"
33
+ placeholder="کد ملی خود را وارد کنید"
34
+ class="w-full p-4 border-2 border-gray-300 rounded-xl text-center text-lg focus:border-teal-500 focus:ring-2 focus:ring-teal-200 outline-none transition-all"
35
+ dir="ltr"
36
+ >
37
+ <p class="text-xs text-gray-500 mt-2 text-center">کد ملی خود را بدون صفر ابتدایی وارد کنید</p>
38
+ ```
39
+
40
+ **CHANGE 2 (around line 207):**
41
+
42
+ Find:
43
+ ```javascript
44
+ if (selectedRole === 'student') {
45
+ let nationalCode = document.getElementById('nationalCode').value.trim();
46
+
47
+ if (!nationalCode) {
48
+ showError('لطفاً کد ملی خود را وارد کنید');
49
+ return;
50
+ }
51
+
52
+ if (nationalCode.length !== 10) {
53
+ showError('کد ملی باید ۱۰ رقم باشد');
54
+ return;
55
+ }
56
+
57
+ // Remove leading zero if present
58
+ if (nationalCode.startsWith('0')) {
59
+ nationalCode = nationalCode.substring(1);
60
+ }
61
+ ```
62
+
63
+ Replace with:
64
+ ```javascript
65
+ if (selectedRole === 'student') {
66
+ let nationalCode = document.getElementById('nationalCode').value.trim();
67
+
68
+ if (!nationalCode) {
69
+ showError('لطفاً کد ملی خود را وارد کنید');
70
+ return;
71
+ }
72
+
73
+ // Check if starts with zero and show warning
74
+ if (nationalCode.startsWith('0')) {
75
+ showError('لطفاً صفر ابتدایی را از کد ملی حذف کنید');
76
+ return;
77
+ }
78
+
79
+ // No length restriction - just check if it matches a student in database
80
+ ```
81
+
82
+ **Then click "Commit changes to main"**
83
+
84
+ ---
85
+
86
+ ### 2. backend/static/pages/group-view.html
87
+
88
+ **Go to:** https://huggingface.co/spaces/TalimBot/talimbot/blob/main/backend/static/pages/group-view.html
89
+
90
+ **Click "Edit" button, then find and replace this section:**
91
+
92
+ **CHANGE (around line 379):**
93
+
94
+ Find:
95
+ ```html
96
+ <div class="flex flex-wrap gap-3 text-sm">
97
+ <span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
98
+ <strong>MBTI:</strong> ${member.mbti || 'ندارد'}
99
+ </span>
100
+ <span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
101
+ <strong>سبک:</strong> ${member.learningStyle || 'ندارد'}
102
+ </span>
103
+ <span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
104
+ <strong>نمره:</strong> ${member.grade.toFixed(2)}
105
+ </span>
106
+ </div>
107
+ ```
108
+
109
+ Replace with:
110
+ ```html
111
+ <div class="flex flex-wrap gap-3 text-sm">
112
+ <span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
113
+ <strong>MBTI:</strong> ${member.mbti || 'ندارد'}
114
+ </span>
115
+ <span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
116
+ <strong>سبک:</strong> ${member.learningStyle || 'ندارد'}
117
+ </span>
118
+ ${member.ams ? `<span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
119
+ <strong>AMS:</strong> ${member.ams}
120
+ </span>` : ''}
121
+ ${member.cooperative ? `<span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
122
+ <strong>همکاری:</strong> ${member.cooperative}
123
+ </span>` : ''}
124
+ </div>
125
+ ```
126
+
127
+ **Then click "Commit changes to main"**
128
+
129
+ ---
130
+
131
+ ## What These Changes Do
132
+
133
+ 1. **Login page**: Removes the 10-digit limit and shows a clear error if someone tries to enter a leading zero
134
+ 2. **Group view page**: Hides teammates' grades (معدل) and shows AMS and Cooperative scores instead
135
+
136
+ ## After Making Changes
137
+
138
+ The Space will automatically rebuild (takes 2-3 minutes) and you'll see the changes live at:
139
+ https://talimbot-talimbot.hf.space/pages/login.html
140
+
141
+ ---
142
+
143
+ **Alternative: Try pushing again later when connection is better**
144
+
145
+ If you prefer to keep trying git push, you can run:
146
+ ```powershell
147
+ git push huggingface main --force
148
+ ```
149
+
150
+ But given the network issues, manual editing on the website is faster and more reliable.
Procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: cd backend && uvicorn main:app --host 0.0.0.0 --port $PORT
README.md ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: TalimBot
3
+ emoji: 🎓
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # TalimBot - AI-Powered Student Grouping System
12
+
13
+ An intelligent educational platform that uses advanced psychology principles and AI to create optimal learning groups for adolescent students (ages 15-16).
14
+
15
+ ## Overview
16
+
17
+ TalimBot is a comprehensive web-based system designed to help teachers form balanced, effective study groups by analyzing student personalities, learning styles, academic motivation, and cooperative skills. The system uses OpenAI's GPT-4o to create groups that maximize learning potential through Zone of Proximal Development (ZPD) theory and complementary personality matching.
18
+
19
+ ## Key Features
20
+
21
+ ### For Teachers
22
+ - **AI-Powered Grouping**: Automated group formation using educational psychology principles
23
+ - **Multi-Factor Analysis**: Considers 7 key criteria with weighted priorities:
24
+ - ZPD Optimization (30%) - Grade-based peer tutoring scaffolding
25
+ - MBTI Complementarity (25%) - Personality type balance
26
+ - VARK Diversity (20%) - Learning style variety
27
+ - Academic Motivation (15%) - AMS score distribution
28
+ - Cooperative Skills (10%) - Teamwork ability balance
29
+ - Course-Specific Requirements
30
+ - Student Preferences (5%)
31
+ - **Real-time Dashboard**: Monitor student profile completion and grouping status
32
+ - **Data Management**: Import/export student data via JSON, fill test data
33
+ - **Result Control**: Show/hide grouping results to students
34
+ - **Flexible Reset Options**:
35
+ - Reset grouping only (preserve student data)
36
+ - Reset all data (complete system reset)
37
+
38
+ ### For Students
39
+ - **Profile Management**: Complete personality and learning assessments
40
+ - **Integrated Tests**:
41
+ - MBTI personality test (16 types)
42
+ - VARK learning style questionnaire (Visual, Aural, Read/Write, Kinesthetic)
43
+ - AMS academic motivation scale (28 questions, 0-196 score)
44
+ - Cooperative learning skills assessment (25 questions, 0-125 score)
45
+ - **Peer Preferences**: Select up to 4 preferred groupmates
46
+ - **Group View**: See assigned group members and AI reasoning (when visible)
47
+ - **Progress Tracking**: Monitor test completion status
48
+
49
+ ## Technical Architecture
50
+
51
+ ### Backend
52
+ - **Framework**: FastAPI (Python)
53
+ - **Database**: JSON file-based storage (students.json)
54
+ - **AI Integration**: OpenRouter API with GPT-4o model
55
+ - **Authentication**: Password-based teacher authentication, national code verification for students
56
+
57
+ ### Frontend
58
+ - **UI Framework**: Tailwind CSS
59
+ - **Language Support**: Persian (RTL layout) with Vazirmatn font
60
+ - **Pages**:
61
+ - Login page (student/teacher authentication)
62
+ - Student dashboard (profile management, tests)
63
+ - Teacher dashboard (grouping management, analytics)
64
+ - Group view (display assigned groups)
65
+ - AMS questionnaire (academic motivation assessment)
66
+ - Cooperative questionnaire (teamwork skills assessment)
67
+
68
+ ### Deployment
69
+ - **Platform**: Railway.app
70
+ - **Server**: Uvicorn ASGI server
71
+ - **Static Files**: Served via FastAPI StaticFiles
72
+ - **Environment Variables**: OPENROUTER_API_KEY
73
+
74
+ ## Project Structure
75
+
76
+ ```
77
+ talimbot/
78
+ ├── backend/
79
+ │ ├── main.py # FastAPI application and API endpoints
80
+ │ ├── grouping_logic.py # AI grouping algorithm
81
+ │ ├── data/
82
+ │ │ ├── students.json # Student data (gitignored)
83
+ │ │ └── students.json.backup # Clean backup template
84
+ │ └── static/
85
+ │ ├── pages/ # HTML pages
86
+ │ │ ├── login.html
87
+ │ │ ├── student-dashboard.html
88
+ │ │ ├── teacher-dashboard.html
89
+ │ │ ├── group-view.html
90
+ │ │ ├── ams-questionnaire.html
91
+ │ │ ├── cooperative-questionnaire.html
92
+ │ │ └── student-data.html
93
+ │ ├── assets/
94
+ │ │ ├── js/
95
+ │ │ │ ├── data.js # API client functions
96
+ │ │ │ └── grouping.js # Grouping utilities
97
+ │ │ └── css/
98
+ │ │ └── styles.css # Custom styles
99
+ │ └── index.html # Landing page
100
+ ├── resources_references/ # Documentation and reference files
101
+ │ ├── RAILWAY_DEPLOYMENT.md # Deployment guide
102
+ │ ├── TEST_RESULTS_AND_SOLUTION.md
103
+ │ ├── angizesh_tahsili.txt # AMS questionnaire source
104
+ │ ├── cooperative.txt # Cooperative questionnaire source
105
+ │ ├── students_class_notebook.txt # Original student data
106
+ │ └── sample-student-data.json # JSON import example
107
+ ├── requirements.txt # Python dependencies
108
+ ├── runtime.txt # Python version for Railway
109
+ ├── Procfile # Railway start command
110
+ └── .gitignore # Git ignore rules
111
+
112
+ ```
113
+
114
+ ## Installation and Setup
115
+
116
+ ### Prerequisites
117
+ - Python 3.11+
118
+ - OpenRouter API key (get from https://openrouter.ai/keys)
119
+
120
+ ### Local Development
121
+
122
+ 1. Clone the repository:
123
+ ```bash
124
+ git clone https://github.com/talimbot/talimbot.git
125
+ cd talimbot
126
+ ```
127
+
128
+ 2. Install dependencies:
129
+ ```bash
130
+ pip install -r requirements.txt
131
+ ```
132
+
133
+ 3. Set environment variable:
134
+ ```bash
135
+ # Windows PowerShell
136
+ $env:OPENROUTER_API_KEY="your-api-key-here"
137
+
138
+ # Linux/Mac
139
+ export OPENROUTER_API_KEY="your-api-key-here"
140
+ ```
141
+
142
+ 4. Run the server:
143
+ ```bash
144
+ cd backend
145
+ uvicorn main:app --reload --host 0.0.0.0 --port 8000
146
+ ```
147
+
148
+ 5. Access the application:
149
+ ```
150
+ http://localhost:8000
151
+ ```
152
+
153
+ ### Railway Deployment
154
+
155
+ 1. Fork this repository to your GitHub account
156
+
157
+ 2. Create a new project on Railway.app
158
+
159
+ 3. Connect your GitHub repository
160
+
161
+ 4. Add environment variable in Railway dashboard:
162
+ - Key: `OPENROUTER_API_KEY`
163
+ - Value: Your OpenRouter API key
164
+
165
+ 5. Railway will auto-deploy using the Procfile
166
+
167
+ ## Usage Guide
168
+
169
+ ### Teacher Workflow
170
+
171
+ 1. **Login**: Access teacher dashboard with password (default: teacher123)
172
+
173
+ 2. **Monitor Students**: View student profile completion status
174
+
175
+ 3. **Fill Test Data** (Optional): Use the test data generator to fill 10 sample students for testing
176
+
177
+ 4. **Import Data** (Optional): Upload JSON file with student information
178
+
179
+ 5. **Perform Grouping**:
180
+ - Enter course name
181
+ - Click "Start Grouping" button
182
+ - Wait for AI processing (30-60 seconds)
183
+ - Review generated groups with detailed reasoning
184
+
185
+ 6. **Show Results**: Toggle result visibility to allow students to view their groups
186
+
187
+ 7. **Reset Options**:
188
+ - Reset Grouping: Clear groups, keep student data
189
+ - Reset All Data: Complete system wipe
190
+
191
+ ### Student Workflow
192
+
193
+ 1. **Login**: Enter student number (S001-S030) and national code
194
+
195
+ 2. **Complete Profile**:
196
+ - Take MBTI personality test (external link)
197
+ - Complete VARK learning style questionnaire
198
+ - Fill AMS academic motivation scale (28 questions)
199
+ - Complete cooperative skills assessment (25 questions)
200
+ - Select up to 4 preferred groupmates (optional)
201
+
202
+ 3. **Save Information**: Click "Save All Information" button
203
+
204
+ 4. **View Group**: Access group view page to see assigned group (when teacher makes it visible)
205
+
206
+ ## Grouping Algorithm
207
+
208
+ The AI grouping system follows a sophisticated 7-tier priority framework:
209
+
210
+ 1. **ZPD Optimization (30%)**: Mixes high and medium performers for peer tutoring
211
+ 2. **MBTI Complementarity (25%)**: Pairs complementary personality types (e.g., ENFP+INTJ)
212
+ 3. **VARK Diversity (20%)**: Ensures multiple learning styles in each group
213
+ 4. **Academic Motivation (15%)**: Distributes high-motivation students across groups
214
+ 5. **Cooperative Skills (10%)**: Balances teamwork abilities
215
+ 6. **Course-Specific Requirements**: Adapts to subject matter needs
216
+ 7. **Student Preferences (5%)**: Honors preferences when they don't compromise other criteria
217
+
218
+ Groups are typically 5 students each, with some groups of 4 to avoid very small groups.
219
+
220
+ ## Data Structure
221
+
222
+ ### Student Object
223
+ ```json
224
+ {
225
+ "studentNumber": "S001",
226
+ "name": "Student Name",
227
+ "nationalCode": "1234567890",
228
+ "mbti": "INTJ",
229
+ "learningStyle": "Visual",
230
+ "ams": "150",
231
+ "cooperative": "95",
232
+ "grade": 18.5,
233
+ "preferredStudents": ["S002", "S003"],
234
+ "group": 1
235
+ }
236
+ ```
237
+
238
+ ## API Endpoints
239
+
240
+ - `GET /api/students` - Get all students
241
+ - `GET /api/student/{student_number}` - Get specific student
242
+ - `PUT /api/student/{student_number}` - Update student data
243
+ - `POST /api/grouping/perform` - Execute AI grouping
244
+ - `GET /api/grouping/status` - Get grouping statistics
245
+ - `POST /api/grouping/reset` - Reset grouping only
246
+ - `POST /api/data/reset-all` - Reset all data
247
+ - `POST /api/grouping/toggle-visibility` - Show/hide results
248
+ - `POST /api/auth/teacher` - Verify teacher password
249
+ - `POST /api/auth/student` - Authenticate student
250
+ - `GET /api/student/{student_number}/group` - Get student's group
251
+
252
+ ## Security Notes
253
+
254
+ - Student data stored in students.json (excluded from version control)
255
+ - Teacher password: "teacher123" (change in production)
256
+ - National codes used for student authentication
257
+ - API key required for AI grouping functionality
258
+
259
+ ## Educational Foundation
260
+
261
+ This system is based on:
262
+ - **Vygotsky's Zone of Proximal Development (ZPD)**: Optimal learning occurs when students work slightly above their current level with peer support
263
+ - **MBTI Complementarity Research**: Diverse personality types enhance team creativity and problem-solving
264
+ - **VARK Learning Theory**: Multiple learning styles improve knowledge retention
265
+ - **Academic Motivation Scale (AMS)**: Measures intrinsic and extrinsic motivation factors
266
+ - **Cooperative Learning Principles**: Teamwork skills are essential for collaborative success
267
+
268
+ ## License
269
+
270
+ This project is for educational purposes.
271
+
272
+ ## Contributors
273
+
274
+ Developed for educational psychology research and classroom implementation.
275
+
276
+ ## Support
277
+
278
+ For issues or questions, please refer to the documentation in the `resources_references/` folder.
README_HF.md ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: TalimBot
3
+ emoji: 🎓
4
+ colorFrom: teal
5
+ colorTo: cyan
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ # TalimBot - AI-Powered Student Grouping System
12
+
13
+ An intelligent educational platform that uses advanced psychology principles and AI to create optimal learning groups for adolescent students (ages 15-16).
14
+
15
+ ## Features
16
+
17
+ - **AI-Powered Grouping**: Automated group formation using OpenAI GPT-4o
18
+ - **Educational Psychology**: Based on ZPD theory, MBTI complementarity, VARK learning styles
19
+ - **Teacher Dashboard**: Monitor students and manage grouping
20
+ - **Student Dashboard**: Complete personality and learning assessments
21
+ - **Persian Language**: Full RTL support with Vazirmatn font
22
+
23
+ ## Tech Stack
24
+
25
+ - **Backend**: FastAPI (Python 3.11)
26
+ - **Frontend**: Vanilla JavaScript, Tailwind CSS
27
+ - **AI**: OpenRouter API with GPT-4o
28
+ - **Deployment**: Hugging Face Spaces (Docker)
29
+
30
+ ## Usage
31
+
32
+ 1. **Teacher Login**: Use password to access teacher dashboard
33
+ 2. **Student Login**: Enter national code (کد ملی) to access student dashboard
34
+ 3. **Complete Profiles**: Students fill MBTI, VARK, AMS, and Cooperative assessments
35
+ 4. **Create Groups**: Teacher runs AI grouping algorithm
36
+ 5. **View Results**: Students see their assigned groups
37
+
38
+ ## Demo Account
39
+
40
+ For demonstration purposes, login with:
41
+ - National Code: 0921111111
42
+ - Name: پریناز عاکف
43
+
44
+ This account is for demo only and won't be included in grouping.
45
+
46
+ ## Configuration
47
+
48
+ This Space requires the `OPENROUTER_API_KEY` environment variable to be set in the Secrets section.
49
+
50
+ Get your free API key at: https://openrouter.ai/keys
51
+
52
+ ## License
53
+
54
+ MIT License - For educational purposes
backend/README.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TalimBot Backend
2
+
3
+ FastAPI backend server for the TalimBot student grouping system.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Install dependencies (one time only)
9
+ pip install -r requirements.txt
10
+
11
+ # Start the server
12
+ python main.py
13
+ ```
14
+
15
+ Server will run on `http://localhost:8000`
16
+
17
+ ## API Endpoints
18
+
19
+ - `GET /` - Health check
20
+ - `GET /api/students` - Get all students
21
+ - `GET /api/student/{id}` - Get specific student
22
+ - `PUT /api/student/{id}` - Update student info
23
+ - `POST /api/grouping/perform` - Perform AI grouping
24
+ - `GET /api/grouping/status` - Get grouping statistics
25
+ - `POST /api/grouping/toggle-visibility` - Show/hide results to students
26
+ - `POST /api/grouping/reset` - Reset grouping
27
+
28
+ ## Configuration
29
+
30
+ ### OpenRouter API Key
31
+ Located in `grouping_logic.py`:
32
+ ```python
33
+ OPENROUTER_API_KEY = 'your-key-here'
34
+ ```
35
+
36
+ ### Teacher Password
37
+ Located in `main.py` (SystemData model):
38
+ ```python
39
+ teacherPassword: str = "teacher123"
40
+ ```
41
+
42
+ ## Data Storage
43
+
44
+ Student data is stored in `data/students.json` (auto-created on first run)
45
+
46
+ ## Dependencies
47
+
48
+ - fastapi - Web framework
49
+ - uvicorn - ASGI server
50
+ - pydantic - Data validation
51
+ - aiohttp - Async HTTP client for API calls
52
+
53
+ See `requirements.txt` for versions.
backend/__pycache__/grouping_logic.cpython-310.pyc ADDED
Binary file (6.03 kB). View file
 
backend/__pycache__/main.cpython-310.pyc ADDED
Binary file (11.4 kB). View file
 
backend/data/students.json ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "students": [
3
+ {
4
+ "studentNumber": "S001",
5
+ "name": "یاسمن آدینه پور",
6
+ "nationalCode": "929986644",
7
+ "mbti": null,
8
+ "learningStyle": null,
9
+ "ams": null,
10
+ "cooperative": null,
11
+ "grade": 18.77,
12
+ "preferredStudents": [],
13
+ "group": null
14
+ },
15
+ {
16
+ "studentNumber": "S002",
17
+ "name": "پریا احمدزاده",
18
+ "nationalCode": "980085330",
19
+ "mbti": null,
20
+ "learningStyle": null,
21
+ "ams": null,
22
+ "cooperative": null,
23
+ "grade": 17.28,
24
+ "preferredStudents": [],
25
+ "group": null
26
+ },
27
+ {
28
+ "studentNumber": "S003",
29
+ "name": "فاطمه اکبرزاده",
30
+ "nationalCode": "970154550",
31
+ "mbti": null,
32
+ "learningStyle": null,
33
+ "ams": null,
34
+ "cooperative": null,
35
+ "grade": 16.71,
36
+ "preferredStudents": [],
37
+ "group": null
38
+ },
39
+ {
40
+ "studentNumber": "S004",
41
+ "name": "آناهیتا الهی مهر",
42
+ "nationalCode": "26425955",
43
+ "mbti": null,
44
+ "learningStyle": null,
45
+ "ams": null,
46
+ "cooperative": null,
47
+ "grade": 19.05,
48
+ "preferredStudents": [],
49
+ "group": null
50
+ },
51
+ {
52
+ "studentNumber": "S005",
53
+ "name": "مریم امیری",
54
+ "nationalCode": "980093341",
55
+ "mbti": null,
56
+ "learningStyle": null,
57
+ "ams": null,
58
+ "cooperative": null,
59
+ "grade": 18.87,
60
+ "preferredStudents": [],
61
+ "group": null
62
+ },
63
+ {
64
+ "studentNumber": "S006",
65
+ "name": "باران برادران رحیمی",
66
+ "nationalCode": "960043985",
67
+ "mbti": null,
68
+ "learningStyle": null,
69
+ "ams": null,
70
+ "cooperative": null,
71
+ "grade": 19.07,
72
+ "preferredStudents": [],
73
+ "group": null
74
+ },
75
+ {
76
+ "studentNumber": "S007",
77
+ "name": "مایسا بصیری امین",
78
+ "nationalCode": "960089446",
79
+ "mbti": null,
80
+ "learningStyle": null,
81
+ "ams": null,
82
+ "cooperative": null,
83
+ "grade": 19.33,
84
+ "preferredStudents": [],
85
+ "group": null
86
+ },
87
+ {
88
+ "studentNumber": "S008",
89
+ "name": "دلارام ثابت عهد",
90
+ "nationalCode": "960125620",
91
+ "mbti": null,
92
+ "learningStyle": null,
93
+ "ams": null,
94
+ "cooperative": null,
95
+ "grade": 19.55,
96
+ "preferredStudents": [],
97
+ "group": null
98
+ },
99
+ {
100
+ "studentNumber": "S009",
101
+ "name": "شاینا جان محمدی",
102
+ "nationalCode": "960068041",
103
+ "mbti": null,
104
+ "learningStyle": null,
105
+ "ams": null,
106
+ "cooperative": null,
107
+ "grade": 19.47,
108
+ "preferredStudents": [],
109
+ "group": null
110
+ },
111
+ {
112
+ "studentNumber": "S010",
113
+ "name": "آیدا جوان",
114
+ "nationalCode": "95112313",
115
+ "mbti": null,
116
+ "learningStyle": null,
117
+ "ams": null,
118
+ "cooperative": null,
119
+ "grade": 16.77,
120
+ "preferredStudents": [],
121
+ "group": null
122
+ },
123
+ {
124
+ "studentNumber": "S011",
125
+ "name": "سارینا حاجی آبادی",
126
+ "nationalCode": "999216751",
127
+ "mbti": null,
128
+ "learningStyle": null,
129
+ "ams": null,
130
+ "cooperative": null,
131
+ "grade": 16.08,
132
+ "preferredStudents": [],
133
+ "group": null
134
+ },
135
+ {
136
+ "studentNumber": "S012",
137
+ "name": "هستی حسن پور جوان",
138
+ "nationalCode": "960074198",
139
+ "mbti": null,
140
+ "learningStyle": null,
141
+ "ams": null,
142
+ "cooperative": null,
143
+ "grade": 19.55,
144
+ "preferredStudents": [],
145
+ "group": null
146
+ },
147
+ {
148
+ "studentNumber": "S013",
149
+ "name": "فاطمه حسینی",
150
+ "nationalCode": "2400410259",
151
+ "mbti": null,
152
+ "learningStyle": null,
153
+ "ams": null,
154
+ "cooperative": null,
155
+ "grade": 19.07,
156
+ "preferredStudents": [],
157
+ "group": null
158
+ },
159
+ {
160
+ "studentNumber": "S014",
161
+ "name": "غزل خسروی",
162
+ "nationalCode": "929995767",
163
+ "mbti": null,
164
+ "learningStyle": null,
165
+ "ams": null,
166
+ "cooperative": null,
167
+ "grade": 15.05,
168
+ "preferredStudents": [],
169
+ "group": null
170
+ },
171
+ {
172
+ "studentNumber": "S015",
173
+ "name": "غزل ذباح",
174
+ "nationalCode": "960110186",
175
+ "mbti": null,
176
+ "learningStyle": null,
177
+ "ams": null,
178
+ "cooperative": null,
179
+ "grade": 19.25,
180
+ "preferredStudents": [],
181
+ "group": null
182
+ },
183
+ {
184
+ "studentNumber": "S016",
185
+ "name": "نازنین زهرا راشکی",
186
+ "nationalCode": "3661516087",
187
+ "mbti": null,
188
+ "learningStyle": null,
189
+ "ams": null,
190
+ "cooperative": null,
191
+ "grade": 17.02,
192
+ "preferredStudents": [],
193
+ "group": null
194
+ },
195
+ {
196
+ "studentNumber": "S017",
197
+ "name": "ویونا روح نواز",
198
+ "nationalCode": "314458344",
199
+ "mbti": null,
200
+ "learningStyle": null,
201
+ "ams": null,
202
+ "cooperative": null,
203
+ "grade": 18.7,
204
+ "preferredStudents": [],
205
+ "group": null
206
+ },
207
+ {
208
+ "studentNumber": "S018",
209
+ "name": "روژینا سعادتی",
210
+ "nationalCode": "960051023",
211
+ "mbti": null,
212
+ "learningStyle": null,
213
+ "ams": null,
214
+ "cooperative": null,
215
+ "grade": 18.2,
216
+ "preferredStudents": [],
217
+ "group": null
218
+ },
219
+ {
220
+ "studentNumber": "S019",
221
+ "name": "ترنم شعبانی",
222
+ "nationalCode": "950083100",
223
+ "mbti": null,
224
+ "learningStyle": null,
225
+ "ams": null,
226
+ "cooperative": null,
227
+ "grade": 19.37,
228
+ "preferredStudents": [],
229
+ "group": null
230
+ },
231
+ {
232
+ "studentNumber": "S020",
233
+ "name": "ستایش شفابخش",
234
+ "nationalCode": "960126899",
235
+ "mbti": null,
236
+ "learningStyle": null,
237
+ "ams": null,
238
+ "cooperative": null,
239
+ "grade": 18.36,
240
+ "preferredStudents": [],
241
+ "group": null
242
+ },
243
+ {
244
+ "studentNumber": "S021",
245
+ "name": "فاطمه شیرزادخان",
246
+ "nationalCode": "980120756",
247
+ "mbti": null,
248
+ "learningStyle": null,
249
+ "ams": null,
250
+ "cooperative": null,
251
+ "grade": 19.33,
252
+ "preferredStudents": [],
253
+ "group": null
254
+ },
255
+ {
256
+ "studentNumber": "S022",
257
+ "name": "آرزو علی جوی",
258
+ "nationalCode": "960054316",
259
+ "mbti": null,
260
+ "learningStyle": null,
261
+ "ams": null,
262
+ "cooperative": null,
263
+ "grade": 17.98,
264
+ "preferredStudents": [],
265
+ "group": null
266
+ },
267
+ {
268
+ "studentNumber": "S023",
269
+ "name": "آناهیتا قنادزاده",
270
+ "nationalCode": "960089836",
271
+ "mbti": null,
272
+ "learningStyle": null,
273
+ "ams": null,
274
+ "cooperative": null,
275
+ "grade": 18.84,
276
+ "preferredStudents": [],
277
+ "group": null
278
+ },
279
+ {
280
+ "studentNumber": "S024",
281
+ "name": "نیایش کارگر",
282
+ "nationalCode": "929956052",
283
+ "mbti": null,
284
+ "learningStyle": null,
285
+ "ams": null,
286
+ "cooperative": null,
287
+ "grade": 17.74,
288
+ "preferredStudents": [],
289
+ "group": null
290
+ },
291
+ {
292
+ "studentNumber": "S025",
293
+ "name": "باران کبریایی نسب",
294
+ "nationalCode": "980119588",
295
+ "mbti": null,
296
+ "learningStyle": null,
297
+ "ams": null,
298
+ "cooperative": null,
299
+ "grade": 18.82,
300
+ "preferredStudents": [],
301
+ "group": null
302
+ },
303
+ {
304
+ "studentNumber": "S026",
305
+ "name": "زینب کیانوش",
306
+ "nationalCode": "970072678",
307
+ "mbti": null,
308
+ "learningStyle": null,
309
+ "ams": null,
310
+ "cooperative": null,
311
+ "grade": 18.58,
312
+ "preferredStudents": [],
313
+ "group": null
314
+ },
315
+ {
316
+ "studentNumber": "S027",
317
+ "name": "ستایش محمودی",
318
+ "nationalCode": "929904656",
319
+ "mbti": null,
320
+ "learningStyle": null,
321
+ "ams": null,
322
+ "cooperative": null,
323
+ "grade": 19.33,
324
+ "preferredStudents": [],
325
+ "group": null
326
+ },
327
+ {
328
+ "studentNumber": "S028",
329
+ "name": "ستایش مشتاقی",
330
+ "nationalCode": "361282217",
331
+ "mbti": null,
332
+ "learningStyle": null,
333
+ "ams": null,
334
+ "cooperative": null,
335
+ "grade": 17.67,
336
+ "preferredStudents": [],
337
+ "group": null
338
+ },
339
+ {
340
+ "studentNumber": "S029",
341
+ "name": "مهتاب معلمی",
342
+ "nationalCode": "960070265",
343
+ "mbti": null,
344
+ "learningStyle": null,
345
+ "ams": null,
346
+ "cooperative": null,
347
+ "grade": 18.56,
348
+ "preferredStudents": [],
349
+ "group": null
350
+ },
351
+ {
352
+ "studentNumber": "S030",
353
+ "name": "باران وحدتی",
354
+ "nationalCode": "929916913",
355
+ "mbti": null,
356
+ "learningStyle": null,
357
+ "ams": null,
358
+ "cooperative": null,
359
+ "grade": 15.02,
360
+ "preferredStudents": [],
361
+ "group": null
362
+ }
363
+ ],
364
+ "courseName": "ریاضی",
365
+ "groupingComplete": true,
366
+ "groupingResults": {
367
+ "groups": [
368
+ {
369
+ "groupNumber": 1,
370
+ "students": [
371
+ "S001",
372
+ "S003",
373
+ "S004"
374
+ ],
375
+ "reasoning": "این گروه شامل ۲ درون‌گرا (S001, S003) و ۱ برون‌گرا (S004) است. همچنین این گروه شامل یک دانش‌آموز با سبک یادگیری بصری (S001)، یک دانش‌آموز با سبک یادگیری حرکتی (S003) و یک دانش‌آموز با سبک یادگیری بصوتی (S004) است. همچنین نمرات این دانش‌آموزان به ترتیب 18.77، 16.71 و 19.05 است که یک توازن خوب بین نمرات دا��ش‌آموزان در این گروه ایجاد می‌کند."
376
+ },
377
+ {
378
+ "groupNumber": 2,
379
+ "students": [
380
+ "S002"
381
+ ],
382
+ "reasoning": "این گروه شامل یک برون‌گرا (S002) است که با توجه به داده‌های ورودی تنها دانش‌آموز برون‌گرا در داده‌ها است. همچنین این دانش‌آموز با سبک یادگیری بصوتی و نمره 17.28 انتخاب شده است."
383
+ }
384
+ ]
385
+ },
386
+ "resultsVisible": false,
387
+ "teacherPassword": "teacher123"
388
+ }
backend/data/students.json.backup ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "students": [
3
+ {
4
+ "studentNumber": "S001",
5
+ "name": "یاسمن آدینه پور",
6
+ "nationalCode": "929986644",
7
+ "mbti": null,
8
+ "learningStyle": null,
9
+ "ams": null,
10
+ "cooperative": null,
11
+ "grade": 18.77,
12
+ "preferredStudents": [],
13
+ "group": null
14
+ },
15
+ {
16
+ "studentNumber": "S002",
17
+ "name": "پریا احمدزاده",
18
+ "nationalCode": "980085330",
19
+ "mbti": null,
20
+ "learningStyle": null,
21
+ "ams": null,
22
+ "cooperative": null,
23
+ "grade": 17.28,
24
+ "preferredStudents": [],
25
+ "group": null
26
+ },
27
+ {
28
+ "studentNumber": "S003",
29
+ "name": "فاطمه اکبرزاده",
30
+ "nationalCode": "970154550",
31
+ "mbti": null,
32
+ "learningStyle": null,
33
+ "ams": null,
34
+ "cooperative": null,
35
+ "grade": 16.71,
36
+ "preferredStudents": [],
37
+ "group": null
38
+ },
39
+ {
40
+ "studentNumber": "S004",
41
+ "name": "آناهیتا الهی مهر",
42
+ "nationalCode": "26425955",
43
+ "mbti": null,
44
+ "learningStyle": null,
45
+ "ams": null,
46
+ "cooperative": null,
47
+ "grade": 19.05,
48
+ "preferredStudents": [],
49
+ "group": null
50
+ },
51
+ {
52
+ "studentNumber": "S005",
53
+ "name": "مریم امیری",
54
+ "nationalCode": "980093341",
55
+ "mbti": null,
56
+ "learningStyle": null,
57
+ "ams": null,
58
+ "cooperative": null,
59
+ "grade": 18.87,
60
+ "preferredStudents": [],
61
+ "group": null
62
+ },
63
+ {
64
+ "studentNumber": "S006",
65
+ "name": "باران برادران رحیمی",
66
+ "nationalCode": "960043985",
67
+ "mbti": null,
68
+ "learningStyle": null,
69
+ "ams": null,
70
+ "cooperative": null,
71
+ "grade": 19.07,
72
+ "preferredStudents": [],
73
+ "group": null
74
+ },
75
+ {
76
+ "studentNumber": "S007",
77
+ "name": "مایسا بصیری امین",
78
+ "nationalCode": "960089446",
79
+ "mbti": null,
80
+ "learningStyle": null,
81
+ "ams": null,
82
+ "cooperative": null,
83
+ "grade": 19.33,
84
+ "preferredStudents": [],
85
+ "group": null
86
+ },
87
+ {
88
+ "studentNumber": "S008",
89
+ "name": "دلارام ثابت عهد",
90
+ "nationalCode": "960125620",
91
+ "mbti": null,
92
+ "learningStyle": null,
93
+ "ams": null,
94
+ "cooperative": null,
95
+ "grade": 19.55,
96
+ "preferredStudents": [],
97
+ "group": null
98
+ },
99
+ {
100
+ "studentNumber": "S009",
101
+ "name": "شاینا جان محمدی",
102
+ "nationalCode": "960068041",
103
+ "mbti": null,
104
+ "learningStyle": null,
105
+ "ams": null,
106
+ "cooperative": null,
107
+ "grade": 19.47,
108
+ "preferredStudents": [],
109
+ "group": null
110
+ },
111
+ {
112
+ "studentNumber": "S010",
113
+ "name": "آیدا جوان",
114
+ "nationalCode": "95112313",
115
+ "mbti": null,
116
+ "learningStyle": null,
117
+ "ams": null,
118
+ "cooperative": null,
119
+ "grade": 16.77,
120
+ "preferredStudents": [],
121
+ "group": null
122
+ },
123
+ {
124
+ "studentNumber": "S011",
125
+ "name": "سارینا حاجی آبادی",
126
+ "nationalCode": "999216751",
127
+ "mbti": null,
128
+ "learningStyle": null,
129
+ "ams": null,
130
+ "cooperative": null,
131
+ "grade": 16.08,
132
+ "preferredStudents": [],
133
+ "group": null
134
+ },
135
+ {
136
+ "studentNumber": "S012",
137
+ "name": "هستی حسن پور جوان",
138
+ "nationalCode": "960074198",
139
+ "mbti": null,
140
+ "learningStyle": null,
141
+ "ams": null,
142
+ "cooperative": null,
143
+ "grade": 19.55,
144
+ "preferredStudents": [],
145
+ "group": null
146
+ },
147
+ {
148
+ "studentNumber": "S013",
149
+ "name": "فاطمه حسینی",
150
+ "nationalCode": "2400410259",
151
+ "mbti": null,
152
+ "learningStyle": null,
153
+ "ams": null,
154
+ "cooperative": null,
155
+ "grade": 19.07,
156
+ "preferredStudents": [],
157
+ "group": null
158
+ },
159
+ {
160
+ "studentNumber": "S014",
161
+ "name": "غزل خسروی",
162
+ "nationalCode": "929995767",
163
+ "mbti": null,
164
+ "learningStyle": null,
165
+ "ams": null,
166
+ "cooperative": null,
167
+ "grade": 15.05,
168
+ "preferredStudents": [],
169
+ "group": null
170
+ },
171
+ {
172
+ "studentNumber": "S015",
173
+ "name": "غزل ذباح",
174
+ "nationalCode": "960110186",
175
+ "mbti": null,
176
+ "learningStyle": null,
177
+ "ams": null,
178
+ "cooperative": null,
179
+ "grade": 19.25,
180
+ "preferredStudents": [],
181
+ "group": null
182
+ },
183
+ {
184
+ "studentNumber": "S016",
185
+ "name": "نازنین زهرا راشکی",
186
+ "nationalCode": "3661516087",
187
+ "mbti": null,
188
+ "learningStyle": null,
189
+ "ams": null,
190
+ "cooperative": null,
191
+ "grade": 17.02,
192
+ "preferredStudents": [],
193
+ "group": null
194
+ },
195
+ {
196
+ "studentNumber": "S017",
197
+ "name": "ویونا روح نواز",
198
+ "nationalCode": "314458344",
199
+ "mbti": null,
200
+ "learningStyle": null,
201
+ "ams": null,
202
+ "cooperative": null,
203
+ "grade": 18.7,
204
+ "preferredStudents": [],
205
+ "group": null
206
+ },
207
+ {
208
+ "studentNumber": "S018",
209
+ "name": "روژینا سعادتی",
210
+ "nationalCode": "960051023",
211
+ "mbti": null,
212
+ "learningStyle": null,
213
+ "ams": null,
214
+ "cooperative": null,
215
+ "grade": 18.2,
216
+ "preferredStudents": [],
217
+ "group": null
218
+ },
219
+ {
220
+ "studentNumber": "S019",
221
+ "name": "ترنم شعبانی",
222
+ "nationalCode": "950083100",
223
+ "mbti": null,
224
+ "learningStyle": null,
225
+ "ams": null,
226
+ "cooperative": null,
227
+ "grade": 19.37,
228
+ "preferredStudents": [],
229
+ "group": null
230
+ },
231
+ {
232
+ "studentNumber": "S020",
233
+ "name": "ستایش شفابخش",
234
+ "nationalCode": "960126899",
235
+ "mbti": null,
236
+ "learningStyle": null,
237
+ "ams": null,
238
+ "cooperative": null,
239
+ "grade": 18.36,
240
+ "preferredStudents": [],
241
+ "group": null
242
+ },
243
+ {
244
+ "studentNumber": "S021",
245
+ "name": "فاطمه شیرزادخان",
246
+ "nationalCode": "980120756",
247
+ "mbti": null,
248
+ "learningStyle": null,
249
+ "ams": null,
250
+ "cooperative": null,
251
+ "grade": 19.33,
252
+ "preferredStudents": [],
253
+ "group": null
254
+ },
255
+ {
256
+ "studentNumber": "S022",
257
+ "name": "آرزو علی جوی",
258
+ "nationalCode": "960054316",
259
+ "mbti": null,
260
+ "learningStyle": null,
261
+ "ams": null,
262
+ "cooperative": null,
263
+ "grade": 17.98,
264
+ "preferredStudents": [],
265
+ "group": null
266
+ },
267
+ {
268
+ "studentNumber": "S023",
269
+ "name": "آناهیتا قنادزاده",
270
+ "nationalCode": "960089836",
271
+ "mbti": null,
272
+ "learningStyle": null,
273
+ "ams": null,
274
+ "cooperative": null,
275
+ "grade": 18.84,
276
+ "preferredStudents": [],
277
+ "group": null
278
+ },
279
+ {
280
+ "studentNumber": "S024",
281
+ "name": "نیایش کارگر",
282
+ "nationalCode": "929956052",
283
+ "mbti": null,
284
+ "learningStyle": null,
285
+ "ams": null,
286
+ "cooperative": null,
287
+ "grade": 17.74,
288
+ "preferredStudents": [],
289
+ "group": null
290
+ },
291
+ {
292
+ "studentNumber": "S025",
293
+ "name": "باران کبریایی نسب",
294
+ "nationalCode": "980119588",
295
+ "mbti": null,
296
+ "learningStyle": null,
297
+ "ams": null,
298
+ "cooperative": null,
299
+ "grade": 18.82,
300
+ "preferredStudents": [],
301
+ "group": null
302
+ },
303
+ {
304
+ "studentNumber": "S026",
305
+ "name": "زینب کیانوش",
306
+ "nationalCode": "970072678",
307
+ "mbti": null,
308
+ "learningStyle": null,
309
+ "ams": null,
310
+ "cooperative": null,
311
+ "grade": 18.58,
312
+ "preferredStudents": [],
313
+ "group": null
314
+ },
315
+ {
316
+ "studentNumber": "S027",
317
+ "name": "ستایش محمودی",
318
+ "nationalCode": "929904656",
319
+ "mbti": null,
320
+ "learningStyle": null,
321
+ "ams": null,
322
+ "cooperative": null,
323
+ "grade": 19.33,
324
+ "preferredStudents": [],
325
+ "group": null
326
+ },
327
+ {
328
+ "studentNumber": "S028",
329
+ "name": "ستایش مشتاقی",
330
+ "nationalCode": "361282217",
331
+ "mbti": null,
332
+ "learningStyle": null,
333
+ "ams": null,
334
+ "cooperative": null,
335
+ "grade": 17.67,
336
+ "preferredStudents": [],
337
+ "group": null
338
+ },
339
+ {
340
+ "studentNumber": "S029",
341
+ "name": "مهتاب معلمی",
342
+ "nationalCode": "960070265",
343
+ "mbti": null,
344
+ "learningStyle": null,
345
+ "ams": null,
346
+ "cooperative": null,
347
+ "grade": 18.56,
348
+ "preferredStudents": [],
349
+ "group": null
350
+ },
351
+ {
352
+ "studentNumber": "S030",
353
+ "name": "باران وحدتی",
354
+ "nationalCode": "929916913",
355
+ "mbti": null,
356
+ "learningStyle": null,
357
+ "ams": null,
358
+ "cooperative": null,
359
+ "grade": 15.02,
360
+ "preferredStudents": [],
361
+ "group": null
362
+ }
363
+ ],
364
+ "courseName": "ریاضی",
365
+ "groupingComplete": true,
366
+ "groupingResults": {
367
+ "groups": [
368
+ {
369
+ "groupNumber": 1,
370
+ "students": [
371
+ "S001",
372
+ "S003",
373
+ "S004"
374
+ ],
375
+ "reasoning": "این گروه شامل ۲ درون‌گرا (S001, S003) و ۱ برون‌گرا (S004) است. همچنین این گروه شامل یک دانش‌آموز با سبک یادگیری بصری (S001)، یک دانش‌آموز با سبک یادگیری حرکتی (S003) و یک دانش‌آموز با سبک یادگیری بصوتی (S004) است. همچنین نمرات این دانش‌آموزان به ترتیب 18.77، 16.71 و 19.05 است که یک توازن خوب بین نمرات دا��ش‌آموزان در این گروه ایجاد می‌کند."
376
+ },
377
+ {
378
+ "groupNumber": 2,
379
+ "students": [
380
+ "S002"
381
+ ],
382
+ "reasoning": "این گروه شامل یک برون‌گرا (S002) است که با توجه به داده‌های ورودی تنها دانش‌آموز برون‌گرا در داده‌ها است. همچنین این دانش‌آموز با سبک یادگیری بصوتی و نمره 17.28 انتخاب شده است."
383
+ }
384
+ ]
385
+ },
386
+ "resultsVisible": false,
387
+ "teacherPassword": "teacher123"
388
+ }
backend/grouping_logic.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import os
4
+ from typing import List, Dict, Any, Optional
5
+
6
+ # API Configuration
7
+ OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions'
8
+
9
+ def analyze_mbti(mbti: str) -> Dict[str, str]:
10
+ """Helper to break down MBTI into explicit tags"""
11
+ if not mbti or len(mbti) < 4:
12
+ return {"type": "Unknown", "tags": []}
13
+
14
+ m = mbti.upper()
15
+ return {
16
+ "energy": 'Introvert (درون‌گرا)' if m[0] == 'I' else 'Extrovert (برون‌گرا)',
17
+ "info": 'Intuitive' if m[1] == 'N' else 'Sensing',
18
+ "decision": 'Thinking' if m[2] == 'T' else 'Feeling',
19
+ "structure": 'Judging' if m[3] == 'J' else 'Perceiving'
20
+ }
21
+
22
+ def group_students_with_ai(students: List[Any], course_name: str, api_key: Optional[str] = None) -> Dict[str, Any]:
23
+ """
24
+ Group students using OpenRouter API (ChatGPT) with advanced educational psychology principles
25
+ Args:
26
+ students: List of student objects
27
+ course_name: Name of the course
28
+ api_key: OpenRouter API key (optional, falls back to env var)
29
+ """
30
+ # Get API key from parameter or environment variable
31
+ openrouter_key = api_key or os.getenv('OPENROUTER_API_KEY', '')
32
+
33
+ # Clean the API key - remove any whitespace and newlines
34
+ if openrouter_key:
35
+ openrouter_key = openrouter_key.strip().replace('\n', '').replace('\r', '')
36
+
37
+ if not openrouter_key or openrouter_key == '':
38
+ raise Exception(
39
+ "OpenRouter API key not configured! "
40
+ "Please add OPENROUTER_API_KEY in Railway Variables tab. "
41
+ "Get your free key at: https://openrouter.ai/keys"
42
+ )
43
+
44
+ # Validate API key format (but don't log the key)
45
+ if not openrouter_key.startswith('sk-or-v1-'):
46
+ raise Exception(
47
+ f"Invalid API key format. OpenRouter keys should start with 'sk-or-v1-'. "
48
+ f"Please check your OPENROUTER_API_KEY in Railway Variables."
49
+ )
50
+
51
+ # Sanitization & Data Enrichment
52
+ valid_student_ids = set(s.studentNumber for s in students)
53
+
54
+ student_data = []
55
+ for s in students:
56
+ mbti_details = analyze_mbti(s.mbti)
57
+ student_data.append({
58
+ "id": s.studentNumber,
59
+ "name": s.name,
60
+ "mbti": s.mbti,
61
+ "mbti_analysis": mbti_details,
62
+ "learningStyle": s.learningStyle,
63
+ "ams": s.ams if hasattr(s, 'ams') else None,
64
+ "cooperative": s.cooperative if hasattr(s, 'cooperative') else None,
65
+ "grade": s.grade,
66
+ "preferredStudents": [id for id in (s.preferredStudents or []) if id in valid_student_ids]
67
+ })
68
+
69
+ # Dynamic Group Size Logic
70
+ total_students = len(students)
71
+ if total_students < 4:
72
+ size_guidance = "a single group"
73
+ elif total_students < 8:
74
+ size_guidance = "groups of 3-4 students"
75
+ else:
76
+ # Prefer groups of 5, but use groups of 4 if needed to avoid very small groups
77
+ # Examples: 30 students = 6 groups of 5
78
+ # 27 students = 5 groups of 5 + 1 group of 2 (bad) → instead: 3 groups of 5 + 3 groups of 4 (good)
79
+ # 25 students = 5 groups of 5
80
+ # 22 students = 4 groups of 5 + 1 group of 2 (bad) → instead: 2 groups of 5 + 3 groups of 4 (good)
81
+ remainder = total_students % 5
82
+ if remainder == 1:
83
+ # e.g., 21 students: would be 4 groups of 5 + 1 of 1 → instead make 3 groups of 5 + 2 groups of 3
84
+ size_guidance = "groups of 5 students, with some groups of 3-4 if needed to avoid groups smaller than 3"
85
+ elif remainder == 2:
86
+ # e.g., 22 students: would be 4 groups of 5 + 1 of 2 → instead make 2 groups of 5 + 3 groups of 4
87
+ size_guidance = "groups of 5 students, with some groups of 4 if needed to avoid groups of 2"
88
+ else:
89
+ size_guidance = "groups of 5 students"
90
+
91
+ # The Enhanced Prompt
92
+ prompt = f"""You are an expert educational psychologist specializing in adolescent team formation and Vygotsky's Zone of Proximal Development (ZPD). Create optimal learning groups for "{course_name}" course with 15-16 year old students.
93
+
94
+ INPUT DATA:
95
+ {json.dumps(student_data, ensure_ascii=False, indent=2)}
96
+
97
+ TOTAL STUDENTS: {total_students}
98
+ GROUPING STRATEGY: Prefer {size_guidance}. IMPORTANT: Avoid creating groups with only 1-2 students. If the math doesn't work out evenly with groups of 5, adjust by creating some groups of 4 to balance the numbers. For example:
99
+ - 30 students = 6 groups of 5 ✓
100
+ - 27 students = 3 groups of 5 + 3 groups of 4 ✓ (NOT 5 groups of 5 + 1 group of 2 ✗)
101
+ - 25 students = 5 groups of 5 ✓
102
+ - 22 students = 2 groups of 5 + 3 groups of 4 ✓ (NOT 4 groups of 5 + 1 group of 2 ✗)
103
+
104
+ STUDENT AGE CONTEXT (15-16 years - Adolescence):
105
+ - High need for peer acceptance and social belonging
106
+ - Developing abstract thinking and metacognition
107
+ - Identity formation through social interactions
108
+ - Sensitivity to feedback from peers
109
+ - Collaborative learning enhances engagement
110
+
111
+ GROUPING FRAMEWORK - PRIORITY ORDER:
112
+
113
+ 1. **ZPD OPTIMIZATION (Zone of Proximal Development)** - 30%
114
+ - Mix academic performance (grade field) to create ZPD scaffolding
115
+ - Place high performers (معدل بالا) with medium performers for peer tutoring
116
+ - Avoid grouping all high or all low performers together
117
+ - Target: Each group should have grade variance of 1-2 points to maximize learning
118
+
119
+ 2. **MBTI COMPLEMENTARITY (NOT Similarity)** - 25%
120
+ Research-based MBTI pairings for adolescent teamwork:
121
+ - ENFP + INTJ: Visionary creativity with strategic planning
122
+ - ENTP + INFJ: Innovation meets deep insight and empathy
123
+ - ENTJ + INFP: Leadership with values-driven creativity
124
+ - ESTJ + ISFP: Organization with practical creativity
125
+ - ESFJ + INTP: Social cohesion with analytical thinking
126
+ - ESTP + ISFJ: Action-oriented with detail consciousness
127
+ - ENFJ + ISTP: Motivational leadership with technical problem-solving
128
+ - ESFP + ISTJ: Enthusiasm with reliability and structure
129
+
130
+ KEY PRINCIPLES:
131
+ - Balance E (Extrovert) and I (Introvert): 2-3 of each per group
132
+ - Complement T (Thinking) with F (Feeling) for balanced decision-making
133
+ - Mix N (Intuitive) with S (Sensing) for big-picture + detail focus
134
+ - Combine J (Judging) with P (Perceiving) for structure + flexibility
135
+
136
+ 3. **VARK DIVERSITY (Learning Styles)** - 20%
137
+ - Include different learning styles in each group:
138
+ * Visual (دیداری): Diagrams, charts, spatial understanding
139
+ * Aural (شنیداری): Discussions, verbal explanations
140
+ * Read/Write: Text-based learning, note-taking
141
+ * Kinesthetic (حرکتی): Hands-on, experiential learning
142
+ - Diversity ensures multiple teaching approaches within group
143
+ - Adolescents learn best when exposed to varied learning methods
144
+
145
+ 4. **ACADEMIC MOTIVATION (AMS Score)** - 15%
146
+ - AMS field: Academic Motivation Scale (0-196)
147
+ - Balance high and moderate motivation levels
148
+ - High motivation students (>140) can inspire others
149
+ - Avoid grouping all low-motivation (<100) students together
150
+ - Target: Each group has at least one high-motivation member
151
+
152
+ 5. **COOPERATIVE LEARNING SKILLS** - 10%
153
+ - Cooperative field: Cooperation ability (0-125)
154
+ - High cooperation students (>88) act as social facilitators
155
+ - Mix cooperation levels for peer modeling
156
+ - Students with strong cooperation skills help integrate introverts
157
+
158
+ 6. **COURSE-SPECIFIC REQUIREMENTS** - Based on "{course_name}":
159
+ - Math/Science: Prioritize T (Thinking) types, Visual/Kinesthetic learners
160
+ - Literature/Humanities: Include F (Feeling) types, Read/Write learners
161
+ - Projects/Labs: Need high Kinesthetic and ESTP/ISTP types
162
+ - Discussion-based: Ensure Aural learners and E (Extrovert) types
163
+
164
+ 7. **STUDENT PREFERENCES** - 5% (Secondary consideration)
165
+ - Honor "preferredStudents" field ONLY if it doesn't compromise above criteria
166
+ - Adolescents benefit from working outside comfort zones
167
+ - Strategic separation can reduce cliques and expand social circles
168
+
169
+ CRITICAL RULES:
170
+ ✓ ALL students MUST be assigned to a group
171
+ ✓ PREFER groups of 5 students to minimize total number of groups
172
+ ✓ Adjust group sizes (use groups of 4) to avoid creating groups with only 1-2 students
173
+ ✓ Each group should have 3-5 students (never 1-2 students alone)
174
+ ✓ Each group needs MBTI balance: 2-3 Introverts + 2-3 Extroverts
175
+ ✓ Each group needs grade diversity: Mix high (>18) with medium (16-18) performers
176
+ ✓ Prioritize complementary MBTI types over similar types
177
+ ✓ Use provided data fields - DO NOT invent values
178
+ ✓ **ABSOLUTELY CRITICAL**: Each student ID can appear in EXACTLY ONE group. NO DUPLICATES. Verify this before outputting.
179
+ ✓ Double-check: Count total students in all groups = input students count
180
+
181
+ OUTPUT FORMAT (Valid JSON Only):
182
+ {{
183
+ "groups": [
184
+ {{
185
+ "groupNumber": 1,
186
+ "students": ["S001", "S002", "S003", "S004"],
187
+ "reasoning": "توضیحات کامل به فارسی - شامل: (1) تحلیل ZPD: معدل‌ها و چگونگی یادگیری همیاری (2) تکمیل MBTI: چرا این تیپ‌ها با هم سازگارند (3) تنوع VARK (4) سطح انگیزش و همکاری (5) مناسب بودن برای درس {course_name}. مثال: 'این گروه دارای ZPD مطلوب است: S001 (معدل 19.5) و S002 (معدل 17.2) به S003 (معدل 16) کمک می‌کنند. تکمیل MBTI: ENFP (S001) با خلاقیت و INTJ (S002) با برنامه‌ریزی استراتژیک همکاری می‌کنند. تنوع یادگیری: 2 Visual، 1 Aural، 1 Kinesthetic. انگیزش بالا (AMS>150) در S001 الهام‌بخش است.'"
188
+ }}
189
+ ]
190
+ }}"""
191
+
192
+ # Make API call using requests library
193
+ headers = {
194
+ 'Content-Type': 'application/json',
195
+ 'Authorization': f'Bearer {openrouter_key}',
196
+ 'HTTP-Referer': 'https://talimbot.up.railway.app',
197
+ 'X-Title': 'TalimBot'
198
+ }
199
+
200
+ payload = {
201
+ 'model': 'openai/gpt-4o', # Using GPT-4o for better accuracy and reasoning
202
+ 'messages': [
203
+ {
204
+ 'role': 'system',
205
+ 'content': 'You are a precise algorithmic grouping assistant. You MUST output ONLY valid JSON - no markdown, no code blocks, no extra text. Start directly with { and end with }. CRITICAL RULE: Each student can appear in EXACTLY ONE group - no duplicates allowed. You rely on the explicit "mbti_analysis" fields provided in the user prompt for your reasoning. Verify that all student IDs appear exactly once across all groups.'
206
+ },
207
+ {
208
+ 'role': 'user',
209
+ 'content': prompt
210
+ }
211
+ ],
212
+ 'temperature': 0.2 # Lower temperature for more consistent, logical grouping
213
+ }
214
+
215
+ print(f"Sending request to OpenRouter API...")
216
+
217
+ response = requests.post(
218
+ OPENROUTER_API_URL,
219
+ headers=headers,
220
+ json=payload,
221
+ timeout=60
222
+ )
223
+
224
+ print(f"Response status: {response.status_code}")
225
+ print(f"Response preview: {response.text[:200]}")
226
+
227
+ if response.status_code == 401:
228
+ try:
229
+ error_data = response.json()
230
+ error_msg = error_data.get('error', {}).get('message', 'Unauthorized')
231
+ except:
232
+ error_msg = response.text
233
+
234
+ raise Exception(
235
+ f"OpenRouter Authentication Error: {error_msg}. "
236
+ f"Your API key is configured but invalid. Please:\n"
237
+ f"1. Go to https://openrouter.ai/keys\n"
238
+ f"2. Check if your key is active and has credits\n"
239
+ f"3. Create a NEW key if needed\n"
240
+ f"4. Update OPENROUTER_API_KEY in Railway Variables"
241
+ )
242
+
243
+ if response.status_code == 402:
244
+ raise Exception(
245
+ "OpenRouter Payment Required: Your account has no credits. "
246
+ "Add credits at https://openrouter.ai/credits"
247
+ )
248
+
249
+ if not response.ok:
250
+ try:
251
+ error_data = response.json()
252
+ error_detail = error_data.get('error', {}).get('message', response.text)
253
+ except:
254
+ error_detail = response.text
255
+ raise Exception(f"API request failed ({response.status_code}): {error_detail}")
256
+
257
+ data = response.json()
258
+ content = data['choices'][0]['message']['content']
259
+ print(f"🔍 DEBUG: Got response content, length: {len(content)}")
260
+
261
+ # Parse Result - Extract JSON from markdown code blocks if present
262
+ try:
263
+ # Try direct JSON parse first
264
+ grouping_result = json.loads(content)
265
+ except json.JSONDecodeError as e:
266
+ # Try to extract JSON from markdown code blocks
267
+ import re
268
+
269
+ # Look for JSON in ```json ... ``` or ``` ... ``` blocks
270
+ json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', content, re.DOTALL)
271
+ if json_match:
272
+ try:
273
+ grouping_result = json.loads(json_match.group(1))
274
+ print(f"✅ Extracted JSON from markdown code block")
275
+ except json.JSONDecodeError:
276
+ print(f"Failed to parse JSON from code block: {json_match.group(1)[:200]}")
277
+ raise Exception("Invalid JSON from API (even after markdown extraction)")
278
+ else:
279
+ # Try to find JSON object in the content
280
+ json_match = re.search(r'\{.*"groups".*\}', content, re.DOTALL)
281
+ if json_match:
282
+ try:
283
+ grouping_result = json.loads(json_match.group(0))
284
+ print(f"✅ Extracted JSON object from response")
285
+ except json.JSONDecodeError:
286
+ print(f"Failed to parse extracted JSON: {json_match.group(0)[:200]}")
287
+ raise Exception("Invalid JSON from API (extraction failed)")
288
+ else:
289
+ print(f"❌ No JSON found in response. Full content:\n{content}")
290
+ raise Exception("Invalid JSON from API - no valid JSON structure found")
291
+
292
+ # Failsafe: Add missing students if AI messed up
293
+ assigned_students = set()
294
+ for group in grouping_result['groups']:
295
+ if 'students' in group:
296
+ assigned_students.update(group['students'])
297
+
298
+ all_ids = [s.studentNumber for s in students]
299
+ missing = [id for id in all_ids if id not in assigned_students]
300
+
301
+ if missing:
302
+ print(f'AI missed students, adding to last group: {missing}')
303
+ if grouping_result['groups']:
304
+ grouping_result['groups'][-1]['students'].extend(missing)
305
+ grouping_result['groups'][-1]['reasoning'] += f" (سیستم دانش‌آموزان {', '.join(missing)} را به این گروه اضافه کرد)"
306
+ else:
307
+ grouping_result['groups'].append({
308
+ "groupNumber": 1,
309
+ "students": missing,
310
+ "reasoning": "گروه بازیابی شده توسط سیستم"
311
+ })
312
+
313
+ return grouping_result
314
+
315
+ async def random_grouping(students: List[Any]) -> Dict[str, Any]:
316
+ """Fallback random grouping if API fails"""
317
+ import random
318
+
319
+ shuffled = students.copy()
320
+ random.shuffle(shuffled)
321
+
322
+ group_size = 5
323
+ num_groups = (len(shuffled) + group_size - 1) // group_size
324
+
325
+ groups = []
326
+ for i in range(num_groups):
327
+ group_students = shuffled[i * group_size:(i + 1) * group_size]
328
+ groups.append({
329
+ "groupNumber": i + 1,
330
+ "students": [s.studentNumber for s in group_students],
331
+ "reasoning": "گروه‌بندی تصادفی (API در دسترس نبود)"
332
+ })
333
+
334
+ return {"groups": groups}
backend/main.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.responses import FileResponse
5
+ from pydantic import BaseModel
6
+ from typing import List, Optional, Dict, Any
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ from dotenv import load_dotenv
11
+
12
+ # Load environment variables from .env file (for local development)
13
+ load_dotenv()
14
+
15
+ app = FastAPI()
16
+
17
+ # CORS configuration - allow all origins for flexibility
18
+ app.add_middleware(
19
+ CORSMiddleware,
20
+ allow_origins=["*"],
21
+ allow_credentials=True,
22
+ allow_methods=["*"],
23
+ allow_headers=["*"],
24
+ )
25
+
26
+ # Data file path
27
+ DATA_FILE = Path(__file__).parent / "data" / "students.json"
28
+ DATA_FILE.parent.mkdir(exist_ok=True)
29
+
30
+ # Pydantic models
31
+ class Student(BaseModel):
32
+ studentNumber: str
33
+ name: str
34
+ nationalCode: str = ""
35
+ mbti: Optional[str] = None
36
+ learningStyle: Optional[str] = None
37
+ ams: Optional[str] = None
38
+ cooperative: Optional[str] = None
39
+ grade: float
40
+ preferredStudents: List[str] = []
41
+ group: Optional[int] = None
42
+
43
+ class StudentUpdate(BaseModel):
44
+ mbti: Optional[str] = None
45
+ learningStyle: Optional[str] = None
46
+ ams: Optional[str] = None
47
+ cooperative: Optional[str] = None
48
+ preferredStudents: Optional[List[str]] = None
49
+
50
+ class GroupingRequest(BaseModel):
51
+ courseName: str
52
+
53
+ class TeacherAuthRequest(BaseModel):
54
+ password: str
55
+
56
+ class StudentAuthRequest(BaseModel):
57
+ studentNumber: str
58
+ nationalCode: str
59
+
60
+ class SystemData(BaseModel):
61
+ students: List[Student]
62
+ courseName: str = ""
63
+ groupingComplete: bool = False
64
+ groupingResults: Optional[Dict[str, Any]] = None
65
+ resultsVisible: bool = False
66
+ teacherPassword: str = "teacher123"
67
+
68
+ # Initialize data
69
+ def load_data() -> SystemData:
70
+ if DATA_FILE.exists():
71
+ with open(DATA_FILE, 'r', encoding='utf-8') as f:
72
+ data = json.load(f)
73
+ return SystemData(**data)
74
+ else:
75
+ # Initialize with real 30 students from class data
76
+ initial_students = [
77
+ Student(studentNumber='S001', name='یاسمن آدینه پور', nationalCode='929986644', grade=18.77),
78
+ Student(studentNumber='S002', name='پریا احمدزاده', nationalCode='980085330', grade=17.28),
79
+ Student(studentNumber='S003', name='فاطمه اکبرزاده', nationalCode='970154550', grade=16.71),
80
+ Student(studentNumber='S004', name='آناهیتا الهی مهر', nationalCode='26425955', grade=19.05),
81
+ Student(studentNumber='S005', name='مریم امیری', nationalCode='980093341', grade=18.87),
82
+ Student(studentNumber='S006', name='باران برادران رحیمی', nationalCode='960043985', grade=19.07),
83
+ Student(studentNumber='S007', name='مایسا بصیری امین', nationalCode='960089446', grade=19.33),
84
+ Student(studentNumber='S008', name='دلارام ثابت عهد', nationalCode='960125620', grade=19.55),
85
+ Student(studentNumber='S009', name='شاینا جان محمدی', nationalCode='960068041', grade=19.47),
86
+ Student(studentNumber='S010', name='آیدا جوان', nationalCode='95112313', grade=16.77),
87
+ Student(studentNumber='S011', name='سارینا حاجی آبادی', nationalCode='999216751', grade=16.08),
88
+ Student(studentNumber='S012', name='هستی حسن پور جوان', nationalCode='960074198', grade=19.55),
89
+ Student(studentNumber='S013', name='فاطمه حسینی', nationalCode='2400410259', grade=19.07),
90
+ Student(studentNumber='S014', name='غزل خسروی', nationalCode='929995767', grade=15.05),
91
+ Student(studentNumber='S015', name='غزل ذباح', nationalCode='960110186', grade=19.25),
92
+ Student(studentNumber='S016', name='نازنین زهرا راشکی', nationalCode='3661516087', grade=17.02),
93
+ Student(studentNumber='S017', name='ویونا روح نواز', nationalCode='314458344', grade=18.70),
94
+ Student(studentNumber='S018', name='روژینا سعادتی', nationalCode='960051023', grade=18.20),
95
+ Student(studentNumber='S019', name='ترنم شعبانی', nationalCode='950083100', grade=19.37),
96
+ Student(studentNumber='S020', name='ستایش شفابخش', nationalCode='960126899', grade=18.36),
97
+ Student(studentNumber='S021', name='فاطمه شیرزادخان', nationalCode='980120756', grade=19.33),
98
+ Student(studentNumber='S022', name='آرزو علی جوی', nationalCode='960054316', grade=17.98),
99
+ Student(studentNumber='S023', name='آناهیتا قنادزاده', nationalCode='960089836', grade=18.84),
100
+ Student(studentNumber='S024', name='نیایش کارگر', nationalCode='929956052', grade=17.74),
101
+ Student(studentNumber='S025', name='باران کبریایی نسب', nationalCode='980119588', grade=18.82),
102
+ Student(studentNumber='S026', name='زینب کیانوش', nationalCode='970072678', grade=18.58),
103
+ Student(studentNumber='S027', name='ستایش محمودی', nationalCode='929904656', grade=19.33),
104
+ Student(studentNumber='S028', name='ستایش مشتاقی', nationalCode='361282217', grade=17.67),
105
+ Student(studentNumber='S029', name='مهتاب معلمی', nationalCode='960070265', grade=18.56),
106
+ Student(studentNumber='S030', name='باران وحدتی', nationalCode='929916913', grade=15.02),
107
+ ]
108
+
109
+ data = SystemData(students=initial_students)
110
+ save_data(data)
111
+ return data
112
+
113
+ def save_data(data: SystemData):
114
+ with open(DATA_FILE, 'w', encoding='utf-8') as f:
115
+ json.dump(data.dict(), f, ensure_ascii=False, indent=2)
116
+
117
+ # API Endpoints
118
+ @app.get("/")
119
+ def read_root():
120
+ return {"message": "TalimBot API is running"}
121
+
122
+ @app.get("/api/students")
123
+ def get_all_students():
124
+ data = load_data()
125
+ return {"students": data.students}
126
+
127
+ @app.get("/api/student/{student_number}")
128
+ def get_student(student_number: str):
129
+ # Return demo account if requested
130
+ if student_number == "DEMO":
131
+ return Student(
132
+ studentNumber="DEMO",
133
+ name="پریناز عاکف",
134
+ nationalCode="0921111111",
135
+ grade=0.0,
136
+ mbti=None,
137
+ learningStyle=None,
138
+ ams=None,
139
+ cooperative=None,
140
+ preferredStudents=[],
141
+ group=None
142
+ )
143
+
144
+ data = load_data()
145
+ student = next((s for s in data.students if s.studentNumber == student_number), None)
146
+ if not student:
147
+ raise HTTPException(status_code=404, detail="Student not found")
148
+ return student
149
+
150
+ @app.put("/api/student/{student_number}")
151
+ def update_student(student_number: str, updates: StudentUpdate):
152
+ # Silently ignore updates to demo account (pretend it worked)
153
+ if student_number == "DEMO":
154
+ demo_student = Student(
155
+ studentNumber="DEMO",
156
+ name="پریناز عاکف",
157
+ nationalCode="0921111111",
158
+ grade=0.0,
159
+ mbti=updates.mbti,
160
+ learningStyle=updates.learningStyle,
161
+ ams=updates.ams,
162
+ cooperative=updates.cooperative,
163
+ preferredStudents=updates.preferredStudents or [],
164
+ group=None
165
+ )
166
+ return {"success": True, "student": demo_student}
167
+
168
+ data = load_data()
169
+ student = next((s for s in data.students if s.studentNumber == student_number), None)
170
+ if not student:
171
+ raise HTTPException(status_code=404, detail="Student not found")
172
+
173
+ # Update only provided fields
174
+ update_dict = updates.dict(exclude_unset=True)
175
+ for key, value in update_dict.items():
176
+ setattr(student, key, value)
177
+
178
+ save_data(data)
179
+ return {"success": True, "student": student}
180
+
181
+ class GroupingRequest(BaseModel):
182
+ courseName: str
183
+
184
+ @app.post("/api/grouping/perform")
185
+ async def perform_grouping(request: GroupingRequest):
186
+ data = load_data()
187
+
188
+ # Get API key from environment variable
189
+ api_key = os.getenv("OPENROUTER_API_KEY")
190
+
191
+ if not api_key:
192
+ raise HTTPException(
193
+ status_code=500,
194
+ detail="OpenRouter API key not configured. Please set OPENROUTER_API_KEY in Railway Variables tab"
195
+ )
196
+
197
+ # Get students with complete info (mbti and learningStyle required)
198
+ students_with_info = [s for s in data.students if s.mbti and s.learningStyle]
199
+
200
+ if len(students_with_info) == 0:
201
+ raise HTTPException(status_code=400, detail="No students have completed their profiles yet")
202
+
203
+ try:
204
+ # Import grouping logic
205
+ from grouping_logic import group_students_with_ai
206
+
207
+ # group_students_with_ai is now synchronous (uses requests library)
208
+ # Run it in a thread pool to avoid blocking
209
+ import asyncio
210
+ from concurrent.futures import ThreadPoolExecutor
211
+
212
+ loop = asyncio.get_event_loop()
213
+ with ThreadPoolExecutor() as executor:
214
+ grouping_result = await loop.run_in_executor(
215
+ executor,
216
+ lambda: group_students_with_ai(students_with_info, request.courseName, api_key)
217
+ )
218
+
219
+ # Apply grouping to students
220
+ for student in data.students:
221
+ student.group = None
222
+
223
+ for group in grouping_result["groups"]:
224
+ for student_number in group["students"]:
225
+ student = next((s for s in data.students if s.studentNumber == student_number), None)
226
+ if student:
227
+ student.group = group["groupNumber"]
228
+
229
+ data.courseName = request.courseName
230
+ data.groupingComplete = True
231
+ data.groupingResults = grouping_result
232
+ data.resultsVisible = False # Teacher must manually show results
233
+
234
+ save_data(data)
235
+ return {"success": True, "results": grouping_result}
236
+
237
+ except Exception as e:
238
+ raise HTTPException(status_code=500, detail=str(e))
239
+
240
+ @app.get("/api/grouping/status")
241
+ def get_grouping_status():
242
+ data = load_data()
243
+ students_with_info = [s for s in data.students if s.mbti and s.learningStyle]
244
+ students_grouped = [s for s in data.students if s.group is not None]
245
+
246
+ return {
247
+ "totalStudents": len(data.students),
248
+ "studentsWithCompleteInfo": len(students_with_info),
249
+ "studentsGrouped": len(students_grouped),
250
+ "groupingComplete": data.groupingComplete,
251
+ "resultsVisible": data.resultsVisible,
252
+ "courseName": data.courseName,
253
+ "groups": data.groupingResults.get("groups", []) if data.groupingResults else []
254
+ }
255
+
256
+ class PasswordRequest(BaseModel):
257
+ password: str
258
+
259
+ @app.post("/api/grouping/toggle-visibility")
260
+ def toggle_results_visibility(request: PasswordRequest):
261
+ data = load_data()
262
+ if request.password != data.teacherPassword:
263
+ raise HTTPException(status_code=403, detail="Invalid password")
264
+
265
+ data.resultsVisible = not data.resultsVisible
266
+ save_data(data)
267
+ return {"success": True, "resultsVisible": data.resultsVisible}
268
+
269
+ @app.post("/api/grouping/reset")
270
+ def reset_grouping(request: PasswordRequest):
271
+ data = load_data()
272
+ if request.password != data.teacherPassword:
273
+ raise HTTPException(status_code=403, detail="Invalid password")
274
+
275
+ # Clear ONLY grouping-related data, keep student profiles intact
276
+ for student in data.students:
277
+ student.group = None
278
+
279
+ data.groupingComplete = False
280
+ data.groupingResults = None
281
+ data.resultsVisible = False
282
+ data.courseName = ""
283
+
284
+ save_data(data)
285
+ return {"success": True}
286
+
287
+ @app.post("/api/data/reset-all")
288
+ def reset_all_data(request: PasswordRequest):
289
+ data = load_data()
290
+ if request.password != data.teacherPassword:
291
+ raise HTTPException(status_code=403, detail="Invalid password")
292
+
293
+ # Clear ALL student data fields AND grouping
294
+ for student in data.students:
295
+ student.group = None
296
+ student.mbti = None
297
+ student.learningStyle = None
298
+ student.ams = None
299
+ student.cooperative = None
300
+ student.preferredStudents = []
301
+
302
+ data.groupingComplete = False
303
+ data.groupingResults = None
304
+ data.resultsVisible = False
305
+ data.courseName = ""
306
+
307
+ save_data(data)
308
+ return {"success": True}
309
+
310
+ @app.post("/api/auth/teacher")
311
+ def check_teacher_password(request: TeacherAuthRequest):
312
+ data = load_data()
313
+ return {"valid": request.password == data.teacherPassword}
314
+
315
+ @app.post("/api/auth/student")
316
+ def authenticate_student(request: StudentAuthRequest):
317
+ # Special demo account - not stored in database
318
+ # Check for national code without leading zero (frontend strips it)
319
+ if request.nationalCode == "921111111":
320
+ demo_student = Student(
321
+ studentNumber="DEMO",
322
+ name="پریناز عاکف",
323
+ nationalCode="921111111",
324
+ grade=0.0,
325
+ mbti=None,
326
+ learningStyle=None,
327
+ ams=None,
328
+ cooperative=None,
329
+ preferredStudents=[],
330
+ group=None
331
+ )
332
+ return {"valid": True, "student": demo_student}
333
+
334
+ data = load_data()
335
+ student = next((s for s in data.students if s.studentNumber == request.studentNumber), None)
336
+ if not student:
337
+ raise HTTPException(status_code=404, detail="Student not found")
338
+
339
+ if student.nationalCode != request.nationalCode:
340
+ raise HTTPException(status_code=401, detail="Invalid national code")
341
+
342
+ return {"valid": True, "student": student}
343
+
344
+ class NationalCodeAuthRequest(BaseModel):
345
+ nationalCode: str
346
+
347
+ @app.post("/api/auth/student-by-nationalcode")
348
+ def authenticate_student_by_nationalcode(request: NationalCodeAuthRequest):
349
+ # Special demo account - not stored in database
350
+ # Check for national code without leading zero (frontend strips it)
351
+ if request.nationalCode == "921111111":
352
+ demo_student = Student(
353
+ studentNumber="DEMO",
354
+ name="پریناز عاکف",
355
+ nationalCode="921111111",
356
+ grade=0.0,
357
+ mbti=None,
358
+ learningStyle=None,
359
+ ams=None,
360
+ cooperative=None,
361
+ preferredStudents=[],
362
+ group=None
363
+ )
364
+ return {"valid": True, "student": demo_student}
365
+
366
+ data = load_data()
367
+ # Find student by national code (without leading zero)
368
+ student = next((s for s in data.students if s.nationalCode == request.nationalCode), None)
369
+ if not student:
370
+ raise HTTPException(status_code=404, detail="کد ملی در سیستم یافت نشد")
371
+
372
+ return {"valid": True, "student": student}
373
+
374
+ @app.get("/api/student/{student_number}/group")
375
+ def get_student_group(student_number: str):
376
+ # Demo account has no group
377
+ if student_number == "DEMO":
378
+ raise HTTPException(status_code=404, detail="Demo account is not part of any group")
379
+
380
+ data = load_data()
381
+
382
+ if not data.resultsVisible:
383
+ raise HTTPException(status_code=403, detail="Results are not yet visible")
384
+
385
+ student = next((s for s in data.students if s.studentNumber == student_number), None)
386
+ if not student:
387
+ raise HTTPException(status_code=404, detail="Student not found")
388
+
389
+ if student.group is None:
390
+ raise HTTPException(status_code=404, detail="Student not assigned to a group yet")
391
+
392
+ # Find all students in the same group
393
+ group_members = [s for s in data.students if s.group == student.group]
394
+
395
+ # Find the group details from results
396
+ group_info = None
397
+ if data.groupingResults:
398
+ for g in data.groupingResults.get("groups", []):
399
+ if g["groupNumber"] == student.group:
400
+ group_info = g
401
+ break
402
+
403
+ return {
404
+ "groupNumber": student.group,
405
+ "members": group_members,
406
+ "reasoning": group_info.get("reasoning", "") if group_info else "",
407
+ "courseName": data.courseName
408
+ }
409
+
410
+ @app.get("/api/data/backup")
411
+ def get_data_backup():
412
+ """Download complete student data as JSON backup for safekeeping"""
413
+ data = load_data()
414
+ return data.dict()
415
+
416
+ # ============================================
417
+ # STATIC FILE SERVING (Frontend HTML/CSS/JS)
418
+ # ============================================
419
+
420
+ # Define static directory path
421
+ STATIC_DIR = Path(__file__).parent / "static"
422
+
423
+ # Mount static files FIRST - this handles all non-API routes
424
+ app.mount("/", StaticFiles(directory=str(STATIC_DIR), html=True), name="static")
425
+
426
+ if __name__ == "__main__":
427
+ import uvicorn
428
+ uvicorn.run(app, host="0.0.0.0", port=8000)
backend/requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn==0.24.0
3
+ pydantic==2.5.0
4
+ requests==2.31.0
5
+ python-multipart==0.0.6
6
+ python-dotenv==1.0.0
backend/static/Icons/Additional/teacherIcon.png ADDED
backend/static/Icons/Additional/teacherIcon2.jpg ADDED
backend/static/Icons/logo/Icon.png ADDED
backend/static/Icons/logo/Logo_blueBackground.jpg ADDED
backend/static/Icons/logo/Logo_noBackground.jpg ADDED
backend/static/Icons/logo/logo.png ADDED
backend/static/Icons/studentIcon.png ADDED
backend/static/Icons/teacherIcon3.png ADDED
backend/static/assets/css/styles.css ADDED
@@ -0,0 +1,727 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* سیستم گروه‌بندی دانشجویان - طراحی مدرن فارسی */
2
+
3
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700&display=swap');
4
+
5
+ * {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ :root {
12
+ --primary-color: #381c80;
13
+ --primary-dark: #2a1560;
14
+ --secondary-color: #1e3a8a;
15
+ --accent-color: #5b21b6;
16
+ --success-color: #10b981;
17
+ --warning-color: #f59e0b;
18
+ --danger-color: #ef4444;
19
+ --text-dark: #1f2937;
20
+ --text-light: #6b7280;
21
+ --bg-light: #f8fafc;
22
+ --bg-white: #ffffff;
23
+ --border-color: #e2e8f0;
24
+ --shadow-sm: 0 1px 2px 0 rgba(56, 28, 128, 0.05);
25
+ --shadow-md: 0 4px 6px -1px rgba(56, 28, 128, 0.1), 0 2px 4px -1px rgba(56, 28, 128, 0.06);
26
+ --shadow-lg: 0 10px 15px -3px rgba(56, 28, 128, 0.15), 0 4px 6px -2px rgba(56, 28, 128, 0.08);
27
+ --shadow-xl: 0 20px 25px -5px rgba(56, 28, 128, 0.2), 0 10px 10px -5px rgba(56, 28, 128, 0.1);
28
+ }
29
+
30
+ body {
31
+ font-family: 'Vazirmatn', sans-serif;
32
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
33
+ min-height: 100vh;
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ padding: 20px;
38
+ color: var(--text-dark);
39
+ line-height: 1.8;
40
+ direction: rtl;
41
+ text-align: right;
42
+ }
43
+
44
+ /* صفحات مختلف با تصاویر پس‌زمینه */
45
+ body.login-page {
46
+ background: url('images/blueBackground.jpg') no-repeat center center fixed;
47
+ background-size: cover;
48
+ }
49
+
50
+ body.dashboard-page {
51
+ background: url('images/darkBlueMixed.png') no-repeat center center fixed;
52
+ background-size: cover;
53
+ align-items: flex-start;
54
+ }
55
+
56
+ body.teacher-page {
57
+ background: url('images/darkBluePlain.jpg') no-repeat center center fixed;
58
+ background-size: cover;
59
+ align-items: flex-start;
60
+ }
61
+
62
+ /* اور‌لی برای خوانایی بهتر متن */
63
+ body.login-page::before,
64
+ body.dashboard-page::before,
65
+ body.teacher-page::before {
66
+ content: '';
67
+ position: fixed;
68
+ top: 0;
69
+ left: 0;
70
+ right: 0;
71
+ bottom: 0;
72
+ background: rgba(56, 28, 128, 0.15);
73
+ z-index: -1;
74
+ }
75
+
76
+ /* انیمیشن‌ها */
77
+ @keyframes fadeInUp {
78
+ from {
79
+ opacity: 0;
80
+ transform: translateY(30px);
81
+ }
82
+ to {
83
+ opacity: 1;
84
+ transform: translateY(0);
85
+ }
86
+ }
87
+
88
+ @keyframes spin {
89
+ to { transform: rotate(360deg); }
90
+ }
91
+
92
+ /* کانتینرها */
93
+ .container {
94
+ background: rgba(255, 255, 255, 0.98);
95
+ backdrop-filter: blur(10px);
96
+ border-radius: 20px;
97
+ box-shadow: var(--shadow-xl);
98
+ padding: 35px;
99
+ max-width: 480px;
100
+ width: 100%;
101
+ animation: fadeInUp 0.5s ease;
102
+ border: 1px solid rgba(255, 255, 255, 0.3);
103
+ }
104
+
105
+ .dashboard-container {
106
+ max-width: 1150px;
107
+ padding: 25px;
108
+ background: transparent;
109
+ box-shadow: none;
110
+ border: none;
111
+ }
112
+
113
+ /* لوگو و هدر */
114
+ .logo-section {
115
+ text-align: center;
116
+ margin-bottom: 20px;
117
+ }
118
+
119
+ .logo-section img {
120
+ max-width: 200px;
121
+ height: auto;
122
+ margin-bottom: 15px;
123
+ }
124
+
125
+ .header {
126
+ text-align: center;
127
+ margin-bottom: 25px;
128
+ }
129
+
130
+ .header h1 {
131
+ font-size: 1.7rem;
132
+ font-weight: 700;
133
+ color: var(--primary-color);
134
+ margin-bottom: 8px;
135
+ }
136
+
137
+ .header p {
138
+ color: var(--text-light);
139
+ font-size: 0.95rem;
140
+ }
141
+
142
+ /* انتخاب نقش */
143
+ .role-selector {
144
+ display: grid;
145
+ grid-template-columns: 1fr 1fr;
146
+ gap: 12px;
147
+ margin-bottom: 20px;
148
+ }
149
+
150
+ .role-option {
151
+ padding: 18px 12px;
152
+ border: 2px solid var(--border-color);
153
+ border-radius: 12px;
154
+ text-align: center;
155
+ cursor: pointer;
156
+ transition: all 0.3s ease;
157
+ background: var(--bg-white);
158
+ }
159
+
160
+ .role-option:hover {
161
+ border-color: var(--primary-color);
162
+ transform: translateY(-2px);
163
+ box-shadow: var(--shadow-md);
164
+ }
165
+
166
+ .role-option.selected {
167
+ border-color: var(--primary-color);
168
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
169
+ color: white;
170
+ }
171
+
172
+ .role-option .icon {
173
+ font-size: 2.2rem;
174
+ margin-bottom: 8px;
175
+ }
176
+
177
+ .role-option h3 {
178
+ font-size: 0.95rem;
179
+ font-weight: 600;
180
+ }
181
+
182
+ /* فرم‌ها */
183
+ .form-group {
184
+ margin-bottom: 18px;
185
+ }
186
+
187
+ .form-group label {
188
+ display: block;
189
+ font-weight: 600;
190
+ margin-bottom: 8px;
191
+ color: var(--text-dark);
192
+ font-size: 0.9rem;
193
+ }
194
+
195
+ .form-group input,
196
+ .form-group select,
197
+ .form-group textarea {
198
+ width: 100%;
199
+ padding: 12px 15px;
200
+ border: 2px solid var(--border-color);
201
+ border-radius: 10px;
202
+ font-size: 0.95rem;
203
+ transition: all 0.3s ease;
204
+ background: var(--bg-white);
205
+ font-family: 'Vazirmatn', sans-serif;
206
+ }
207
+
208
+ .form-group input:focus,
209
+ .form-group select:focus,
210
+ .form-group textarea:focus {
211
+ outline: none;
212
+ border-color: var(--primary-color);
213
+ box-shadow: 0 0 0 3px rgba(56, 28, 128, 0.1);
214
+ }
215
+
216
+ .form-row {
217
+ display: grid;
218
+ grid-template-columns: 1fr 1fr;
219
+ gap: 15px;
220
+ }
221
+
222
+ /* دکمه‌ها */
223
+ .btn {
224
+ width: 100%;
225
+ padding: 13px 20px;
226
+ border: none;
227
+ border-radius: 10px;
228
+ font-size: 0.95rem;
229
+ font-weight: 600;
230
+ cursor: pointer;
231
+ transition: all 0.3s ease;
232
+ text-align: center;
233
+ text-decoration: none;
234
+ display: inline-block;
235
+ font-family: 'Vazirmatn', sans-serif;
236
+ }
237
+
238
+ /* دکمه‌های جمع‌وجور که در وسط کانتینر قرار می‌گیرند (یک سوم/نیم عرض) */
239
+ .btn--compact {
240
+ display: block;
241
+ width: 33%;
242
+ max-width: 360px;
243
+ min-width: 160px;
244
+ margin: 0.35rem auto;
245
+ padding: 10px 12px;
246
+ border-radius: 10px;
247
+ }
248
+
249
+ .btn-primary {
250
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
251
+ color: white;
252
+ }
253
+
254
+ .btn-primary:hover {
255
+ transform: translateY(-2px);
256
+ box-shadow: var(--shadow-lg);
257
+ }
258
+
259
+ .btn-primary:active {
260
+ transform: translateY(0);
261
+ }
262
+
263
+ .btn-secondary {
264
+ background: var(--bg-light);
265
+ color: var(--text-dark);
266
+ border: 2px solid var(--border-color);
267
+ }
268
+
269
+ .btn-secondary:hover {
270
+ background: var(--border-color);
271
+ }
272
+
273
+ .btn-success {
274
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
275
+ color: white;
276
+ }
277
+
278
+ .btn-success:hover {
279
+ transform: translateY(-2px);
280
+ box-shadow: var(--shadow-lg);
281
+ }
282
+
283
+ /* کارت‌ها */
284
+ .card {
285
+ background: rgba(255, 255, 255, 0.98);
286
+ backdrop-filter: blur(10px);
287
+ border-radius: 16px;
288
+ padding: 22px;
289
+ margin-bottom: 18px;
290
+ border: 1px solid rgba(255, 255, 255, 0.4);
291
+ box-shadow: 0 8px 32px rgba(56, 28, 128, 0.12);
292
+ transition: all 0.3s ease;
293
+ }
294
+
295
+ /* Header card tweaks: centered title and positioned logout button */
296
+ .header-card {
297
+ padding-top: 14px;
298
+ padding-bottom: 14px;
299
+ }
300
+
301
+ .header-card .logout-btn {
302
+ position: absolute;
303
+ right: 18px; /* put logout on the right like previous layout */
304
+ top: 14px;
305
+ display: inline-flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ padding: 8px 12px;
309
+ color: var(--text-dark);
310
+ background: var(--bg-light);
311
+ border: 1px solid var(--border-color);
312
+ border-radius: 10px;
313
+ text-decoration: none;
314
+ font-weight: 700;
315
+ transition: all 0.18s ease;
316
+ }
317
+
318
+ .header-card .logout-btn:hover {
319
+ background: rgba(56, 28, 128, 0.04);
320
+ transform: translateY(-2px);
321
+ }
322
+
323
+ /* two-column grid used in teacher page to align cards horizontally */
324
+ .two-col-grid {
325
+ display: grid;
326
+ grid-template-columns: repeat(2, 1fr);
327
+ gap: 16px;
328
+ }
329
+
330
+ @media (max-width: 900px) {
331
+ .two-col-grid {
332
+ grid-template-columns: 1fr;
333
+ }
334
+ }
335
+
336
+ /* small inline button variant for header logout when needed */
337
+ .btn--small {
338
+ display: inline-block;
339
+ width: auto;
340
+ padding: 8px 12px;
341
+ }
342
+
343
+ /* Group results layout */
344
+ #groupResultsList {
345
+ display: flex;
346
+ gap: 16px;
347
+ flex-wrap: wrap;
348
+ }
349
+
350
+ .group-card {
351
+ flex: 1 1 calc(50% - 16px);
352
+ max-width: calc(50% - 16px);
353
+ box-sizing: border-box;
354
+ padding: 18px;
355
+ background: var(--bg-light);
356
+ border-radius: 12px;
357
+ border-right: 4px solid var(--primary-color);
358
+ display: flex;
359
+ flex-direction: column;
360
+ gap: 10px;
361
+ }
362
+
363
+ .group-card-header {
364
+ color: var(--primary-color);
365
+ margin: 0;
366
+ font-size: 1.05rem;
367
+ }
368
+
369
+ .group-reasoning {
370
+ color: var(--text-light);
371
+ font-size: 0.9rem;
372
+ margin: 0;
373
+ }
374
+
375
+ .group-members-list {
376
+ list-style: none;
377
+ padding: 0;
378
+ margin: 0;
379
+ max-height: 220px;
380
+ overflow: auto;
381
+ display: flex;
382
+ flex-direction: column;
383
+ gap: 8px;
384
+ }
385
+
386
+ .group-member-item {
387
+ padding: 10px;
388
+ background: white;
389
+ border-radius: 8px;
390
+ font-size: 0.9rem;
391
+ }
392
+
393
+ @media (max-width: 900px) {
394
+ .group-card {
395
+ flex-basis: 100%;
396
+ max-width: 100%;
397
+ }
398
+ }
399
+
400
+ /* cap the overall results area so it doesn't push the whole page too far; individual groups scroll internally */
401
+ #groupResultsCard .card-body {
402
+ max-height: 520px;
403
+ overflow: auto;
404
+ }
405
+
406
+ .card:hover {
407
+ box-shadow: 0 12px 40px rgba(56, 28, 128, 0.18);
408
+ transform: translateY(-3px);
409
+ }
410
+
411
+ .card-header {
412
+ font-size: 1.15rem;
413
+ font-weight: 700;
414
+ color: var(--primary-color);
415
+ margin-bottom: 15px;
416
+ display: flex;
417
+ align-items: center;
418
+ gap: 8px;
419
+ }
420
+
421
+ .card-body {
422
+ color: var(--text-dark);
423
+ }
424
+
425
+ /* اطلاعات نمایشی */
426
+ .info-grid {
427
+ display: grid;
428
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
429
+ gap: 12px;
430
+ margin-bottom: 15px;
431
+ }
432
+
433
+ .info-display {
434
+ background: var(--bg-light);
435
+ padding: 12px 15px;
436
+ border-radius: 10px;
437
+ border-right: 3px solid var(--primary-color);
438
+ }
439
+
440
+ .info-display .label {
441
+ font-weight: 500;
442
+ color: var(--text-light);
443
+ font-size: 0.8rem;
444
+ margin-bottom: 4px;
445
+ }
446
+
447
+ .info-display .value {
448
+ font-size: 1.05rem;
449
+ color: var(--text-dark);
450
+ font-weight: 700;
451
+ }
452
+
453
+ /* لینک‌های تست */
454
+ .links-section {
455
+ display: grid;
456
+ grid-template-columns: 1fr 1fr;
457
+ gap: 12px;
458
+ margin-bottom: 15px;
459
+ }
460
+
461
+ .external-link {
462
+ padding: 16px 12px;
463
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
464
+ border: none;
465
+ border-radius: 12px;
466
+ text-align: center;
467
+ text-decoration: none;
468
+ color: white;
469
+ font-weight: 600;
470
+ transition: all 0.3s ease;
471
+ display: flex;
472
+ flex-direction: column;
473
+ align-items: center;
474
+ gap: 6px;
475
+ font-size: 0.9rem;
476
+ box-shadow: 0 4px 12px rgba(56, 28, 128, 0.2);
477
+ }
478
+
479
+ .external-link:hover {
480
+ transform: translateY(-3px);
481
+ box-shadow: 0 8px 20px rgba(56, 28, 128, 0.3);
482
+ background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary-color) 100%);
483
+ }
484
+
485
+ .external-link .icon {
486
+ font-size: 1.8rem;
487
+ }
488
+
489
+ /* هشدارها */
490
+ .alert {
491
+ padding: 12px 16px;
492
+ border-radius: 10px;
493
+ margin-bottom: 15px;
494
+ font-weight: 500;
495
+ font-size: 0.9rem;
496
+ }
497
+
498
+ .alert-info {
499
+ background: rgba(56, 28, 128, 0.1);
500
+ color: var(--primary-color);
501
+ border: 1px solid rgba(56, 28, 128, 0.3);
502
+ }
503
+
504
+ .alert-success {
505
+ background: rgba(16, 185, 129, 0.1);
506
+ color: var(--success-color);
507
+ border: 1px solid rgba(16, 185, 129, 0.3);
508
+ }
509
+
510
+ .alert-warning {
511
+ background: rgba(245, 158, 11, 0.1);
512
+ color: var(--warning-color);
513
+ border: 1px solid rgba(245, 158, 11, 0.3);
514
+ }
515
+
516
+ /* آمار */
517
+ .stats-grid {
518
+ display: grid;
519
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
520
+ gap: 15px;
521
+ margin-bottom: 20px;
522
+ }
523
+
524
+ .stat-card {
525
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
526
+ color: white;
527
+ padding: 20px;
528
+ border-radius: 12px;
529
+ text-align: center;
530
+ }
531
+
532
+ .stat-card .stat-value {
533
+ font-size: 2.2rem;
534
+ font-weight: 700;
535
+ margin-bottom: 5px;
536
+ }
537
+
538
+ .stat-card .stat-label {
539
+ font-size: 0.9rem;
540
+ opacity: 0.95;
541
+ }
542
+
543
+ /* لیست گروه */
544
+ .group-list {
545
+ list-style: none;
546
+ }
547
+
548
+ .group-member {
549
+ background: var(--bg-light);
550
+ padding: 12px 15px;
551
+ border-radius: 10px;
552
+ margin-bottom: 10px;
553
+ display: flex;
554
+ align-items: center;
555
+ gap: 12px;
556
+ transition: all 0.3s ease;
557
+ }
558
+
559
+ .group-member:hover {
560
+ background: var(--border-color);
561
+ transform: translateX(5px);
562
+ }
563
+
564
+ .group-member .member-icon {
565
+ font-size: 1.4rem;
566
+ }
567
+
568
+ .group-member .member-info {
569
+ flex: 1;
570
+ }
571
+
572
+ .group-member .member-name {
573
+ font-weight: 600;
574
+ color: var(--text-dark);
575
+ font-size: 0.95rem;
576
+ margin-bottom: 3px;
577
+ }
578
+
579
+ .group-member .member-details {
580
+ font-size: 0.8rem;
581
+ color: var(--text-light);
582
+ }
583
+
584
+ /* چک‌باکس‌ها */
585
+ .checkbox-group {
586
+ max-height: 250px;
587
+ overflow-y: auto;
588
+ border: 2px solid var(--border-color);
589
+ border-radius: 10px;
590
+ padding: 8px;
591
+ }
592
+
593
+ .checkbox-item {
594
+ padding: 10px;
595
+ display: flex;
596
+ align-items: center;
597
+ gap: 10px;
598
+ cursor: pointer;
599
+ border-radius: 8px;
600
+ transition: all 0.2s ease;
601
+ }
602
+
603
+ .checkbox-item:hover {
604
+ background: var(--bg-light);
605
+ }
606
+
607
+ .checkbox-item input[type="checkbox"] {
608
+ width: 18px;
609
+ height: 18px;
610
+ cursor: pointer;
611
+ }
612
+
613
+ .checkbox-item label {
614
+ cursor: pointer;
615
+ margin: 0;
616
+ font-weight: 500;
617
+ font-size: 0.9rem;
618
+ }
619
+
620
+ /* لینک بازگشت */
621
+ .back-link {
622
+ display: inline-block;
623
+ margin-bottom: 15px;
624
+ color: var(--primary-color);
625
+ text-decoration: none;
626
+ font-weight: 600;
627
+ transition: all 0.3s ease;
628
+ font-size: 0.95rem;
629
+ }
630
+
631
+ .back-link:hover {
632
+ transform: translateX(5px);
633
+ }
634
+
635
+ /* حالت خالی */
636
+ .empty-state {
637
+ text-align: center;
638
+ padding: 35px 20px;
639
+ }
640
+
641
+ .empty-state .icon {
642
+ font-size: 3.5rem;
643
+ opacity: 0.3;
644
+ margin-bottom: 15px;
645
+ }
646
+
647
+ .empty-state h3 {
648
+ font-size: 1.2rem;
649
+ color: var(--text-dark);
650
+ margin-bottom: 10px;
651
+ }
652
+
653
+ .empty-state p {
654
+ color: var(--text-light);
655
+ font-size: 0.9rem;
656
+ }
657
+
658
+ /* لودینگ */
659
+ .loading {
660
+ display: inline-block;
661
+ width: 20px;
662
+ height: 20px;
663
+ border: 3px solid rgba(255, 255, 255, 0.3);
664
+ border-radius: 50%;
665
+ border-top-color: white;
666
+ animation: spin 1s ease-in-out infinite;
667
+ }
668
+
669
+ /* طراحی ریسپانسیو */
670
+ @media (max-width: 768px) {
671
+ .container {
672
+ padding: 25px;
673
+ }
674
+
675
+ .header h1 {
676
+ font-size: 1.5rem;
677
+ }
678
+
679
+ .role-selector {
680
+ grid-template-columns: 1fr;
681
+ }
682
+
683
+ .links-section {
684
+ grid-template-columns: 1fr;
685
+ }
686
+
687
+ .stats-grid {
688
+ grid-template-columns: 1fr;
689
+ }
690
+
691
+ .form-row {
692
+ grid-template-columns: 1fr;
693
+ }
694
+
695
+ .info-grid {
696
+ grid-template-columns: 1fr;
697
+ }
698
+ }
699
+
700
+ @media (min-width: 769px) and (max-width: 1024px) {
701
+ .dashboard-container {
702
+ max-width: 900px;
703
+ }
704
+ }
705
+
706
+ /* تنظیمات اسکرول */
707
+ ::-webkit-scrollbar {
708
+ width: 8px;
709
+ }
710
+
711
+ ::-webkit-scrollbar-track {
712
+ background: var(--bg-light);
713
+ }
714
+
715
+ ::-webkit-scrollbar-thumb {
716
+ background: var(--primary-color);
717
+ border-radius: 4px;
718
+ }
719
+
720
+ ::-webkit-scrollbar-thumb:hover {
721
+ background: var(--primary-dark);
722
+ }
723
+
724
+ /* ترانزیشن‌های نرم */
725
+ a, button, input, select, .role-option, .card, .external-link, .checkbox-item {
726
+ transition: all 0.3s ease;
727
+ }
backend/static/assets/js/data.js ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Student Grouping System - Data Management
2
+ // This file handles all data operations using Backend API
3
+
4
+ // Backend API Configuration
5
+ // Auto-detects if running on Railway deployment or localhost
6
+ function getApiBaseUrl() {
7
+ // When deployed to Railway, it will use the same domain
8
+ // Railway serves both frontend and backend from the same URL
9
+ // So we use relative paths for API calls
10
+ if (window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') {
11
+ // PRODUCTION: Running on Railway - use relative paths
12
+ return '/api';
13
+ }
14
+
15
+ // DEVELOPMENT: Running locally
16
+ return 'http://localhost:8000/api';
17
+ }
18
+
19
+ const API_BASE_URL = getApiBaseUrl();
20
+
21
+ // Utility function for API calls
22
+ async function apiCall(endpoint, options = {}) {
23
+ try {
24
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
25
+ ...options,
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ ...options.headers
29
+ }
30
+ });
31
+
32
+ if (!response.ok) {
33
+ const error = await response.json();
34
+ throw new Error(error.detail || 'API request failed');
35
+ }
36
+
37
+ return await response.json();
38
+ } catch (error) {
39
+ console.error('API Error:', error);
40
+ throw error;
41
+ }
42
+ }
43
+
44
+ // Get all data (for backward compatibility)
45
+ async function getData() {
46
+ const students = await getAllStudents();
47
+ const status = await getGroupingStats();
48
+ return {
49
+ students: students,
50
+ courseName: status.courseName,
51
+ groupingComplete: status.groupingComplete,
52
+ resultsVisible: status.resultsVisible,
53
+ teacherPassword: 'teacher123'
54
+ };
55
+ }
56
+
57
+ // Get student by student number
58
+ async function getStudent(studentNumber) {
59
+ try {
60
+ return await apiCall(`/student/${studentNumber}`);
61
+ } catch (error) {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ // Update student information
67
+ async function updateStudent(studentNumber, updates) {
68
+ try {
69
+ const result = await apiCall(`/student/${studentNumber}`, {
70
+ method: 'PUT',
71
+ body: JSON.stringify(updates)
72
+ });
73
+ return result.success;
74
+ } catch (error) {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ // Get all students
80
+ async function getAllStudents() {
81
+ const result = await apiCall('/students');
82
+ return result.students;
83
+ }
84
+
85
+ // Check if a student number exists in the system
86
+ async function studentExists(studentNumber) {
87
+ const student = await getStudent(studentNumber);
88
+ return student !== null;
89
+ }
90
+
91
+ // Get students with complete information (REQUIRED fields only: MBTI and Learning Style)
92
+ async function getStudentsWithCompleteInfo() {
93
+ const students = await getAllStudents();
94
+ return students.filter(s => s.mbti && s.learningStyle);
95
+ }
96
+
97
+ // Check if a student has complete profile (all fields filled)
98
+ async function hasCompleteProfile(studentNumber) {
99
+ const student = await getStudent(studentNumber);
100
+ if (!student) return false;
101
+
102
+ return !!(student.mbti && student.learningStyle &&
103
+ student.ams !== null && student.cooperative !== null);
104
+ }
105
+
106
+ // Get completion percentage for a student
107
+ async function getProfileCompletion(studentNumber) {
108
+ const student = await getStudent(studentNumber);
109
+ if (!student) return 0;
110
+
111
+ let completed = 0;
112
+ const total = 4;
113
+
114
+ if (student.mbti) completed++;
115
+ if (student.learningStyle) completed++;
116
+ if (student.ams !== null) completed++;
117
+ if (student.cooperative !== null) completed++;
118
+
119
+ return Math.round((completed / total) * 100);
120
+ }
121
+
122
+ // Get grouping statistics
123
+ async function getGroupingStats() {
124
+ return await apiCall('/grouping/status');
125
+ }
126
+
127
+ // Check if grouping is complete
128
+ async function isGroupingComplete() {
129
+ const stats = await getGroupingStats();
130
+ return stats.groupingComplete;
131
+ }
132
+
133
+ // Get course name
134
+ async function getCourseName() {
135
+ const stats = await getGroupingStats();
136
+ return stats.courseName;
137
+ }
138
+
139
+ // Check teacher password
140
+ async function checkTeacherPassword(password) {
141
+ try {
142
+ const result = await apiCall('/auth/teacher', {
143
+ method: 'POST',
144
+ body: JSON.stringify({ password })
145
+ });
146
+ return result.valid;
147
+ } catch (error) {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ // Get student's group (only if results are visible)
153
+ async function getStudentGroup(studentNumber) {
154
+ try {
155
+ return await apiCall(`/student/${studentNumber}/group`);
156
+ } catch (error) {
157
+ if (error.message.includes('not yet visible')) {
158
+ return { error: 'results_not_visible', message: 'نتایج هنوز توسط معلم نمایش داده نشده است.' };
159
+ }
160
+ if (error.message.includes('not assigned')) {
161
+ return { error: 'not_assigned', message: 'شما هنوز به گروهی اختصاص داده نشده‌اید.' };
162
+ }
163
+ throw error;
164
+ }
165
+ }
166
+
167
+ // Reset grouping only (keep student data)
168
+ async function resetGrouping(password) {
169
+ return await apiCall('/grouping/reset', {
170
+ method: 'POST',
171
+ body: JSON.stringify({ password })
172
+ });
173
+ }
174
+
175
+ // Reset ALL data (student profiles + grouping)
176
+ async function resetAllData(password) {
177
+ return await apiCall('/data/reset-all', {
178
+ method: 'POST',
179
+ body: JSON.stringify({ password })
180
+ });
181
+ }
182
+
183
+ // Toggle results visibility
184
+ async function toggleResultsVisibility(password) {
185
+ return await apiCall('/grouping/toggle-visibility', {
186
+ method: 'POST',
187
+ body: JSON.stringify({ password })
188
+ });
189
+ }
190
+
191
+ // Legacy functions for backward compatibility (these are handled by backend now)
192
+ function saveData(data) {
193
+ console.warn('saveData is deprecated - data is automatically saved to backend');
194
+ }
195
+
196
+ function initializeData() {
197
+ console.log('Data is managed by backend');
198
+ return true;
199
+ }
200
+
201
+ function addNewStudent(studentNumber, name, grade = 15) {
202
+ console.warn('addNewStudent should be done through admin panel');
203
+ return { success: false, message: 'Use backend API for adding students' };
204
+ }
205
+
206
+ function saveStudents(students) {
207
+ console.warn('saveStudents is deprecated - use backend API');
208
+ }
209
+
210
+ function deleteStudent(studentNumber) {
211
+ console.warn('deleteStudent should be done through admin panel');
212
+ return false;
213
+ }
214
+
215
+ function setGroupingComplete(status) {
216
+ console.warn('setGroupingComplete is handled by backend');
217
+ }
218
+
219
+ function setCourseName(name) {
220
+ console.warn('setCourseName is handled by backend');
221
+ }
222
+
223
+ function resetData() {
224
+ console.warn('resetData should be done through teacher dashboard');
225
+ }
226
+
227
+ // Export functions for use in other files
228
+ if (typeof module !== 'undefined' && module.exports) {
229
+ module.exports = {
230
+ getData,
231
+ getStudent,
232
+ updateStudent,
233
+ getAllStudents,
234
+ studentExists,
235
+ getStudentsWithCompleteInfo,
236
+ hasCompleteProfile,
237
+ getProfileCompletion,
238
+ isGroupingComplete,
239
+ getCourseName,
240
+ checkTeacherPassword,
241
+ getGroupingStats,
242
+ getStudentGroup
243
+ };
244
+ }
backend/static/assets/js/grouping.js ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Grouping Logic with Backend API Integration
2
+ // This file handles the student grouping process through the backend
3
+
4
+ // API_BASE_URL is defined in data.js (loaded first)
5
+ // No need to redeclare it here to avoid "already been declared" error
6
+
7
+ /**
8
+ * Main grouping function - calls backend API
9
+ * @param {string} courseName - The name of the course
10
+ * @returns {Promise<Object>} - Grouping results
11
+ */
12
+ async function performGrouping(courseName) {
13
+ try {
14
+ console.log(`Requesting grouping from backend for course: ${courseName}`);
15
+
16
+ const response = await fetch(`${API_BASE_URL}/grouping/perform`, {
17
+ method: 'POST',
18
+ headers: {
19
+ 'Content-Type': 'application/json'
20
+ },
21
+ body: JSON.stringify({
22
+ courseName
23
+ })
24
+ });
25
+
26
+ if (!response.ok) {
27
+ const error = await response.json();
28
+ throw new Error(error.detail || 'Grouping failed');
29
+ }
30
+
31
+ const result = await response.json();
32
+ console.log('Grouping successful!', result);
33
+ return result.results;
34
+ } catch (error) {
35
+ console.error('Grouping error:', error);
36
+ throw error;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Get grouping statistics
42
+ * @returns {Promise<Object>} - Statistics about current grouping
43
+ */
44
+ async function getGroupingStats() {
45
+ try {
46
+ const response = await fetch(`${API_BASE_URL}/grouping/status`);
47
+ if (!response.ok) {
48
+ throw new Error('Failed to get grouping status');
49
+ }
50
+ return await response.json();
51
+ } catch (error) {
52
+ console.error('Error getting grouping stats:', error);
53
+ throw error;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Toggle visibility of results to students
59
+ * @param {string} password - Teacher password
60
+ * @returns {Promise<Object>} - New visibility status
61
+ */
62
+ async function toggleResultsVisibility(password) {
63
+ try {
64
+ const response = await fetch(`${API_BASE_URL}/grouping/toggle-visibility`, {
65
+ method: 'POST',
66
+ headers: {
67
+ 'Content-Type': 'application/json'
68
+ },
69
+ body: JSON.stringify({ password })
70
+ });
71
+
72
+ if (!response.ok) {
73
+ const error = await response.json();
74
+ throw new Error(error.detail || 'Failed to toggle visibility');
75
+ }
76
+
77
+ const result = await response.json();
78
+ return result;
79
+ } catch (error) {
80
+ console.error('Error toggling visibility:', error);
81
+ throw error;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Reset grouping (clear all group assignments)
87
+ * @param {string} password - Teacher password
88
+ */
89
+ async function resetGrouping(password) {
90
+ try {
91
+ const response = await fetch(`${API_BASE_URL}/grouping/reset`, {
92
+ method: 'POST',
93
+ headers: {
94
+ 'Content-Type': 'application/json'
95
+ },
96
+ body: JSON.stringify({ password })
97
+ });
98
+
99
+ if (!response.ok) {
100
+ const error = await response.json();
101
+ throw new Error(error.detail || 'Failed to reset grouping');
102
+ }
103
+
104
+ return await response.json();
105
+ } catch (error) {
106
+ console.error('Error resetting grouping:', error);
107
+ throw error;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Get student's group information
113
+ * @param {string} studentNumber - Student number
114
+ * @returns {Promise<Object>} - Group information
115
+ */
116
+ async function getStudentGroup(studentNumber) {
117
+ try {
118
+ const response = await fetch(`${API_BASE_URL}/student/${studentNumber}/group`);
119
+
120
+ if (!response.ok) {
121
+ const error = await response.json();
122
+ if (error.detail.includes('not yet visible')) {
123
+ return {
124
+ error: 'not_visible',
125
+ message: 'نتایج هنوز توسط استاد نمایش داده نشده است.'
126
+ };
127
+ }
128
+ if (error.detail.includes('not assigned')) {
129
+ return {
130
+ error: 'not_assigned',
131
+ message: 'شما هنوز به گروهی اختصاص داده نشده‌اید.'
132
+ };
133
+ }
134
+ throw new Error(error.detail);
135
+ }
136
+
137
+ return await response.json();
138
+ } catch (error) {
139
+ console.error('Error getting student group:', error);
140
+ throw error;
141
+ }
142
+ }
143
+
144
+ // Legacy function - now handled by backend
145
+ function applyGrouping(groupingResult, courseName) {
146
+ console.warn('applyGrouping is now handled automatically by the backend');
147
+ }
148
+
149
+ // Export functions
150
+ if (typeof module !== 'undefined' && module.exports) {
151
+ module.exports = {
152
+ performGrouping,
153
+ getGroupingStats,
154
+ toggleResultsVisibility,
155
+ resetGrouping,
156
+ getStudentGroup,
157
+ applyGrouping
158
+ };
159
+ }
backend/static/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="refresh" content="0; url=pages/login.html">
7
+ <title>TalimBot - سیستم گروه‌بندی هوشمند</title>
8
+ <script>window.location.href = 'pages/login.html';</script>
9
+ </head>
10
+ <body>
11
+ <p style="text-align: center; padding: 2rem;">در حال انتقال به صفحه ورود...</p>
12
+ </body>
13
+ </html>
backend/static/pages/ams-questionnaire.html ADDED
@@ -0,0 +1,436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>پرسشنامه انگیزش تحصیلی (AMS) - سیستم گروه‌بندی</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif;
14
+ }
15
+ .question-card {
16
+ transition: all 0.3s ease;
17
+ }
18
+ .question-card:hover {
19
+ transform: translateY(-2px);
20
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
21
+ }
22
+ .scale-option {
23
+ transition: all 0.2s ease;
24
+ }
25
+ .scale-option:hover {
26
+ transform: scale(1.05);
27
+ }
28
+ .scale-option input:checked + label {
29
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
30
+ color: white;
31
+ font-weight: 600;
32
+ }
33
+ </style>
34
+ <script>
35
+ tailwind.config = {
36
+ theme: {
37
+ extend: {
38
+ fontFamily: {
39
+ vazir: ['Vazirmatn', 'sans-serif']
40
+ }
41
+ }
42
+ }
43
+ }
44
+ </script>
45
+ </head>
46
+ <body class="font-vazir bg-gray-50 min-h-screen">
47
+ <!-- Top Navigation Bar -->
48
+ <nav style="background-color: #1b6e6e;" class="shadow-lg border-b border-gray-200 sticky top-0 z-50">
49
+ <div class="max-w-7xl mx-auto px-4">
50
+ <div class="flex items-center justify-between h-16">
51
+ <div class="flex items-center gap-3">
52
+ <div class="bg-white/20 w-10 h-10 rounded-lg flex items-center justify-center">
53
+ <svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
54
+ <path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z"/>
55
+ </svg>
56
+ </div>
57
+ <div>
58
+ <h1 class="text-xl font-bold text-white">TalimBot</h1>
59
+ <p class="text-xs text-white/80">پرسشنامه انگیزش تحصیلی</p>
60
+ </div>
61
+ </div>
62
+ <button onclick="goBack()" class="bg-white text-teal-700 px-4 py-2 rounded-lg font-semibold hover:bg-teal-50 transition-colors flex items-center gap-2">
63
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
64
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
65
+ </svg>
66
+ بازگشت به داشبورد
67
+ </button>
68
+ </div>
69
+ </div>
70
+ </nav>
71
+
72
+ <!-- Main Content -->
73
+ <div class="max-w-5xl mx-auto px-4 py-8">
74
+ <!-- Header -->
75
+ <div class="bg-white rounded-2xl shadow-lg p-8 mb-8">
76
+ <h2 class="text-3xl font-bold text-gray-900 mb-4 text-center">پرسشنامه انگیزش تحصیلی (AMS)</h2>
77
+ <p class="text-gray-600 text-center mb-6">لطفاً برای هر جمله مشخص کنید که تا چه حد درباره شما درست است</p>
78
+
79
+ <!-- Progress Bar -->
80
+ <div class="bg-gray-200 rounded-full h-3 mb-4">
81
+ <div id="progressBar" class="bg-gradient-to-r from-blue-500 to-blue-600 h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
82
+ </div>
83
+ <p class="text-center text-sm text-gray-600">
84
+ <span id="progressText">0 از 28 سؤال پاسخ داده شده</span>
85
+ </p>
86
+ </div>
87
+
88
+ <!-- Scale Legend -->
89
+ <div class="bg-blue-50 border-2 border-blue-200 rounded-xl p-6 mb-8">
90
+ <h3 class="font-bold text-blue-900 mb-4 text-center">مقیاس پاسخ‌دهی:</h3>
91
+ <div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-2 text-sm">
92
+ <div class="bg-white p-2 rounded text-center border border-blue-200">
93
+ <div class="font-bold text-blue-900">1</div>
94
+ <div class="text-xs text-gray-600">اصلاً درست نیست</div>
95
+ </div>
96
+ <div class="bg-white p-2 rounded text-center border border-blue-200">
97
+ <div class="font-bold text-blue-900">2</div>
98
+ <div class="text-xs text-gray-600">خیلی کم درست است</div>
99
+ </div>
100
+ <div class="bg-white p-2 rounded text-center border border-blue-200">
101
+ <div class="font-bold text-blue-900">3</div>
102
+ <div class="text-xs text-gray-600">کمی درست است</div>
103
+ </div>
104
+ <div class="bg-white p-2 rounded text-center border border-blue-200">
105
+ <div class="font-bold text-blue-900">4</div>
106
+ <div class="text-xs text-gray-600">تا حدی درست است</div>
107
+ </div>
108
+ <div class="bg-white p-2 rounded text-center border border-blue-200">
109
+ <div class="font-bold text-blue-900">5</div>
110
+ <div class="text-xs text-gray-600">نسبتاً درست است</div>
111
+ </div>
112
+ <div class="bg-white p-2 rounded text-center border border-blue-200">
113
+ <div class="font-bold text-blue-900">6</div>
114
+ <div class="text-xs text-gray-600">خیلی درست است</div>
115
+ </div>
116
+ <div class="bg-white p-2 rounded text-center border border-blue-200">
117
+ <div class="font-bold text-blue-900">7</div>
118
+ <div class="text-xs text-gray-600">کاملاً درست است</div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+
123
+ <!-- Questions -->
124
+ <form id="amsForm">
125
+ <!-- Section A: بی‌انگیزشی -->
126
+ <div class="mb-8">
127
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-red-50 to-orange-50 p-4 rounded-xl border-r-4 border-red-500">
128
+ <svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
129
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
130
+ </svg>
131
+ بی‌انگیزشی
132
+ </h3>
133
+ <div class="space-y-4" id="sectionA"></div>
134
+ </div>
135
+
136
+ <!-- Section B: تنظیم بیرونی -->
137
+ <div class="mb-8">
138
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-yellow-50 to-amber-50 p-4 rounded-xl border-r-4 border-yellow-500">
139
+ <svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
140
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
141
+ </svg>
142
+ انگیزش بیرونی – تنظیم بیرونی
143
+ </h3>
144
+ <div class="space-y-4" id="sectionB"></div>
145
+ </div>
146
+
147
+ <!-- Section C: درون‌فکنی‌شده -->
148
+ <div class="mb-8">
149
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-orange-50 to-red-50 p-4 rounded-xl border-r-4 border-orange-500">
150
+ <svg class="w-6 h-6 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
151
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"/>
152
+ </svg>
153
+ انگیزش بیرونی – درون‌فکنی‌شده
154
+ </h3>
155
+ <div class="space-y-4" id="sectionC"></div>
156
+ </div>
157
+
158
+ <!-- Section D: همانندسازی‌شده -->
159
+ <div class="mb-8">
160
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-blue-50 to-indigo-50 p-4 rounded-xl border-r-4 border-blue-500">
161
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
162
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"/>
163
+ </svg>
164
+ انگیزش بیرونی – همانندسازی‌شده
165
+ </h3>
166
+ <div class="space-y-4" id="sectionD"></div>
167
+ </div>
168
+
169
+ <!-- Section E: کسب دانش -->
170
+ <div class="mb-8">
171
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-green-50 to-emerald-50 p-4 rounded-xl border-r-4 border-green-500">
172
+ <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
173
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
174
+ </svg>
175
+ انگیزش درونی برای کسب دانش
176
+ </h3>
177
+ <div class="space-y-4" id="sectionE"></div>
178
+ </div>
179
+
180
+ <!-- Section F: کسب موفقیت -->
181
+ <div class="mb-8">
182
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-teal-50 to-cyan-50 p-4 rounded-xl border-r-4 border-teal-500">
183
+ <svg class="w-6 h-6 text-teal-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
184
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"/>
185
+ </svg>
186
+ انگیزش درونی برای کسب موفقیت
187
+ </h3>
188
+ <div class="space-y-4" id="sectionF"></div>
189
+ </div>
190
+
191
+ <!-- Section G: تجربه تحریک/هیجان -->
192
+ <div class="mb-8">
193
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-purple-50 to-pink-50 p-4 rounded-xl border-r-4 border-purple-500">
194
+ <svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
195
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
196
+ </svg>
197
+ انگیزش درونی برای تجربه تحریک/هیجان
198
+ </h3>
199
+ <div class="space-y-4" id="sectionG"></div>
200
+ </div>
201
+
202
+ <!-- Submit Button -->
203
+ <div class="flex justify-center">
204
+ <button type="submit" class="bg-gradient-to-r from-green-600 to-green-700 text-white px-12 py-4 rounded-xl font-bold text-lg hover:from-green-700 hover:to-green-800 shadow-lg hover:shadow-xl transform hover:scale-105 transition-all flex items-center gap-3">
205
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
206
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
207
+ </svg>
208
+ محاسبه نتیجه
209
+ </button>
210
+ </div>
211
+ </form>
212
+
213
+ <!-- Results Modal (Hidden by default) -->
214
+ <div id="resultsModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
215
+ <div class="bg-white rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
216
+ <div class="bg-gradient-to-r from-blue-600 to-blue-700 text-white p-6 rounded-t-2xl">
217
+ <h3 class="text-2xl font-bold text-center">نتایج پرسشنامه انگیزش تحصیلی</h3>
218
+ </div>
219
+ <div class="p-6">
220
+ <div id="resultsContent" class="space-y-4"></div>
221
+
222
+ <div class="mt-6 bg-blue-50 border-2 border-blue-300 rounded-xl p-4">
223
+ <p class="text-center text-blue-900 font-semibold mb-2">نمره کل شما:</p>
224
+ <p id="totalScore" class="text-4xl font-bold text-center text-blue-700"></p>
225
+ <p id="interpretation" class="text-center text-gray-700 mt-2"></p>
226
+ </div>
227
+
228
+ <div class="mt-6 flex gap-4">
229
+ <button onclick="saveAndReturn()" class="flex-1 bg-blue-600 text-white py-3 rounded-xl font-semibold hover:bg-blue-700 transition-colors flex items-center justify-center gap-2">
230
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
231
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"/>
232
+ </svg>
233
+ ذخیره و بازگشت
234
+ </button>
235
+ <button onclick="closeResults()" class="flex-1 bg-gray-600 text-white py-3 rounded-xl font-semibold hover:bg-gray-700 transition-colors">
236
+ بستن
237
+ </button>
238
+ </div>
239
+ </div>
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <script>
245
+ const questions = {
246
+ A: [
247
+ "گاهی نمی‌دانم چرا باید برای درس خواندن تلاش کنم.",
248
+ "احساس می‌کنم درس خواندن برایم فایده‌ای ندارد.",
249
+ "بعضی وقت‌ها فکر می‌کنم ادامه دادن درس هیچ هدف مشخصی برایم ندارد.",
250
+ "وقتی درس می‌خوانم، نمی‌دانم برای چه چیزی زحمت می‌کشم."
251
+ ],
252
+ B: [
253
+ "برای اینکه والدین یا معلمانم از من ناراحت نشوند، درس می‌خوانم.",
254
+ "بیشتر برای گرفتن نمره خوب تلاش می‌کنم، نه چیز دیگر.",
255
+ "درس می‌خوانم چون از پیامدهای منفی نمره پایین می‌ترسم.",
256
+ "پیشرفت تحصیلی‌ام برایم مهم است چون دیگران از من انتظار دارند."
257
+ ],
258
+ C: [
259
+ "وقتی درس نمی‌خوانم احساس گناه می‌کنم.",
260
+ "درس خواندن باعث می‌شود حس بهتری نسبت به خودم داشته باشم.",
261
+ "اگر برای امتحان آماده نباشم، از خودم ناراحت می‌شوم.",
262
+ "بعضی وقت‌ها برای اینکه احساس ارزشمندی کنم، بیشتر درس می‌خوانم."
263
+ ],
264
+ D: [
265
+ "درس خواندن برایم مهم است چون می‌دانم در آینده به من کمک می‌کند.",
266
+ "یادگیری مطالب مدرسه را ارزشمند می‌دانم.",
267
+ "موفقیت در درس را بخشی از پیشرفت شخصی خودم می‌دانم.",
268
+ "برای این درس می‌خوانم که به هدف‌های آینده‌ام نزدیک‌تر شوم."
269
+ ],
270
+ E: [
271
+ "از یادگرفتن مطالب جدید لذت می‌برم.",
272
+ "وقتی چیزی را واقعاً یاد می‌گیرم، احساس رضایت می‌کنم.",
273
+ "فهمیدن موضوعات سخت برایم هیجان‌انگیز است.",
274
+ "دوست دارم درباره درس‌ها بیشتر بدانم، حتی خارج از کلاس."
275
+ ],
276
+ F: [
277
+ "حل کردن یک مسئله سخت به من حس موفقیت می‌دهد.",
278
+ "وقتی یک تکلیف را عالی انجام می‌دهم، احساس افتخار می‌کنم.",
279
+ "دوست دارم در درس‌ها عملکردی بالاتر از حد معمول داشته باشم.",
280
+ "تلاش می‌کنم چون می‌خواهم توانایی‌هایم را نشان بدهم."
281
+ ],
282
+ G: [
283
+ "بعضی درس‌ها برایم هیجان‌انگیز و جذاب هستند.",
284
+ "یادگیری برایم لذت‌بخش است، حتی وقتی سخت باشد.",
285
+ "وقتی در فعالیت‌های آموزشی مشارکت می‌کنم، احساس انرژی و شادی می‌کنم.",
286
+ "تجربه کردن روش‌های جدید یادگیری برایم جالب و هیجان‌آور است."
287
+ ]
288
+ };
289
+
290
+ const sectionNames = {
291
+ A: "بی‌انگیزشی",
292
+ B: "تنظیم بیرونی",
293
+ C: "درون‌فکنی‌شده",
294
+ D: "همانندسازی‌شده",
295
+ E: "کسب دانش",
296
+ F: "کسب موفقیت",
297
+ G: "تجربه هیجان"
298
+ };
299
+
300
+ let questionNumber = 1;
301
+
302
+ function createQuestion(text, section, index) {
303
+ const container = document.getElementById(`section${section}`);
304
+ const questionDiv = document.createElement('div');
305
+ questionDiv.className = 'question-card bg-white p-6 rounded-xl shadow-md border border-gray-200';
306
+
307
+ let html = `
308
+ <div class="mb-4">
309
+ <p class="font-semibold text-gray-800 text-lg">
310
+ <span class="inline-block bg-blue-600 text-white w-8 h-8 rounded-full text-center leading-8 ml-2">${questionNumber}</span>
311
+ ${text}
312
+ </p>
313
+ </div>
314
+ <div class="grid grid-cols-7 gap-2">
315
+ `;
316
+
317
+ for (let i = 1; i <= 7; i++) {
318
+ html += `
319
+ <div class="scale-option">
320
+ <input type="radio" id="q${questionNumber}_${i}" name="q${questionNumber}" value="${i}" class="hidden" onchange="updateProgress()">
321
+ <label for="q${questionNumber}_${i}" class="block text-center py-3 px-2 rounded-lg border-2 border-gray-300 cursor-pointer hover:border-blue-400 transition-all">
322
+ <div class="font-bold text-gray-700">${i}</div>
323
+ </label>
324
+ </div>
325
+ `;
326
+ }
327
+
328
+ html += '</div>';
329
+ questionDiv.innerHTML = html;
330
+ container.appendChild(questionDiv);
331
+ questionNumber++;
332
+ }
333
+
334
+ function initializeQuestions() {
335
+ Object.keys(questions).forEach(section => {
336
+ questions[section].forEach((question, index) => {
337
+ createQuestion(question, section, index);
338
+ });
339
+ });
340
+ }
341
+
342
+ function updateProgress() {
343
+ const totalQuestions = 28;
344
+ const answeredQuestions = document.querySelectorAll('input[type="radio"]:checked').length;
345
+ const percentage = (answeredQuestions / totalQuestions) * 100;
346
+
347
+ document.getElementById('progressBar').style.width = percentage + '%';
348
+ document.getElementById('progressText').textContent = `${answeredQuestions} از ${totalQuestions} سؤال پاسخ داده شده`;
349
+ }
350
+
351
+ function calculateResults() {
352
+ const results = {};
353
+ let totalScore = 0;
354
+ let currentQ = 1;
355
+
356
+ Object.keys(questions).forEach(section => {
357
+ let sectionScore = 0;
358
+ for (let i = 0; i < 4; i++) {
359
+ const answer = document.querySelector(`input[name="q${currentQ}"]:checked`);
360
+ if (answer) {
361
+ sectionScore += parseInt(answer.value);
362
+ }
363
+ currentQ++;
364
+ }
365
+ results[section] = sectionScore;
366
+ totalScore += sectionScore;
367
+ });
368
+
369
+ return { sections: results, total: totalScore };
370
+ }
371
+
372
+ function displayResults(results) {
373
+ const content = document.getElementById('resultsContent');
374
+ content.innerHTML = '';
375
+
376
+ Object.keys(results.sections).forEach(section => {
377
+ const score = results.sections[section];
378
+ const div = document.createElement('div');
379
+ div.className = 'bg-gray-50 p-4 rounded-lg border border-gray-200';
380
+ div.innerHTML = `
381
+ <div class="flex justify-between items-center">
382
+ <span class="font-semibold text-gray-800">${sectionNames[section]}</span>
383
+ <span class="text-2xl font-bold text-blue-600">${score}/28</span>
384
+ </div>
385
+ `;
386
+ content.appendChild(div);
387
+ });
388
+
389
+ document.getElementById('totalScore').textContent = `${results.total}/196`;
390
+
391
+ let interpretation = '';
392
+ if (results.total <= 70) {
393
+ interpretation = '📉 انگیزش تحصیلی پایین - توصیه می‌شود با مشاور تحصیلی صحبت کنید';
394
+ } else if (results.total <= 140) {
395
+ interpretation = '📊 انگیزش تحصیلی متوسط - در مسیر مناسبی هستید';
396
+ } else {
397
+ interpretation = '📈 انگیزش تحصیلی بالا - عالی! انگیزه قوی برای یادگیری دارید';
398
+ }
399
+ document.getElementById('interpretation').textContent = interpretation;
400
+
401
+ document.getElementById('resultsModal').classList.remove('hidden');
402
+ }
403
+
404
+ function saveAndReturn() {
405
+ const totalText = document.getElementById('totalScore').textContent;
406
+ const scoreOnly = totalText.split('/')[0].trim();
407
+
408
+ sessionStorage.setItem('amsScore', scoreOnly);
409
+ window.location.href = 'student-dashboard.html';
410
+ }
411
+
412
+ function closeResults() {
413
+ document.getElementById('resultsModal').classList.add('hidden');
414
+ }
415
+
416
+ function goBack() {
417
+ window.location.href = 'student-dashboard.html';
418
+ }
419
+
420
+ document.getElementById('amsForm').addEventListener('submit', function(e) {
421
+ e.preventDefault();
422
+
423
+ const answeredQuestions = document.querySelectorAll('input[type="radio"]:checked').length;
424
+ if (answeredQuestions < 28) {
425
+ alert(`لطفاً به همه سؤالات پاسخ دهید. شما ${answeredQuestions} از 28 سؤال را پاسخ داده‌اید.`);
426
+ return;
427
+ }
428
+
429
+ const results = calculateResults();
430
+ displayResults(results);
431
+ });
432
+
433
+ window.onload = initializeQuestions;
434
+ </script>
435
+ </body>
436
+ </html>
backend/static/pages/cooperative-questionnaire.html ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>پرسشنامه یادگیری مشارکتی - سیستم گروه‌بندی</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif;
14
+ }
15
+ .question-card {
16
+ transition: all 0.3s ease;
17
+ }
18
+ .question-card:hover {
19
+ transform: translateY(-2px);
20
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
21
+ }
22
+ .scale-option {
23
+ transition: all 0.2s ease;
24
+ }
25
+ .scale-option:hover {
26
+ transform: scale(1.05);
27
+ }
28
+ .scale-option input:checked + label {
29
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
30
+ color: white;
31
+ font-weight: 600;
32
+ }
33
+ </style>
34
+ <script>
35
+ tailwind.config = {
36
+ theme: {
37
+ extend: {
38
+ fontFamily: {
39
+ vazir: ['Vazirmatn', 'sans-serif']
40
+ }
41
+ }
42
+ }
43
+ }
44
+ </script>
45
+ </head>
46
+ <body class="font-vazir bg-gray-50 min-h-screen">
47
+ <!-- Top Navigation Bar -->
48
+ <nav style="background-color: #1b6e6e;" class="shadow-lg border-b border-gray-200 sticky top-0 z-50">
49
+ <div class="max-w-7xl mx-auto px-4">
50
+ <div class="flex items-center justify-between h-16">
51
+ <div class="flex items-center gap-3">
52
+ <div class="bg-white/20 w-10 h-10 rounded-lg flex items-center justify-center">
53
+ <svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
54
+ <path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z"/>
55
+ </svg>
56
+ </div>
57
+ <div>
58
+ <h1 class="text-xl font-bold text-white">TalimBot</h1>
59
+ <p class="text-xs text-white/80">پرسشنامه یادگیری مشارکتی</p>
60
+ </div>
61
+ </div>
62
+ <button onclick="goBack()" class="bg-white text-teal-700 px-4 py-2 rounded-lg font-semibold hover:bg-teal-50 transition-colors flex items-center gap-2">
63
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
64
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
65
+ </svg>
66
+ بازگشت به داشبورد
67
+ </button>
68
+ </div>
69
+ </div>
70
+ </nav>
71
+
72
+ <!-- Main Content -->
73
+ <div class="max-w-5xl mx-auto px-4 py-8">
74
+ <!-- Header -->
75
+ <div class="bg-white rounded-2xl shadow-lg p-8 mb-8">
76
+ <h2 class="text-3xl font-bold text-gray-900 mb-4 text-center">پرسشنامه یادگیری مشارکتی</h2>
77
+ <p class="text-gray-600 text-center mb-2">بر اساس مدل جانسون و جانسون، ۱۹۹۴</p>
78
+ <p class="text-gray-600 text-center mb-6">لطفاً میزان موافقت خود را با هر جمله مشخص کنید</p>
79
+
80
+ <!-- Progress Bar -->
81
+ <div class="bg-gray-200 rounded-full h-3 mb-4">
82
+ <div id="progressBar" class="bg-gradient-to-r from-green-500 to-green-600 h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
83
+ </div>
84
+ <p class="text-center text-sm text-gray-600">
85
+ <span id="progressText">0 از 25 سؤال پاسخ داده شده</span>
86
+ </p>
87
+ </div>
88
+
89
+ <!-- Scale Legend -->
90
+ <div class="bg-green-50 border-2 border-green-200 rounded-xl p-6 mb-8">
91
+ <h3 class="font-bold text-green-900 mb-4 text-center">مقیاس پاسخ‌دهی:</h3>
92
+ <div class="grid grid-cols-2 md:grid-cols-5 gap-2 text-sm">
93
+ <div class="bg-white p-3 rounded text-center border border-green-200">
94
+ <div class="font-bold text-green-900">1</div>
95
+ <div class="text-xs text-gray-600">کاملاً مخالفم</div>
96
+ </div>
97
+ <div class="bg-white p-3 rounded text-center border border-green-200">
98
+ <div class="font-bold text-green-900">2</div>
99
+ <div class="text-xs text-gray-600">مخالفم</div>
100
+ </div>
101
+ <div class="bg-white p-3 rounded text-center border border-green-200">
102
+ <div class="font-bold text-green-900">3</div>
103
+ <div class="text-xs text-gray-600">نظری ندارم</div>
104
+ </div>
105
+ <div class="bg-white p-3 rounded text-center border border-green-200">
106
+ <div class="font-bold text-green-900">4</div>
107
+ <div class="text-xs text-gray-600">موافقم</div>
108
+ </div>
109
+ <div class="bg-white p-3 rounded text-center border border-green-200">
110
+ <div class="font-bold text-green-900">5</div>
111
+ <div class="text-xs text-gray-600">کاملاً موافقم</div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+
116
+ <!-- Questions -->
117
+ <form id="cooperativeForm">
118
+ <!-- Section 1: وابستگی متقابل مثبت -->
119
+ <div class="mb-8">
120
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-blue-50 to-indigo-50 p-4 rounded-xl border-r-4 border-blue-500">
121
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
122
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
123
+ </svg>
124
+ بخش اول: وابستگی متقابل مثبت
125
+ </h3>
126
+ <div class="space-y-4" id="section1"></div>
127
+ </div>
128
+
129
+ <!-- Section 2: پاسخگویی فردی -->
130
+ <div class="mb-8">
131
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-green-50 to-emerald-50 p-4 rounded-xl border-r-4 border-green-500">
132
+ <svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
133
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
134
+ </svg>
135
+ بخش دوم: پاسخگویی فردی
136
+ </h3>
137
+ <div class="space-y-4" id="section2"></div>
138
+ </div>
139
+
140
+ <!-- Section 3: تعامل چهره‌به‌چهره -->
141
+ <div class="mb-8">
142
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-yellow-50 to-amber-50 p-4 rounded-xl border-r-4 border-yellow-500">
143
+ <svg class="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
144
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
145
+ </svg>
146
+ بخش سوم: تعامل چهره‌به‌چهره / ارتقای متقابل
147
+ </h3>
148
+ <div class="space-y-4" id="section3"></div>
149
+ </div>
150
+
151
+ <!-- Section 4: مهارت‌های اجتماعی -->
152
+ <div class="mb-8">
153
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-purple-50 to-pink-50 p-4 rounded-xl border-r-4 border-purple-500">
154
+ <svg class="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
155
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
156
+ </svg>
157
+ بخش چهارم: مهارت‌های اجتماعی
158
+ </h3>
159
+ <div class="space-y-4" id="section4"></div>
160
+ </div>
161
+
162
+ <!-- Section 5: پردازش گروهی -->
163
+ <div class="mb-8">
164
+ <h3 class="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2 bg-gradient-to-r from-teal-50 to-cyan-50 p-4 rounded-xl border-r-4 border-teal-500">
165
+ <svg class="w-6 h-6 text-teal-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
166
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"/>
167
+ </svg>
168
+ بخش پنجم: پردازش گروهی / بازاندیشی در عملکرد گروه
169
+ </h3>
170
+ <div class="space-y-4" id="section5"></div>
171
+ </div>
172
+
173
+ <!-- Submit Button -->
174
+ <div class="flex justify-center">
175
+ <button type="submit" class="bg-gradient-to-r from-green-600 to-green-700 text-white px-12 py-4 rounded-xl font-bold text-lg hover:from-green-700 hover:to-green-800 shadow-lg hover:shadow-xl transform hover:scale-105 transition-all flex items-center gap-3">
176
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
177
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
178
+ </svg>
179
+ محاسبه نتیجه
180
+ </button>
181
+ </div>
182
+ </form>
183
+
184
+ <!-- Results Modal (Hidden by default) -->
185
+ <div id="resultsModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
186
+ <div class="bg-white rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto">
187
+ <div class="bg-gradient-to-r from-green-600 to-green-700 text-white p-6 rounded-t-2xl">
188
+ <h3 class="text-2xl font-bold text-center">نتایج پرسشنامه یادگیری مشارکتی</h3>
189
+ </div>
190
+ <div class="p-6">
191
+ <div id="resultsContent" class="space-y-4"></div>
192
+
193
+ <div class="mt-6 bg-green-50 border-2 border-green-300 rounded-xl p-4">
194
+ <p class="text-center text-green-900 font-semibold mb-2">نمره کل شما:</p>
195
+ <p id="totalScore" class="text-4xl font-bold text-center text-green-700"></p>
196
+ <p id="interpretation" class="text-center text-gray-700 mt-2"></p>
197
+ </div>
198
+
199
+ <div class="mt-6 flex gap-4">
200
+ <button onclick="saveAndReturn()" class="flex-1 bg-green-600 text-white py-3 rounded-xl font-semibold hover:bg-green-700 transition-colors flex items-center justify-center gap-2">
201
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
202
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"/>
203
+ </svg>
204
+ ذخیره و بازگشت
205
+ </button>
206
+ <button onclick="closeResults()" class="flex-1 bg-gray-600 text-white py-3 rounded-xl font-semibold hover:bg-gray-700 transition-colors">
207
+ بستن
208
+ </button>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ </div>
214
+
215
+ <script>
216
+ const questions = {
217
+ section1: [
218
+ "موفقیت من در فعالیت‌های این درس به همکاری اعضای گروه بستگی دارد.",
219
+ "اگر یکی از اعضای گروه کار خود را انجام ندهد، عملکرد کل گروه تحت تأثیر قرار می‌گیرد.",
220
+ "اعضای گروه برای رسیدن به هدف مشترک احساس مسئولیت مشترک دارند.",
221
+ "در فعالیت‌های گروهی، پیشرفت هر عضو به پیشرفت گروه کمک می‌کند.",
222
+ "هدف‌های گروه طوری طراحی شده‌اند که بدون همکاری، دستیابی به آن‌ها دشوار است."
223
+ ],
224
+ section2: [
225
+ "هر عضو گروه مسئول بخش مشخصی از کار است.",
226
+ "عملکرد من در گروه به‌طور جداگانه نیز مورد ارزیابی قرار می‌گیرد.",
227
+ "من باید وظیفه خودم را به‌خوبی انجام دهم تا گروه موفق شود.",
228
+ "همه اعضا می‌دانند که سهم هر فرد چه تأثیری بر نتیجه نهایی دارد.",
229
+ "هر فرد در گروه باید بتواند بخش مربوط به خود را توضیح دهد."
230
+ ],
231
+ section3: [
232
+ "اعضای گروه در کلاس به‌طور مستقیم با یکدیگر گفت‌وگو می‌کنند.",
233
+ "هنگام انجام فعالیت‌های گروهی، اعضا به یکدیگر کمک می‌کنند تا بهتر یاد بگیرند.",
234
+ "در گروه، توضیح دادن مطالب به یکدیگر امری معمول است.",
235
+ "اعضای گروه به یکدیگر بازخورد سازنده می‌دهند.",
236
+ "در فعالیت‌های گروهی، فرصت کافی برای تبادل نظر وجود دارد."
237
+ ],
238
+ section4: [
239
+ "اعضای گروه با احترام به نوبت یکدیگر صحبت می‌کنند.",
240
+ "اعضا برای حل اختلاف‌ها از گفت‌وگو و مذاکره استفاده می‌کنند.",
241
+ "اعضای گروه به یکدیگر گوش می‌دهند و صحبت‌های هم‌گروهی‌ها را قطع نمی‌کنند.",
242
+ "مدیریت زمان و تقسیم وظایف به شکل مؤثر انجام می‌شود.",
243
+ "اعضای گروه در تعاملات خود ادب و احترام را رعایت می‌کنند."
244
+ ],
245
+ section5: [
246
+ "در پایان فعالیت‌ها، گروه درباره نقاط قوت خود بحث می‌کند.",
247
+ "گروه به‌طور منظم درباره این‌که چگونه می‌توانند همکاری بهتری داشته باشند، بازاندیشی می‌کند.",
248
+ "اعضا درباره مشکلات گروه و راه‌حل‌های ممکن با هم صحبت می‌کنند.",
249
+ "گروه پس از هر فعالیت، ارزیابی کوتاهی از نحوه عملکرد خود انجام می‌دهد.",
250
+ "اعضای گروه در مورد بهبود عملکرد آینده تصمیم‌گیری مشترک می‌کنند."
251
+ ]
252
+ };
253
+
254
+ const sectionNames = {
255
+ section1: "وابستگی متقابل مثبت",
256
+ section2: "پاسخگویی فردی",
257
+ section3: "تعامل چهره‌به‌چهره",
258
+ section4: "مهارت‌های اجتماعی",
259
+ section5: "پردازش گروهی"
260
+ };
261
+
262
+ let questionNumber = 1;
263
+
264
+ function createQuestion(text, section, index) {
265
+ const container = document.getElementById(section);
266
+ const questionDiv = document.createElement('div');
267
+ questionDiv.className = 'question-card bg-white p-6 rounded-xl shadow-md border border-gray-200';
268
+
269
+ let html = `
270
+ <div class="mb-4">
271
+ <p class="font-semibold text-gray-800 text-lg">
272
+ <span class="inline-block bg-green-600 text-white w-8 h-8 rounded-full text-center leading-8 ml-2">${questionNumber}</span>
273
+ ${text}
274
+ </p>
275
+ </div>
276
+ <div class="grid grid-cols-5 gap-2">
277
+ `;
278
+
279
+ const scaleLabels = ['کاملاً مخالفم', 'مخالفم', 'نظری ندارم', 'موافقم', 'کاملاً موافقم'];
280
+
281
+ for (let i = 1; i <= 5; i++) {
282
+ html += `
283
+ <div class="scale-option">
284
+ <input type="radio" id="q${questionNumber}_${i}" name="q${questionNumber}" value="${i}" class="hidden" onchange="updateProgress()">
285
+ <label for="q${questionNumber}_${i}" class="block text-center py-3 px-2 rounded-lg border-2 border-gray-300 cursor-pointer hover:border-green-400 transition-all">
286
+ <div class="font-bold text-gray-700">${i}</div>
287
+ <div class="text-xs text-gray-500 mt-1">${scaleLabels[i-1]}</div>
288
+ </label>
289
+ </div>
290
+ `;
291
+ }
292
+
293
+ html += '</div>';
294
+ questionDiv.innerHTML = html;
295
+ container.appendChild(questionDiv);
296
+ questionNumber++;
297
+ }
298
+
299
+ function initializeQuestions() {
300
+ Object.keys(questions).forEach(section => {
301
+ questions[section].forEach((question, index) => {
302
+ createQuestion(question, section, index);
303
+ });
304
+ });
305
+ }
306
+
307
+ function updateProgress() {
308
+ const totalQuestions = 25;
309
+ const answeredQuestions = document.querySelectorAll('input[type="radio"]:checked').length;
310
+ const percentage = (answeredQuestions / totalQuestions) * 100;
311
+
312
+ document.getElementById('progressBar').style.width = percentage + '%';
313
+ document.getElementById('progressText').textContent = `${answeredQuestions} از ${totalQuestions} سؤال پاسخ داده شده`;
314
+ }
315
+
316
+ function calculateResults() {
317
+ const results = {};
318
+ let totalScore = 0;
319
+ let currentQ = 1;
320
+
321
+ Object.keys(questions).forEach(section => {
322
+ let sectionScore = 0;
323
+ for (let i = 0; i < 5; i++) {
324
+ const answer = document.querySelector(`input[name="q${currentQ}"]:checked`);
325
+ if (answer) {
326
+ sectionScore += parseInt(answer.value);
327
+ }
328
+ currentQ++;
329
+ }
330
+ results[section] = sectionScore;
331
+ totalScore += sectionScore;
332
+ });
333
+
334
+ return { sections: results, total: totalScore };
335
+ }
336
+
337
+ function displayResults(results) {
338
+ const content = document.getElementById('resultsContent');
339
+ content.innerHTML = '';
340
+
341
+ Object.keys(results.sections).forEach(section => {
342
+ const score = results.sections[section];
343
+ const div = document.createElement('div');
344
+ div.className = 'bg-gray-50 p-4 rounded-lg border border-gray-200';
345
+ div.innerHTML = `
346
+ <div class="flex justify-between items-center">
347
+ <span class="font-semibold text-gray-800">${sectionNames[section]}</span>
348
+ <span class="text-2xl font-bold text-green-600">${score}/25</span>
349
+ </div>
350
+ `;
351
+ content.appendChild(div);
352
+ });
353
+
354
+ document.getElementById('totalScore').textContent = `${results.total}/125`;
355
+
356
+ let interpretation = '';
357
+ if (results.total <= 50) {
358
+ interpretation = '📉 یادگیری مشارکتی کم - نیاز به توسعه مهارت‌های کار گروهی';
359
+ } else if (results.total <= 88) {
360
+ interpretation = '📊 یادگیری مشارکتی متوسط - در مسیر خوبی هستید';
361
+ } else {
362
+ interpretation = '📈 یادگیری مشارکتی زیاد - عالی! مهارت‌های همکاری قوی دارید';
363
+ }
364
+ document.getElementById('interpretation').textContent = interpretation;
365
+
366
+ document.getElementById('resultsModal').classList.remove('hidden');
367
+ }
368
+
369
+ function saveAndReturn() {
370
+ const totalText = document.getElementById('totalScore').textContent;
371
+ const scoreOnly = totalText.split('/')[0].trim();
372
+
373
+ sessionStorage.setItem('cooperativeScore', scoreOnly);
374
+ window.location.href = 'student-dashboard.html';
375
+ }
376
+
377
+ function closeResults() {
378
+ document.getElementById('resultsModal').classList.add('hidden');
379
+ }
380
+
381
+ function goBack() {
382
+ window.location.href = 'student-dashboard.html';
383
+ }
384
+
385
+ document.getElementById('cooperativeForm').addEventListener('submit', function(e) {
386
+ e.preventDefault();
387
+
388
+ const answeredQuestions = document.querySelectorAll('input[type="radio"]:checked').length;
389
+ if (answeredQuestions < 25) {
390
+ alert(`لطفاً به همه سؤالات پاسخ دهید. شما ${answeredQuestions} از 25 سؤال را پاسخ داده‌اید.`);
391
+ return;
392
+ }
393
+
394
+ const results = calculateResults();
395
+ displayResults(results);
396
+ });
397
+
398
+ window.onload = initializeQuestions;
399
+ </script>
400
+ </body>
401
+ </html>
backend/static/pages/group-view.html ADDED
@@ -0,0 +1,405 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>گروه من - سیستم گروه‌بندی</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif;
14
+ }
15
+ </style>
16
+ <script>
17
+ tailwind.config = {
18
+ theme: {
19
+ extend: {
20
+ fontFamily: {
21
+ 'vazir': ['Vazirmatn', 'system-ui', '-apple-system', 'sans-serif'],
22
+ }
23
+ }
24
+ }
25
+ }
26
+ </script>
27
+ </head>
28
+ <body class="font-vazir bg-gray-50 min-h-screen">
29
+ <!-- Top Navigation Bar -->
30
+ <nav style="background-color: #1b6e6e;" class="shadow-lg border-b border-gray-200 sticky top-0 z-50">
31
+ <div class="max-w-7xl mx-auto px-4">
32
+ <div class="flex items-center justify-between h-16">
33
+ <!-- Logo/Brand -->
34
+ <div class="flex items-center gap-3">
35
+ <div class="bg-white/20 w-10 h-10 rounded-lg flex items-center justify-center">
36
+ <svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
37
+ <path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z"/>
38
+ </svg>
39
+ </div>
40
+ <div>
41
+ <h1 class="text-xl font-bold text-white">TalimBot</h1>
42
+ <p class="text-xs text-white/80">سیستم گروه‌بندی هوشمند</p>
43
+ </div>
44
+ </div>
45
+
46
+ <!-- Navigation Links -->
47
+ <div class="hidden md:flex items-center gap-1">
48
+ <a href="student-dashboard.html" class="px-4 py-2 text-white/80 hover:text-white hover:bg-white/10 rounded-lg font-medium transition-colors">
49
+ <span class="flex items-center gap-2">
50
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
51
+ <path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
52
+ </svg>
53
+ داشبورد
54
+ </span>
55
+ </a>
56
+ <a href="#" class="px-4 py-2 text-white bg-white/20 rounded-lg font-semibold border-b-2 border-white">
57
+ <span class="flex items-center gap-2">
58
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
59
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
60
+ </svg>
61
+ گروه من
62
+ </span>
63
+ </a>
64
+ </div>
65
+
66
+ <!-- User Menu -->
67
+ <div class="flex items-center gap-4">
68
+ <div class="hidden md:block text-left">
69
+ <p class="text-sm font-semibold text-white" id="navStudentName">دانش آموز</p>
70
+ <p class="text-xs text-white/80" id="navStudentNumber">-</p>
71
+ </div>
72
+ <div class="w-10 h-10 rounded-full bg-white/20 flex items-center justify-center text-white font-bold">
73
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
74
+ <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/>
75
+ </svg>
76
+ </div>
77
+ <a href="login.html" class="text-white/80 hover:text-red-300 transition-colors" title="خروج">
78
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
79
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
80
+ </svg>
81
+ </a>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </nav>
86
+
87
+ <!-- Main Content -->
88
+ <div class="max-w-7xl mx-auto px-4 py-8">
89
+ <!-- Page Header -->
90
+ <div class="mb-8 flex items-center gap-3">
91
+ <svg class="w-10 h-10 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
92
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
93
+ </svg>
94
+ <div>
95
+ <h2 class="text-3xl font-bold text-gray-900">گروه مطالعاتی شما</h2>
96
+ <p class="text-gray-600" id="headerSubtext">مشاهده اعضای گروه و اطلاعات همکاران</p>
97
+ </div>
98
+ </div>
99
+
100
+
101
+ <!-- Loading State -->
102
+ <div id="loadingState" class="hidden">
103
+ <div class="bg-white rounded-xl shadow-md p-12 text-center">
104
+ <div class="inline-block w-16 h-16 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mb-4"></div>
105
+ <p class="text-gray-600 font-semibold">در حال بارگذاری اطلاعات گروه...</p>
106
+ </div>
107
+ </div>
108
+
109
+ <!-- Not Grouped State -->
110
+ <div id="notGroupedState" class="hidden">
111
+ <div class="bg-white rounded-xl shadow-md p-12">
112
+ <div class="text-center mb-8">
113
+ <svg class="w-24 h-24 mx-auto mb-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
114
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
115
+ </svg>
116
+ <h3 class="text-2xl font-bold text-gray-900 mb-3" id="notGroupedTitle">گروه‌بندی هنوز انجام نشده است</h3>
117
+ <p class="text-gray-600 mb-2" id="notGroupedMessage">معلم هنوز فرآیند گروه‌بندی را تکمیل نکرده است.</p>
118
+ <p class="text-gray-500">لطفاً بعداً دوباره بررسی کنید یا با معلم خود تماس بگیرید.</p>
119
+ </div>
120
+ <div class="flex gap-4 justify-center">
121
+ <button onclick="window.location.href='student-dashboard.html'"
122
+ class="bg-gray-600 hover:bg-gray-700 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
123
+ بازگشت به داشبورد
124
+ </button>
125
+ <button onclick="window.location.reload()"
126
+ class="bg-blue-600 hover:bg-blue-700 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
127
+ بروزرسانی صفحه
128
+ </button>
129
+ </div>
130
+ </div>
131
+ </div>
132
+
133
+ <!-- Grouped State -->
134
+ <div id="groupedState" class="hidden space-y-6">
135
+ <!-- Course Info -->
136
+ <div id="courseInfo" class="bg-blue-50 border-r-4 border-blue-500 rounded-lg p-4 flex items-center gap-2">
137
+ <svg class="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
138
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
139
+ </svg>
140
+ <p class="text-blue-800 font-semibold"><strong>درس:</strong> <span id="courseName">-</span></p>
141
+ </div>
142
+
143
+ <!-- Group Info & My Profile -->
144
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
145
+ <!-- Group Number Card -->
146
+ <div class="bg-white rounded-xl shadow-md p-6">
147
+ <div class="flex items-center gap-2 mb-4">
148
+ <svg class="w-7 h-7 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
149
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"/>
150
+ </svg>
151
+ <h3 class="text-xl font-bold text-gray-900">گروه <span id="groupNumber">-</span></h3>
152
+ </div>
153
+ <div class="grid grid-cols-2 gap-4">
154
+ <div class="bg-blue-50 rounded-lg p-4 text-center">
155
+ <p class="text-gray-600 text-sm mb-1">تعداد اعضا</p>
156
+ <p class="text-3xl font-bold text-blue-600" id="memberCount">-</p>
157
+ </div>
158
+ <div class="bg-green-50 rounded-lg p-4 text-center">
159
+ <p class="text-gray-600 text-sm mb-1">وضعیت گروه</p>
160
+ <p class="text-lg font-bold text-green-600">فعال</p>
161
+ </div>
162
+ </div>
163
+ </div>
164
+
165
+ <!-- My Profile Card -->
166
+ <div class="bg-white rounded-xl shadow-md p-6">
167
+ <div class="flex items-center gap-2 mb-4">
168
+ <svg class="w-7 h-7 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
169
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
170
+ </svg>
171
+ <h3 class="text-xl font-bold text-gray-900">پروفایل من در گروه</h3>
172
+ </div>
173
+ <div class="space-y-3">
174
+ <div class="flex justify-between items-center py-2 border-b border-gray-200">
175
+ <span class="text-gray-600 font-medium">نوع شخصیت MBTI</span>
176
+ <span class="text-gray-900 font-bold" id="myMbti">-</span>
177
+ </div>
178
+ <div class="flex justify-between items-center py-2 border-b border-gray-200">
179
+ <span class="text-gray-600 font-medium">سبک یادگیری</span>
180
+ <span class="text-gray-900 font-bold" id="myLearningStyle">-</span>
181
+ </div>
182
+ <div class="flex justify-between items-center py-2">
183
+ <span class="text-gray-600 font-medium">نمره</span>
184
+ <span class="text-gray-900 font-bold" id="myGrade">-</span>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ </div>
189
+
190
+ <!-- Group Members Card -->
191
+ <div class="bg-white rounded-xl shadow-md p-6">
192
+ <div class="flex items-center gap-2 mb-4">
193
+ <svg class="w-7 h-7 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
194
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
195
+ </svg>
196
+ <h3 class="text-xl font-bold text-gray-900">اعضای گروه</h3>
197
+ </div>
198
+ <div class="space-y-3" id="groupMembersList">
199
+ <!-- Populated by JS -->
200
+ </div>
201
+ </div>
202
+
203
+ <!-- Action Buttons -->
204
+ <div class="flex gap-4">
205
+ <button onclick="window.location.href='student-dashboard.html'"
206
+ class="flex-1 bg-gray-600 hover:bg-gray-700 text-white py-4 px-6 rounded-xl font-bold text-lg transition-colors">
207
+ بازگشت به داشبورد
208
+ </button>
209
+ <button onclick="window.location.reload()"
210
+ class="flex-1 bg-blue-600 hover:bg-blue-700 text-white py-4 px-6 rounded-xl font-bold text-lg transition-colors flex items-center justify-center gap-2">
211
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
212
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
213
+ </svg>
214
+ بروزرسانی اطلاعات
215
+ </button>
216
+ </div>
217
+ </div>
218
+
219
+ <!-- Error State -->
220
+ <div id="errorState" class="hidden">
221
+ <div class="bg-white rounded-xl shadow-md p-12">
222
+ <div class="text-center mb-8">
223
+ <svg class="w-24 h-24 mx-auto mb-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
224
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
225
+ </svg>
226
+ <h3 class="text-2xl font-bold text-gray-900 mb-3">خطا در بارگذاری اطلاعات!</h3>
227
+ <p class="text-gray-600" id="errorMessage">قادر به بارگذاری اطلاعات گروه نیستیم.</p>
228
+ </div>
229
+ <div class="text-center">
230
+ <button onclick="window.location.href='student-dashboard.html'"
231
+ class="bg-blue-600 hover:bg-blue-700 text-white py-3 px-6 rounded-lg font-semibold transition-colors">
232
+ بازگشت به داشبورد
233
+ </button>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </div>
238
+
239
+ <!-- Footer -->
240
+ <footer class="bg-white border-t border-gray-200 mt-16">
241
+ <div class="max-w-7xl mx-auto px-4 py-6">
242
+ <div class="text-center text-gray-600 text-sm">
243
+ <p>© ۲۰۲۵ TalimBot - سیستم گروه‌بندی هوشمند دانش آموزان</p>
244
+ </div>
245
+ </div>
246
+ </footer>
247
+
248
+ <script src="../assets/js/data.js"></script>
249
+ <script src="../assets/js/grouping.js"></script>
250
+ <script>
251
+ let currentStudent = null;
252
+
253
+ // Check authentication on page load
254
+ function checkAuth() {
255
+ const studentNumber = sessionStorage.getItem('currentStudent');
256
+ if (!studentNumber) {
257
+ window.location.href = 'login.html';
258
+ return null;
259
+ }
260
+ return studentNumber;
261
+ }
262
+
263
+ function showState(state) {
264
+ document.getElementById('loadingState').classList.add('hidden');
265
+ document.getElementById('notGroupedState').classList.add('hidden');
266
+ document.getElementById('groupedState').classList.add('hidden');
267
+ document.getElementById('errorState').classList.add('hidden');
268
+
269
+ if (state) {
270
+ document.getElementById(state).classList.remove('hidden');
271
+ }
272
+ }
273
+
274
+ async function loadGroupInfo() {
275
+ showState('loadingState');
276
+
277
+ const studentNumber = checkAuth();
278
+ if (!studentNumber) return;
279
+
280
+ try {
281
+ currentStudent = await getStudent(studentNumber);
282
+
283
+ if (!currentStudent) {
284
+ showState('errorState');
285
+ document.getElementById('errorMessage').textContent = 'دانشجو یافت نشد. لطفاً دوباره وارد شوید.';
286
+ return;
287
+ }
288
+
289
+ // Update nav
290
+ document.getElementById('navStudentName').textContent = currentStudent.name;
291
+ document.getElementById('navStudentNumber').textContent = currentStudent.studentNumber;
292
+
293
+ if (!currentStudent.mbti || !currentStudent.learningStyle) {
294
+ showState('errorState');
295
+ document.getElementById('errorMessage').textContent = 'لطفاً ابتدا پروفایل خود را در داشبورد تکمیل کنید.';
296
+ return;
297
+ }
298
+
299
+ const status = await getGroupingStats();
300
+
301
+ if (!status.groupingComplete || !currentStudent.group) {
302
+ showState('notGroupedState');
303
+ document.getElementById('notGroupedTitle').textContent = 'گروه‌بندی هنوز انجام نشده است';
304
+ document.getElementById('notGroupedMessage').textContent = 'معلم هنوز فرآیند گروه‌بندی را تکمیل نکرده است.';
305
+ return;
306
+ }
307
+
308
+ // Check if teacher has made results visible
309
+ if (!status.resultsVisible) {
310
+ showState('notGroupedState');
311
+ document.getElementById('notGroupedTitle').textContent = 'نتایج گروه‌بندی هنوز نمایش داده نشده';
312
+ document.getElementById('notGroupedMessage').textContent = 'معلم گروه‌بندی را انجام داده است اما هنوز آن را برای دانش‌آموزان نمایش نداده است.';
313
+ return;
314
+ }
315
+
316
+ await displayGroupInfo();
317
+ } catch (error) {
318
+ console.error('Error loading group info:', error);
319
+ showState('errorState');
320
+ document.getElementById('errorMessage').textContent = 'خطا در بارگذاری اطلاعات. لطفاً اطمینان حاصل کنید که سرور در حال اجرا است.';
321
+ }
322
+ }
323
+
324
+ async function displayGroupInfo() {
325
+ const allStudents = await getAllStudents();
326
+ const groupMembers = allStudents.filter(s => s.group === currentStudent.group);
327
+
328
+ document.getElementById('headerSubtext').textContent = `شما در گروه ${currentStudent.group} قرار دارید`;
329
+
330
+ const status = await getGroupingStats();
331
+ const courseName = status.courseName;
332
+ if (courseName) {
333
+ document.getElementById('courseInfo').classList.remove('hidden');
334
+ document.getElementById('courseName').textContent = courseName;
335
+ } else {
336
+ document.getElementById('courseInfo').classList.add('hidden');
337
+ }
338
+
339
+ document.getElementById('groupNumber').textContent = currentStudent.group;
340
+ document.getElementById('memberCount').textContent = groupMembers.length;
341
+
342
+ document.getElementById('myMbti').textContent = currentStudent.mbti || 'تنظیم نشده';
343
+ document.getElementById('myLearningStyle').textContent = currentStudent.learningStyle || 'تنظیم نشده';
344
+ document.getElementById('myGrade').textContent = currentStudent.grade.toFixed(2);
345
+
346
+ const membersList = document.getElementById('groupMembersList');
347
+ membersList.innerHTML = '';
348
+
349
+ groupMembers.forEach(member => {
350
+ const div = document.createElement('div');
351
+ const isMe = member.studentNumber === currentStudent.studentNumber;
352
+
353
+ div.className = `flex items-start gap-4 p-4 rounded-lg border-2 transition-all ${
354
+ isMe
355
+ ? 'bg-blue-50 border-blue-500 shadow-md'
356
+ : 'bg-gray-50 border-gray-200 hover:border-blue-300 hover:shadow-sm'
357
+ }`;
358
+
359
+ div.innerHTML = `
360
+ <div class="flex-shrink-0">
361
+ <div class="w-12 h-12 rounded-full ${isMe ? 'bg-blue-500' : 'bg-gray-400'} flex items-center justify-center text-white">
362
+ <svg class="w-7 h-7" fill="currentColor" viewBox="0 0 20 20">
363
+ ${isMe
364
+ ? '<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />'
365
+ : '<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/>'
366
+ }
367
+ </svg>
368
+ </div>
369
+ </div>
370
+ <div class="flex-1">
371
+ <div class="flex items-center gap-2 mb-1">
372
+ <h4 class="font-bold text-lg text-gray-900">${member.name}</h4>
373
+ ${isMe ? '<span class="bg-blue-500 text-white text-xs px-2 py-1 rounded-full font-semibold">شما</span>' : ''}
374
+ </div>
375
+ <p class="text-sm text-gray-600 mb-2">${member.studentNumber}</p>
376
+ <div class="flex flex-wrap gap-3 text-sm">
377
+ <span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
378
+ <strong>MBTI:</strong> ${member.mbti || 'ندارد'}
379
+ </span>
380
+ <span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
381
+ <strong>سبک:</strong> ${member.learningStyle || 'ندارد'}
382
+ </span>
383
+ ${member.ams ? `<span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
384
+ <strong>AMS:</strong> ${member.ams}
385
+ </span>` : ''}
386
+ ${member.cooperative ? `<span class="bg-white px-3 py-1 rounded-lg border border-gray-200">
387
+ <strong>همکاری:</strong> ${member.cooperative}
388
+ </span>` : ''}
389
+ </div>
390
+ </div>
391
+ `;
392
+
393
+ membersList.appendChild(div);
394
+ });
395
+
396
+ showState('groupedState');
397
+ }
398
+
399
+ window.onload = () => {
400
+ setTimeout(loadGroupInfo, 500);
401
+ };
402
+ </script>
403
+ </body>
404
+ </html>
405
+
backend/static/pages/login.html ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ورود - سیستم گروه‌بندی دانش آموزان</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif;
14
+ }
15
+ /* Hide browser's default password reveal button */
16
+ input[type="password"]::-ms-reveal,
17
+ input[type="password"]::-ms-clear {
18
+ display: none;
19
+ }
20
+ input[type="password"]::-webkit-credentials-auto-fill-button,
21
+ input[type="password"]::-webkit-caps-lock-indicator {
22
+ display: none;
23
+ }
24
+ </style>
25
+ <script>
26
+ tailwind.config = {
27
+ theme: {
28
+ extend: {
29
+ fontFamily: {
30
+ 'vazir': ['Vazirmatn', 'system-ui', '-apple-system', 'sans-serif'],
31
+ }
32
+ }
33
+ }
34
+ }
35
+ </script>
36
+ </head>
37
+ <body class="font-vazir bg-gray-50">
38
+ <!-- Split Layout -->
39
+ <div class="min-h-screen flex flex-col md:flex-row">
40
+ <!-- Left: Illustration Section -->
41
+ <div class="bg-gradient-to-br from-teal-600 via-cyan-500 to-blue-500 p-8 md:p-12 flex flex-col items-center justify-center md:w-2/5">
42
+ <div class="max-w-sm w-full">
43
+ <!-- Illustration Placeholder -->
44
+ <div class="bg-white/20 backdrop-blur-sm rounded-3xl p-8 mb-8 shadow-2xl">
45
+ <div class="text-center text-white">
46
+ <img src="../Icons/logo/logo.png" alt="TalimBot Logo" class="w-32 h-32 mx-auto mb-4 rounded-2xl">
47
+ <h2 class="text-2xl font-bold mb-2">سیستم گروه‌بندی هوشمند</h2>
48
+ <p class="text-teal-100 text-sm">مدیریت و گروه‌بندی دانش آموزان بر اساس شخصیت و سبک یادگیری</p>
49
+ </div>
50
+ </div>
51
+ <h1 class="text-4xl font-extrabold text-white text-center drop-shadow-lg">
52
+ TalimBot
53
+ </h1>
54
+ </div>
55
+ </div>
56
+
57
+ <!-- Right: Form Section -->
58
+ <div class="bg-white p-6 md:p-12 flex-1 flex items-center justify-center">
59
+ <div class="w-full max-w-md space-y-8">
60
+ <!-- Header -->
61
+ <div class="text-center">
62
+ <h2 class="text-3xl font-bold text-gray-900 mb-2">سیستم گروه‌بندی دانش آموزان</h2>
63
+ <p class="text-gray-600">لطفاً نقش خود را انتخاب کنید</p>
64
+ </div>
65
+
66
+ <!-- Role Selector -->
67
+ <!-- Role Selector -->
68
+ <div class="grid grid-cols-2 gap-4">
69
+ <button type="button" id="studentRole" onclick="selectRole('student')"
70
+ class="role-card p-6 border-2 border-gray-200 rounded-2xl hover:border-teal-500 hover:bg-teal-50 transition-all duration-300 group">
71
+ <img src="../Icons/studentIcon.png" alt="Student" class="w-24 h-24 mx-auto mb-3 object-cover">
72
+ <h3 class="text-lg font-bold text-gray-800 group-hover:text-teal-600">دانش آموز</h3>
73
+ </button>
74
+ <button type="button" id="teacherRole" onclick="selectRole('teacher')"
75
+ class="role-card p-6 border-2 border-gray-200 rounded-2xl hover:border-cyan-500 hover:bg-cyan-50 transition-all duration-300 group">
76
+ <img src="../Icons/teacherIcon3.png" alt="Teacher" class="w-24 h-24 mx-auto mb-3 object-cover">
77
+ <h3 class="text-lg font-bold text-gray-800 group-hover:text-cyan-600">معلم</h3>
78
+ </button>
79
+ </div>
80
+
81
+ <!-- Login Form -->
82
+ <form id="loginForm" onsubmit="handleLogin(event)" class="space-y-6">
83
+ <!-- Student Login -->
84
+ <div id="studentLogin" class="hidden space-y-4">
85
+ <div>
86
+ <label for="nationalCode" class="block text-sm font-semibold text-gray-700 mb-2">
87
+ کد ملی
88
+ </label>
89
+ <input
90
+ type="text"
91
+ id="nationalCode"
92
+ placeholder="کد ملی خود را وارد کنید"
93
+ class="w-full p-4 border-2 border-gray-300 rounded-xl text-center text-lg focus:border-teal-500 focus:ring-2 focus:ring-teal-200 outline-none transition-all"
94
+ dir="ltr"
95
+ >
96
+ <p class="text-xs text-gray-500 mt-2 text-center">کد ملی خود را بدون صفر ابتدایی وارد کنید</p>
97
+ </div>
98
+ </div>
99
+
100
+ <!-- Teacher Login -->
101
+ <div id="teacherLogin" class="hidden">
102
+ <label for="teacherPassword" class="block text-sm font-semibold text-gray-700 mb-2">
103
+ رمز عبور
104
+ </label>
105
+ <div class="relative">
106
+ <input
107
+ type="password"
108
+ id="teacherPassword"
109
+ placeholder="رمز عبور خود را وارد کنید"
110
+ class="w-full p-4 pl-12 border-2 border-gray-300 rounded-xl focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition-all"
111
+ >
112
+ <button type="button" onclick="togglePassword()" class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 hover:text-gray-700 focus:outline-none">
113
+ <svg id="eyeIcon" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
114
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
115
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
116
+ </svg>
117
+ </button>
118
+ </div>
119
+ <!-- <p class="text-xs text-gray-500 mt-2 text-center flex items-center justify-center gap-1">
120
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
121
+ <path d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z"/>
122
+ </svg>
123
+ رمز عبور دمو: teacher123
124
+ </p> -->
125
+ </div>
126
+
127
+ <!-- Submit Button -->
128
+ <button
129
+ type="submit"
130
+ id="loginButton"
131
+ disabled
132
+ class="w-full bg-gradient-to-r from-teal-600 to-cyan-600 text-white py-4 rounded-xl font-bold text-lg hover:from-teal-700 hover:to-cyan-700 disabled:from-gray-300 disabled:to-gray-400 disabled:cursor-not-allowed transform hover:scale-[1.02] active:scale-[0.98] transition-all duration-300 shadow-lg disabled:shadow-none"
133
+ >
134
+ ورود به سیستم
135
+ </button>
136
+ </form>
137
+
138
+ <!-- Error Message -->
139
+ <div id="errorMessage" class="hidden bg-red-50 border-r-4 border-red-500 text-red-700 p-4 rounded-lg">
140
+ <div class="flex items-center gap-2">
141
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
142
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
143
+ </svg>
144
+ <span id="errorText" class="text-sm font-medium"></span>
145
+ </div>
146
+ </div>
147
+
148
+ <!-- Footer -->
149
+ <div class="text-center text-sm text-gray-500">
150
+ <p>نسخه 1.0.0 | آبان 1404</p>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </div>
155
+
156
+ <script src="../assets/js/data.js"></script>
157
+ <script>
158
+ let selectedRole = null;
159
+
160
+ function selectRole(role) {
161
+ selectedRole = role;
162
+
163
+ // Update UI
164
+ const studentBtn = document.getElementById('studentRole');
165
+ const teacherBtn = document.getElementById('teacherRole');
166
+
167
+ studentBtn.className = 'role-card p-6 border-2 rounded-2xl transition-all duration-300 group';
168
+ teacherBtn.className = 'role-card p-6 border-2 rounded-2xl transition-all duration-300 group';
169
+
170
+ if (role === 'student') {
171
+ studentBtn.className += ' border-teal-500 bg-teal-50 shadow-lg scale-105';
172
+ document.getElementById('loginButton').className = document.getElementById('loginButton').className.replace('from-teal-600 to-cyan-600', 'from-teal-600 to-teal-700').replace('hover:from-teal-700 hover:to-cyan-700', 'hover:from-teal-700 hover:to-teal-800');
173
+ } else {
174
+ teacherBtn.className += ' border-cyan-500 bg-cyan-50 shadow-lg scale-105';
175
+ document.getElementById('loginButton').className = document.getElementById('loginButton').className.replace('from-teal-600 to-teal-700', 'from-teal-600 to-cyan-600').replace('hover:from-teal-700 hover:to-teal-800', 'hover:from-teal-700 hover:to-cyan-700');
176
+ }
177
+
178
+ // Show appropriate form
179
+ document.getElementById('studentLogin').classList.toggle('hidden', role !== 'student');
180
+ document.getElementById('teacherLogin').classList.toggle('hidden', role !== 'teacher');
181
+
182
+ // Enable login button
183
+ document.getElementById('loginButton').disabled = false;
184
+
185
+ // Hide error
186
+ document.getElementById('errorMessage').classList.add('hidden');
187
+ }
188
+
189
+ function togglePassword() {
190
+ const input = document.getElementById('teacherPassword');
191
+ const type = input.type === 'password' ? 'text' : 'password';
192
+ input.type = type;
193
+ }
194
+
195
+ async function handleLogin(event) {
196
+ event.preventDefault();
197
+
198
+ const errorDiv = document.getElementById('errorMessage');
199
+ errorDiv.classList.add('hidden');
200
+
201
+ if (selectedRole === 'student') {
202
+ let nationalCode = document.getElementById('nationalCode').value.trim();
203
+
204
+ if (!nationalCode) {
205
+ showError('لطفاً کد ملی خود را وارد کنید');
206
+ return;
207
+ }
208
+
209
+ // Check if starts with zero and show warning
210
+ if (nationalCode.startsWith('0')) {
211
+ showError('لطفاً صفر ابتدایی را از کد ملی حذف کنید');
212
+ return;
213
+ }
214
+
215
+ // No length restriction - just check if it matches a student in database
216
+
217
+ try {
218
+ // Authenticate student with backend using national code only
219
+ const response = await fetch('/api/auth/student-by-nationalcode', {
220
+ method: 'POST',
221
+ headers: {
222
+ 'Content-Type': 'application/json'
223
+ },
224
+ body: JSON.stringify({ nationalCode })
225
+ });
226
+
227
+ if (!response.ok) {
228
+ const error = await response.json();
229
+ showError(error.detail || 'خطا در ورود');
230
+ return;
231
+ }
232
+
233
+ const result = await response.json();
234
+ sessionStorage.setItem('currentStudent', result.student.studentNumber);
235
+ sessionStorage.setItem('studentName', result.student.name);
236
+ window.location.href = 'student-dashboard.html';
237
+ } catch (error) {
238
+ showError('خطا در اتصال به سرور. لطفاً اطمینان حاصل کنید که سرور در حال اجرا است');
239
+ console.error('Login error:', error);
240
+ }
241
+
242
+ } else if (selectedRole === 'teacher') {
243
+ const password = document.getElementById('teacherPassword').value;
244
+
245
+ if (!password) {
246
+ showError('لطفاً رمز عبور را وارد کنید');
247
+ return;
248
+ }
249
+
250
+ try {
251
+ const isValid = await checkTeacherPassword(password);
252
+ if (!isValid) {
253
+ showError('رمز عبور نادرست است. دوباره تلاش کنید.');
254
+ return;
255
+ }
256
+
257
+ sessionStorage.setItem('isTeacher', 'true');
258
+ window.location.href = 'teacher-dashboard.html';
259
+ } catch (error) {
260
+ showError('خطا در اتصال به سرور');
261
+ console.error('Login error:', error);
262
+ }
263
+ }
264
+ }
265
+
266
+ function showError(message) {
267
+ const errorDiv = document.getElementById('errorMessage');
268
+ const errorText = document.getElementById('errorText');
269
+ errorText.textContent = message;
270
+ errorDiv.classList.remove('hidden');
271
+ }
272
+ </script>
273
+ </body>
274
+ </html>
backend/static/pages/student-dashboard.html ADDED
@@ -0,0 +1,580 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
7
+ <meta http-equiv="Pragma" content="no-cache">
8
+ <meta http-equiv="Expires" content="0">
9
+ <!-- Version: 2.1 - Fixed AMS/Cooperative field preservation -->
10
+ <title>داشبورد دانش آموز - سیستم گروه‌بندی</title>
11
+ <script src="https://cdn.tailwindcss.com"></script>
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
15
+ <style>
16
+ body {
17
+ font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif;
18
+ }
19
+ </style>
20
+ <script>
21
+ tailwind.config = {
22
+ theme: {
23
+ extend: {
24
+ fontFamily: {
25
+ 'vazir': ['Vazirmatn', 'system-ui', '-apple-system', 'sans-serif'],
26
+ }
27
+ }
28
+ }
29
+ }
30
+ </script>
31
+ </head>
32
+ <body class="font-vazir bg-gray-50 min-h-screen">
33
+ <!-- Top Navigation Bar -->
34
+ <nav style="background-color: #1b6e6e;" class="shadow-lg border-b border-gray-200 sticky top-0 z-50">
35
+ <div class="max-w-7xl mx-auto px-4">
36
+ <div class="flex items-center justify-between h-16">
37
+ <!-- Logo/Brand -->
38
+ <div class="flex items-center gap-3">
39
+ <div class="bg-white/20 w-10 h-10 rounded-lg flex items-center justify-center">
40
+ <svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
41
+ <path d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z"/>
42
+ </svg>
43
+ </div>
44
+ <div>
45
+ <h1 class="text-xl font-bold text-white">TalimBot</h1>
46
+ <p class="text-xs text-white/80">سیستم گروه‌بندی هوشمند</p>
47
+ </div>
48
+ </div>
49
+
50
+ <!-- Navigation Links -->
51
+ <div class="hidden md:flex items-center gap-1">
52
+ <a href="#" class="px-4 py-2 text-white bg-white/20 rounded-lg font-semibold border-b-2 border-white">
53
+ <span class="flex items-center gap-2">
54
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
55
+ <path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
56
+ </svg>
57
+ داشبورد
58
+ </span>
59
+ </a>
60
+ <a href="group-view.html" class="px-4 py-2 text-white/80 hover:text-white hover:bg-white/10 rounded-lg font-medium transition-colors">
61
+ <span class="flex items-center gap-2">
62
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
63
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/>
64
+ </svg>
65
+ گروه من
66
+ </span>
67
+ </a>
68
+ </div>
69
+
70
+ <!-- User Menu -->
71
+ <div class="flex items-center gap-4">
72
+ <div class="hidden md:block text-left">
73
+ <p class="text-sm font-semibold text-white" id="navStudentName">دانش آموز</p>
74
+ <p class="text-xs text-white/80" id="navStudentNumber">-</p>
75
+ </div>
76
+ <div class="w-10 h-10 rounded-full bg-white/20 flex items-center justify-center text-white font-bold">
77
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
78
+ <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/>
79
+ </svg>
80
+ </div>
81
+ <a href="login.html" class="text-white/80 hover:text-red-300 transition-colors" title="خروج">
82
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
83
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
84
+ </svg>
85
+ </a>
86
+ </div>
87
+ </div>
88
+ </div>
89
+ </nav>
90
+
91
+ <!-- Main Content -->
92
+ <div class="max-w-7xl mx-auto px-4 py-8">
93
+ <!-- Welcome Section -->
94
+ <div class="mb-8">
95
+ <h2 class="text-3xl font-bold text-gray-900 mb-2" id="studentName"></h2>
96
+ <p class="text-gray-600 mb-3">لطفاً تمام تست‌های شخصیتی خود را تکمیل کنید و اطلاعات را ذخیره نمایید.</p>
97
+ <div class="bg-blue-50 border-r-4 border-blue-500 p-4 rounded-lg">
98
+ <p class="text-sm text-blue-800"><strong>💡 چرا این اطلاعات مهم است؟</strong> سیستم گروه‌بندی هوشمند از روش‌های علمی مانند <strong>ZPD</strong>، تکمیل شخصیتی MBTI، و تنوع سبک یادگیری VARK برای ساخت بهترین تیم‌ها استفاده می‌کند. هر چه اطلاعات کامل‌تری وارد کنید، گروه‌بندی دقیق‌تری خواهید داشت!</p>
99
+ </div>
100
+ </div>
101
+
102
+ <!-- Quick Stats -->
103
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
104
+ <div class="bg-white rounded-xl shadow-md p-6 border-r-4 border-blue-500">
105
+ <div class="flex items-center justify-between">
106
+ <div>
107
+ <p class="text-gray-600 text-sm font-medium">شماره دانش‌آموزی</p>
108
+ <p class="text-2xl font-bold text-gray-900 mt-1" id="studentNumber">-</p>
109
+ </div>
110
+ <div class="bg-blue-100 p-3 rounded-lg">
111
+ <svg class="w-6 h-6 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
112
+ <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/>
113
+ </svg>
114
+ </div>
115
+ </div>
116
+ </div>
117
+
118
+ <div class="bg-white rounded-xl shadow-md p-6 border-r-4 border-green-500">
119
+ <div class="flex items-center justify-between">
120
+ <div>
121
+ <p class="text-gray-600 text-sm font-medium">تست‌های تکمیل شده</p>
122
+ <p class="text-2xl font-bold text-gray-900 mt-1"><span id="testsCompleted">0</span> / 4</p>
123
+ </div>
124
+ <div class="bg-green-100 p-3 rounded-lg">
125
+ <svg class="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 20 20">
126
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
127
+ </svg>
128
+ </div>
129
+ </div>
130
+ </div>
131
+
132
+ <div class="bg-white rounded-xl shadow-md p-6 border-r-4 border-orange-500">
133
+ <div class="flex items-center justify-between">
134
+ <div>
135
+ <p class="text-gray-600 text-sm font-medium">معدل</p>
136
+ <p class="text-2xl font-bold text-gray-900 mt-1" id="gradeDisplay">-</p>
137
+ </div>
138
+ <div class="bg-orange-100 p-3 rounded-lg">
139
+ <svg class="w-6 h-6 text-orange-600" fill="currentColor" viewBox="0 0 20 20">
140
+ <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
141
+ </svg>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
+ <!-- Tests Section -->
148
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
149
+ <!-- MBTI Test -->
150
+ <div class="bg-white rounded-xl shadow-md hover:shadow-xl transition-shadow p-6">
151
+ <div class="flex items-start gap-4 mb-4">
152
+ <div class="bg-gradient-to-br from-blue-500 to-blue-600 p-4 rounded-xl flex-shrink-0">
153
+ <svg class="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 20 20">
154
+ <path d="M9 4.804A7.968 7.968 0 005.5 4c-1.255 0-2.443.29-3.5.804v10A7.969 7.969 0 015.5 14c1.669 0 3.218.51 4.5 1.385A7.962 7.962 0 0114.5 14c1.255 0 2.443.29 3.5.804v-10A7.968 7.968 0 0014.5 4c-1.255 0-2.443.29-3.5.804V12a1 1 0 11-2 0V4.804z"/>
155
+ </svg>
156
+ </div>
157
+ <div class="flex-1">
158
+ <h3 class="font-bold text-lg text-gray-900 mb-2">MBTI - تیپ شخصیتی</h3>
159
+ <p class="text-sm text-gray-600 leading-relaxed">شناخت تیپ شخصیتی برای ایجاد تعادل در گروه و درک نقاط قوت شما</p>
160
+ </div>
161
+ </div>
162
+ <div class="space-y-3">
163
+ <div class="flex gap-2">
164
+ <a href="https://www.16personalities.com/fa" target="_blank"
165
+ class="flex-1 bg-blue-600 hover:bg-blue-700 text-white text-sm font-semibold py-3 px-4 rounded-lg transition-colors text-center flex items-center justify-center gap-2">
166
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
167
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9"/>
168
+ </svg>
169
+ شروع آزمون
170
+ </a>
171
+ <a href="https://www.16personalities.com/" target="_blank"
172
+ class="bg-blue-100 hover:bg-blue-200 text-blue-700 text-sm font-semibold py-3 px-4 rounded-lg transition-colors">
173
+ EN
174
+ </a>
175
+ </div>
176
+ <div>
177
+ <label class="block text-sm font-semibold text-gray-700 mb-2">نتیجه تست</label>
178
+ <select id="mbtiInput"
179
+ class="w-full border-2 border-gray-300 rounded-lg py-3 px-4 focus:border-green-500 focus:ring-2 focus:ring-green-200 outline-none transition-all">
180
+ <option value="">انتخاب کنید...</option>
181
+ <option value="INTJ">INTJ</option>
182
+ <option value="INTP">INTP</option>
183
+ <option value="ENTJ">ENTJ</option>
184
+ <option value="ENTP">ENTP</option>
185
+ <option value="INFJ">INFJ</option>
186
+ <option value="INFP">INFP</option>
187
+ <option value="ENFJ">ENFJ</option>
188
+ <option value="ENFP">ENFP</option>
189
+ <option value="ISTJ">ISTJ</option>
190
+ <option value="ISFJ">ISFJ</option>
191
+ <option value="ESTJ">ESTJ</option>
192
+ <option value="ESFJ">ESFJ</option>
193
+ <option value="ISTP">ISTP</option>
194
+ <option value="ISFP">ISFP</option>
195
+ <option value="ESTP">ESTP</option>
196
+ <option value="ESFP">ESFP</option>
197
+ </select>
198
+ </div>
199
+ </div>
200
+ </div>
201
+
202
+ <!-- VARK Test -->
203
+ <div class="bg-white rounded-xl shadow-md hover:shadow-xl transition-shadow p-6">
204
+ <div class="flex items-start gap-4 mb-4">
205
+ <div class="bg-gradient-to-br from-green-500 to-green-600 p-4 rounded-xl flex-shrink-0">
206
+ <svg class="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 20 20">
207
+ <path fill-rule="evenodd" d="M6 6V5a3 3 0 013-3h2a3 3 0 013 3v1h2a2 2 0 012 2v3.57A22.952 22.952 0 0110 13a22.95 22.95 0 01-8-1.43V8a2 2 0 012-2h2zm2-1a1 1 0 011-1h2a1 1 0 011 1v1H8V5zm1 5a1 1 0 011-1h.01a1 1 0 110 2H10a1 1 0 01-1-1z" clip-rule="evenodd"/>
208
+ <path d="M2 13.692V16a2 2 0 002 2h12a2 2 0 002-2v-2.308A24.974 24.974 0 0110 15c-2.796 0-5.487-.46-8-1.308z"/>
209
+ </svg>
210
+ </div>
211
+ <div class="flex-1">
212
+ <h3 class="font-bold text-lg text-gray-900 mb-2">VARK - سبک یادگیری</h3>
213
+ <p class="text-sm text-gray-600 leading-relaxed">شناسایی سبک یادگیری شما (دیداری، شنیداری، خواندن/نوشتن، حرکتی)</p>
214
+ </div>
215
+ </div>
216
+ <div class="space-y-3">
217
+ <div class="flex gap-2">
218
+ <a href="https://vark-learn.com/%D9%BE%D8%B1%D8%B3%D8%B4%D9%86%D8%A7%D9%85%D9%87-%D9%88%D8%A7%D8%B1%DA%A9/" target="_blank"
219
+ class="flex-1 bg-green-600 hover:bg-green-700 text-white text-sm font-semibold py-3 px-4 rounded-lg transition-colors text-center flex items-center justify-center gap-2">
220
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
221
+ <path fill-rule="evenodd" d="M3 6a3 3 0 013-3h10a1 1 0 01.8 1.6L14.25 8l2.55 3.4A1 1 0 0116 13H6a1 1 0 00-1 1v3a1 1 0 11-2 0V6z" clip-rule="evenodd"/>
222
+ </svg>
223
+ شروع آزمون
224
+ </a>
225
+ <a href="https://vark-learn.com/the-vark-questionnaire/" target="_blank"
226
+ class="bg-green-100 hover:bg-green-200 text-green-700 text-sm font-semibold py-3 px-4 rounded-lg transition-colors">
227
+ EN
228
+ </a>
229
+ </div>
230
+ <div>
231
+ <label class="block text-sm font-semibold text-gray-700 mb-2">نتیجه تست</label>
232
+ <select id="varkInput"
233
+ class="w-full border-2 border-gray-300 rounded-lg py-3 px-4 focus:border-green-500 focus:ring-2 focus:ring-green-200 outline-none transition-all">
234
+ <option value="">انتخاب کنید...</option>
235
+ <option value="Visual">Visual (دیداری)</option>
236
+ <option value="Aural">Aural (شنیداری)</option>
237
+ <option value="Read/Write">Read/Write (خواندن/نوشتن)</option>
238
+ <option value="Kinesthetic">Kinesthetic (حرکتی/عملی)</option>
239
+ </select>
240
+ </div>
241
+ </div>
242
+ </div>
243
+
244
+ <!-- AMS Test -->
245
+ <div class="bg-white rounded-xl shadow-md hover:shadow-xl transition-shadow p-6">
246
+ <div class="flex items-start gap-4 mb-4">
247
+ <div class="bg-gradient-to-br from-orange-500 to-orange-600 p-4 rounded-xl flex-shrink-0">
248
+ <svg class="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 20 20">
249
+ <path fill-rule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clip-rule="evenodd"/>
250
+ </svg>
251
+ </div>
252
+ <div class="flex-1">
253
+ <h3 class="font-bold text-lg text-gray-900 mb-2">AMS - انگیزش تحصیلی</h3>
254
+ <p class="text-sm text-gray-600 leading-relaxed">سنجش انگیزه درونی و بیرونی برای یادگیری و پیشرفت تحصیلی</p>
255
+ </div>
256
+ </div>
257
+ <div class="space-y-3">
258
+ <div class="flex gap-2">
259
+ <a href="ams-questionnaire.html"
260
+ class="flex-1 bg-orange-600 hover:bg-orange-700 text-white text-sm font-semibold py-3 px-4 rounded-lg transition-colors text-center flex items-center justify-center gap-2">
261
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
262
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
263
+ </svg>
264
+ شروع آزمون (28 سؤال)
265
+ </a>
266
+ </div>
267
+ <div>
268
+ <label class="block text-sm font-semibold text-gray-700 mb-2">نمره کل (از 196)</label>
269
+ <input type="number" id="amsInput" placeholder="نمره را پس از تکمیل آزمون وارد کنید" min="0" max="196"
270
+ class="w-full text-center text-lg font-bold border-2 border-gray-300 rounded-lg py-3 px-4 focus:border-orange-500 focus:ring-2 focus:ring-orange-200 outline-none transition-all">
271
+ </div>
272
+ </div>
273
+ </div>
274
+
275
+ <!-- Cooperative Test -->
276
+ <div class="bg-white rounded-xl shadow-md hover:shadow-xl transition-shadow p-6">
277
+ <div class="flex items-start gap-4 mb-4">
278
+ <div class="bg-gradient-to-br from-purple-500 to-purple-600 p-4 rounded-xl flex-shrink-0">
279
+ <svg class="w-8 h-8 text-white" fill="currentColor" viewBox="0 0 20 20">
280
+ <path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"/>
281
+ </svg>
282
+ </div>
283
+ <div class="flex-1">
284
+ <h3 class="font-bold text-lg text-gray-900 mb-2">توانایی همکاری</h3>
285
+ <p class="text-sm text-gray-600 leading-relaxed">سنجش مهارت‌های کار گروهی، تعامل و همکاری با دیگران</p>
286
+ </div>
287
+ </div>
288
+ <div class="space-y-3">
289
+ <div class="flex gap-2">
290
+ <a href="cooperative-questionnaire.html"
291
+ class="flex-1 bg-purple-600 hover:bg-purple-700 text-white text-sm font-semibold py-3 px-4 rounded-lg transition-colors text-center flex items-center justify-center gap-2">
292
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
293
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
294
+ </svg>
295
+ شروع آزمون (25 سؤال)
296
+ </a>
297
+ </div>
298
+ <div>
299
+ <label class="block text-sm font-semibold text-gray-700 mb-2">نمره کل (از 125)</label>
300
+ <input type="number" id="cooperativeInput" placeholder="نمره را پس از تکمیل آزمون وارد کنید" min="0" max="125"
301
+ class="w-full text-center text-lg font-bold border-2 border-gray-300 rounded-lg py-3 px-4 focus:border-purple-500 focus:ring-2 focus:ring-purple-200 outline-none transition-all">
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+
307
+ <!-- Preferences Section -->
308
+ <div class="bg-white rounded-xl shadow-md p-6 mb-8">
309
+ <div class="mb-6">
310
+ <h3 class="font-bold text-xl text-gray-900 mb-2 flex items-center gap-2">
311
+ <svg class="w-6 h-6 text-gray-700" fill="currentColor" viewBox="0 0 20 20">
312
+ <path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"/>
313
+ </svg>
314
+ ترجیحات همگروهی
315
+ </h3>
316
+ <p class="text-gray-600">می‌توانید حداکثر 4 نفر از همکلاسی‌های خود را که دوست دارید در یک گروه باشید انتخاب کنید (اختیاری)</p>
317
+ </div>
318
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 mb-4" id="preferredStudentsCheckbox">
319
+ <!-- Populated by JS -->
320
+ </div>
321
+ <div class="bg-blue-50 border border-blue-200 rounded-lg p-3">
322
+ <p class="text-sm text-blue-800 font-medium text-center" id="selectedCount">انتخاب شده: 0/4</p>
323
+ </div>
324
+ </div>
325
+
326
+ <!-- Action Buttons -->
327
+ <div class="flex flex-col items-center gap-4">
328
+ <button onclick="saveAllResults()"
329
+ class="w-full md:w-1/3 bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 text-white py-4 px-6 rounded-xl font-bold text-lg shadow-lg hover:shadow-xl transform hover:scale-[1.02] active:scale-[0.98] transition-all flex items-center justify-center gap-2">
330
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
331
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"/>
332
+ </svg>
333
+ ذخیره تمام اطلاعات
334
+ </button>
335
+ <button onclick="viewGroup()"
336
+ class="w-full md:w-1/3 bg-gradient-to-r from-purple-600 to-purple-700 hover:from-purple-700 hover:to-purple-800 text-white py-4 px-6 rounded-xl font-bold text-lg shadow-lg hover:shadow-xl transform hover:scale-[1.02] active:scale-[0.98] transition-all flex items-center justify-center gap-2">
337
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
338
+ <path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"/>
339
+ </svg>
340
+ مشاهده گروه من
341
+ </button>
342
+ </div>
343
+ </div>
344
+
345
+ <!-- Success Toast -->
346
+ <div id="successToast" class="hidden fixed top-20 left-1/2 -translate-x-1/2 bg-green-500 text-white px-6 py-4 rounded-xl shadow-2xl z-50">
347
+ <div class="flex items-center gap-2">
348
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
349
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
350
+ </svg>
351
+ <span class="font-semibold">اطلاعات با موفقیت ذخیره شد!</span>
352
+ </div>
353
+ </div>
354
+
355
+ <!-- Footer -->
356
+ <footer class="bg-white border-t border-gray-200 mt-16">
357
+ <div class="max-w-7xl mx-auto px-4 py-6">
358
+ <div class="text-center text-gray-600 text-sm">
359
+ <p class="mb-2">نسخه 1.0.0 | آبان 1404</p>
360
+ <p>© ۲۰۲۵ TalimBot - سیستم گروه‌بندی هوشمند دانش آموزان</p>
361
+ </div>
362
+ </div>
363
+ </footer>
364
+
365
+ <script src="../assets/js/data.js"></script>
366
+ <script>
367
+ let currentStudent = null;
368
+ let allStudents = [];
369
+
370
+ // Check if user is authenticated
371
+ function checkAuth() {
372
+ const studentNumber = sessionStorage.getItem('currentStudent');
373
+ if (!studentNumber) {
374
+ window.location.href = 'login.html';
375
+ return null;
376
+ }
377
+ return studentNumber;
378
+ }
379
+
380
+ async function loadStudentData() {
381
+ const studentNumber = checkAuth();
382
+ if (!studentNumber) return;
383
+
384
+ try {
385
+ currentStudent = await getStudent(studentNumber);
386
+ allStudents = await getAllStudents();
387
+
388
+ if (!currentStudent) {
389
+ alert('دانش آموز یافت نشد');
390
+ window.location.href = 'login.html';
391
+ return;
392
+ }
393
+
394
+ // Update nav and page info with Persian name
395
+ const displayName = currentStudent.name || studentNumber;
396
+ document.getElementById('studentName').textContent = `خوش آمدید، ${displayName}`;
397
+ document.getElementById('navStudentName').textContent = displayName;
398
+ document.getElementById('studentNumber').textContent = currentStudent.studentNumber;
399
+ document.getElementById('navStudentNumber').textContent = currentStudent.studentNumber;
400
+ document.getElementById('gradeDisplay').textContent = currentStudent.grade.toFixed(2);
401
+
402
+ // Count completed tests
403
+ let testsCompleted = 0;
404
+ if (currentStudent.mbti) testsCompleted++;
405
+ if (currentStudent.learningStyle) testsCompleted++;
406
+ if (currentStudent.ams) testsCompleted++;
407
+ if (currentStudent.cooperative) testsCompleted++;
408
+ document.getElementById('testsCompleted').textContent = testsCompleted;
409
+
410
+ // Load existing data from backend
411
+ if (currentStudent.mbti) document.getElementById('mbtiInput').value = currentStudent.mbti;
412
+ if (currentStudent.learningStyle) document.getElementById('varkInput').value = currentStudent.learningStyle;
413
+ if (currentStudent.ams) document.getElementById('amsInput').value = currentStudent.ams;
414
+ if (currentStudent.cooperative) document.getElementById('cooperativeInput').value = currentStudent.cooperative;
415
+
416
+ // Check for scores from questionnaires in sessionStorage (takes priority)
417
+ const amsScore = sessionStorage.getItem('amsScore');
418
+ const cooperativeScore = sessionStorage.getItem('cooperativeScore');
419
+
420
+ if (amsScore) {
421
+ document.getElementById('amsInput').value = amsScore;
422
+ sessionStorage.removeItem('amsScore');
423
+ }
424
+
425
+ if (cooperativeScore) {
426
+ document.getElementById('cooperativeInput').value = cooperativeScore;
427
+ sessionStorage.removeItem('cooperativeScore');
428
+ }
429
+
430
+ // Restore any unsaved form data from sessionStorage (in case user navigated away without saving)
431
+ const unsavedMbti = sessionStorage.getItem('unsavedMbti');
432
+ const unsavedVark = sessionStorage.getItem('unsavedVark');
433
+ const unsavedAms = sessionStorage.getItem('unsavedAms');
434
+ const unsavedCooperative = sessionStorage.getItem('unsavedCooperative');
435
+
436
+ if (unsavedMbti) {
437
+ document.getElementById('mbtiInput').value = unsavedMbti;
438
+ sessionStorage.removeItem('unsavedMbti');
439
+ }
440
+
441
+ if (unsavedVark) {
442
+ document.getElementById('varkInput').value = unsavedVark;
443
+ sessionStorage.removeItem('unsavedVark');
444
+ }
445
+
446
+ if (unsavedAms) {
447
+ document.getElementById('amsInput').value = unsavedAms;
448
+ sessionStorage.removeItem('unsavedAms');
449
+ }
450
+
451
+ if (unsavedCooperative) {
452
+ document.getElementById('cooperativeInput').value = unsavedCooperative;
453
+ sessionStorage.removeItem('unsavedCooperative');
454
+ }
455
+
456
+ populatePreferredStudents();
457
+ } catch (error) {
458
+ console.error('Error loading student data:', error);
459
+ alert('خطا در بارگذاری اطلاعات. لطفاً دوباره تلاش کنید');
460
+ }
461
+ }
462
+
463
+ function populatePreferredStudents() {
464
+ const container = document.getElementById('preferredStudentsCheckbox');
465
+ container.innerHTML = '';
466
+
467
+ allStudents.forEach(student => {
468
+ if (student.studentNumber === currentStudent.studentNumber) return;
469
+
470
+ const div = document.createElement('div');
471
+ div.className = 'flex items-center gap-2 p-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors';
472
+
473
+ const checkbox = document.createElement('input');
474
+ checkbox.type = 'checkbox';
475
+ checkbox.id = 'student_' + student.studentNumber;
476
+ checkbox.value = student.studentNumber;
477
+ checkbox.onchange = updateSelectedCount;
478
+ checkbox.className = 'w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500';
479
+
480
+ if (currentStudent.preferredStudents && currentStudent.preferredStudents.includes(student.studentNumber)) {
481
+ checkbox.checked = true;
482
+ }
483
+
484
+ const label = document.createElement('label');
485
+ label.htmlFor = 'student_' + student.studentNumber;
486
+ label.textContent = `${student.name} (${student.studentNumber})`;
487
+ label.className = 'text-sm text-gray-700 cursor-pointer flex-1';
488
+
489
+ div.appendChild(checkbox);
490
+ div.appendChild(label);
491
+ container.appendChild(div);
492
+ });
493
+
494
+ updateSelectedCount();
495
+ }
496
+
497
+ function updateSelectedCount() {
498
+ const checkboxes = document.querySelectorAll('#preferredStudentsCheckbox input[type="checkbox"]:checked');
499
+ const count = checkboxes.length;
500
+ document.getElementById('selectedCount').textContent = `انتخاب شده: ${count}/4`;
501
+
502
+ const allCheckboxes = document.querySelectorAll('#preferredStudentsCheckbox input[type="checkbox"]');
503
+ allCheckboxes.forEach(cb => {
504
+ if (!cb.checked && count >= 4) {
505
+ cb.disabled = true;
506
+ cb.parentElement.classList.add('opacity-50');
507
+ } else {
508
+ cb.disabled = false;
509
+ cb.parentElement.classList.remove('opacity-50');
510
+ }
511
+ });
512
+ }
513
+
514
+ async function saveAllResults() {
515
+ const mbti = document.getElementById('mbtiInput').value.trim().toUpperCase();
516
+ const vark = document.getElementById('varkInput').value.trim();
517
+ const ams = document.getElementById('amsInput').value.trim();
518
+ const cooperative = document.getElementById('cooperativeInput').value.trim();
519
+
520
+ const checkboxes = document.querySelectorAll('#preferredStudentsCheckbox input[type="checkbox"]:checked');
521
+ const preferredStudents = Array.from(checkboxes).map(cb => cb.value);
522
+
523
+ // Build update object with only non-empty values to prevent overwriting existing data
524
+ const updates = {};
525
+ if (mbti) updates.mbti = mbti;
526
+ if (vark) updates.learningStyle = vark;
527
+ if (ams) updates.ams = ams;
528
+ if (cooperative) updates.cooperative = cooperative;
529
+ // Always update preferred students (can be empty array)
530
+ updates.preferredStudents = preferredStudents;
531
+
532
+ const success = await updateStudent(currentStudent.studentNumber, updates);
533
+
534
+ if (success) {
535
+ const toast = document.getElementById('successToast');
536
+ toast.classList.remove('hidden');
537
+ setTimeout(() => toast.classList.add('hidden'), 3000);
538
+ await loadStudentData();
539
+ } else {
540
+ alert('خطا در ذخیره اطلاعات!');
541
+ }
542
+ }
543
+
544
+ function viewGroup() {
545
+ window.location.href = 'group-view.html';
546
+ }
547
+
548
+ // Preserve unsaved form data when navigating to questionnaire pages
549
+ function saveFormState() {
550
+ const mbti = document.getElementById('mbtiInput').value.trim();
551
+ const vark = document.getElementById('varkInput').value.trim();
552
+ const ams = document.getElementById('amsInput').value.trim();
553
+ const cooperative = document.getElementById('cooperativeInput').value.trim();
554
+
555
+ // Only save to sessionStorage if values exist
556
+ if (mbti) sessionStorage.setItem('unsavedMbti', mbti);
557
+ if (vark) sessionStorage.setItem('unsavedVark', vark);
558
+ if (ams) sessionStorage.setItem('unsavedAms', ams);
559
+ if (cooperative) sessionStorage.setItem('unsavedCooperative', cooperative);
560
+ }
561
+
562
+ // Add click listeners to questionnaire links to save form state
563
+ window.onload = function() {
564
+ loadStudentData();
565
+
566
+ // Save form state when clicking on questionnaire links
567
+ const amsLink = document.querySelector('a[href="ams-questionnaire.html"]');
568
+ const cooperativeLink = document.querySelector('a[href="cooperative-questionnaire.html"]');
569
+
570
+ if (amsLink) {
571
+ amsLink.addEventListener('click', saveFormState);
572
+ }
573
+
574
+ if (cooperativeLink) {
575
+ cooperativeLink.addEventListener('click', saveFormState);
576
+ }
577
+ };
578
+ </script>
579
+ </body>
580
+ </html>
backend/static/pages/student-data.html ADDED
@@ -0,0 +1,388 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ورود اطلاعات دانش‌آموزان - TalimBot</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Vazirmatn', sans-serif;
14
+ }
15
+ </style>
16
+ </head>
17
+ <body class="bg-gray-50">
18
+ <!-- Top Navigation -->
19
+ <nav style="background-color: #1b6e6e;" class="shadow-lg border-b border-gray-200 sticky top-0 z-50">
20
+ <div class="max-w-7xl mx-auto px-4">
21
+ <div class="flex justify-between items-center h-16">
22
+ <!-- Logo -->
23
+ <div class="flex items-center gap-3">
24
+ <div class="w-10 h-10 bg-white/20 rounded-lg flex items-center justify-center text-white text-xl font-bold">
25
+ T
26
+ </div>
27
+ <span class="text-xl font-bold text-white">TalimBot</span>
28
+ </div>
29
+
30
+ <!-- Navigation Links -->
31
+ <div class="hidden md:flex items-center gap-6">
32
+ <a href="teacher-dashboard.html" class="text-white/80 hover:text-white font-medium transition-colors">
33
+ داشبورد
34
+ </a>
35
+ <a href="student-data.html" class="text-white font-bold border-b-2 border-white pb-1">
36
+ ورود اطلاعات
37
+ </a>
38
+ </div>
39
+
40
+ <!-- User Menu -->
41
+ <div class="flex items-center gap-4">
42
+ <div class="flex items-center gap-2">
43
+ <div class="w-10 h-10 bg-white/20 rounded-full flex items-center justify-center">
44
+ <svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
45
+ <path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"/>
46
+ <path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1z"/>
47
+ </svg>
48
+ </div>
49
+ <div class="hidden md:block text-right">
50
+ <p class="text-sm font-bold text-white">معلم</p>
51
+ <p class="text-xs text-white/80">مدیر سیستم</p>
52
+ </div>
53
+ </div>
54
+ <a href="login.html" class="text-white/80 hover:text-red-300 transition-colors" title="خروج">
55
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
56
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
57
+ </svg>
58
+ </a>
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </nav>
63
+
64
+ <!-- Main Content -->
65
+ <div class="max-w-7xl mx-auto px-4 py-8">
66
+ <!-- Page Header -->
67
+ <div class="mb-8">
68
+ <h1 class="text-4xl font-bold text-gray-900 mb-2 flex items-center gap-3">
69
+ <svg class="w-10 h-10 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
70
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
71
+ </svg>
72
+ ورود اطلاعات دانش‌آموزان
73
+ </h1>
74
+ <p class="text-gray-600">اطلاعات دانش‌آموزان را وارد کنید تا در سیستم گروه‌بندی ثبت شود</p>
75
+ </div>
76
+
77
+ <!-- Instructions Card -->
78
+ <div class="bg-blue-50 border-r-4 border-blue-500 rounded-lg p-4 mb-6">
79
+ <div class="flex items-start gap-3">
80
+ <svg class="w-6 h-6 text-blue-600 flex-shrink-0 mt-1" fill="currentColor" viewBox="0 0 20 20">
81
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
82
+ </svg>
83
+ <div>
84
+ <h3 class="font-bold text-blue-900 mb-1">راهنما</h3>
85
+ <p class="text-blue-800 text-sm">نام، شماره دانش‌آموزی و نمره را وارد کنید. پس از هر ورودی روی "ذخیره" کلیک کنید تا فرم خالی شود و آماده ورود دانش‌آموز بعدی باشد.</p>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- Stats Cards -->
91
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
92
+ <div class="bg-white rounded-xl shadow-md p-6 border-r-4 border-purple-500">
93
+ <div class="flex items-center justify-between">
94
+ <div>
95
+ <p class="text-gray-600 text-sm mb-1">تعداد دانش‌آموزان ثبت شده</p>
96
+ <p class="text-3xl font-bold text-purple-600" id="totalCount">0</p>
97
+ </div>
98
+ <div class="w-14 h-14 bg-purple-100 rounded-full flex items-center justify-center">
99
+ <svg class="w-8 h-8 text-purple-600" fill="currentColor" viewBox="0 0 20 20">
100
+ <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
101
+ </svg>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <div class="bg-white rounded-xl shadow-md p-6 border-r-4 border-green-500">
107
+ <div class="flex items-center justify-between">
108
+ <div>
109
+ <p class="text-gray-600 text-sm mb-1">آخرین دانش‌آموز ثبت شده</p>
110
+ <p class="text-xl font-bold text-green-600" id="lastStudent">-</p>
111
+ </div>
112
+ <div class="w-14 h-14 bg-green-100 rounded-full flex items-center justify-center">
113
+ <svg class="w-8 h-8 text-green-600" fill="currentColor" viewBox="0 0 20 20">
114
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
115
+ </svg>
116
+ </div>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Data Entry Form Card -->
122
+ <div class="bg-white rounded-xl shadow-md overflow-hidden mb-8">
123
+ <div class="p-6 border-b border-gray-200 bg-gradient-to-r from-purple-600 to-blue-600">
124
+ <h2 class="text-xl font-bold text-white flex items-center gap-2">
125
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
126
+ <path d="M8 9a3 3 0 100-6 3 3 0 000 6zM8 11a6 6 0 016 6H2a6 6 0 016-6zM16 7a1 1 0 10-2 0v1h-1a1 1 0 100 2h1v1a1 1 0 102 0v-1h1a1 1 0 100-2h-1V7z"/>
127
+ </svg>
128
+ افزودن دانش‌آموز
129
+ </h2>
130
+ </div>
131
+
132
+ <div class="p-8">
133
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
134
+ <div>
135
+ <label class="block text-sm font-bold text-gray-700 mb-2">نام دانش‌آموز</label>
136
+ <input
137
+ type="text"
138
+ id="studentName"
139
+ placeholder="نام و نام خانوادگی..."
140
+ class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-200 focus:outline-none transition-all"
141
+ >
142
+ </div>
143
+ <div>
144
+ <label class="block text-sm font-bold text-gray-700 mb-2">شماره دانش‌آموزی</label>
145
+ <input
146
+ type="text"
147
+ id="studentId"
148
+ placeholder="شماره دانش‌آموز..."
149
+ class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-200 focus:outline-none transition-all"
150
+ >
151
+ </div>
152
+ <div>
153
+ <label class="block text-sm font-bold text-gray-700 mb-2">نمره (از 20)</label>
154
+ <input
155
+ type="number"
156
+ id="studentGrade"
157
+ min="0"
158
+ max="20"
159
+ placeholder="نمره..."
160
+ class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:border-purple-500 focus:ring-2 focus:ring-purple-200 focus:outline-none transition-all text-center"
161
+ >
162
+ </div>
163
+ </div>
164
+
165
+ <div class="flex justify-center">
166
+ <button onclick="addStudent()" class="w-1/3 bg-gradient-to-r from-purple-600 to-blue-600 hover:from-purple-700 hover:to-blue-700 text-white py-3 px-8 rounded-lg font-bold transition-all shadow-md hover:shadow-lg transform hover:scale-105 flex items-center justify-center gap-2">
167
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
168
+ <path d="M7.707 10.293a1 1 0 10-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 11.586V6h5a2 2 0 012 2v7a2 2 0 01-2 2H4a2 2 0 01-2-2V8a2 2 0 012-2h5v5.586l-1.293-1.293zM9 4a1 1 0 012 0v2H9V4z"/>
169
+ </svg>
170
+ ذخیره
171
+ </button>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <!-- Students List -->
177
+ <div class="bg-white rounded-xl shadow-md overflow-hidden">
178
+ <div class="p-6 border-b border-gray-200 bg-gradient-to-r from-purple-600 to-blue-600">
179
+ <h2 class="text-xl font-bold text-white flex items-center gap-2">
180
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
181
+ <path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/>
182
+ </svg>
183
+ لیست دانش‌آموزان ذخیره شده
184
+ </h2>
185
+ </div>
186
+
187
+ <div class="overflow-x-auto">
188
+ <table class="w-full">
189
+ <thead class="bg-gray-100">
190
+ <tr>
191
+ <th class="px-6 py-4 text-center text-sm font-bold text-gray-700 border-b">ردیف</th>
192
+ <th class="px-6 py-4 text-center text-sm font-bold text-gray-700 border-b">شماره دانش‌آموز</th>
193
+ <th class="px-6 py-4 text-center text-sm font-bold text-gray-700 border-b">نام دانش‌آموز</th>
194
+ <th class="px-6 py-4 text-center text-sm font-bold text-gray-700 border-b">نمره (از 20)</th>
195
+ </tr>
196
+ </thead>
197
+ <tbody id="studentsList">
198
+ <tr>
199
+ <td colspan="4" class="px-6 py-12 text-center text-gray-400">
200
+ <svg class="w-16 h-16 mx-auto mb-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20">
201
+ <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
202
+ </svg>
203
+ <p class="text-lg font-medium">هنوز دانش‌آموزی ثبت نشده است</p>
204
+ <p class="text-sm mt-2">از فرم بالا برای افزودن دانش‌آموز استفاده کنید</p>
205
+ </td>
206
+ </tr>
207
+ </tbody>
208
+ </table>
209
+ </div>
210
+ </div>
211
+
212
+ <!-- Help Card -->
213
+ <!-- <div class="mt-6 bg-purple-50 rounded-xl shadow-md p-6">
214
+ <h3 class="text-lg font-bold text-purple-900 mb-3 flex items-center gap-2">
215
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
216
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
217
+ </svg>
218
+ سؤالات متداول
219
+ </h3>
220
+ <div class="space-y-2 text-sm text-purple-800">
221
+ <p><strong>چگونه نمره وارد کنم؟</strong> نمره باید عدد بین 0 تا 20 باشد.</p>
222
+ <p><strong>آیا باید همه فیلدها پر شود؟</strong> بله، برای گروه‌بندی موفق، اطلاعات همه دانش‌آموزان لازم است.</p>
223
+ <p><strong>اطلاعات کجا ذخیره می‌شود؟</strong> در نسخه فعلی در مرورگر ذخیره می‌شود. در آینده به پایگاه داده متصل خواهد شد.</p>
224
+ </div>
225
+ </div> -->
226
+
227
+ <!-- Footer -->
228
+ <footer class="bg-white border-t border-gray-200 mt-16">
229
+ <div class="max-w-7xl mx-auto px-4 py-6">
230
+ <div class="text-center text-gray-600 text-sm">
231
+ <p class="mb-2">نسخه 1.0.0 | آبان 1404</p>
232
+ <p>© ۲۰۲۵ TalimBot - سیستم گروه‌بندی هوشمند دانش آموزان</p>
233
+ </div>
234
+ </div>
235
+ </footer>
236
+ </div>
237
+
238
+ <script src="../assets/js/data.js"></script>
239
+ <script>
240
+ // Check authentication on page load
241
+ function checkAuth() {
242
+ const isTeacher = sessionStorage.getItem('isTeacher');
243
+ if (isTeacher !== 'true') {
244
+ window.location.href = 'login.html';
245
+ }
246
+ }
247
+
248
+ function logout() {
249
+ sessionStorage.clear();
250
+ window.location.href = 'login.html';
251
+ }
252
+
253
+ // Add student function
254
+ function addStudent() {
255
+ const name = document.getElementById('studentName').value.trim();
256
+ const studentId = document.getElementById('studentId').value.trim().toUpperCase();
257
+ const grade = document.getElementById('studentGrade').value.trim();
258
+
259
+ // Validation
260
+ if (!name) {
261
+ alert('لطفاً نام دانش‌آموز را وارد کنید');
262
+ return;
263
+ }
264
+ if (!studentId) {
265
+ alert('لطفاً شماره دانش‌آموزی را وارد کنید');
266
+ return;
267
+ }
268
+ if (!grade) {
269
+ alert('لطفاً نمره را وارد کنید');
270
+ return;
271
+ }
272
+ if (parseFloat(grade) < 0 || parseFloat(grade) > 20) {
273
+ alert('نمره باید بین 0 تا 20 باشد');
274
+ return;
275
+ }
276
+
277
+ // Validate student number format (must be S followed by numbers)
278
+ if (!/^S\d+$/.test(studentId)) {
279
+ alert('⚠️ فرمت شماره دانش‌آموزی نادرست است!\n\nشماره باید با حرف S شروع شود و بعد از آن فقط اعداد باشد\n\nمثال‌های صحیح: S001, S031, S100');
280
+ return;
281
+ }
282
+
283
+ // Try to add student using data.js function
284
+ const result = addNewStudent(studentId, name, parseFloat(grade));
285
+
286
+ if (result.success) {
287
+ // Clear form
288
+ document.getElementById('studentName').value = '';
289
+ document.getElementById('studentId').value = '';
290
+ document.getElementById('studentGrade').value = '';
291
+
292
+ // Focus back on student ID field
293
+ document.getElementById('studentId').focus();
294
+
295
+ // Update the list display
296
+ updateStudentsList();
297
+
298
+ // Update stats
299
+ updateStats();
300
+
301
+ // Show success message
302
+ showToast('✅ دانش‌آموز با موفقیت اضافه شد!\n' + name + ' (' + studentId + ')');
303
+ } else {
304
+ alert('❌ ' + result.message);
305
+ }
306
+ }
307
+
308
+ // Update students list display
309
+ function updateStudentsList() {
310
+ const tbody = document.getElementById('studentsList');
311
+ const allStudents = getAllStudents();
312
+
313
+ if (allStudents.length === 0) {
314
+ tbody.innerHTML = `
315
+ <tr>
316
+ <td colspan="4" class="px-6 py-12 text-center text-gray-400">
317
+ <svg class="w-16 h-16 mx-auto mb-4 text-gray-300" fill="currentColor" viewBox="0 0 20 20">
318
+ <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
319
+ </svg>
320
+ <p class="text-lg font-medium">هنوز دانش‌آموزی ثبت نشده است</p>
321
+ <p class="text-sm mt-2">از فرم بالا برای افزودن دانش‌آموز استفاده کنید</p>
322
+ </td>
323
+ </tr>
324
+ `;
325
+ return;
326
+ }
327
+
328
+ tbody.innerHTML = '';
329
+ allStudents.forEach((student, index) => {
330
+ const row = document.createElement('tr');
331
+ row.className = 'hover:bg-gray-50 transition-colors';
332
+ row.innerHTML = `
333
+ <td class="px-6 py-4 text-center border-b border-gray-200">
334
+ <span class="font-bold text-gray-700">${index + 1}</span>
335
+ </td>
336
+ <td class="px-6 py-4 text-center border-b border-gray-200">
337
+ <span class="text-gray-600 font-medium">${student.studentNumber}</span>
338
+ </td>
339
+ <td class="px-6 py-4 text-center border-b border-gray-200">
340
+ <span class="text-gray-900 font-medium">${student.name}</span>
341
+ </td>
342
+ <td class="px-6 py-4 text-center border-b border-gray-200">
343
+ <span class="text-purple-600 font-bold">${student.grade}</span>
344
+ </td>
345
+ `;
346
+ tbody.appendChild(row);
347
+ });
348
+ }
349
+
350
+ // Update statistics
351
+ function updateStats() {
352
+ const allStudents = getAllStudents();
353
+ document.getElementById('totalCount').textContent = allStudents.length;
354
+ if (allStudents.length > 0) {
355
+ document.getElementById('lastStudent').textContent = allStudents[allStudents.length - 1].name;
356
+ } else {
357
+ document.getElementById('lastStudent').textContent = '-';
358
+ }
359
+ }
360
+
361
+ // Show success toast
362
+ function showToast(message) {
363
+ const toast = document.createElement('div');
364
+ toast.className = 'fixed top-20 left-1/2 -translate-x-1/2 bg-green-500 text-white px-6 py-4 rounded-xl shadow-2xl z-50 flex items-center gap-2';
365
+ toast.innerHTML = `
366
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
367
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
368
+ </svg>
369
+ <span class="font-semibold">${message}</span>
370
+ `;
371
+ document.body.appendChild(toast);
372
+
373
+ setTimeout(() => {
374
+ toast.remove();
375
+ }, 3000);
376
+ }
377
+
378
+ // Initialize on page load
379
+ window.onload = () => {
380
+ checkAuth();
381
+ updateStudentsList();
382
+ updateStats();
383
+ };
384
+ </script>
385
+ </body>
386
+ </html>
387
+
388
+
backend/static/pages/teacher-dashboard.html ADDED
@@ -0,0 +1,1017 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fa" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
7
+ <meta http-equiv="Pragma" content="no-cache">
8
+ <meta http-equiv="Expires" content="0">
9
+ <!-- Version: 2.1 - Fixed VARK 4 options + field preservation -->
10
+ <title>داشبورد معلم - سیستم گروه‌بندی</title>
11
+ <script src="https://cdn.tailwindcss.com"></script>
12
+ <link rel="preconnect" href="https://fonts.googleapis.com">
13
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
14
+ <link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
15
+ <style>
16
+ body {
17
+ font-family: 'Vazirmatn', system-ui, -apple-system, sans-serif;
18
+ }
19
+ @keyframes fadeIn {
20
+ from { opacity: 0; transform: translateY(-10px); }
21
+ to { opacity: 1; transform: translateY(0); }
22
+ }
23
+ .fade-in {
24
+ animation: fadeIn 0.3s ease-out;
25
+ }
26
+ </style>
27
+ <script>
28
+ tailwind.config = {
29
+ theme: {
30
+ extend: {
31
+ fontFamily: {
32
+ 'vazir': ['Vazirmatn', 'system-ui', '-apple-system', 'sans-serif'],
33
+ }
34
+ }
35
+ }
36
+ }
37
+ </script>
38
+ </head>
39
+ <body class="font-vazir bg-gray-50">
40
+ <div class="min-h-screen">
41
+ <!-- Top Navigation -->
42
+ <nav style="background-color: #1b6e6e;" class="shadow-lg border-b border-gray-200 sticky top-0 z-50">
43
+ <div class="max-w-7xl mx-auto px-4">
44
+ <div class="flex justify-between items-center h-16">
45
+ <!-- Logo -->
46
+ <div class="flex items-center gap-3">
47
+ <div class="w-10 h-10 bg-white/20 rounded-lg flex items-center justify-center text-white text-xl font-bold">
48
+ T
49
+ </div>
50
+ <span class="text-xl font-bold text-white">TalimBot</span>
51
+ </div>
52
+
53
+ <!-- Navigation Links -->
54
+ <div class="hidden md:flex items-center gap-6">
55
+ <a href="teacher-dashboard.html" class="text-white font-bold border-b-2 border-white pb-1">
56
+ داشبورد
57
+ </a>
58
+ <!-- student-data.html removed - all 30 students already exist -->
59
+ </div>
60
+
61
+ <!-- User Menu -->
62
+ <div class="flex items-center gap-4">
63
+ <div class="flex items-center gap-2">
64
+ <div class="w-10 h-10 bg-white/20 rounded-full flex items-center justify-center">
65
+ <svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
66
+ <path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"/>
67
+ <path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1z"/>
68
+ </svg>
69
+ </div>
70
+ <div class="hidden md:block text-right">
71
+ <p class="text-sm font-bold text-white">معلم</p>
72
+ <p class="text-xs text-white/80">مدیر سیستم</p>
73
+ </div>
74
+ </div>
75
+ <a href="login.html" class="text-white/80 hover:text-red-300 transition-colors" title="خروج">
76
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
77
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"/>
78
+ </svg>
79
+ </a>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ </nav>
84
+
85
+ <!-- Page Header -->
86
+ <div class="max-w-7xl mx-auto px-4 py-8">
87
+ <div class="mb-8">
88
+ <h1 class="text-4xl font-bold text-gray-900 mb-2 flex items-center gap-3">
89
+ <svg class="w-10 h-10 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
90
+ <path d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"/>
91
+ <path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1z"/>
92
+ </svg>
93
+ پنل مدیریت معلم
94
+ </h1>
95
+ <p class="text-gray-600">مدیریت گروه‌بندی دانش آموزان</p>
96
+ </div>
97
+ </div>
98
+
99
+ <div class="max-w-7xl mx-auto px-4 py-6 space-y-6">
100
+ <!-- Stats Grid -->
101
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
102
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-r-4 border-blue-500">
103
+ <div class="flex items-center justify-between">
104
+ <div>
105
+ <p class="text-gray-600 text-sm font-medium">تعداد کل دانش آموزان</p>
106
+ <p class="text-3xl font-bold text-gray-900 mt-2" id="totalStudents">30</p>
107
+ </div>
108
+ <div class="bg-blue-100 p-4 rounded-xl">
109
+ <svg class="w-8 h-8 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
110
+ <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
111
+ </svg>
112
+ </div>
113
+ </div>
114
+ </div>
115
+
116
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-r-4 border-green-500">
117
+ <div class="flex items-center justify-between">
118
+ <div>
119
+ <p class="text-gray-600 text-sm font-medium">پروفایل‌های تکمیل شده</p>
120
+ <p class="text-3xl font-bold text-gray-900 mt-2" id="studentsWithInfo">0</p>
121
+ </div>
122
+ <div class="bg-green-100 p-4 rounded-xl">
123
+ <svg class="w-8 h-8 text-green-600" fill="currentColor" viewBox="0 0 20 20">
124
+ <path fill-rule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
125
+ </svg>
126
+ </div>
127
+ </div>
128
+ </div>
129
+
130
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-r-4 border-orange-500">
131
+ <div class="flex items-center justify-between">
132
+ <div>
133
+ <p class="text-gray-600 text-sm font-medium">دانش آموزان گروه‌بندی شده</p>
134
+ <p class="text-3xl font-bold text-gray-900 mt-2" id="studentsGrouped">0</p>
135
+ </div>
136
+ <div class="bg-orange-100 p-4 rounded-xl">
137
+ <svg class="w-8 h-8 text-orange-600" fill="currentColor" viewBox="0 0 20 20">
138
+ <path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"/>
139
+ </svg>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <!-- Course & Status -->
146
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
147
+ <!-- Course Name -->
148
+ <div class="bg-white rounded-2xl shadow-lg p-6">
149
+ <h2 class="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
150
+ <svg class="w-6 h-6 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
151
+ <path d="M9 4.804A7.968 7.968 0 005.5 4c-1.255 0-2.443.29-3.5.804v10A7.969 7.969 0 015.5 14c1.669 0 3.218.51 4.5 1.385A7.962 7.962 0 0114.5 14c1.255 0 2.443.29 3.5.804v-10A7.968 7.968 0 0014.5 4c-1.255 0-2.443.29-3.5.804V12a1 1 0 11-2 0V4.804z"/>
152
+ </svg>
153
+ اطلاعات درس
154
+ </h2>
155
+ <form id="courseForm" onsubmit="saveCourse(event)" class="space-y-4">
156
+ <div>
157
+ <label for="courseNameInput" class="block text-sm font-semibold text-gray-700 mb-2">نام درس</label>
158
+ <input
159
+ type="text"
160
+ id="courseNameInput"
161
+ placeholder="مثال: ریاضی، ادبیات، علوم"
162
+ class="w-full p-3 border-2 border-gray-300 rounded-xl focus:border-blue-500 focus:ring-2 focus:ring-blue-200 outline-none transition-all"
163
+ required
164
+ >
165
+ </div>
166
+ <div class="flex justify-center">
167
+ <button type="submit" class="w-full md:w-1/3 bg-blue-600 text-white py-3 rounded-xl font-semibold hover:bg-blue-700 transition-colors flex items-center justify-center gap-2">
168
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
169
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"/>
170
+ </svg>
171
+ ذخیره نام درس
172
+ </button>
173
+ </div>
174
+ </form>
175
+ </div>
176
+
177
+ <!-- Grouping Status -->
178
+ <div class="bg-white rounded-2xl shadow-lg p-6">
179
+ <h2 class="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
180
+ <svg class="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 20 20">
181
+ <path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z"/>
182
+ </svg>
183
+ وضعیت گروه‌بندی
184
+ </h2>
185
+ <div id="statusInfo" class="fade-in">
186
+ <!-- Populated by JS -->
187
+ </div>
188
+ </div>
189
+ </div>
190
+
191
+ <!-- Grouping Actions -->
192
+ <div class="bg-white rounded-2xl shadow-lg p-6">
193
+ <h2 class="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
194
+ <span class="text-2xl">⚙️</span>
195
+ عملیات گروه‌بندی
196
+ </h2>
197
+ <div class="space-y-4">
198
+ <div class="bg-blue-50 border border-blue-200 rounded-xl p-4">
199
+ <h4 class="font-bold text-blue-900 mb-2 flex items-center gap-2">
200
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
201
+ <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
202
+ </svg>
203
+ آماده گروه‌بندی
204
+ </h4>
205
+ <p class="text-gray-700 mb-2">
206
+ <strong class="text-2xl text-blue-600" id="readyStudentsCount">0</strong>
207
+ دانش آموز آماده گروه‌بندی هستند.
208
+ </p>
209
+ <div class="text-sm text-gray-700 space-y-1 bg-white rounded-lg p-3 mt-2">
210
+ <p class="font-semibold text-gray-800 mb-2">🎯 معیارهای گروه‌بندی علمی (سنین 15-16 سال، به ترتیب اولویت):</p>
211
+ <div class="mr-4 space-y-1">
212
+ <p><strong>1. بهینه‌سازی ZPD ناحیه رشد نزدیک (30%):</strong> ترکیب نمرات بالا با متوسط برای یادگیری همیاری و آموزش همتا-به-همتا</p>
213
+ <p><strong>2. تکمیل MBTI (25%):</strong> ترکیب شخصیت‌های مکمل (نه مشابه) - مثال: ENFP+INTJ، ENTP+INFJ - تعادل درون‌گرا/برون‌گرا</p>
214
+ <p><strong>3. تنوع VARK (20%):</strong> ترکیب سبک‌های یادگیری دیداری، شنیداری، خواندن/نوشتن و حرکتی در هر گروه</p>
215
+ <p><strong>4. انگیزش تحصیلی - AMS (15%):</strong> توزیع دانش‌آموزان با انگیزه بالا (>140) در گروه‌ها برای الهام‌بخشی</p>
216
+ <p><strong>5. مهارت همکاری (10%):</strong> دانش‌آموزان با مهارت همکاری بالا (>88) به عنوان تسهیل‌گر اجتماعی</p>
217
+ <p><strong>6. ویژگی‌های درس:</strong> <span id="courseNameInCriteria">تطبیق با نیازهای درس (ریاضی: تفکر منطقی، ادبیات: احساسی)</span></p>
218
+ <p class="text-orange-600 italic mt-1"><strong>7. ترجیحات دانش‌آموزان (5%):</strong> فقط اگر با معیارهای بالا تضاد نداشته باشد - نوجوانان از کار با افراد جدید رشد می‌کنند</p>
219
+ </div>
220
+ </div>
221
+ <div class="bg-amber-50 border border-amber-300 rounded-lg p-3 mt-3">
222
+ <p class="text-sm text-amber-800 font-medium">
223
+ ⚡ نکته مهم: فقط دانش‌آموزانی که MBTI و سبک یادگیری خود را تکمیل کرده‌اند در گروه‌بندی شرکت می‌کنند.
224
+ اگر دانش‌آموزی دوستان خود را انتخاب کرده ولی آن دوستان فرم را تکمیل نکرده‌اند، این انتخاب در نظر گرفته نمی‌شود.
225
+ </p>
226
+ </div>
227
+ </div>
228
+
229
+ <div id="groupingActions" class="flex flex-col items-center gap-4">
230
+ <button onclick="startGrouping()" id="startGroupingBtn"
231
+ class="w-full md:w-1/3 bg-gradient-to-r from-green-600 to-green-700 text-white py-4 rounded-xl font-bold text-lg hover:from-green-700 hover:to-green-800 transform hover:scale-[1.02] active:scale-[0.98] transition-all shadow-lg flex items-center justify-center gap-2">
232
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
233
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/>
234
+ </svg>
235
+ شروع فرآیند گروه‌بندی
236
+ </button>
237
+
238
+ <button onclick="handleResetGrouping()" id="resetGroupingBtn"
239
+ class="hidden w-full md:w-1/3 bg-gray-600 text-white py-4 rounded-xl font-bold text-lg hover:bg-gray-700 transform hover:scale-[1.02] active:scale-[0.98] transition-all flex items-center justify-center gap-2">
240
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
241
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
242
+ </svg>
243
+ بازنشانی گروه‌بندی
244
+ </button>
245
+ <button onclick="toggleVisibility()" id="toggleVisibilityBtn"
246
+ class="hidden w-full md:w-1/3 bg-blue-600 text-white py-4 rounded-xl font-bold text-lg hover:bg-blue-700 transform hover:scale-[1.02] active:scale-[0.98] transition-all flex items-center justify-center gap-2">
247
+ <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
248
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
249
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>
250
+ </svg>
251
+ <span id="visibilityButtonText">نمایش نتایج به دانش‌آموزان</span>
252
+ </button>
253
+ </div>
254
+
255
+ <div id="groupingProgress" class="hidden text-center py-8">
256
+ <div class="inline-block w-16 h-16 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mb-4"></div>
257
+ <p class="text-gray-700 font-semibold">در حال پردازش الگوریتم گروه‌بندی...</p>
258
+ <p class="text-sm text-gray-500 mt-2">ممکن است چند لحظه طول بکشد</p>
259
+ </div>
260
+
261
+ <div id="groupingSuccess" class="hidden bg-green-50 border-r-4 border-green-500 p-4 rounded-lg">
262
+ <p class="text-green-700 font-semibold flex items-center gap-2">
263
+ <svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
264
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
265
+ </svg>
266
+ گروه‌بندی با موفقیت انجام شد!
267
+ </p>
268
+ </div>
269
+ </div>
270
+ </div>
271
+
272
+ <!-- Students List -->
273
+ <div class="bg-white rounded-2xl shadow-lg p-6">
274
+ <h2 class="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
275
+ <span class="text-2xl">📋</span>
276
+ لیست تمام دانش آموزان
277
+ </h2>
278
+
279
+ <div class="text-center mb-4 flex gap-3 justify-center">
280
+ <button onclick="toggleStudentList()" id="toggleStudentBtn"
281
+ class="bg-blue-600 text-white px-6 py-3 rounded-xl font-semibold hover:bg-blue-700 transition-colors">
282
+ نمایش لیست دانش آموزان
283
+ </button>
284
+ <button onclick="downloadAllData()"
285
+ class="bg-green-600 text-white px-6 py-3 rounded-xl font-semibold hover:bg-green-700 transition-colors">
286
+ 📥 دانلود همه داده‌ها (JSON)
287
+ </button>
288
+ </div>
289
+ <div id="studentListContainer" class="hidden">
290
+ <div class="overflow-x-auto rounded-xl border border-gray-200">
291
+ <table class="w-full">
292
+ <thead class="bg-gray-100 sticky top-0">
293
+ <tr class="border-b-2 border-blue-500">
294
+ <th class="p-3 text-right text-sm font-bold text-gray-700">شماره دانش آموز</th>
295
+ <th class="p-3 text-right text-sm font-bold text-gray-700">نام</th>
296
+ <th class="p-3 text-center text-sm font-bold text-gray-700">MBTI</th>
297
+ <th class="p-3 text-center text-sm font-bold text-gray-700">سبک یادگیری</th>
298
+ <th class="p-3 text-center text-sm font-bold text-gray-700">AMS</th>
299
+ <th class="p-3 text-center text-sm font-bold text-gray-700">Cooperative</th>
300
+ <th class="p-3 text-center text-sm font-bold text-gray-700">معدل</th>
301
+ <th class="p-3 text-center text-sm font-bold text-gray-700">گروه</th>
302
+ <th class="p-3 text-center text-sm font-bold text-gray-700">عملیات</th>
303
+ </tr>
304
+ </thead>
305
+ <tbody id="studentTableBody">
306
+ <!-- Populated by JS -->
307
+ </tbody>
308
+ </table>
309
+ </div>
310
+ </div>
311
+ </div>
312
+
313
+ <!-- Reset Data Section -->
314
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-r-4 border-orange-500">
315
+ <div class="flex items-start gap-4">
316
+ <div class="flex-shrink-0">
317
+ <svg class="w-8 h-8 text-orange-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
318
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
319
+ </svg>
320
+ </div>
321
+ <div class="flex-1">
322
+ <h3 class="text-lg font-bold text-gray-900 mb-2">بازنشانی داده‌ها</h3>
323
+ <p class="text-sm text-gray-600 mb-4">
324
+ این عملیات تمام اطلاعات سیستم را به حالت اولیه بازمی‌گرداند. همه 30 دانش‌آموز (S001 تا S030) بازگردانده می‌شوند،
325
+ تمام تغییرات و گروه‌بندی‌ها حذف می‌شوند و سیستم به حالت پیش‌فرض برمی‌گردد.
326
+ </p>
327
+ <button onclick="resetAllData()" class="bg-orange-600 hover:bg-orange-700 text-white py-3 px-6 rounded-xl font-semibold transition-colors flex items-center gap-2">
328
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
329
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
330
+ </svg>
331
+ بازنشانی داده‌ها
332
+ </button>
333
+ </div>
334
+ </div>
335
+ </div>
336
+
337
+ <!-- Test Data Generator Section -->
338
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-r-4 border-green-500">
339
+ <div class="flex items-start gap-4">
340
+ <div class="flex-shrink-0">
341
+ <svg class="w-8 h-8 text-green-600" fill="currentColor" viewBox="0 0 20 20">
342
+ <path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM14 11a1 1 0 011 1v1h1a1 1 0 110 2h-1v1a1 1 0 11-2 0v-1h-1a1 1 0 110-2h1v-1a1 1 0 011-1z"/>
343
+ </svg>
344
+ </div>
345
+ <div class="flex-1">
346
+ <h3 class="text-lg font-bold text-gray-900 mb-2">پر کردن داده‌های تست</h3>
347
+ <p class="text-sm text-gray-600 mb-4">
348
+ برای تست سیستم گروه‌بندی، 10 دانش‌آموز اول (S001 تا S010) را با داده‌های کامل و تصادفی پر کنید.
349
+ این شامل MBTI، VARK، نمرات AMS و Cooperative و ترجیحات دانش‌آموزی می‌شود.
350
+ </p>
351
+ <div class="flex gap-3">
352
+ <button onclick="fillTestData()" id="fillTestDataBtn" class="bg-green-600 hover:bg-green-700 text-white py-3 px-6 rounded-xl font-semibold transition-colors flex items-center gap-2">
353
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
354
+ <path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM14 11a1 1 0 011 1v1h1a1 1 0 110 2h-1v1a1 1 0 11-2 0v-1h-1a1 1 0 110-2h1v-1a1 1 0 011-1z"/>
355
+ </svg>
356
+ پر کردن داده‌های تست
357
+ </button>
358
+ <div id="testDataProgress" class="hidden flex items-center gap-2 text-sm text-gray-600">
359
+ <div class="inline-block w-5 h-5 border-2 border-green-600 border-t-transparent rounded-full animate-spin"></div>
360
+ <span id="testDataProgressText">در حال پردازش...</span>
361
+ </div>
362
+ </div>
363
+ </div>
364
+ </div>
365
+ </div>
366
+
367
+ <!-- Import JSON Data Section -->
368
+ <div class="bg-white rounded-2xl shadow-lg p-6 border-r-4 border-blue-500">
369
+ <div class="flex items-start gap-4">
370
+ <div class="flex-shrink-0">
371
+ <svg class="w-8 h-8 text-blue-600" fill="currentColor" viewBox="0 0 20 20">
372
+ <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM6.293 6.707a1 1 0 010-1.414l3-3a1 1 0 011.414 0l3 3a1 1 0 01-1.414 1.414L11 5.414V13a1 1 0 11-2 0V5.414L7.707 6.707a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
373
+ </svg>
374
+ </div>
375
+ <div class="flex-1">
376
+ <h3 class="text-lg font-bold text-gray-900 mb-2">وارد کردن داده‌ها از JSON</h3>
377
+ <p class="text-sm text-gray-600 mb-4">
378
+ یک فایل JSON حاوی اطلاعات دانش‌آموزان را انتخاب کنید. فایل باید شامل آرایه‌ای از دانش‌آموزان با فیلدهای
379
+ mbti، learningStyle، ams، cooperative و preferredStudents باشد.
380
+ </p>
381
+ <div class="flex gap-3 items-center">
382
+ <input type="file" id="jsonFileInput" accept=".json" class="hidden">
383
+ <button onclick="document.getElementById('jsonFileInput').click()" class="bg-blue-600 hover:bg-blue-700 text-white py-3 px-6 rounded-xl font-semibold transition-colors flex items-center gap-2">
384
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
385
+ <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM6.293 6.707a1 1 0 010-1.414l3-3a1 1 0 011.414 0l3 3a1 1 0 01-1.414 1.414L11 5.414V13a1 1 0 11-2 0V5.414L7.707 6.707a1 1 0 01-1.414 0z" clip-rule="evenodd"/>
386
+ </svg>
387
+ انتخاب فایل JSON
388
+ </button>
389
+ <span id="jsonFileName" class="text-sm text-gray-600"></span>
390
+ <div id="jsonImportProgress" class="hidden flex items-center gap-2 text-sm text-gray-600">
391
+ <div class="inline-block w-5 h-5 border-2 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
392
+ <span id="jsonImportProgressText">در حال پردازش...</span>
393
+ </div>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+
399
+ <!-- Group Results -->
400
+ <div id="groupResultsCard" class="hidden bg-white rounded-2xl shadow-lg p-6">
401
+ <h2 class="text-lg font-bold text-gray-900 mb-4 flex items-center gap-2">
402
+ <svg class="w-6 h-6 text-purple-600" fill="currentColor" viewBox="0 0 20 20">
403
+ <path d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"/>
404
+ </svg>
405
+ نتایج گروه‌بندی
406
+ </h2>
407
+ <div id="groupResultsList" class="grid grid-cols-1 md:grid-cols-2 gap-4">
408
+ <!-- Populated by JS -->
409
+ </div>
410
+ </div>
411
+
412
+ <!-- Footer -->
413
+ <footer class="bg-white border-t border-gray-200 mt-16">
414
+ <div class="max-w-7xl mx-auto px-4 py-6">
415
+ <div class="text-center text-gray-600 text-sm">
416
+ <p class="mb-2">نسخه 1.0.0 | آبان 1404</p>
417
+ <p>© ۲۰۲۵ TalimBot - سیستم گروه‌بندی هوشمند دانش آموزان</p>
418
+ </div>
419
+ </div>
420
+ </footer>
421
+ </div>
422
+ </div>
423
+
424
+ <script src="../assets/js/data.js"></script>
425
+ <script src="../assets/js/grouping.js"></script>
426
+ <script>
427
+ let isStudentListVisible = false;
428
+
429
+ // Check if user is authenticated as teacher
430
+ function checkAuth() {
431
+ const isTeacher = sessionStorage.getItem('isTeacher');
432
+ if (!isTeacher) {
433
+ window.location.href = 'login.html';
434
+ return false;
435
+ }
436
+ return true;
437
+ }
438
+
439
+ async function loadDashboard() {
440
+ if (!checkAuth()) return;
441
+
442
+ const stats = await getGroupingStats();
443
+ const courseName = await getCourseName();
444
+
445
+ document.getElementById('totalStudents').textContent = stats.totalStudents;
446
+ document.getElementById('studentsWithInfo').textContent = stats.studentsWithCompleteInfo;
447
+ document.getElementById('studentsGrouped').textContent = stats.studentsGrouped;
448
+ document.getElementById('readyStudentsCount').textContent = stats.studentsWithCompleteInfo;
449
+
450
+ if (courseName) {
451
+ document.getElementById('courseNameInput').value = courseName;
452
+ // Update the course name in the criteria description
453
+ const courseNameEl = document.getElementById('courseNameInCriteria');
454
+ if (courseNameEl) {
455
+ courseNameEl.textContent = `انتخاب بر اساس نیاز درس "${courseName}"`;
456
+ }
457
+ }
458
+
459
+ await updateStatus(stats);
460
+ await updateStudentTable();
461
+ }
462
+
463
+ async function updateStatus(stats) {
464
+ const statusDiv = document.getElementById('statusInfo');
465
+ const toggleBtn = document.getElementById('toggleVisibilityBtn');
466
+ const toggleBtnText = document.getElementById('visibilityButtonText');
467
+
468
+ if (stats.groupingComplete) {
469
+ statusDiv.innerHTML = `
470
+ <div class="bg-green-50 border-r-4 border-green-500 p-4 rounded-lg">
471
+ <p class="font-bold text-green-700 mb-1 flex items-center gap-2">
472
+ <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
473
+ <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
474
+ </svg>
475
+ گروه‌بندی انجام شد!
476
+ </p>
477
+ <p class="text-sm text-green-600">${stats.studentsGrouped} دانش آموز در ${stats.groups.length} گروه برای درس <strong>${stats.courseName}</strong></p>
478
+ ${stats.resultsVisible ?
479
+ '<p class="text-sm text-blue-600 mt-2">✅ نتایج برای دانش‌آموزان نمایش داده می‌شود</p>' :
480
+ '<p class="text-sm text-orange-600 mt-2">⚠️ نتایج هنوز برای دانش‌آموزان مخفی است</p>'}
481
+ </div>
482
+ `;
483
+ document.getElementById('startGroupingBtn').classList.add('hidden');
484
+ document.getElementById('resetGroupingBtn').classList.remove('hidden');
485
+ toggleBtn.classList.remove('hidden');
486
+
487
+ // Update button text based on visibility status
488
+ if (stats.resultsVisible) {
489
+ toggleBtnText.textContent = 'مخفی کردن نتایج از دانش‌آموزان';
490
+ toggleBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
491
+ toggleBtn.classList.add('bg-orange-600', 'hover:bg-orange-700');
492
+ } else {
493
+ toggleBtnText.textContent = 'نمایش نتایج به دانش‌آموزان';
494
+ toggleBtn.classList.remove('bg-orange-600', 'hover:bg-orange-700');
495
+ toggleBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
496
+ }
497
+
498
+ await displayGroupResults(stats.groups);
499
+ } else {
500
+ statusDiv.innerHTML = `
501
+ <div class="bg-blue-50 border-r-4 border-blue-500 p-4 rounded-lg">
502
+ <p class="font-bold text-blue-700 mb-1">ℹ️ آماده گروه‌بندی</p>
503
+ <p class="text-sm text-blue-600">${stats.studentsWithCompleteInfo} دانش آموز آماده هستند</p>
504
+ </div>
505
+ `;
506
+ document.getElementById('startGroupingBtn').classList.remove('hidden');
507
+ document.getElementById('resetGroupingBtn').classList.add('hidden');
508
+ toggleBtn.classList.add('hidden');
509
+ document.getElementById('groupResultsCard').classList.add('hidden');
510
+ }
511
+ }
512
+
513
+ function saveCourse(event) {
514
+ event.preventDefault();
515
+ const courseName = document.getElementById('courseNameInput').value.trim();
516
+
517
+ if (courseName) {
518
+ setCourseName(courseName);
519
+ // Update the display immediately
520
+ const courseNameEl = document.getElementById('courseNameInCriteria');
521
+ if (courseNameEl) {
522
+ courseNameEl.textContent = `انتخاب بر اساس نیاز درس "${courseName}"`;
523
+ }
524
+ alert('نام درس با موفقیت ذخیره شد!');
525
+ }
526
+ }
527
+
528
+ async function startGrouping() {
529
+ const courseName = document.getElementById('courseNameInput').value.trim();
530
+
531
+ if (!courseName) {
532
+ alert('لطفاً ابتدا نام درس را وارد کنید!');
533
+ return;
534
+ }
535
+
536
+ const stats = await getGroupingStats();
537
+
538
+ if (stats.studentsWithCompleteInfo === 0) {
539
+ alert('هیچ دانش آموزی پروفایل خود را تکمیل نکرده است!');
540
+ return;
541
+ }
542
+
543
+ const confirmed = confirm(`آماده گروه‌بندی ${stats.studentsWithCompleteInfo} دانش آموز؟`);
544
+ if (!confirmed) return;
545
+
546
+ document.getElementById('groupingActions').classList.add('hidden');
547
+ document.getElementById('groupingProgress').classList.remove('hidden');
548
+
549
+ try {
550
+ const groupingResult = await performGrouping(courseName);
551
+
552
+ document.getElementById('groupingProgress').classList.add('hidden');
553
+ document.getElementById('groupingSuccess').classList.remove('hidden');
554
+ document.getElementById('groupingActions').classList.remove('hidden');
555
+
556
+ setTimeout(() => {
557
+ loadDashboard();
558
+ }, 2000);
559
+
560
+ } catch (error) {
561
+ let errorMsg = 'خطا در گروه‌بندی: ' + error.message;
562
+ alert(errorMsg);
563
+
564
+ document.getElementById('groupingProgress').classList.add('hidden');
565
+ document.getElementById('groupingActions').classList.remove('hidden');
566
+ }
567
+ }
568
+
569
+ async function handleResetGrouping() {
570
+ if (!confirm('آیا مطمئن هستید که می‌خواهید گروه‌بندی را بازنشانی کنید؟\n\n✓ گروه‌بندی‌های فعلی حذف می‌شوند\n✓ اطلاعات دانش‌آموزان (MBTI، VARK، AMS، Cooperative) حفظ می‌مانند\n✓ می‌توانید دوباره گروه‌بندی انجام دهید')) return;
571
+
572
+ const password = prompt('لطفاً رمز عبور استاد را وارد کنید:');
573
+ if (!password) return;
574
+
575
+ try {
576
+ await resetGrouping(password);
577
+ // Hide success message
578
+ document.getElementById('groupingSuccess').classList.add('hidden');
579
+ alert('✅ گروه‌بندی با موفقیت بازنشانی شد!\n\nاطلاعات دانش‌آموزان حفظ شده و می‌توانید دوباره گروه‌بندی کنید.');
580
+ loadDashboard();
581
+ } catch (error) {
582
+ if (error.message.includes('Invalid password')) {
583
+ alert('رمز عبور اشتباه است!');
584
+ } else {
585
+ alert('خطا در بازنشانی: ' + error.message);
586
+ }
587
+ }
588
+ }
589
+
590
+ async function toggleVisibility() {
591
+ const password = prompt('لطفاً رمز عبور استاد را وارد کنید:');
592
+ if (!password) return;
593
+
594
+ try {
595
+ const result = await toggleResultsVisibility(password);
596
+ if (result.success) {
597
+ const message = result.resultsVisible ?
598
+ 'نتایج اکنون برای دانش‌آموزان قابل مشاهده است! ✅' :
599
+ 'نتایج از دانش‌آموزان مخفی شد! 🔒';
600
+ alert(message);
601
+ loadDashboard();
602
+ } else {
603
+ alert('خطا در تغییر وضعیت نمایش');
604
+ }
605
+ } catch (error) {
606
+ if (error.message.includes('Invalid password')) {
607
+ alert('رمز عبور اشتباه است!');
608
+ } else {
609
+ alert('خطا: ' + error.message);
610
+ }
611
+ }
612
+ }
613
+
614
+ async function displayGroupResults(groups) {
615
+ const card = document.getElementById('groupResultsCard');
616
+ const container = document.getElementById('groupResultsList');
617
+
618
+ card.classList.remove('hidden');
619
+ container.innerHTML = '';
620
+
621
+ const allStudents = await getAllStudents();
622
+
623
+ groups.forEach(group => {
624
+ const groupDiv = document.createElement('div');
625
+ groupDiv.className = 'bg-gradient-to-br from-blue-50 to-blue-100 border-2 border-blue-300 rounded-xl p-4';
626
+
627
+ let html = `<h3 class="font-bold text-blue-900 mb-3">گروه ${group.groupNumber} (${group.students.length} نفر)</h3>`;
628
+
629
+ if (group.reasoning) {
630
+ html += `<p class="text-xs text-blue-700 mb-3 italic">${group.reasoning}</p>`;
631
+ }
632
+
633
+ html += '<ul class="space-y-1">';
634
+ group.students.forEach(studentNumber => {
635
+ const student = allStudents.find(s => s.studentNumber === studentNumber);
636
+ if (student) {
637
+ html += `<li class="text-sm text-gray-700">👤 ${student.name} (${student.mbti || 'N/A'})</li>`;
638
+ }
639
+ });
640
+ html += '</ul>';
641
+
642
+ groupDiv.innerHTML = html;
643
+ container.appendChild(groupDiv);
644
+ });
645
+ }
646
+
647
+ function toggleStudentList() {
648
+ isStudentListVisible = !isStudentListVisible;
649
+ const container = document.getElementById('studentListContainer');
650
+ const btn = document.getElementById('toggleStudentBtn');
651
+
652
+ container.classList.toggle('hidden', !isStudentListVisible);
653
+ btn.textContent = isStudentListVisible ? 'مخفی کردن لیست' : 'نمایش لیست دانش آموزان';
654
+ }
655
+
656
+ async function updateStudentTable() {
657
+ const tbody = document.getElementById('studentTableBody');
658
+ tbody.innerHTML = '';
659
+
660
+ const allStudents = await getAllStudents();
661
+
662
+ allStudents.forEach((student, index) => {
663
+ const tr = document.createElement('tr');
664
+ tr.className = index % 2 === 0 ? 'bg-gray-50' : 'bg-white';
665
+ tr.id = `row-${student.studentNumber}`;
666
+ tr.innerHTML = `
667
+ <td class="p-3 text-sm">${student.studentNumber}</td>
668
+ <td class="p-3 text-sm">
669
+ <span class="view-mode">${student.name}</span>
670
+ <input type="text" class="edit-mode hidden w-full p-1 border border-gray-300 rounded" value="${student.name}">
671
+ </td>
672
+ <td class="p-3 text-sm text-center">
673
+ <span class="view-mode">${student.mbti || '-'}</span>
674
+ <select class="edit-mode hidden w-full p-1 border border-gray-300 rounded text-xs">
675
+ <option value="">انتخاب کنید</option>
676
+ <option value="INTJ" ${student.mbti === 'INTJ' ? 'selected' : ''}>INTJ</option>
677
+ <option value="INTP" ${student.mbti === 'INTP' ? 'selected' : ''}>INTP</option>
678
+ <option value="ENTJ" ${student.mbti === 'ENTJ' ? 'selected' : ''}>ENTJ</option>
679
+ <option value="ENTP" ${student.mbti === 'ENTP' ? 'selected' : ''}>ENTP</option>
680
+ <option value="INFJ" ${student.mbti === 'INFJ' ? 'selected' : ''}>INFJ</option>
681
+ <option value="INFP" ${student.mbti === 'INFP' ? 'selected' : ''}>INFP</option>
682
+ <option value="ENFJ" ${student.mbti === 'ENFJ' ? 'selected' : ''}>ENFJ</option>
683
+ <option value="ENFP" ${student.mbti === 'ENFP' ? 'selected' : ''}>ENFP</option>
684
+ <option value="ISTJ" ${student.mbti === 'ISTJ' ? 'selected' : ''}>ISTJ</option>
685
+ <option value="ISFJ" ${student.mbti === 'ISFJ' ? 'selected' : ''}>ISFJ</option>
686
+ <option value="ESTJ" ${student.mbti === 'ESTJ' ? 'selected' : ''}>ESTJ</option>
687
+ <option value="ESFJ" ${student.mbti === 'ESFJ' ? 'selected' : ''}>ESFJ</option>
688
+ <option value="ISTP" ${student.mbti === 'ISTP' ? 'selected' : ''}>ISTP</option>
689
+ <option value="ISFP" ${student.mbti === 'ISFP' ? 'selected' : ''}>ISFP</option>
690
+ <option value="ESTP" ${student.mbti === 'ESTP' ? 'selected' : ''}>ESTP</option>
691
+ <option value="ESFP" ${student.mbti === 'ESFP' ? 'selected' : ''}>ESFP</option>
692
+ </select>
693
+ </td>
694
+ <td class="p-3 text-sm text-center">
695
+ <span class="view-mode">${student.learningStyle || '-'}</span>
696
+ <select class="edit-mode hidden w-full p-1 border border-gray-300 rounded text-xs">
697
+ <option value="">انتخاب کنید</option>
698
+ <option value="Visual" ${student.learningStyle === 'Visual' ? 'selected' : ''}>Visual (دیداری)</option>
699
+ <option value="Aural" ${student.learningStyle === 'Aural' ? 'selected' : ''}>Aural (شنیداری)</option>
700
+ <option value="Read/Write" ${student.learningStyle === 'Read/Write' ? 'selected' : ''}>Read/Write (خواندن/نوشتن)</option>
701
+ <option value="Kinesthetic" ${student.learningStyle === 'Kinesthetic' ? 'selected' : ''}>Kinesthetic (حرکتی/عملی)</option>
702
+ </select>
703
+ </td>
704
+ <td class="p-3 text-sm text-center">
705
+ <span class="view-mode">${student.ams || '-'}</span>
706
+ <input type="text" class="edit-mode hidden w-full p-1 border border-gray-300 rounded text-xs" value="${student.ams || ''}">
707
+ </td>
708
+ <td class="p-3 text-sm text-center">
709
+ <span class="view-mode">${student.cooperative || '-'}</span>
710
+ <input type="text" class="edit-mode hidden w-full p-1 border border-gray-300 rounded text-xs" value="${student.cooperative || ''}">
711
+ </td>
712
+ <td class="p-3 text-sm text-center font-semibold">
713
+ <span class="view-mode">${student.grade}</span>
714
+ <input type="number" class="edit-mode hidden w-20 p-1 border border-gray-300 rounded text-center" value="${student.grade}" min="0" max="20" step="0.1">
715
+ </td>
716
+ <td class="p-3 text-sm text-center text-blue-600 font-bold">${student.group || '-'}</td>
717
+ <td class="p-3 text-center">
718
+ <div class="flex gap-1 justify-center">
719
+ <button onclick="editStudent('${student.studentNumber}')" class="view-mode bg-blue-600 text-white px-3 py-1 rounded-lg text-xs hover:bg-blue-700">
720
+ ✏️ ویرایش
721
+ </button>
722
+ <button onclick="saveStudent('${student.studentNumber}')" class="edit-mode hidden bg-green-600 text-white px-3 py-1 rounded-lg text-xs hover:bg-green-700">
723
+ ✓ ذخیره
724
+ </button>
725
+ <button onclick="cancelEdit('${student.studentNumber}')" class="edit-mode hidden bg-gray-600 text-white px-3 py-1 rounded-lg text-xs hover:bg-gray-700">
726
+ ✕ لغو
727
+ </button>
728
+ </div>
729
+ </td>
730
+ `;
731
+ tbody.appendChild(tr);
732
+ });
733
+ }
734
+
735
+ function editStudent(studentNumber) {
736
+ const row = document.getElementById(`row-${studentNumber}`);
737
+ row.querySelectorAll('.view-mode').forEach(el => el.classList.add('hidden'));
738
+ row.querySelectorAll('.edit-mode').forEach(el => el.classList.remove('hidden'));
739
+ }
740
+
741
+ function cancelEdit(studentNumber) {
742
+ const row = document.getElementById(`row-${studentNumber}`);
743
+ row.querySelectorAll('.edit-mode').forEach(el => el.classList.add('hidden'));
744
+ row.querySelectorAll('.view-mode').forEach(el => el.classList.remove('hidden'));
745
+ }
746
+
747
+ async function saveStudent(studentNumber) {
748
+ const row = document.getElementById(`row-${studentNumber}`);
749
+
750
+ // Get specific input elements - order matters!
751
+ const textInputs = row.querySelectorAll('input[type="text"]');
752
+ const nameInput = textInputs[0]; // First text input is name
753
+ const amsInput = textInputs[1]; // Second text input is AMS
754
+ const cooperativeInput = textInputs[2]; // Third text input is cooperative
755
+ const mbtiSelect = row.querySelectorAll('select')[0];
756
+ const learningStyleSelect = row.querySelectorAll('select')[1];
757
+ const gradeInput = row.querySelector('input[type="number"]');
758
+
759
+ // Build update object with only non-empty values to prevent overwriting existing data
760
+ const updatedData = {};
761
+
762
+ const name = nameInput.value.trim();
763
+ const mbti = mbtiSelect.value;
764
+ const learningStyle = learningStyleSelect.value;
765
+ const ams = amsInput.value.trim();
766
+ const cooperative = cooperativeInput.value.trim();
767
+ const grade = gradeInput.value;
768
+
769
+ // Name is required
770
+ if (!name) {
771
+ alert('نام نمی‌تواند خالی باشد!');
772
+ return;
773
+ }
774
+ updatedData.name = name;
775
+
776
+ // Only include other fields if they have values
777
+ if (mbti) updatedData.mbti = mbti;
778
+ if (learningStyle) updatedData.learningStyle = learningStyle;
779
+ if (ams) updatedData.ams = ams;
780
+ if (cooperative) updatedData.cooperative = cooperative;
781
+ if (grade) updatedData.grade = parseFloat(grade);
782
+
783
+ // Update student using the proper data.js function
784
+ const success = await updateStudent(studentNumber, updatedData);
785
+
786
+ if (success) {
787
+ alert('اطلاعات دانش آموز با موفقیت ذخیره شد!');
788
+ cancelEdit(studentNumber);
789
+ await updateStudentTable();
790
+ await loadDashboard();
791
+ } else {
792
+ alert('خطا در ذخیره اطلاعات!');
793
+ }
794
+ }
795
+
796
+ async function resetAllData() {
797
+ if (!confirm('⚠️ هشدار: این عملیات غیرقابل بازگشت است!\n\nآیا مطمئن هستید که می‌خواهید تمام داده‌ها را بازنشانی کنید؟\n\n• همه اطلاعات دانش‌آموزان (MBTI، VARK، AMS، Cooperative، ترجیحات) حذف می‌شوند\n• تمام گروه‌بندی‌ها پاک می‌شوند\n• نام درس حذف می‌شود\n• سیستم به حالت اولیه برمی‌گردد')) {
798
+ return;
799
+ }
800
+
801
+ const password = prompt('لطفاً رمز عبور استاد را وارد کنید:');
802
+ if (!password) return;
803
+
804
+ try {
805
+ const result = await fetch(`${API_BASE_URL}/data/reset-all`, {
806
+ method: 'POST',
807
+ headers: { 'Content-Type': 'application/json' },
808
+ body: JSON.stringify({ password })
809
+ });
810
+
811
+ if (!result.ok) {
812
+ const error = await result.json();
813
+ throw new Error(error.detail || 'Reset failed');
814
+ }
815
+
816
+ alert('✅ تمام داده‌ها با موفقیت بازنشانی شدند!\n\nسیستم به حالت اولیه بازگردانده شد.');
817
+ window.location.reload();
818
+ } catch (error) {
819
+ if (error.message.includes('Invalid password') || error.message.includes('403')) {
820
+ alert('رمز عبور اشتباه است!');
821
+ } else {
822
+ alert('خطا در بازنشانی: ' + error.message);
823
+ }
824
+ }
825
+ }
826
+
827
+ // Test data generator
828
+ async function fillTestData() {
829
+ if (!confirm('آیا می‌خواهید 10 دانش‌آموز اول (S001 تا S010) را با داده‌های تست کامل پر کنید؟\n\nاین عملیات داده‌های موجود این دانش‌آموزان را جایگزین می‌کند.')) {
830
+ return;
831
+ }
832
+
833
+ const MBTI_TYPES = ['INTJ', 'INTP', 'ENTJ', 'ENTP', 'INFJ', 'INFP', 'ENFJ', 'ENFP',
834
+ 'ISTJ', 'ISFJ', 'ESTJ', 'ESFJ', 'ISTP', 'ISFP', 'ESTP', 'ESFP'];
835
+ const VARK_STYLES = ['Visual', 'Aural', 'Read/Write', 'Kinesthetic'];
836
+
837
+ function randomChoice(arr) {
838
+ return arr[Math.floor(Math.random() * arr.length)];
839
+ }
840
+
841
+ function randomInt(min, max) {
842
+ return Math.floor(Math.random() * (max - min + 1)) + min;
843
+ }
844
+
845
+ const btn = document.getElementById('fillTestDataBtn');
846
+ const progress = document.getElementById('testDataProgress');
847
+ const progressText = document.getElementById('testDataProgressText');
848
+
849
+ btn.disabled = true;
850
+ progress.classList.remove('hidden');
851
+
852
+ let successCount = 0;
853
+
854
+ try {
855
+ for (let i = 1; i <= 10; i++) {
856
+ const studentId = `S${String(i).padStart(3, '0')}`;
857
+ progressText.textContent = `در حال پردازش ${studentId}... (${successCount}/10)`;
858
+
859
+ // Generate 2-3 random preferred students (excluding self)
860
+ const allStudents = Array.from({length: 30}, (_, idx) => `S${String(idx + 1).padStart(3, '0')}`);
861
+ const otherStudents = allStudents.filter(s => s !== studentId);
862
+ const numPreferred = randomInt(2, 3);
863
+ const preferred = [];
864
+ for (let j = 0; j < numPreferred; j++) {
865
+ const randomStudent = otherStudents[Math.floor(Math.random() * otherStudents.length)];
866
+ if (!preferred.includes(randomStudent)) {
867
+ preferred.push(randomStudent);
868
+ }
869
+ }
870
+
871
+ const testData = {
872
+ mbti: randomChoice(MBTI_TYPES),
873
+ learningStyle: randomChoice(VARK_STYLES),
874
+ ams: String(randomInt(80, 180)),
875
+ cooperative: String(randomInt(60, 120)),
876
+ preferredStudents: preferred
877
+ };
878
+
879
+ await updateStudent(studentId, testData);
880
+ successCount++;
881
+ progressText.textContent = `${studentId} انجام شد... (${successCount}/10)`;
882
+ }
883
+
884
+ progress.classList.add('hidden');
885
+ alert(`✅ موفق!\n\n10 دانش‌آموز با داده‌های تست پر شدند:\n• MBTI: تصادفی از 16 نوع\n• VARK: ${VARK_STYLES.join(', ')}\n• AMS: 80-180\n• Cooperative: 60-120\n• ترجیحات: 2-3 دانش‌آموز برای هر نفر\n\nحالا می‌توانید گروه‌بندی را تست کنید!`);
886
+ window.location.reload();
887
+ } catch (error) {
888
+ progress.classList.add('hidden');
889
+ alert('خطا در پر کردن داده‌های تست: ' + error.message);
890
+ } finally {
891
+ btn.disabled = false;
892
+ }
893
+ }
894
+
895
+ // JSON file import handler
896
+ document.getElementById('jsonFileInput').addEventListener('change', async function(event) {
897
+ const file = event.target.files[0];
898
+ if (!file) return;
899
+
900
+ document.getElementById('jsonFileName').textContent = file.name;
901
+
902
+ const reader = new FileReader();
903
+ reader.onload = async function(e) {
904
+ try {
905
+ const jsonData = JSON.parse(e.target.result);
906
+
907
+ // Accept both formats: direct array or {students: [...]} object
908
+ let studentsArray;
909
+ if (Array.isArray(jsonData)) {
910
+ studentsArray = jsonData;
911
+ } else if (jsonData.students && Array.isArray(jsonData.students)) {
912
+ studentsArray = jsonData.students;
913
+ } else {
914
+ alert('خطا: فایل JSON باید شامل آرایه students باشد یا یک آرایه مستقیم از دانش‌آموزان.');
915
+ return;
916
+ }
917
+
918
+ if (!confirm(`آیا می‌خواهید داده‌های ${studentsArray.length} دانش‌آموز را از فایل JSON وارد کنید؟\n\nاین عملیات داده‌های موجود را جایگزین می‌کند.`)) {
919
+ return;
920
+ }
921
+
922
+ const progress = document.getElementById('jsonImportProgress');
923
+ const progressText = document.getElementById('jsonImportProgressText');
924
+
925
+ progress.classList.remove('hidden');
926
+ let successCount = 0;
927
+ let errorCount = 0;
928
+
929
+ for (const student of studentsArray) {
930
+ // Validate required field
931
+ if (!student.studentNumber) {
932
+ console.warn('Student missing studentNumber, skipping:', student);
933
+ errorCount++;
934
+ continue;
935
+ }
936
+
937
+ progressText.textContent = `در حال پردازش ${student.studentNumber}... (${successCount}/${studentsArray.length})`;
938
+
939
+ // Build update object with only provided fields
940
+ const updates = {};
941
+ if (student.mbti) updates.mbti = student.mbti;
942
+ if (student.learningStyle) updates.learningStyle = student.learningStyle;
943
+ if (student.ams !== undefined && student.ams !== null) updates.ams = String(student.ams);
944
+ if (student.cooperative !== undefined && student.cooperative !== null) updates.cooperative = String(student.cooperative);
945
+ if (student.preferredStudents) updates.preferredStudents = student.preferredStudents;
946
+
947
+ try {
948
+ await updateStudent(student.studentNumber, updates);
949
+ successCount++;
950
+ } catch (error) {
951
+ console.error(`Error updating ${student.studentNumber}:`, error);
952
+ errorCount++;
953
+ }
954
+ }
955
+
956
+ progress.classList.add('hidden');
957
+
958
+ let message = `✅ موفق!\n\n${successCount} دانش‌آموز با موفقیت به‌روزرسانی شدند.`;
959
+ if (errorCount > 0) {
960
+ message += `\n\n⚠️ ${errorCount} دانش‌آموز دارای خطا بودند.`;
961
+ }
962
+
963
+ alert(message);
964
+ window.location.reload();
965
+
966
+ } catch (error) {
967
+ alert('خطا در خواندن فایل JSON: ' + error.message);
968
+ document.getElementById('jsonImportProgress').classList.add('hidden');
969
+ }
970
+ };
971
+
972
+ reader.readAsText(file);
973
+ });
974
+
975
+ async function downloadAllData() {
976
+ try {
977
+ const response = await fetch(`${API_BASE_URL}/data/backup`);
978
+ if (!response.ok) {
979
+ throw new Error('Failed to fetch data');
980
+ }
981
+
982
+ const data = await response.json();
983
+
984
+ // Create a blob with the JSON data
985
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
986
+
987
+ // Create download link
988
+ const url = window.URL.createObjectURL(blob);
989
+ const a = document.createElement('a');
990
+ a.href = url;
991
+
992
+ // Generate filename with current date/time
993
+ const now = new Date();
994
+ const dateStr = now.toISOString().replace(/[:.]/g, '-').slice(0, -5);
995
+ a.download = `talimbot-data-${dateStr}.json`;
996
+
997
+ // Trigger download
998
+ document.body.appendChild(a);
999
+ a.click();
1000
+
1001
+ // Cleanup
1002
+ window.URL.revokeObjectURL(url);
1003
+ document.body.removeChild(a);
1004
+
1005
+ alert('✅ داده‌ها با موفقیت دانلود شد!');
1006
+ } catch (error) {
1007
+ console.error('Download error:', error);
1008
+ alert('❌ خطا در دانلود داده‌ها: ' + error.message);
1009
+ }
1010
+ }
1011
+
1012
+ window.onload = loadDashboard;
1013
+ </script>
1014
+ </body>
1015
+ </html>
1016
+
1017
+
render.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Render.com Deployment Configuration
2
+
3
+ ## Build Command:
4
+ pip install -r backend/requirements.txt
5
+
6
+ ## Start Command:
7
+ cd backend && uvicorn main:app --host 0.0.0.0 --port $PORT
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi==0.104.1
2
+ uvicorn==0.24.0
3
+ pydantic==2.5.0
4
+ requests==2.31.0
5
+ python-multipart==0.0.6
6
+ python-dotenv==1.0.0
resources_references/DEPLOYMENT_CHECKLIST.md ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ✅ Railway Deployment Checklist
2
+
3
+ ## Pre-Deployment Verification
4
+
5
+ ### ✅ Local Testing Complete
6
+ - [x] Project restructured with `backend/static/` folder
7
+ - [x] All frontend files copied to `backend/static/`
8
+ - [x] `main.py` updated to serve static files
9
+ - [x] `data.js` updated for Railway deployment
10
+ - [x] Local server tested and working (`http://localhost:8000`)
11
+ - [x] All pages accessible (index, login, dashboards, questionnaires)
12
+ - [x] API endpoints working (grouping, data saving)
13
+
14
+ ### ✅ Git Repository Status
15
+ - [x] `.env` file in `.gitignore` (API key NOT committed)
16
+ - [x] All changes committed to Git
17
+ - [x] Changes pushed to GitHub main branch
18
+ - [x] Repository: `https://github.com/talimbot/talimbot`
19
+
20
+ ---
21
+
22
+ ## 🚂 Railway Deployment Steps
23
+
24
+ ### Step 1: Create Railway Account
25
+ - [ ] Go to https://railway.app
26
+ - [ ] Click "Start a New Project"
27
+ - [ ] Sign in with GitHub account
28
+ - [ ] Authorize Railway to access your repositories
29
+
30
+ ### Step 2: Deploy from GitHub
31
+ - [ ] Click "Deploy from GitHub repo"
32
+ - [ ] Select `talimbot/talimbot` repository
33
+ - [ ] Railway auto-detects Python project
34
+ - [ ] Deployment starts automatically
35
+
36
+ ### Step 3: Configure Environment Variables
37
+ - [ ] Go to project dashboard
38
+ - [ ] Click "Variables" tab
39
+ - [ ] Click "+ New Variable"
40
+ - [ ] Add variable:
41
+ - **Key**: `OPENROUTER_API_KEY`
42
+ - **Value**: `sk-or-v1-your-actual-key-here`
43
+ - [ ] Click "Add"
44
+
45
+ ### Step 4: Generate Public URL
46
+ - [ ] Go to "Settings" tab
47
+ - [ ] Scroll to "Networking" section
48
+ - [ ] Click "Generate Domain"
49
+ - [ ] Copy your URL: `https://talimbot-production-xxxx.up.railway.app`
50
+
51
+ ### Step 5: Wait for Deployment
52
+ - [ ] Monitor deployment in "Deployments" tab
53
+ - [ ] Wait 2-3 minutes for build to complete
54
+ - [ ] Look for "✅ Deployment successful" message
55
+
56
+ ---
57
+
58
+ ## 🧪 Post-Deployment Testing
59
+
60
+ ### Test All Features
61
+ Visit your Railway URL and verify:
62
+
63
+ #### Frontend Pages
64
+ - [ ] Main page loads (`/`)
65
+ - [ ] Login page works (`/pages/login.html`)
66
+ - [ ] Student dashboard loads and functions
67
+ - [ ] Teacher dashboard loads and functions
68
+ - [ ] AMS questionnaire page works
69
+ - [ ] Cooperative questionnaire page works
70
+ - [ ] Group view page displays correctly
71
+
72
+ #### Authentication & Navigation
73
+ - [ ] Can login as student (use student number from data)
74
+ - [ ] Can login as teacher (password: `teacher123`)
75
+ - [ ] Navigation between pages works
76
+ - [ ] Back buttons work correctly
77
+ - [ ] Logout functionality works
78
+
79
+ #### Student Features
80
+ - [ ] MBTI test can be filled and saved
81
+ - [ ] VARK test can be filled and saved
82
+ - [ ] AMS questionnaire completes and auto-saves score
83
+ - [ ] Cooperative questionnaire completes and auto-saves score
84
+ - [ ] Preferred students selection works (max 4)
85
+ - [ ] "Save All Data" button saves successfully
86
+ - [ ] Data persists after page refresh
87
+ - [ ] Can view assigned group
88
+
89
+ #### Teacher Features
90
+ - [ ] Can view all students in table
91
+ - [ ] Can edit student information
92
+ - [ ] Can add new students
93
+ - [ ] Can delete students
94
+ - [ ] Download data button works
95
+ - [ ] Toggle visibility for students works
96
+ - [ ] AI Grouping functionality works
97
+ - [ ] Can select course (Riazi, Faravari, etc.)
98
+ - [ ] Grouping completes successfully
99
+ - [ ] Groups are created and saved
100
+ - [ ] Students can see their groups
101
+ - [ ] AMS and Cooperation columns display correctly
102
+
103
+ #### API & Data Persistence
104
+ - [ ] API calls complete without errors (check browser console)
105
+ - [ ] Student data saves to backend
106
+ - [ ] Data persists between sessions
107
+ - [ ] Groups persist after creation
108
+ - [ ] No 404 errors for API endpoints
109
+ - [ ] No CORS errors in console
110
+
111
+ ---
112
+
113
+ ## 🔍 Troubleshooting Guide
114
+
115
+ ### If pages don't load:
116
+ 1. Check Railway deployment logs for errors
117
+ 2. Verify `backend/static/` folder contains all files
118
+ 3. Check browser console for 404 errors
119
+
120
+ ### If API calls fail:
121
+ 1. Verify environment variable `OPENROUTER_API_KEY` is set
122
+ 2. Check Railway logs for API errors
123
+ 3. Test API endpoints directly: `https://your-url.railway.app/api/students`
124
+
125
+ ### If grouping doesn't work:
126
+ 1. Verify OpenRouter API key is correct
127
+ 2. Check you have credits in OpenRouter account
128
+ 3. View Railway logs during grouping attempt
129
+ 4. Ensure request is reaching `/api/grouping` endpoint
130
+
131
+ ### If static files are missing:
132
+ 1. Verify files exist in `backend/static/` folder locally
133
+ 2. Ensure git committed and pushed all files
134
+ 3. Trigger manual redeploy in Railway
135
+ 4. Check file paths in HTML are correct (relative paths)
136
+
137
+ ---
138
+
139
+ ## 📊 Monitoring
140
+
141
+ ### Check Deployment Status
142
+ - [ ] Railway dashboard shows "Active" status
143
+ - [ ] Latest deployment timestamp is recent
144
+ - [ ] No error messages in deployment logs
145
+
146
+ ### Monitor Usage
147
+ - [ ] View usage in Railway "Usage" tab
148
+ - [ ] Verify you're within free tier ($5/month)
149
+ - [ ] Estimated usage: ~$2-3/month
150
+
151
+ ### View Logs
152
+ - [ ] Real-time logs available in "Deployments" tab
153
+ - [ ] Check logs for any errors or warnings
154
+ - [ ] Monitor API request logs
155
+
156
+ ---
157
+
158
+ ## 🎯 Final Verification
159
+
160
+ ### Complete Success Criteria
161
+ - [ ] Application is accessible from any device with internet
162
+ - [ ] All features work identically to localhost version
163
+ - [ ] No errors in browser console
164
+ - [ ] No errors in Railway logs
165
+ - [ ] Data persistence works correctly
166
+ - [ ] AI grouping creates groups successfully
167
+ - [ ] Teachers can manage students
168
+ - [ ] Students can complete questionnaires
169
+
170
+ ### Share with Users
171
+ - [ ] Copy Railway URL
172
+ - [ ] Share URL with teacher
173
+ - [ ] Provide login instructions:
174
+ - Teacher: Password is `teacher123`
175
+ - Students: Use their student number
176
+ - [ ] Explain how to access from any device
177
+
178
+ ---
179
+
180
+ ## 🚀 You're Done!
181
+
182
+ Once all checkboxes are checked, your TalimBot is:
183
+ - ✅ **Fully deployed** on Railway
184
+ - ✅ **Accessible from anywhere** via the internet
185
+ - ✅ **Secure** (API key in environment variables)
186
+ - ✅ **Free** (within Railway's $5/month credit)
187
+ - ✅ **Professional** (real production website)
188
+
189
+ **Your Live URL**: `https://your-project.up.railway.app`
190
+
191
+ ---
192
+
193
+ ## 📝 Next Steps
194
+
195
+ 1. **Bookmark your Railway dashboard** for monitoring
196
+ 2. **Keep your OpenRouter API key safe** (never share it)
197
+ 3. **Monitor Railway usage** to stay within free tier
198
+ 4. **Test from multiple devices** (phone, tablet, different computers)
199
+ 5. **Collect feedback** from teachers and students
200
+
201
+ ---
202
+
203
+ ## 🆘 Need Help?
204
+
205
+ - **Railway Docs**: https://docs.railway.app
206
+ - **Railway Discord**: https://discord.gg/railway
207
+ - **Review**: `RAILWAY_SETUP_GUIDE.md` for detailed instructions
resources_references/Icons/Additional/teacherIcon.png ADDED
resources_references/Icons/Additional/teacherIcon2.jpg ADDED
resources_references/Icons/logo/Logo_blueBackground.jpg ADDED
resources_references/Icons/logo/Logo_noBackground.jpg ADDED
resources_references/Icons/logo/logo.png ADDED
resources_references/Icons/studentIcon.png ADDED
resources_references/Icons/teacherIcon3.png ADDED
resources_references/RAILWAY_DEPLOYMENT.md ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚂 Railway Deployment Guide for TalimBot
2
+
3
+ ## Quick Deploy to Railway.app
4
+
5
+ ### Step 1: Create Railway Account
6
+ 1. Go to: **https://railway.app**
7
+ 2. Click **"Login"** → **"Login with GitHub"**
8
+ 3. Authorize Railway to access your repositories
9
+
10
+ ### Step 2: Deploy from GitHub
11
+ 1. Click **"New Project"**
12
+ 2. Select **"Deploy from GitHub repo"**
13
+ 3. Choose your **"talimbot"** repository
14
+ 4. Railway will automatically detect it's a Python project!
15
+
16
+ ### Step 3: Add Environment Variable (IMPORTANT!)
17
+ 1. In your Railway project, click on your service
18
+ 2. Go to **"Variables"** tab
19
+ 3. Click **"New Variable"**
20
+ 4. Add:
21
+ - **Name:** `OPENROUTER_API_KEY`
22
+ - **Value:** `sk-or-v1-your-actual-key-here` (get from https://openrouter.ai)
23
+ 5. Click **"Add"**
24
+
25
+ ### Step 4: Configure Build (if needed)
26
+ Railway usually auto-detects, but if it doesn't:
27
+ 1. Go to **"Settings"** tab
28
+ 2. **Build Command:** `pip install -r backend/requirements.txt`
29
+ 3. **Start Command:** `cd backend && uvicorn main:app --host 0.0.0.0 --port $PORT`
30
+
31
+ ### Step 5: Deploy!
32
+ 1. Railway deploys automatically
33
+ 2. Wait 2-3 minutes for build to complete
34
+ 3. You'll see "Active" when ready
35
+
36
+ ### Step 6: Get Your URL
37
+ 1. Click **"Settings"** tab
38
+ 2. Scroll to **"Networking"**
39
+ 3. Click **"Generate Domain"**
40
+ 4. You'll get: `https://talimbot-production-xxxx.up.railway.app`
41
+ 5. **Copy this URL!**
42
+
43
+ ### Step 7: Update Frontend
44
+ Edit `assets/js/data.js` line 11:
45
+ ```javascript
46
+ return 'https://talimbot-production-xxxx.up.railway.app/api';
47
+ ```
48
+
49
+ ### Step 8: Push to GitHub
50
+ ```bash
51
+ git add assets/js/data.js
52
+ git commit -m "Configure Railway backend URL"
53
+ git push origin main
54
+ ```
55
+
56
+ ### Step 9: Test!
57
+ Visit: `https://talimbot.github.io/talimbot/`
58
+
59
+ ---
60
+
61
+ ## Environment Variables Explained
62
+
63
+ ### Local Development (.env file)
64
+ ```
65
+ OPENROUTER_API_KEY=sk-or-v1-your-key-here
66
+ ```
67
+
68
+ ### Railway (Variables tab in dashboard)
69
+ - **Variable Name:** OPENROUTER_API_KEY
70
+ - **Variable Value:** sk-or-v1-your-key-here
71
+
72
+ **Important:** NEVER commit your .env file to GitHub! It's already in .gitignore.
73
+
74
+ ---
75
+
76
+ ## Cost
77
+
78
+ Railway offers:
79
+ - **$5 free credit per month** (renews monthly)
80
+ - **$0.000463 per GB-hour** of RAM
81
+ - Usually enough for small projects like this
82
+ - Can upgrade to Developer plan ($5/month) for more resources
83
+
84
+ ---
85
+
86
+ ## Advantages over Render
87
+
88
+ ✅ **No sleep** - stays online 24/7
89
+ ✅ **Faster deployments** - usually 1-2 minutes
90
+ ✅ **Better free tier** - $5 credit (vs Render's sleep after 15min)
91
+ ✅ **Auto-deploy** from GitHub on push
92
+ ✅ **Easy environment variables** - simple UI
93
+
94
+ ---
95
+
96
+ ## Troubleshooting
97
+
98
+ ### Build Fails
99
+ - Check Logs tab for error messages
100
+ - Verify `requirements.txt` has all dependencies
101
+ - Make sure `python-dotenv` is included
102
+
103
+ ### "API key not configured" Error
104
+ - Go to Variables tab
105
+ - Verify OPENROUTER_API_KEY is set
106
+ - Make sure there are no extra spaces
107
+ - Redeploy after adding variable
108
+
109
+ ### Can't Access Backend
110
+ - Check service is "Active" (not deploying/failed)
111
+ - Verify you generated a domain in Settings → Networking
112
+ - Try accessing: `https://your-url.up.railway.app/api/students`
113
+
114
+ ---
115
+
116
+ ## Next Steps
117
+
118
+ After deployment:
119
+ 1. Test login from multiple devices
120
+ 2. Test grouping functionality
121
+ 3. Download JSON backup
122
+ 4. Share link with students!
123
+
124
+ ---
125
+
126
+ **Your website is now live and independent!** 🎉
resources_references/RAILWAY_SETUP_GUIDE.md ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚂 Railway Deployment Guide for TalimBot
2
+
3
+ ## 📋 Overview
4
+ This guide will help you deploy your complete TalimBot application (Frontend + Backend) to Railway for free, making it accessible from anywhere.
5
+
6
+ ## ✅ What We've Done (Preparation Complete!)
7
+
8
+ ### 1. **Restructured the Project**
9
+ - ✅ Created `backend/static/` folder
10
+ - ✅ Moved all frontend files (HTML, CSS, JS, Icons) to `backend/static/`
11
+ - ✅ Updated `main.py` to serve static files
12
+ - ✅ Updated `data.js` to use relative API paths for Railway
13
+
14
+ ### 2. **Updated FastAPI Configuration**
15
+ Your `backend/main.py` now:
16
+ - Serves static files from `backend/static/` folder
17
+ - Uses environment variable for OpenRouter API key
18
+ - Handles both frontend (HTML pages) and backend (API endpoints) from the same server
19
+ - Works seamlessly on Railway's deployment platform
20
+
21
+ ### 3. **Project Structure**
22
+ ```
23
+ talimbot/
24
+ ├── .env # LOCAL ONLY - Contains your API key (NEVER commit this!)
25
+ ├── .env.example # Template for environment variables
26
+ ├── .gitignore # Ensures .env is not committed
27
+ ├── Procfile # Tells Railway how to start the server
28
+ ├── runtime.txt # Specifies Python version
29
+ ├── README.md
30
+ ├── RAILWAY_DEPLOYMENT.md
31
+ ├── backend/
32
+ │ ├── main.py # ✅ UPDATED - Serves static files + API endpoints
33
+ │ ├── grouping_logic.py
34
+ │ ├── requirements.txt
35
+ │ ├── data/
36
+ │ │ └── students.json
37
+ │ └── static/ # ✅ NEW - All frontend files
38
+ │ ├── index.html
39
+ │ ├── assets/ # CSS and JS files
40
+ │ │ ├── css/
41
+ │ │ │ └── styles.css
42
+ │ │ └── js/
43
+ │ │ ├── data.js # ✅ UPDATED - Uses relative API paths
44
+ │ │ └── grouping.js
45
+ │ ├── pages/ # All HTML pages
46
+ │ │ ├── login.html
47
+ │ │ ├── student-dashboard.html
48
+ │ │ ├── teacher-dashboard.html
49
+ │ │ ├── ams-questionnaire.html
50
+ │ │ ├── cooperative-questionnaire.html
51
+ │ │ └── group-view.html
52
+ │ └── Icons/ # Logo and icons
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 🚀 Deployment Steps
58
+
59
+ ### **Step 1: Verify Local Setup**
60
+
61
+ 1. **Create `.env` file** (if you haven't already):
62
+ ```bash
63
+ # In the project root (talimbot/) folder
64
+ echo OPENROUTER_API_KEY=sk-or-v1-your-actual-key-here > .env
65
+ ```
66
+
67
+ 2. **Test locally**:
68
+ ```bash
69
+ cd backend
70
+ python main.py
71
+ ```
72
+
73
+ 3. **Open browser** to `http://localhost:8000`
74
+ - You should see the index.html page
75
+ - All pages should work (login, dashboards, questionnaires)
76
+ - API calls should work (grouping, data saving)
77
+
78
+ ---
79
+
80
+ ### **Step 2: Commit Changes to GitHub**
81
+
82
+ ⚠️ **IMPORTANT**: Make sure `.env` is in `.gitignore` (it already is!)
83
+
84
+ ```bash
85
+ # From the talimbot/ directory
86
+ git add .
87
+ git status # Verify .env is NOT listed (should only see modified files)
88
+
89
+ git commit -m "Restructure project for Railway deployment - serve frontend from backend"
90
+ git push origin main
91
+ ```
92
+
93
+ ---
94
+
95
+ ### **Step 3: Deploy to Railway**
96
+
97
+ #### A. **Sign Up / Log In**
98
+ 1. Go to [railway.app](https://railway.app)
99
+ 2. Click **"Start a New Project"**
100
+ 3. Sign in with your GitHub account
101
+
102
+ #### B. **Create New Project**
103
+ 1. Click **"Deploy from GitHub repo"**
104
+ 2. Select your `talimbot` repository
105
+ 3. Railway will automatically detect it's a Python project
106
+
107
+ #### C. **Configure Environment Variables**
108
+ 1. In the Railway dashboard, go to your project
109
+ 2. Click on the **"Variables"** tab
110
+ 3. Click **"+ New Variable"**
111
+ 4. Add:
112
+ - **Key**: `OPENROUTER_API_KEY`
113
+ - **Value**: `sk-or-v1-your-actual-openrouter-api-key`
114
+ 5. Click **"Add"**
115
+
116
+ #### D. **Verify Deployment Settings**
117
+ Railway auto-detects settings from your files:
118
+ - ✅ **Build Command**: None needed (Python dependencies auto-installed)
119
+ - ✅ **Start Command**: From `Procfile` → `cd backend && uvicorn main:app --host 0.0.0.0 --port $PORT`
120
+ - ✅ **Python Version**: From `runtime.txt` → `python-3.11.0`
121
+
122
+ #### E. **Deploy!**
123
+ 1. Click **"Deploy"**
124
+ 2. Wait 2-3 minutes for deployment
125
+ 3. Railway will show deployment logs
126
+ 4. When complete, you'll see: ✅ **"Deployment successful"**
127
+
128
+ #### F. **Get Your URL**
129
+ 1. In Railway dashboard, click **"Settings"** tab
130
+ 2. Scroll to **"Networking"** section
131
+ 3. Click **"Generate Domain"**
132
+ 4. Copy your URL (e.g., `https://talimbot-production-abc123.up.railway.app`)
133
+
134
+ ---
135
+
136
+ ### **Step 4: Test Your Deployed Application**
137
+
138
+ 1. **Open your Railway URL** in a browser
139
+ 2. **Test all features**:
140
+ - ✅ Main page loads (`index.html`)
141
+ - ✅ Login page works (`/pages/login.html`)
142
+ - ✅ Student dashboard loads
143
+ - ✅ Teacher dashboard loads
144
+ - ✅ Questionnaires work (AMS, Cooperative)
145
+ - ✅ Grouping functionality works
146
+ - ✅ Data saves correctly
147
+
148
+ ---
149
+
150
+ ## 🔧 How It Works
151
+
152
+ ### **Single Server Architecture**
153
+ Railway runs ONE server that handles BOTH:
154
+
155
+ 1. **Frontend (Static Files)**:
156
+ - `GET /` → Serves `index.html`
157
+ - `GET /pages/login.html` → Serves login page
158
+ - `GET /assets/css/styles.css` → Serves CSS
159
+ - `GET /assets/js/data.js` → Serves JavaScript
160
+
161
+ 2. **Backend (API Endpoints)**:
162
+ - `POST /api/grouping` → AI grouping logic
163
+ - `GET /api/students` → Get all students
164
+ - `PUT /api/students/{id}` → Update student
165
+ - All other API routes in `main.py`
166
+
167
+ ### **How Requests Are Routed**
168
+
169
+ ```
170
+ User Browser → Railway URL
171
+
172
+ FastAPI Server (main.py)
173
+
174
+ ┌─────────┴─────────┐
175
+ ↓ ↓
176
+ /api/* Everything else
177
+ (API Endpoints) (Static Files)
178
+ ↓ ↓
179
+ grouping_logic.py backend/static/
180
+ OpenRouter API (HTML/CSS/JS)
181
+ ```
182
+
183
+ ### **Environment Variable Flow**
184
+
185
+ ```
186
+ Local Development:
187
+ .env file → load_dotenv() → os.getenv("OPENROUTER_API_KEY")
188
+
189
+ Railway Production:
190
+ Railway Variables → os.getenv("OPENROUTER_API_KEY")
191
+ ```
192
+
193
+ ---
194
+
195
+ ## 📊 Monitoring & Management
196
+
197
+ ### **View Logs**
198
+ 1. Go to Railway dashboard
199
+ 2. Click on your project
200
+ 3. Click **"Deployments"** tab
201
+ 4. Click on the latest deployment
202
+ 5. View real-time logs
203
+
204
+ ### **Check Usage**
205
+ - Railway free tier: **$5 credit/month**
206
+ - Your app should use: **~$2-3/month**
207
+ - Monitor usage in **"Usage"** tab
208
+
209
+ ### **Redeploy (After Code Changes)**
210
+ 1. Make changes locally
211
+ 2. Test locally (`python main.py`)
212
+ 3. Commit and push to GitHub:
213
+ ```bash
214
+ git add .
215
+ git commit -m "Your changes"
216
+ git push origin main
217
+ ```
218
+ 4. Railway **auto-deploys** within 1-2 minutes!
219
+
220
+ ---
221
+
222
+ ## 🆘 Troubleshooting
223
+
224
+ ### **Problem: API calls fail (404 errors)**
225
+ **Solution**: API routes must start with `/api/`
226
+ - ✅ Correct: `POST /api/grouping`
227
+ - ❌ Wrong: `POST /grouping`
228
+
229
+ ### **Problem: Static files not loading (CSS/JS missing)**
230
+ **Solution**:
231
+ 1. Verify files are in `backend/static/` folder
232
+ 2. Check browser console for 404 errors
233
+ 3. Ensure paths in HTML are relative (e.g., `/assets/css/styles.css`)
234
+
235
+ ### **Problem: OpenRouter API errors**
236
+ **Solution**:
237
+ 1. Verify API key is correct in Railway Variables
238
+ 2. Check you have credits in your OpenRouter account
239
+ 3. View logs in Railway to see exact error message
240
+
241
+ ### **Problem: Server won't start**
242
+ **Solution**:
243
+ 1. Check Railway logs for error messages
244
+ 2. Verify `requirements.txt` has all dependencies
245
+ 3. Ensure `Procfile` command is correct
246
+
247
+ ---
248
+
249
+ ## 🎯 Success Checklist
250
+
251
+ After deployment, verify:
252
+
253
+ - [ ] Railway URL loads the main page
254
+ - [ ] All navigation links work
255
+ - [ ] Login system works (student/teacher)
256
+ - [ ] Student dashboard loads
257
+ - [ ] Teacher dashboard loads
258
+ - [ ] AMS questionnaire works and saves
259
+ - [ ] Cooperative questionnaire works and saves
260
+ - [ ] AI grouping creates groups successfully
261
+ - [ ] Student data persists after refresh
262
+ - [ ] API calls complete without errors
263
+ - [ ] No console errors in browser DevTools
264
+
265
+ ---
266
+
267
+ ## 💡 Next Steps
268
+
269
+ Once deployed successfully:
270
+
271
+ 1. **Share the Railway URL** with your teacher
272
+ 2. **Test from different devices** (phone, tablet)
273
+ 3. **Monitor Railway dashboard** for any errors
274
+ 4. **Keep your OpenRouter API key secure**
275
+ 5. **Consider upgrading Railway plan** if you exceed free tier
276
+
277
+ ---
278
+
279
+ ## 📞 Support Resources
280
+
281
+ - **Railway Docs**: https://docs.railway.app
282
+ - **OpenRouter Docs**: https://openrouter.ai/docs
283
+ - **FastAPI Docs**: https://fastapi.tiangolo.com
284
+ - **Your Deployment Guide**: `RAILWAY_DEPLOYMENT.md`
285
+
286
+ ---
287
+
288
+ ## 🎉 Congratulations!
289
+
290
+ Your TalimBot is now a **real, independent website** accessible from anywhere! 🚀
291
+
292
+ **Your app URL**: `https://your-project.up.railway.app`
293
+
294
+ Teachers and students can access it from:
295
+ - ✅ Home computers
296
+ - ✅ School computers
297
+ - ✅ Phones (any device with internet)
298
+ - ✅ Tablets
299
+
300
+ No need for localhost, no need for running Python locally - it's fully online! 🌐
resources_references/README.md ADDED
@@ -0,0 +1,407 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TalimBot - سیستم گروه‌بندی هوشمند دانش‌آموزان
2
+
3
+ A full-stack intelligent student grouping system using AI-powered analysis with FastAPI backend and GitHub Pages frontend.
4
+
5
+ ## 🎯 Overview
6
+
7
+ TalimBot groups 30 real students based on MBTI personality types, learning styles, math grades, and peer preferences using AI (GPT-4o-mini via OpenRouter). Teachers control when students can view their group assignments.
8
+
9
+ ### Real Student Data
10
+ - **30 Students** from class 1061 (دهم تجربی)
11
+ - **Persian Names**: Full Persian names from actual class roster
12
+ - **National Code Authentication**: Students login with their national ID
13
+ - **Math Grades**: Real grades from student records
14
+
15
+ ---
16
+
17
+ ## 🚀 Quick Start (Local Development)
18
+
19
+ ### Prerequisites
20
+ - Python 3.8+ installed
21
+ - Modern web browser
22
+ - Internet connection (for AI grouping)
23
+
24
+ ### Step 1: Start Backend (30 seconds)
25
+
26
+ ```powershell
27
+ # Navigate to backend folder
28
+ cd backend
29
+
30
+ # Install dependencies (first time only)
31
+ pip install -r requirements.txt
32
+
33
+ # Start the server
34
+ python main.py
35
+ ```
36
+
37
+ **Server runs on:** `http://localhost:8000`
38
+
39
+ **Keep this terminal running!**
40
+
41
+ ### Step 2: Open Frontend (10 seconds)
42
+
43
+ **Option A - Direct Open (Easiest):**
44
+ - Double-click `index.html` in File Explorer
45
+
46
+ **Option B - Local Server (Recommended):**
47
+ ```powershell
48
+ # In a NEW terminal window
49
+ python -m http.server 3000
50
+ ```
51
+ Then visit: `http://localhost:3000`
52
+
53
+ ---
54
+
55
+ ## 👥 Login Credentials
56
+
57
+ ### Students (30 students available)
58
+ Login with:
59
+ - **Student Number:** S001, S002, ..., S030
60
+ - **National Code:** Each student's 10-digit national ID
61
+
62
+ **Example:**
63
+ - Student Number: `S001`
64
+ - National Code: `929986644`
65
+ - Name: آدینه پور - یاسمن
66
+
67
+ ### Teacher
68
+ - **Password:** `teacher123`
69
+
70
+ ---
71
+
72
+ ## 📋 How It Works
73
+
74
+ ### For Students:
75
+
76
+ 1. **Login**
77
+ - Enter student number (S001-S030)
78
+ - Enter your 10-digit national code
79
+ - System displays your Persian name
80
+
81
+ 2. **Complete Profile**
82
+ - Take MBTI test (link provided)
83
+ - Take VARK learning style test (link provided)
84
+ - Fill out AMS and Cooperative preferences
85
+ - Select up to 4 preferred groupmates
86
+ - Save your information
87
+
88
+ 3. **View Group** (after teacher enables)
89
+ - See your group number
90
+ - View all group members
91
+ - Read AI reasoning in Persian
92
+
93
+ ### For Teachers:
94
+
95
+ 1. **Monitor Progress**
96
+ - View dashboard with statistics
97
+ - See which students completed profiles
98
+ - Check readiness for grouping
99
+
100
+ 2. **Perform Grouping**
101
+ - Enter course name
102
+ - Click "Start Grouping"
103
+ - AI analyzes all data (10-30 seconds)
104
+ - Review generated groups with Persian reasoning
105
+
106
+ 3. **Control Visibility**
107
+ - Groups are hidden by default
108
+ - Click "Show Results to Students"
109
+ - Toggle visibility on/off anytime
110
+
111
+ 4. **Manage Data**
112
+ - View all student information
113
+ - Edit student data if needed
114
+ - Reset grouping when necessary
115
+
116
+ ---
117
+
118
+ ## 🌐 Deployment to GitHub Pages
119
+
120
+ ### Part 1: Deploy Frontend to GitHub Pages
121
+
122
+ 1. **Create GitHub Repository**
123
+ ```bash
124
+ # Initialize git (if not already done)
125
+ git init
126
+ git add .
127
+ git commit -m "Initial commit with real student data"
128
+
129
+ # Create repo on GitHub and push
130
+ git remote add origin https://github.com/talimbot/talimbot.git
131
+ git branch -M main
132
+ git push -u origin main
133
+ ```
134
+
135
+ 2. **Enable GitHub Pages**
136
+ - Go to repository Settings → Pages
137
+ - Source: Deploy from branch `main`
138
+ - Folder: `/ (root)`
139
+ - Click Save
140
+
141
+ 3. **Your site will be live at:**
142
+ `https://talimbot.github.io/talimbot/`
143
+
144
+ ### Part 2: Deploy Backend to Render.com (Free)
145
+
146
+ 1. **Create Render Account**
147
+ - Visit [render.com](https://render.com)
148
+ - Sign up with GitHub
149
+
150
+ 2. **Create New Web Service**
151
+ - Click "New +" → "Web Service"
152
+ - Connect your GitHub repository
153
+ - Settings:
154
+ - **Name:** `talimbot-backend`
155
+ - **Environment:** `Python 3`
156
+ - **Build Command:** `pip install -r backend/requirements.txt`
157
+ - **Start Command:** `cd backend && uvicorn main:app --host 0.0.0.0 --port $PORT`
158
+ - **Plan:** Free
159
+
160
+ 3. **Add Environment Variable**
161
+ - In Render dashboard → Environment
162
+ - Add: `OPENROUTER_API_KEY` = `your-api-key`
163
+ - Optionally: `TEACHER_PASSWORD` = `your-password`
164
+
165
+ 4. **Get Your Backend URL**
166
+ - Copy the URL (e.g., `https://talimbot-backend.onrender.com`)
167
+
168
+ ### Part 3: Update Frontend to Use Deployed Backend
169
+
170
+ Update `API_BASE_URL` in BOTH files:
171
+ - `assets/js/data.js`
172
+ - `assets/js/grouping.js`
173
+
174
+ ```javascript
175
+ // Change from:
176
+ const API_BASE_URL = 'http://localhost:8000/api';
177
+
178
+ // To:
179
+ const API_BASE_URL = 'https://talimbot-backend.onrender.com/api';
180
+ ```
181
+
182
+ Commit and push changes:
183
+ ```bash
184
+ git add .
185
+ git commit -m "Update API URL for production"
186
+ git push
187
+ ```
188
+
189
+ GitHub Pages will auto-deploy in ~1 minute.
190
+
191
+ ---
192
+
193
+ ## 🔧 Configuration
194
+
195
+ ### API Key (OpenRouter)
196
+ Located in `backend/grouping_logic.py`:
197
+ ```python
198
+ OPENROUTER_API_KEY = 'sk-or-v1-...'
199
+ ```
200
+
201
+ ### Teacher Password
202
+ Located in `backend/main.py` (SystemData model):
203
+ ```python
204
+ teacherPassword: str = "teacher123"
205
+ ```
206
+
207
+ Change as needed for security.
208
+
209
+ ### Student Data
210
+ Students are initialized in `backend/main.py` in the `load_data()` function.
211
+ All 30 real students with Persian names and national codes are pre-loaded.
212
+
213
+ ---
214
+
215
+ ## 📁 Project Structure
216
+
217
+ ```
218
+ talimbot/
219
+ ├── backend/
220
+ │ ├── main.py # FastAPI server with real student data
221
+ │ ├── grouping_logic.py # AI grouping with OpenRouter
222
+ │ ├── requirements.txt # Python dependencies
223
+ │ ├── README.md # Backend documentation
224
+ │ └── data/
225
+ │ └── students.json # Auto-created data storage
226
+
227
+ ├── assets/
228
+ │ ├── css/
229
+ │ │ └── styles.css
230
+ │ └── js/
231
+ │ ├── data.js # API client for student data
232
+ │ └── grouping.js # API client for grouping
233
+
234
+ ├── pages/
235
+ │ ├── login.html # Login with national code
236
+ │ ├── student-dashboard.html
237
+ │ ├── student-data.html
238
+ │ ├── teacher-dashboard.html
239
+ │ └── group-view.html
240
+
241
+ ├── Icons/ # UI icons
242
+ ├── index.html # Redirects to login
243
+ ├── README.md # This file
244
+ ├── .gitignore # Git ignore rules
245
+ └── start-backend.ps1 # Windows startup script
246
+ ```
247
+
248
+ ---
249
+
250
+ ## 🎯 Features
251
+
252
+ ### ✅ Implemented
253
+ - **Real Student Data**: 30 actual students with Persian names
254
+ - **National Code Authentication**: Secure login using national IDs
255
+ - **Persistent Storage**: JSON database on backend
256
+ - **AI-Powered Grouping**: OpenRouter GPT-4o-mini integration
257
+ - **Persian Language**: Full RTL support with Persian reasoning
258
+ - **Teacher Controls**: Show/hide results, reset grouping
259
+ - **Math Grades**: Real grades from class records
260
+ - **MBTI & Learning Styles**: Comprehensive personality analysis
261
+ - **Peer Preferences**: Students select preferred groupmates
262
+
263
+ ### 🎨 UI Features
264
+ - Modern, responsive design with Tailwind CSS
265
+ - Persian (RTL) interface
266
+ - Real-time validation
267
+ - Progress indicators
268
+
269
+ ---
270
+
271
+ ## 🐛 Troubleshooting
272
+
273
+ ### "Failed to fetch" errors
274
+ **Problem:** Backend not running or wrong URL
275
+ **Solution:**
276
+ 1. Check backend is running: Visit `http://localhost:8000`
277
+ 2. Should see: `{"message":"TalimBot API is running"}`
278
+ 3. Check `API_BASE_URL` in JS files matches backend
279
+
280
+ ### Students can't login
281
+ **Problem:** Wrong national code
282
+ **Solution:**
283
+ - National code must be exactly 10 digits
284
+ - Must match the code in backend database
285
+ - Check `backend/main.py` for correct codes
286
+
287
+ ### Students can't see groups
288
+ **Problem:** Teacher hasn't enabled visibility
289
+ **Solution:**
290
+ - Teacher must click "Show Results to Students"
291
+ - Button appears after grouping is complete
292
+
293
+ ### Grouping fails
294
+ **Problem:** API error or no internet
295
+ **Solution:**
296
+ 1. Check internet connection
297
+ 2. Verify OpenRouter API key is valid
298
+ 3. Check backend logs for errors
299
+ 4. System has fallback random grouping if AI fails
300
+
301
+ ---
302
+
303
+ ## 📊 Student List (Sample)
304
+
305
+ | # | Student Number | Name | National Code | Math Grade |
306
+ |---|----------------|------|---------------|------------|
307
+ | 1 | S001 | آدینه پور - یاسمن | 929986644 | 16.0 |
308
+ | 2 | S002 | احمدزاده - پریا | 980085330 | 12.0 |
309
+ | 3 | S003 | اکبرزاده - فاطمه | 970154550 | 11.0 |
310
+ | 4 | S004 | الهی مهر - آناهیتا | 26425955 | 17.0 |
311
+ | 5 | S005 | امیری - مریم | 980093341 | 18.0 |
312
+ | ... | ... | ... | ... | ... |
313
+ | 30 | S030 | وحدتی - باران | 929916913 | 12.0 |
314
+
315
+ *Full list available in `backend/main.py`*
316
+
317
+ ---
318
+
319
+ ## 🔐 Security Notes
320
+
321
+ ### Current (Demo/Development):
322
+ - ✅ Simple password authentication
323
+ - ✅ CORS allows all origins
324
+ - ✅ National codes as passwords
325
+
326
+ ### Recommended for Production:
327
+ - 🔒 Use environment variables for secrets
328
+ - 🔒 Implement JWT token authentication
329
+ - 🔒 Enable HTTPS only
330
+ - 🔒 Add rate limiting
331
+ - 🔒 Use PostgreSQL instead of JSON
332
+ - 🔒 Hash national codes
333
+ - 🔒 Implement proper session management
334
+
335
+ ---
336
+
337
+ ## 📞 API Endpoints
338
+
339
+ | Endpoint | Method | Description |
340
+ |----------|--------|-------------|
341
+ | `/` | GET | Health check |
342
+ | `/api/students` | GET | Get all students |
343
+ | `/api/student/{id}` | GET | Get one student |
344
+ | `/api/student/{id}` | PUT | Update student |
345
+ | `/api/auth/student` | POST | Authenticate student |
346
+ | `/api/auth/teacher` | POST | Authenticate teacher |
347
+ | `/api/grouping/perform` | POST | Run AI grouping |
348
+ | `/api/grouping/status` | GET | Get stats |
349
+ | `/api/grouping/toggle-visibility` | POST | Show/hide results |
350
+ | `/api/grouping/reset` | POST | Clear grouping |
351
+ | `/api/student/{id}/group` | GET | Get student's group |
352
+
353
+ ---
354
+
355
+ ## 🎓 Technologies Used
356
+
357
+ ### Backend
358
+ - **FastAPI** - Modern Python web framework
359
+ - **Uvicorn** - ASGI server
360
+ - **Pydantic** - Data validation
361
+ - **aiohttp** - Async HTTP client
362
+ - **OpenRouter API** - AI integration
363
+
364
+ ### Frontend
365
+ - **HTML5 / CSS3** - Structure and styling
366
+ - **Tailwind CSS** - Utility-first CSS
367
+ - **JavaScript (Async/Await)** - Modern JS
368
+ - **Fetch API** - HTTP requests
369
+
370
+ ### Deployment
371
+ - **GitHub Pages** - Frontend hosting (free)
372
+ - **Render.com** - Backend hosting (free)
373
+
374
+ ---
375
+
376
+ ## 📝 License
377
+
378
+ This project is for educational purposes.
379
+
380
+ ---
381
+
382
+ ## 🙏 Credits
383
+
384
+ - Student data from class 1061 (دهم تجربی)
385
+ - AI grouping powered by OpenRouter (GPT-4o-mini)
386
+
387
+ ---
388
+
389
+ ## ✅ Pre-Launch Checklist
390
+
391
+ - [ ] Backend running locally
392
+ - [ ] Students can login with national codes
393
+ - [ ] Teacher can login with password
394
+ - [ ] Grouping works with AI
395
+ - [ ] Visibility toggle works
396
+ - [ ] All 30 students load correctly
397
+ - [ ] Persian names display properly
398
+ - [ ] Math grades show correctly
399
+ - [ ] Backend deployed to Render
400
+ - [ ] Frontend deployed to GitHub Pages
401
+ - [ ] API URLs updated for production
402
+
403
+ ---
404
+
405
+ **Live URL:** `https://talimbot.github.io/talimbot/pages/login.html`
406
+
407
+ **Last Updated:** December 2025
resources_references/TEST_RESULTS_AND_SOLUTION.md ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ✅ SERVER ISSUE - FULLY DIAGNOSED & READY TO FIX
2
+
3
+ ## 🎯 Test Results (Just Ran)
4
+
5
+ ✅ **Server Status:** Running on port 8000
6
+ ✅ **API Response:** Returns data successfully
7
+ ✅ **Firewall Rules:** Properly configured for Python
8
+ ✅ **Local Network:** Server IS accessible from 192.168.114.1
9
+ ✅ **Deployment Files:** All created and ready
10
+
11
+ ---
12
+
13
+ ## 🔍 Problem Diagnosis
14
+
15
+ **Why GitHub Pages doesn't work:**
16
+ ```
17
+ https://talimbot.github.io/talimbot/
18
+ ↓ tries to connect to
19
+ http://localhost:8000 ← This doesn't exist on the internet!
20
+ ```
21
+
22
+ **Why phone doesn't work:**
23
+ Your phone tries to connect to "localhost" which means "this phone" not "your laptop"
24
+
25
+ ---
26
+
27
+ ## 🎯 TWO SOLUTIONS - Choose One
28
+
29
+ ### 📱 Solution A: Test on Phone NOW (Temporary)
30
+
31
+ Your server **IS** accessible from local network!
32
+
33
+ **On Your Phone:**
34
+ 1. Connect to **same Wi-Fi** as laptop
35
+ 2. Open browser and visit:
36
+ ```
37
+ http://192.168.114.1:8000/index.html
38
+ ```
39
+ Or just test API:
40
+ ```
41
+ http://192.168.114.1:8000/api/students
42
+ ```
43
+
44
+ 3. Should work! ✅
45
+
46
+ **To make GitHub Pages work with your laptop server:**
47
+ 1. Edit `assets/js/data.js`
48
+ 2. Change:
49
+ ```javascript
50
+ const API_BASE_URL = 'http://localhost:8000/api';
51
+ ```
52
+ To:
53
+ ```javascript
54
+ const API_BASE_URL = 'http://192.168.114.1:8000/api';
55
+ ```
56
+ 3. Push to GitHub
57
+ 4. Now GitHub Pages will try to connect to your laptop
58
+ 5. **Only works when laptop is on and server is running!**
59
+
60
+ ⚠️ **Limitations:**
61
+ - Only works on your Wi-Fi network
62
+ - Laptop must be on with server running
63
+ - If laptop IP changes, you need to update code
64
+ - Not suitable for real deployment
65
+
66
+ ---
67
+
68
+ ### 🌐 Solution B: Deploy to Cloud (PERMANENT) ⭐ RECOMMENDED
69
+
70
+ Make your backend accessible from anywhere!
71
+
72
+ **5-Minute Render.com Deployment:**
73
+
74
+ 1. **Sign up:** https://render.com (use GitHub login)
75
+
76
+ 2. **New Web Service:**
77
+ - Click "New +" → "Web Service"
78
+ - Connect your `talimbot` repository
79
+ - Select the repo
80
+
81
+ 3. **Configure:**
82
+ ```
83
+ Name: talimbot-api
84
+ Build Command: pip install -r backend/requirements.txt
85
+ Start Command: cd backend && uvicorn main:app --host 0.0.0.0 --port $PORT
86
+ Instance Type: Free
87
+ ```
88
+
89
+ 4. **Deploy:** Click "Create Web Service"
90
+
91
+ 5. **Get URL:** Copy your URL (e.g., `https://talimbot-api.onrender.com`)
92
+
93
+ 6. **Update Frontend:**
94
+ Edit `assets/js/data.js`:
95
+ ```javascript
96
+ const API_BASE_URL = 'https://talimbot-api.onrender.com/api';
97
+ ```
98
+
99
+ 7. **Push to GitHub:**
100
+ ```bash
101
+ git add .
102
+ git commit -m "Update API URL for production"
103
+ git push
104
+ ```
105
+
106
+ 8. **Done!** Your site works from anywhere! 🎉
107
+
108
+ ✅ **Benefits:**
109
+ - Works from anywhere in the world
110
+ - Works on any device
111
+ - No laptop needed
112
+ - Professional setup
113
+ - FREE tier available
114
+
115
+ ---
116
+
117
+ ## 📋 Files I Created for You
118
+
119
+ | File | Purpose |
120
+ |------|---------|
121
+ | `DEPLOYMENT_GUIDE.md` | Complete deployment tutorial (Render/Railway/PythonAnywhere) |
122
+ | `FIXING_SERVER_ERROR.md` | Troubleshooting guide |
123
+ | `Procfile` | Tells Render how to start server |
124
+ | `runtime.txt` | Specifies Python version |
125
+ | `render.yaml` | Render configuration |
126
+ | `setup-firewall.ps1` | Windows firewall setup (if needed) |
127
+ | `THIS FILE` | Summary of everything |
128
+
129
+ ---
130
+
131
+ ## 🚀 WHAT TO DO NOW
132
+
133
+ ### For Quick Test (Next 5 Minutes):
134
+ 1. **On phone**, visit: `http://192.168.114.1:8000/api/students`
135
+ 2. Should see JSON data ✅
136
+ 3. This proves your server works!
137
+
138
+ ### For Real Deployment (Next 15 Minutes):
139
+ 1. **Go to:** https://render.com
140
+ 2. **Follow steps** in Solution B above
141
+ 3. **Read full guide:** `DEPLOYMENT_GUIDE.md`
142
+ 4. **Update code** with Render URL
143
+ 5. **Push to GitHub**
144
+ 6. **Your app works worldwide!** 🌍
145
+
146
+ ---
147
+
148
+ ## 📊 Current vs After Deployment
149
+
150
+ **Current (localhost):**
151
+ ```
152
+ ✅ Laptop browser → Works
153
+ ❌ Phone → Doesn't work
154
+ ❌ GitHub Pages → Doesn't work
155
+ ❌ Other people → Can't access
156
+ ```
157
+
158
+ **After Deployment:**
159
+ ```
160
+ ✅ Laptop browser → Works
161
+ ✅ Phone → Works
162
+ ✅ GitHub Pages → Works
163
+ ✅ Other people → Can access
164
+ ✅ From anywhere → Works
165
+ ```
166
+
167
+ ---
168
+
169
+ ## 💡 Quick Comparison
170
+
171
+ | Method | Time | Cost | Works Everywhere | Reliable |
172
+ |--------|------|------|------------------|----------|
173
+ | **Localhost** | 0 min | Free | ❌ No | - |
174
+ | **Local IP (192.168.x.x)** | 0 min | Free | ❌ Wi-Fi only | ❌ |
175
+ | **Render.com** | 5 min | Free | ✅ Yes | ✅ |
176
+ | **Railway.app** | 5 min | $5 credit | ✅ Yes | ✅ |
177
+ | **PythonAnywhere** | 15 min | Free | ✅ Yes | ✅ |
178
+
179
+ **Recommendation:** Render.com (easiest, fastest, free)
180
+
181
+ ---
182
+
183
+ ## 🎓 What You Learned
184
+
185
+ 1. **localhost = your computer only** (not accessible from internet)
186
+ 2. **192.168.x.x = local network only** (same Wi-Fi)
187
+ 3. **https://your-app.onrender.com = anywhere** (real internet URL)
188
+ 4. **GitHub Pages = frontend only** (needs backend somewhere else)
189
+ 5. **Backend must be deployed separately** from frontend
190
+
191
+ ---
192
+
193
+ ## ❓ Common Questions
194
+
195
+ **Q: Can't I just use localhost?**
196
+ A: No, localhost doesn't exist on the internet.
197
+
198
+ **Q: Why does it work on my laptop?**
199
+ A: Your laptop's browser can reach localhost because the server is running ON your laptop.
200
+
201
+ **Q: Will phone work if I deploy?**
202
+ A: Yes! After deployment, phone/laptop/anyone can access it.
203
+
204
+ **Q: Is Render really free?**
205
+ A: Yes, free tier available. Service sleeps after 15 min inactivity (wakes up in 30s on first request).
206
+
207
+ **Q: What if Render is too slow?**
208
+ A: Try Railway ($5 free credit) or upgrade Render later.
209
+
210
+ ---
211
+
212
+ ## 🎯 Bottom Line
213
+
214
+ **Your server works perfectly!** ✅
215
+ It's just not on the internet yet.
216
+
217
+ **Two choices:**
218
+ 1. Quick test on phone: Use `http://192.168.114.1:8000` (same Wi-Fi)
219
+ 2. Real deployment: Deploy to Render (5 minutes, works everywhere)
220
+
221
+ **I recommend:** Deploy to Render - then your project is truly online! 🚀
222
+
223
+ ---
224
+
225
+ **All files are ready. All tests passed. Ready to deploy!** ✅