diff --git a/docs/backend-vs-ui-gaps.md b/docs/backend-vs-ui-gaps.md
new file mode 100644
index 0000000..75970b5
--- /dev/null
+++ b/docs/backend-vs-ui-gaps.md
@@ -0,0 +1,216 @@
+# Backend (Postman) vs Frontend — fields & endpoints to decide on
+
+Source of truth: the Postman collection covering Terms, Courses, Sessions, Exams, Homeworks, Media (2026-05). This doc lists every place where backend and FE disagree on shape, plus FE-side concepts the backend doc has no slot for. Each item needs a product/design call before we wire it up.
+
+Conventions in the rest of this doc:
+- **B → FE** means the backend exposes a field/endpoint that the FE doesn't surface yet.
+- **FE → B** means the FE shows/sends a field the backend doc doesn't accept.
+- **shape diff** means both sides handle the concept but in different shapes (enum values, nesting, naming).
+
+---
+
+## Terms
+
+### Endpoints
+
+| Backend | FE today | Status |
+|---|---|---|
+| `GET /terms?per_page&active_only` | `getTermsList` | aligned |
+| `GET /terms/:id` | `showTerm` | aligned |
+| `POST /terms` | `addNewTerm` | aligned |
+| `PATCH /terms/:id` | `updateTerm` | aligned |
+| `DELETE /terms/:id` | `deleteTerm` | aligned |
+| — | `cloneTerm` (`POST /admin/terms/:id/clone`) | **FE-only; backend has nothing.** Decide: drop the clone button, or ask backend to add it. |
+| — | `changeStatusTerm` (`POST /admin/terms/:id/status`) | **Use `PATCH /terms/:id` with `is_active`** — dedicated status endpoint dropped. |
+| — | `listUserTerm`, `addUserTerm`, `removeUserTerm`, `changeLeaveStatus` | **FE-only.** Term-students subtab + leave toggle. Backend exposes nothing equivalent. Keep mock-only until backend adds. |
+| — | `listCourseTerm`, `addCourseTerm`, `removeCourseTerm` | **FE-only.** Can be replaced by `GET /courses?term_id=` for the list; the attach/detach side has no backend equivalent (course `term_id` is set at create-time). |
+
+### Fields
+
+| Backend → FE | FE has it as | Notes |
+|---|---|---|
+| `starts_at`, `ends_at` (ISO datetime) | `startDate`, `endDate` | Naming diff; we send `starts_at`/`ends_at` in the mock already — confirm the FE form should rename or keep an adapter. |
+| `cover_url` (read) + `cover_media_id` (write) | `coverUrl` / `coverMediaId` | aligned in shape, just camelCase. |
+| `created_at` | `createdAt` | aligned. |
+| `is_active` | `isActive` | aligned. |
+| `description` | `description` | aligned. |
+
+**FE → B** (need decision):
+- `studentsCount`, `coursesCount` — computed UI counts. Backend response doesn't include them. Either compute client-side or ask backend for `?include=counts`.
+- `image` (UI alias of cover) — FE keeps this for a fallback. Backend response only has `cover_url`.
+
+---
+
+## Courses
+
+### Endpoints
+
+| Backend | FE today | Status |
+|---|---|---|
+| `GET /courses?term_id&per_page` | `getCoursesList` | aligned (drop `/admin/` prefix in `endpoints.js`). |
+| `GET /courses/:id` | `showCourse` | aligned. Backend response includes nested `term` and `teacher` — FE already reads `course.term` / `course.teacher`, good. |
+| `POST /courses` | `addNewCourse` | aligned. Backend currently requires `term_id` (422 example), but per user instruction the standalone (template-tab) flow must allow `term_id: null`. **Decide:** ask backend to make `term_id` nullable, or refuse to submit until a term is picked. |
+| `PATCH /courses/:id` | `updateCourse` | aligned. Used now for status toggle too (drops dedicated `/status` endpoint). |
+| `DELETE /courses/:id` | `deleteCourse` | aligned. |
+| — | `changeStatusCourse` (`/admin/courses/:id/status`) | **Dropped.** Use `PATCH /courses/:id { is_active }`. |
+| — | `listCourseStudents`, `addCourseStudent`, `removeCourseStudent` | **FE-only.** CourseDetailsModal "students" tab + `AddCourseStudentModal`. Backend doesn't expose course-students; either add a sub-resource or remove the UI. Kept mock-only for now. |
+| — | `listCourseSessions`, `attachCourseSession`, `detachCourseSession` | **FE-only.** CourseDetailsModal "sessions" tab can be served by `GET /sessions?course_id=`. The attach/detach side (M:N) has no backend equivalent — backend uses session.course_id (1:N). Kept mock-only for now; recommend swapping the list tab to the regular sessions query. |
+
+### Fields
+
+| Backend ↔ FE | Notes |
+|---|---|
+| `term_id` ⇄ `termId` | aligned. Nullable in CourseFormPage, required in AddOfferedCourseModal — schema reflects this. |
+| `teacher_id` ⇄ `teacherId` | aligned. |
+| `capacity` ⇄ `capacity` | aligned. |
+| `is_active` ⇄ `isActive` | aligned. |
+| `cover_media_id` (write) / `cover_url` (read) ⇄ `coverMediaId` / `coverUrl` | aligned. |
+| `description` ⇄ `description` | aligned. |
+| Nested `term`, `teacher` on show response | FE already reads. |
+
+**FE → B** (need decision):
+- `sessionsCount` — number-of-sessions field on the create form. Backend has nothing. Either compute server-side from related sessions, or drop the field.
+- `prerequisites` (array of { courseId, course }) — backend has no prerequisite relation. Drop or ask backend for it.
+- `contentType` (video/voice/text) + `contentMediaId` — single course-level content file. Backend treats files only as session media. Decide whether course-level content should move to "intro session" or stay a course concept.
+- `image` (UI alias of cover_url) — kept as a fallback alongside `coverUrl`.
+
+---
+
+## Sessions
+
+### Endpoints
+
+| Backend | FE today | Status |
+|---|---|---|
+| `GET /sessions?course_id&per_page` | `getSessionsList` | aligned. |
+| `GET /sessions/:id` | `showSession` | aligned. |
+| `POST /sessions` | `addNewSession` | aligned. |
+| `PATCH /sessions/:id` | `updateSession` | aligned. Used for status toggle now. |
+| `DELETE /sessions/:id` | `deleteSession` | aligned. |
+| — | `changeStatusSession` (`/admin/sessions/:id/toggle-status`) | **Dropped.** Use PATCH with `is_active`. |
+| — | `getSessionsAttendance` (`/admin/sessions/:sessionId/attendances`) | **FE-only.** `SessionAttendanceModal` depends on this. Kept mock-only. |
+
+### Fields — biggest gap of all five resources
+
+| Backend | FE today | Notes |
+|---|---|---|
+| `type` enum: `online \| offline \| content` | `sessionType` enum: `in_person`, `online`, `video`, `audio`, `text`, `slide`, `pdf` | **Shape diff.** FE has 7 values; backend has 3. There's an existing `SESSION_TYPE_TO_SPEC` mapper in `services/mock/fixtures/admin-sessions.js`. Decide whether the FE keeps the richer 7-value enum (and we map down to backend's 3) or collapses. |
+| `starts_at`, `location`, `link` | All three live **inside** `form.sessionConfig.*` plus also derived to top-level `startsAt` / `location` / `link` in the mock | **Structural diff.** Backend wants flat fields; FE form nests them under `sessionConfig` keyed by `sessionType`. The mock derives top-level from `sessionConfig.*` for show payloads. Decide whether the FE form should flatten the schema to match backend or keep the conditional-by-type config UI. |
+| `media_ids[]` (write) / `media[]` (read with `collection_name`, `file_name`, `mime_type`, `file_size`, `url`, `download_url`) | `materials[]` with `{ fileId, isRequired, type, title, order }` | **Shape diff.** Backend's media rows are typed by upload-purpose (video/voice/pdf/slide/attachment); FE has its own `type` enum. Decide which shape the FE keeps. |
+| — | `durationMinutes`, `order`, `sessionConfig.minWatchedPercent`, `sessionConfig.minReadPercent`, `sessionConfig.mustCompleteBeforeNext`, `sessionConfig.platform` | **FE → B**, all UI-only fields. Backend has nothing equivalent. Drop, move into a `metadata` JSON, or ask backend to add. |
+| — | `usedInTerms` | UI-only count, no backend. |
+| — | `image` (vs `media`) | UI-only thumbnail; backend doesn't separate. |
+
+---
+
+## Exams
+
+### Endpoints
+
+| Backend | FE today | Status |
+|---|---|---|
+| `GET /exams/:id` (with questions+options) | `showExam` | aligned. |
+| `POST /exams` | `addNewExam` | aligned. |
+| `PATCH /exams/:id` | `updateExam` | aligned. |
+| `DELETE /exams/:id` | `deleteExam` | aligned. |
+| `POST /exams/:examId/questions` | — | **Backend → FE.** New endpoint. Today the FE submits the whole question list inside the exam create/update payload. Decide whether to keep "all-in-one" submission (and ask backend to accept it) or switch to add-questions-after-create. |
+| `POST /questions/:questionId/options` | — | **Backend → FE.** Same as above — backend lets you add options one at a time. FE today bundles all options with the question. |
+| `POST /exams/:examId/submit` | — (student-side feature) | **Backend → FE.** Student-side; not in current admin UI. |
+| — | `getExamsList` (`/admin/exams`) | **FE-only.** ExamsListPage depends on it. Kept mock-only; ask backend to add a list endpoint. |
+| — | `getExamParticipants`, `showExamParticipant` | **FE-only.** ExamParticipantsModal + ExamParticipantDetailsModal depend on these. Kept mock-only. |
+
+### Fields
+
+| Backend ↔ FE | Notes |
+|---|---|
+| `session_id` ⇄ `sessionId` | aligned. |
+| `title` ⇄ `title` | aligned. |
+| `description` ⇄ `description` | aligned. |
+| `pass_score` ⇄ `passingScore` | aligned (naming diff). |
+| `is_active` | **Backend → FE.** Exam form has no active toggle. Decide whether to add it. |
+| Backend question shape: `{ question_text, position, options: [{ option_text, is_correct }] }` | FE: `{ title, score, correctAnswerId, answers: [{ id, title }] }` | **Shape diff.** Backend hides `is_correct` from public reads (only on add). FE concept of `score` (per-question weighting) has no backend slot. Decide: keep FE scoring (ask backend to store) or drop. |
+
+**FE → B** (need decision):
+- `durationMinutes` — no backend slot.
+- `randomize` — no backend slot.
+- `endDate` / `startDate` — exam validity window, no backend slot.
+- `usedInTerms` — derived UI count.
+
+---
+
+## Homeworks (FE calls them "assignments")
+
+### Endpoints
+
+| Backend | FE today | Status |
+|---|---|---|
+| `POST /homeworks` | `addNewAssignment` | aligned (URL renamed to `/homeworks`; FE key name kept). |
+| `PATCH /homeworks/:id` | `updateAssignment` | aligned. |
+| `DELETE /homeworks/:id` | `deleteAssignment` | aligned. |
+| `POST /homeworks/:homeworkId/submit` (student) | — | **Backend → FE.** Student submit, not in admin UI yet. |
+| `PATCH /homework-submissions/:submissionId/review` | `reviewAssignmentSubmission` | aligned (URL renamed). |
+| — | `getAssignmentsList` | **FE-only.** AssignmentsListPage depends on it. Kept mock-only. |
+| — | `showAssignment`, `getAssignmentSubmissions`, `showAssignmentSubmission` | **FE-only.** Detail + submissions list — kept mock-only. |
+
+### Fields
+
+| Backend ↔ FE | Notes |
+|---|---|
+| `session_id` ⇄ `sessionId` | aligned. |
+| `title`, `description` | aligned. |
+| `deadline` (single datetime) | FE: `startDate` + `endDate` + computed `durationDays` | **Shape diff.** Backend has one deadline; FE has a window. Decide: drop start/end and use single deadline, or ask backend to add a window. |
+| `is_active` | **Backend → FE.** FE form has no active toggle. |
+| Submission `status`: `accepted \| denied` | FE: `pending \| approved \| rejected \| needs_revision` | **Shape diff.** Backend has two states; FE has four. The FE `pending/needs_revision` have no backend slot. |
+| Submission `media_id` (single) | FE `attachments[]` (multiple) | **Shape diff.** Backend allows one file per submission; FE expects many. |
+| Submission `teacher_feedback` ⇄ `reviewerNote` | naming diff. |
+| Submission `reviewed_at` (read) | — | Backend provides; FE doesn't surface. |
+
+**FE → B** (need decision):
+- `priority` enum (`mandatory \| optional`) — no backend slot.
+- `submissionsCount` — derived count, no backend slot.
+
+---
+
+## Media
+
+### Endpoints
+
+| Backend | FE today | Status |
+|---|---|---|
+| `POST /media` (multipart with `purpose` + `file`) | `uploadMedia` | aligned URL & method. |
+| `GET /media/:id/download` | — | **Backend → FE.** Add as `downloadMedia`. |
+| `DELETE /media/:id` | — | **Backend → FE.** Add as `deleteMedia`. |
+
+### Fields
+
+- Backend response: `{ id, collection_name, file_name, mime_type, file_size, url, download_url }`. FE today reads `payload.id`, `payload.url`, sometimes `payload.uploadId` (a now-stale field). **Cleanup needed:** drop `uploadId`, use `id` everywhere.
+- Backend `purpose` enum: `avatar | cover | video | voice | pdf | slide | attachment | homework_file`. FE upload calls currently hardcode `purpose: 'cover'` or `purpose: 'content'`. **`content` is not in the backend enum.** Decide which of the backend purposes each FE uploader should send (e.g., session video → `video`, course PDF → `pdf`, homework upload → `homework_file`).
+- Pending media TTL: backend deletes unreferenced pending uploads after 24h. FE doesn't track this; if a user uploads a cover, abandons the form, and comes back next day, the `cover_media_id` reference will 404 on submit. Document the failure mode.
+
+---
+
+## Cross-cutting
+
+### Snake_case vs camelCase
+
+Backend wire format is snake_case throughout. FE today reads camelCase (e.g., the mock returns `coverUrl`, `isActive`). When the real backend lands, the FE will either need:
+- a HTTP-layer transformer (camelize on response, snake_case on request), or
+- camelCase field aliases on the backend serializer.
+
+Decide before swapping the mock for the real API. Affects every screen.
+
+### `/admin/` URL prefix
+
+Backend doc has none — endpoints live at `/terms`, `/courses`, etc. The FE previously had `/admin/courses`, `/admin/sessions`, `/admin/exams`, `/admin/assignments`. **Aligned to backend (prefix dropped).** Auth context (admin role) is implicit in the token, not the URL.
+
+### Sub-features that depend on missing backend endpoints
+
+UI screens that work today against the mock but have no backend equivalent in the current doc (kept mock-only with TODOs in `endpoints.js`):
+
+- Term: clone term, term status toggle, students-in-term subtab, leave toggle, courses-in-term subtab (could swap to `GET /courses?term_id=`)
+- Course: status toggle (swapped to PATCH), students-in-course subtab, attach/detach sessions (M:N), `AddCourseStudentModal`, `AddSessionToCourseModal`
+- Session: status toggle (swapped to PATCH), attendance roster
+- Exam: list page, participants list, participant detail
+- Assignment/Homework: list page, detail show, submissions list, submission detail
+
+For each of these we need to either (a) ask backend to expose the endpoint, or (b) drop the UI surface.
diff --git a/src/global-components/Badge.vue b/src/components/Badge.vue
similarity index 95%
rename from src/global-components/Badge.vue
rename to src/components/Badge.vue
index dbf2a4d..7a0aad1 100644
--- a/src/global-components/Badge.vue
+++ b/src/components/Badge.vue
@@ -21,7 +21,7 @@ import { computed } from 'vue'
import SvgIcon from '@/components/icons/SvgIcon.vue'
const props = defineProps({
- /** @type {import('vue').PropType<'neutral' | 'success' | 'danger' | 'warning' | 'primary' | 'info'>} */
+ /** @type {import('vue').PropType<'neutral' | 'success' | 'danger' | 'warning' | 'primary' | 'info' | 'cyan'>} */
variant: { type: String, default: 'neutral' },
/** @type {import('vue').PropType<'sm' | 'md' | 'lg'>} */
size: { type: String, default: 'md' },
@@ -133,5 +133,10 @@ const onClick = (event) => {
background: rgba(104, 104, 104, 10%);
color: #686868;
}
+
+ &--cyan {
+ background: rgba(104, 104, 104, 10%);
+ color: #686868;
+ }
}
diff --git a/src/global-components/Tinymce.vue b/src/components/Tinymce.vue
similarity index 92%
rename from src/global-components/Tinymce.vue
rename to src/components/Tinymce.vue
index b46451e..ee54bf0 100644
--- a/src/global-components/Tinymce.vue
+++ b/src/components/Tinymce.vue
@@ -5,14 +5,12 @@
diff --git a/src/features/admin/terms/components/TermItem.vue b/src/features/admin/terms/components/TermItem.vue
index ad0afbc..f12410a 100644
--- a/src/features/admin/terms/components/TermItem.vue
+++ b/src/features/admin/terms/components/TermItem.vue
@@ -43,16 +43,6 @@
-
-
-
-
-
props.term.faStartDate || formatJalaaliDate(props.term.startDate) || ''
-)
-const endDate = computed(() => props.term.faEndDate || formatJalaaliDate(props.term.endDate) || '')
+const startDate = computed(() => formatJalaaliDate(props.term.startsAt) || '')
+const endDate = computed(() => formatJalaaliDate(props.term.endsAt) || '')