Commit ·
2fe573b
0
Parent(s):
Fresh start: TalimBot project without binary files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +53 -0
- .gitattributes +35 -0
- .gitignore +0 -0
- Dockerfile +35 -0
- HUGGINGFACE_DEPLOYMENT.md +232 -0
- MANUAL_UPDATE_INSTRUCTIONS.md +150 -0
- Procfile +1 -0
- README.md +278 -0
- README_HF.md +54 -0
- backend/README.md +53 -0
- backend/__pycache__/grouping_logic.cpython-310.pyc +0 -0
- backend/__pycache__/main.cpython-310.pyc +0 -0
- backend/data/students.json +388 -0
- backend/data/students.json.backup +388 -0
- backend/grouping_logic.py +334 -0
- backend/main.py +428 -0
- backend/requirements.txt +6 -0
- backend/static/Icons/Additional/teacherIcon.png +0 -0
- backend/static/Icons/Additional/teacherIcon2.jpg +0 -0
- backend/static/Icons/logo/Icon.png +0 -0
- backend/static/Icons/logo/Logo_blueBackground.jpg +0 -0
- backend/static/Icons/logo/Logo_noBackground.jpg +0 -0
- backend/static/Icons/logo/logo.png +0 -0
- backend/static/Icons/studentIcon.png +0 -0
- backend/static/Icons/teacherIcon3.png +0 -0
- backend/static/assets/css/styles.css +727 -0
- backend/static/assets/js/data.js +244 -0
- backend/static/assets/js/grouping.js +159 -0
- backend/static/index.html +13 -0
- backend/static/pages/ams-questionnaire.html +436 -0
- backend/static/pages/cooperative-questionnaire.html +401 -0
- backend/static/pages/group-view.html +405 -0
- backend/static/pages/login.html +274 -0
- backend/static/pages/student-dashboard.html +580 -0
- backend/static/pages/student-data.html +388 -0
- backend/static/pages/teacher-dashboard.html +1017 -0
- render.yaml +7 -0
- requirements.txt +6 -0
- resources_references/DEPLOYMENT_CHECKLIST.md +207 -0
- resources_references/Icons/Additional/teacherIcon.png +0 -0
- resources_references/Icons/Additional/teacherIcon2.jpg +0 -0
- resources_references/Icons/logo/Logo_blueBackground.jpg +0 -0
- resources_references/Icons/logo/Logo_noBackground.jpg +0 -0
- resources_references/Icons/logo/logo.png +0 -0
- resources_references/Icons/studentIcon.png +0 -0
- resources_references/Icons/teacherIcon3.png +0 -0
- resources_references/RAILWAY_DEPLOYMENT.md +126 -0
- resources_references/RAILWAY_SETUP_GUIDE.md +300 -0
- resources_references/README.md +407 -0
- 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!** ✅
|