From 74227d50215ab25fe7c609cc01b1170b6d3a7107 Mon Sep 17 00:00:00 2001 From: sajjadtalkhabi <97734061+sajjadtalkhabi@users.noreply.github.com> Date: Thu, 21 May 2026 15:51:32 +0330 Subject: [PATCH] fix --- docs/backend-vs-ui-gaps.md | 216 +++++++ .../Badge.vue | 7 +- .../Tinymce.vue | 10 +- src/constants/fields.js | 5 - src/enums/index.js | 2 +- .../assignments/components/AssignmentItem.vue | 2 +- .../components/AssignmentsFilters.vue | 19 +- .../components/modals/AddAssignmentModal.vue | 27 +- .../modals/AssignmentDetailsModal.vue | 2 +- .../AssignmentSubmissionDetailsModal.vue | 2 +- .../assignments/pages/AssignmentsListPage.vue | 2 +- .../admin/assignments/schema/index.js | 2 +- .../admin/courses/components/CourseItem.vue | 51 +- .../modals/AddCourseStudentModal.vue | 55 +- .../modals/AddOfferedCourseModal.vue | 24 +- .../modals/AddSessionToCourseModal.vue | 36 +- .../components/modals/CourseDetailsModal.vue | 42 +- ...emplateFormPage.vue => CourseFormPage.vue} | 61 +- .../admin/courses/pages/CoursesListPage.vue | 131 ++-- src/features/admin/courses/schema/index.js | 10 +- .../admin/exams/components/ExamItem.vue | 2 +- .../exams/components/ExamQuestionBuilder.vue | 103 +-- .../admin/exams/components/ExamsFilters.vue | 19 +- .../components/modals/ExamDetailsModal.vue | 32 +- .../admin/exams/pages/ExamFormPage.vue | 173 ++--- .../admin/exams/pages/ExamsListPage.vue | 2 +- src/features/admin/exams/schema/index.js | 5 +- .../admin/messages/components/TicketItem.vue | 35 +- .../components/modals/TicketDetailsModal.vue | 65 +- src/features/admin/router.js | 28 +- .../admin/sessions/components/SessionItem.vue | 2 +- .../sessions/components/SessionsFilters.vue | 17 +- .../components/modals/SessionDetailsModal.vue | 4 +- .../admin/sessions/pages/SessionFormPage.vue | 598 ++++++++---------- .../admin/sessions/pages/SessionsListPage.vue | 18 +- src/features/admin/sessions/schema/index.js | 16 +- .../admin/terms/components/CourseItem.vue | 229 +++++++ .../admin/terms/components/TermItem.vue | 16 +- .../admin/terms/components/TermsFilters.vue | 2 +- .../modals/AttachCourseToTermModal.vue | 43 +- .../components/modals/TermDetailsModal.vue | 58 +- .../admin/terms/pages/TermFormPage.vue | 28 +- .../admin/terms/pages/TermsListPage.vue | 13 +- .../consultants/components/ConsultantItem.vue | 2 +- .../services/components/ServiceItem.vue | 2 +- src/layouts/AdminLayout.vue | 18 +- src/services/api/admin-assignments.js | 17 +- src/services/api/admin-course-templates.js | 35 - src/services/api/admin-courses.js | 19 +- src/services/api/admin-exams.js | 13 +- src/services/api/admin-sessions.js | 3 - src/services/api/admin-terms.js | 3 - src/services/api/admin-tickets.js | 3 +- src/services/api/endpoints.js | 119 ++-- .../mock/fixtures/admin-assignments.js | 12 +- src/services/mock/fixtures/admin-courses.js | 135 ++-- src/services/mock/fixtures/admin-exams.js | 110 ++-- src/services/mock/fixtures/admin-sessions.js | 20 +- src/services/mock/fixtures/admin-tickets.js | 162 +++-- src/services/mock/routes/admin-assignments.js | 43 +- src/services/mock/routes/admin-courses.js | 215 ++----- src/services/mock/routes/admin-exams.js | 141 +++-- src/services/mock/routes/admin-sessions.js | 113 ++-- src/services/mock/routes/admin-terms.js | 12 +- src/services/mock/routes/admin-tickets.js | 50 +- src/services/query/admin-course-templates.js | 125 ---- src/services/query/admin-courses.js | 66 +- src/services/query/admin-exams.js | 14 + src/services/query/admin-sessions.js | 6 - src/services/query/admin-terms.js | 4 - src/services/query/admin-tickets.js | 4 + 71 files changed, 2014 insertions(+), 1666 deletions(-) create mode 100644 docs/backend-vs-ui-gaps.md rename src/{global-components => components}/Badge.vue (95%) rename src/{global-components => components}/Tinymce.vue (92%) rename src/features/admin/courses/pages/{CourseTemplateFormPage.vue => CourseFormPage.vue} (88%) create mode 100644 src/features/admin/terms/components/CourseItem.vue delete mode 100644 src/services/api/admin-course-templates.js delete mode 100644 src/services/query/admin-course-templates.js 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) || '')