fix
This commit is contained in:
@@ -7,17 +7,20 @@ export const apiShowAdminAssignment = (id) => http.get(buildUrl(endpoints.showAs
|
||||
|
||||
export const apiAddAdminAssignment = (payload) => http.post(endpoints.addNewAssignment, payload)
|
||||
|
||||
// Backend uses PATCH /homeworks/:id (not PUT).
|
||||
export const apiUpdateAdminAssignment = (id, payload) =>
|
||||
http.put(buildUrl(endpoints.updateAssignment, { id }), payload)
|
||||
http.patch(buildUrl(endpoints.updateAssignment, { id }), payload)
|
||||
|
||||
export const apiDeleteAdminAssignment = (id) =>
|
||||
http.delete(buildUrl(endpoints.deleteAssignment, { id }))
|
||||
|
||||
export const apiGetAdminAssignmentSubmissions = (assignmentId, params) =>
|
||||
http.get(buildUrl(endpoints.getAssignmentSubmissions, { assignmentId }), { params })
|
||||
export const apiGetAdminAssignmentSubmissions = (homeworkId, params) =>
|
||||
http.get(buildUrl(endpoints.getAssignmentSubmissions, { homeworkId }), { params })
|
||||
|
||||
export const apiShowAdminAssignmentSubmission = (assignmentId, submissionId) =>
|
||||
http.get(buildUrl(endpoints.showAssignmentSubmission, { assignmentId, submissionId }))
|
||||
// Backend exposes submissions flat under /homework-submissions/:submissionId —
|
||||
// the parent `homeworkId` is not part of the URL, so we ignore it here.
|
||||
export const apiShowAdminAssignmentSubmission = (_homeworkId, submissionId) =>
|
||||
http.get(buildUrl(endpoints.showAssignmentSubmission, { submissionId }))
|
||||
|
||||
export const apiReviewAdminAssignmentSubmission = (assignmentId, submissionId, payload) =>
|
||||
http.post(buildUrl(endpoints.reviewAssignmentSubmission, { assignmentId, submissionId }), payload)
|
||||
export const apiReviewAdminAssignmentSubmission = (_homeworkId, submissionId, payload) =>
|
||||
http.patch(buildUrl(endpoints.reviewAssignmentSubmission, { submissionId }), payload)
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { http } from '@/services/api/http'
|
||||
import { buildUrl, endpoints } from '@/services/api/endpoints'
|
||||
|
||||
export const apiGetAdminCourseTemplates = (params) => http.get(endpoints.getCoursesList, { params })
|
||||
|
||||
export const apiShowAdminCourseTemplate = (id) => http.get(buildUrl(endpoints.showCourse, { id }))
|
||||
|
||||
export const apiAddAdminCourseTemplate = (payload) => http.post(endpoints.addNewCourse, payload)
|
||||
|
||||
export const apiUpdateAdminCourseTemplate = (id, payload) =>
|
||||
http.put(buildUrl(endpoints.updateCourseTemplate, { id }), payload)
|
||||
|
||||
export const apiDeleteAdminCourseTemplate = (id) =>
|
||||
http.delete(buildUrl(endpoints.deleteCourseTemplate, { id }))
|
||||
|
||||
export const apiChangeAdminCourseTemplateStatus = (id, payload) =>
|
||||
http.post(buildUrl(endpoints.changeStatusCourseTemplate, { id }), payload)
|
||||
|
||||
export const apiGetAdminTemplateStudents = (templateId, params) =>
|
||||
http.get(buildUrl(endpoints.listTemplateStudents, { templateId }), { params })
|
||||
|
||||
export const apiAddAdminTemplateStudent = (templateId, payload) =>
|
||||
http.post(buildUrl(endpoints.addTemplateStudent, { templateId }), payload)
|
||||
|
||||
export const apiRemoveAdminTemplateStudent = (templateId, userId) =>
|
||||
http.delete(buildUrl(endpoints.removeTemplateStudent, { templateId, userId }))
|
||||
|
||||
export const apiGetAdminTemplateSessions = (templateId, params) =>
|
||||
http.get(buildUrl(endpoints.listTemplateSessions, { templateId }), { params })
|
||||
|
||||
export const apiAttachAdminTemplateSession = (templateId, payload) =>
|
||||
http.post(buildUrl(endpoints.attachTemplateSession, { templateId }), payload)
|
||||
|
||||
export const apiDetachAdminTemplateSession = (templateId, sessionId) =>
|
||||
http.delete(buildUrl(endpoints.detachTemplateSession, { templateId, sessionId }))
|
||||
@@ -12,5 +12,20 @@ export const apiUpdateAdminCourse = (id, payload) =>
|
||||
|
||||
export const apiDeleteAdminCourse = (id) => http.delete(buildUrl(endpoints.deleteCourse, { id }))
|
||||
|
||||
export const apiChangeAdminCourseStatus = (id, payload) =>
|
||||
http.post(buildUrl(endpoints.changeStatusCourse, { id }), payload)
|
||||
export const apiGetAdminCourseStudents = (courseId, params) =>
|
||||
http.get(buildUrl(endpoints.listCourseStudents, { courseId }), { params })
|
||||
|
||||
export const apiAddAdminCourseStudent = (courseId, payload) =>
|
||||
http.post(buildUrl(endpoints.addCourseStudent, { courseId }), payload)
|
||||
|
||||
export const apiRemoveAdminCourseStudent = (courseId, userId) =>
|
||||
http.delete(buildUrl(endpoints.removeCourseStudent, { courseId, userId }))
|
||||
|
||||
export const apiGetAdminCourseSessions = (courseId, params) =>
|
||||
http.get(buildUrl(endpoints.listCourseSessions, { courseId }), { params })
|
||||
|
||||
export const apiAttachAdminCourseSession = (courseId, payload) =>
|
||||
http.post(buildUrl(endpoints.attachCourseSession, { courseId }), payload)
|
||||
|
||||
export const apiDetachAdminCourseSession = (courseId, sessionId) =>
|
||||
http.delete(buildUrl(endpoints.detachCourseSession, { courseId, sessionId }))
|
||||
|
||||
@@ -7,11 +7,22 @@ export const apiShowAdminExam = (id) => http.get(buildUrl(endpoints.showExam, {
|
||||
|
||||
export const apiAddAdminExam = (payload) => http.post(endpoints.addNewExam, payload)
|
||||
|
||||
// Backend uses PATCH /exams/:id (not PUT).
|
||||
export const apiUpdateAdminExam = (id, payload) =>
|
||||
http.put(buildUrl(endpoints.updateExam, { id }), payload)
|
||||
http.patch(buildUrl(endpoints.updateExam, { id }), payload)
|
||||
|
||||
export const apiDeleteAdminExam = (id) => http.delete(buildUrl(endpoints.deleteExam, { id }))
|
||||
|
||||
// POST /exams/:examId/questions — payload: { questionText, position, options: [{ optionText, isCorrect }] }
|
||||
// Must include ≥2 options; question + options created atomically.
|
||||
export const apiAddAdminExamQuestion = (examId, payload) =>
|
||||
http.post(buildUrl(endpoints.addExamQuestion, { examId }), payload)
|
||||
|
||||
// POST /questions/:questionId/options — payload: { optionText, isCorrect }
|
||||
export const apiAddAdminQuestionOption = (questionId, payload) =>
|
||||
http.post(buildUrl(endpoints.addQuestionOption, { questionId }), payload)
|
||||
|
||||
// Mock-only — backend has no /exams/:id/participants endpoint yet.
|
||||
export const apiGetAdminExamParticipants = (examId, params) =>
|
||||
http.get(buildUrl(endpoints.getExamParticipants, { examId }), { params })
|
||||
|
||||
|
||||
@@ -12,8 +12,5 @@ export const apiUpdateAdminSession = (id, payload) =>
|
||||
|
||||
export const apiDeleteAdminSession = (id) => http.delete(buildUrl(endpoints.deleteSession, { id }))
|
||||
|
||||
export const apiChangeAdminSessionStatus = (id, payload) =>
|
||||
http.post(buildUrl(endpoints.changeStatusSession, { id }), payload)
|
||||
|
||||
export const apiGetSessionAttendance = (sessionId, params) =>
|
||||
http.get(buildUrl(endpoints.getSessionsAttendance, { sessionId }), { params })
|
||||
|
||||
@@ -14,9 +14,6 @@ export const apiDeleteAdminTerm = (id) => http.delete(buildUrl(endpoints.deleteT
|
||||
|
||||
export const apiCloneAdminTerm = (id) => http.post(buildUrl(endpoints.cloneTerm, { id }))
|
||||
|
||||
export const apiChangeAdminTermStatus = (id, payload) =>
|
||||
http.post(buildUrl(endpoints.changeStatusTerm, { id }), payload)
|
||||
|
||||
export const apiGetAdminTermStudents = (termId, params) =>
|
||||
http.get(buildUrl(endpoints.listUserTerm, { termId }), { params })
|
||||
|
||||
|
||||
@@ -8,5 +8,6 @@ export const apiShowAdminTicket = (id) => http.get(buildUrl(endpoints.showTicket
|
||||
export const apiSendAdminTicketMessage = (id, payload) =>
|
||||
http.post(buildUrl(endpoints.sendTicketMessage, { id }), payload)
|
||||
|
||||
// Backend uses PATCH (not POST).
|
||||
export const apiChangeAdminTicketStatus = (id, payload) =>
|
||||
http.post(buildUrl(endpoints.changeTicketStatus, { id }), payload)
|
||||
http.patch(buildUrl(endpoints.changeTicketStatus, { id }), payload)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export const endpoints = {
|
||||
// ─── Auth & profile ────────────────────────────────────────────────────────
|
||||
register: '/register',
|
||||
resendVerificationCodeForRegister: '/resend-verification-code',
|
||||
verifyCode: '/verify-code',
|
||||
@@ -10,13 +11,14 @@ export const endpoints = {
|
||||
verifyForgotPasswordCode: '/verify-forgot-password-code',
|
||||
resetPassword: '/reset-password',
|
||||
logout: '/logout',
|
||||
uploadMedia: '/media',
|
||||
me: '/auth/me',
|
||||
updateProfile: '/profile',
|
||||
|
||||
// ─── Geo ───────────────────────────────────────────────────────────────────
|
||||
provinceList: '/provinces',
|
||||
citiesList: '/provinces/:provinceId/cities',
|
||||
|
||||
me: '/auth/me',
|
||||
updateProfile: '/profile',
|
||||
// ─── Student-facing ────────────────────────────────────────────────────────
|
||||
getStudentCourses: '/student/courses',
|
||||
showStudentCourse: '/student/courses/:id',
|
||||
getStudentTerms: '/student/terms',
|
||||
@@ -45,12 +47,12 @@ export const endpoints = {
|
||||
getStudentCertificates: '/student/certificates',
|
||||
downloadStudentCertificate: '/student/certificates/:id/download',
|
||||
|
||||
// ─── Admin: users (out of scope of the Terms/Courses backend doc) ──────────
|
||||
getPendingStudents: '/admin/pending-students',
|
||||
showPendingStudent: '/admin/pending-students/:id',
|
||||
changeStatusPendingStudent: '/admin/pending-students/:id/status',
|
||||
downloadPdfPendingStudentInfo: '/admin/pending-students/:id/pdf',
|
||||
getApprovedUsers: '/admin/users',
|
||||
|
||||
showUserDetails: '/admin/users/:id',
|
||||
addNewUser: '/admin/users',
|
||||
updateUser: '/admin/users/:id',
|
||||
@@ -59,63 +61,92 @@ export const endpoints = {
|
||||
changeUserStatus: '/admin/users/:id/status',
|
||||
deleteUser: '/admin/users/:id',
|
||||
|
||||
// ─── Backend-aligned: Terms ────────────────────────────────────────────────
|
||||
getTermsList: '/terms',
|
||||
addNewTerm: '/terms',
|
||||
showTerm: '/terms/:id',
|
||||
updateTerm: '/terms/:id',
|
||||
deleteTerm: '/terms/:id',
|
||||
cloneTerm: '/admin/terms/:id/clone',
|
||||
changeStatusTerm: '/admin/terms/:id/status',
|
||||
|
||||
listUserTerm: '/admin/terms/:termId/students',
|
||||
addUserTerm: '/admin/terms/:termId/students',
|
||||
removeUserTerm: '/admin/terms/:termId/students/:userId',
|
||||
changeLeaveStatus: '/admin/terms/:termId/students/:userId/toggle-leave',
|
||||
|
||||
listCourseTerm: '/admin/terms/:termId/courses',
|
||||
addCourseTerm: '/admin/terms/:termId/courses',
|
||||
removeCourseTerm: '/admin/terms/:termId/courses/:courseId',
|
||||
|
||||
// ─── Backend-aligned: Courses ──────────────────────────────────────────────
|
||||
getCoursesList: '/courses',
|
||||
addNewCourse: '/courses',
|
||||
showCourse: '/courses/:id',
|
||||
updateCourse: '/courses/:id',
|
||||
deleteCourse: '/courses/:id',
|
||||
changeStatusCourse: '/admin/courses/:id/toggle-status',
|
||||
|
||||
getCourseTemplatesList: '/admin/courses',
|
||||
addNewCourseTemplate: '/admin/courses',
|
||||
showCourseTemplate: '/admin/courses/:id',
|
||||
updateCourseTemplate: '/admin/courses/:id',
|
||||
deleteCourseTemplate: '/admin/courses/:id',
|
||||
changeStatusCourseTemplate: '/admin/course-templates/:id/status',
|
||||
|
||||
listTemplateStudents: '/admin/course-templates/:templateId/students',
|
||||
addTemplateStudent: '/admin/course-templates/:templateId/students',
|
||||
removeTemplateStudent: '/admin/course-templates/:templateId/students/:userId',
|
||||
|
||||
listTemplateSessions: '/admin/course-templates/:templateId/sessions',
|
||||
attachTemplateSession: '/admin/course-templates/:templateId/sessions',
|
||||
detachTemplateSession: '/admin/course-templates/:templateId/sessions/:sessionId',
|
||||
|
||||
// ─── Backend-aligned: Sessions ─────────────────────────────────────────────
|
||||
getSessionsList: '/sessions',
|
||||
addNewSession: '/sessions',
|
||||
showSession: '/sessions/:id',
|
||||
updateSession: '/sessions/:id',
|
||||
deleteSession: '/sessions/:id',
|
||||
changeStatusSession: '/admin/sessions/:id/toggle-status',
|
||||
|
||||
// ─── Backend-aligned: Exams ────────────────────────────────────────────────
|
||||
// `getExamsList` is not yet in the backend Postman doc, but per the user
|
||||
// the backend will add it at /exams — wired up now so the FE doesn't need
|
||||
// to change when the endpoint lands.
|
||||
getExamsList: '/exams',
|
||||
showExam: '/exams/:id',
|
||||
addNewExam: '/exams',
|
||||
updateExam: '/exams/:id',
|
||||
deleteExam: '/exams/:id',
|
||||
addExamQuestion: '/exams/:examId/questions',
|
||||
addQuestionOption: '/questions/:questionId/options',
|
||||
submitExam: '/exams/:examId/submit',
|
||||
|
||||
// ─── Backend-aligned: Homeworks (FE calls them "assignments") ──────────────
|
||||
addNewAssignment: '/homeworks',
|
||||
updateAssignment: '/homeworks/:id',
|
||||
deleteAssignment: '/homeworks/:id',
|
||||
submitHomework: '/homeworks/:homeworkId/submit',
|
||||
reviewAssignmentSubmission: '/homework-submissions/:submissionId/review',
|
||||
|
||||
// ─── Backend-aligned: Media ────────────────────────────────────────────────
|
||||
uploadMedia: '/media',
|
||||
downloadMedia: '/media/:id/download',
|
||||
deleteMedia: '/media/:id',
|
||||
|
||||
// ─── FE mock-only — NOT in backend Postman doc ─────────────────────────────
|
||||
// Kept so existing UI screens continue to work against the mock layer.
|
||||
// See docs/backend-vs-ui-gaps.md for the list of decisions pending.
|
||||
|
||||
// Terms — clone + status + students-in-term + courses-in-term
|
||||
cloneTerm: '/admin/terms/:id/clone',
|
||||
listUserTerm: '/admin/terms/:termId/students',
|
||||
addUserTerm: '/admin/terms/:termId/students',
|
||||
removeUserTerm: '/admin/terms/:termId/students/:userId',
|
||||
changeLeaveStatus: '/admin/terms/:termId/students/:userId/toggle-leave',
|
||||
listCourseTerm: '/admin/terms/:termId/courses',
|
||||
addCourseTerm: '/admin/terms/:termId/courses',
|
||||
removeCourseTerm: '/admin/terms/:termId/courses/:courseId',
|
||||
|
||||
// Courses — students-in-course + attach/detach sessions (backend uses 1:N via session.course_id)
|
||||
listCourseStudents: '/admin/courses/:courseId/students',
|
||||
addCourseStudent: '/admin/courses/:courseId/students',
|
||||
removeCourseStudent: '/admin/courses/:courseId/students/:userId',
|
||||
listCourseSessions: '/admin/courses/:courseId/sessions',
|
||||
attachCourseSession: '/admin/courses/:courseId/sessions',
|
||||
detachCourseSession: '/admin/courses/:courseId/sessions/:sessionId',
|
||||
|
||||
// Sessions — attendance roster
|
||||
getSessionsAttendance: '/admin/sessions/:sessionId/attendances',
|
||||
|
||||
getAssignmentsList: '/admin/assignments',
|
||||
addNewAssignment: '/admin/assignments',
|
||||
showAssignment: '/admin/assignments/:id',
|
||||
updateAssignment: '/admin/assignments/:id',
|
||||
deleteAssignment: '/admin/assignments/:id',
|
||||
getAssignmentSubmissions: '/admin/assignments/:assignmentId/submissions',
|
||||
showAssignmentSubmission: '/admin/assignments/:assignmentId/submissions/:submissionId',
|
||||
reviewAssignmentSubmission: '/admin/assignments/:assignmentId/submissions/:submissionId/review',
|
||||
// Exams — participants only (backend has /exams/:id but no participants endpoints)
|
||||
getExamParticipants: '/admin/exams/:examId/participants',
|
||||
showExamParticipant: '/admin/exams/:examId/participants/:participantId',
|
||||
|
||||
// Homeworks — list, show, submissions list, show submission
|
||||
// Paths aligned to backend `/homeworks` and `/homework-submissions`. The
|
||||
// backend Postman doc currently exposes only create/update/delete/submit/review;
|
||||
// list + show + submissions list + show-submission stay mock-only until backend
|
||||
// adds them, but URL shape is locked in.
|
||||
getAssignmentsList: '/homeworks',
|
||||
showAssignment: '/homeworks/:id',
|
||||
getAssignmentSubmissions: '/homeworks/:homeworkId/submissions',
|
||||
showAssignmentSubmission: '/homework-submissions/:submissionId',
|
||||
|
||||
// ─── Tickets / Consultations (out of scope of Terms/Courses backend doc) ───
|
||||
getTicketsList: '/admin/tickets',
|
||||
showTicket: '/admin/tickets/:id',
|
||||
sendTicketMessage: '/admin/tickets/:id/messages',
|
||||
@@ -125,14 +156,6 @@ export const endpoints = {
|
||||
showConsultation: '/admin/consultations/:id',
|
||||
sendConsultationMessage: '/admin/consultations/:id/messages',
|
||||
changeConsultationStatus: '/admin/consultations/:id/status',
|
||||
|
||||
getExamsList: '/admin/exams',
|
||||
addNewExam: '/admin/exams',
|
||||
showExam: '/admin/exams/:id',
|
||||
updateExam: '/admin/exams/:id',
|
||||
deleteExam: '/admin/exams/:id',
|
||||
getExamParticipants: '/admin/exams/:examId/participants',
|
||||
showExamParticipant: '/admin/exams/:examId/participants/:participantId',
|
||||
}
|
||||
|
||||
export const buildUrl = (template, params = {}) =>
|
||||
|
||||
@@ -3,8 +3,8 @@ export const adminAssignments = [
|
||||
id: 301,
|
||||
title: 'یادداشتبرداری از جلسه اول',
|
||||
description: 'خلاصهای از مباحث جلسه اول را در دو صفحه بنویسید.',
|
||||
courseTemplate: { id: 1, title: 'اصول اخلاق اسلامی' },
|
||||
courseTemplateTitle: 'اصول اخلاق اسلامی',
|
||||
course: { id: 1, title: 'اصول اخلاق اسلامی' },
|
||||
courseTitle: 'اصول اخلاق اسلامی',
|
||||
session: { id: 101, title: 'مقدمهای بر اخلاق اسلامی' },
|
||||
sessionTitle: 'مقدمهای بر اخلاق اسلامی',
|
||||
startDate: '2025-09-26T00:00:00.000Z',
|
||||
@@ -19,8 +19,8 @@ export const adminAssignments = [
|
||||
id: 302,
|
||||
title: 'تحلیل تفسیری سوره حمد',
|
||||
description: 'تحلیل سه آیه از سوره حمد را ارسال نمایید.',
|
||||
courseTemplate: { id: 2, title: 'مفاهیم قرآنی' },
|
||||
courseTemplateTitle: 'مفاهیم قرآنی',
|
||||
course: { id: 2, title: 'مفاهیم قرآنی' },
|
||||
courseTitle: 'مفاهیم قرآنی',
|
||||
session: { id: 102, title: 'تفسیر سوره حمد' },
|
||||
sessionTitle: 'تفسیر سوره حمد',
|
||||
startDate: '2025-10-05T00:00:00.000Z',
|
||||
@@ -55,7 +55,7 @@ export const assignmentSubmissions = new Map([
|
||||
},
|
||||
},
|
||||
termTitle: 'ترم پاییز ۱۴۰۴',
|
||||
courseTemplateTitle: 'اصول اخلاق اسلامی',
|
||||
courseTitle: 'اصول اخلاق اسلامی',
|
||||
sessionTitle: 'مقدمهای بر اخلاق اسلامی',
|
||||
userDescription: 'یادداشتهای جلسه اول به همراه برداشت شخصی ضمیمه است.',
|
||||
attachments: [{ id: 1, title: 'یادداشت-جلسه-اول.pdf', fileUrl: '#', type: 'document' }],
|
||||
@@ -82,7 +82,7 @@ export const assignmentSubmissions = new Map([
|
||||
},
|
||||
},
|
||||
termTitle: 'ترم پاییز ۱۴۰۴',
|
||||
courseTemplateTitle: 'اصول اخلاق اسلامی',
|
||||
courseTitle: 'اصول اخلاق اسلامی',
|
||||
sessionTitle: 'مقدمهای بر اخلاق اسلامی',
|
||||
userDescription: 'خلاصهای از جلسه به همراه پرسش پایان.',
|
||||
attachments: [],
|
||||
|
||||
@@ -1,50 +1,5 @@
|
||||
export const adminCourseTemplates = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'اصول اخلاق اسلامی',
|
||||
description: 'دوره مقدماتی برای آشنایی با اخلاق اسلامی.',
|
||||
image: 'https://picsum.photos/seed/course1/200/200',
|
||||
defaultTeacher: { id: 5, firstName: 'علی', lastName: 'حسنی', fullName: 'علی حسنی' },
|
||||
defaultCapacity: 30,
|
||||
isActiveByDefault: true,
|
||||
prerequisitesCount: 0,
|
||||
prerequisites: [],
|
||||
createdAt: '2025-08-10T08:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'مفاهیم قرآنی',
|
||||
description: 'بررسی مفاهیم قرآنی به همراه تفسیر مختصر.',
|
||||
image: 'https://picsum.photos/seed/course2/200/200',
|
||||
defaultTeacher: { id: 6, firstName: 'حسین', lastName: 'مرادی', fullName: 'حسین مرادی' },
|
||||
defaultCapacity: 25,
|
||||
isActiveByDefault: true,
|
||||
prerequisitesCount: 1,
|
||||
prerequisites: [{ courseId: 1, course: { id: 1, title: 'اصول اخلاق اسلامی' } }],
|
||||
createdAt: '2025-09-12T08:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: 'فقه عبادات',
|
||||
description: 'مرور احکام عملی نماز و روزه.',
|
||||
image: '',
|
||||
defaultTeacher: { id: 7, firstName: 'مهدی', lastName: 'سهرابی', fullName: 'مهدی سهرابی' },
|
||||
defaultCapacity: 20,
|
||||
isActiveByDefault: false,
|
||||
prerequisitesCount: 0,
|
||||
prerequisites: [],
|
||||
createdAt: '2026-01-05T08:00:00.000Z',
|
||||
},
|
||||
]
|
||||
|
||||
// Offered courses carry both the spec keys (term_id/teacher_id/
|
||||
// description/capacity/is_active/cover_url) and the UI-only keys the
|
||||
// current frontend reads (image, template/templateId, term/teacher
|
||||
// nested objects, prerequisitesCount, startDate, endDate). See
|
||||
// docs/backend-api-todo.md.
|
||||
const makeTeacherSnapshot = (overrides) => ({
|
||||
id: overrides.id,
|
||||
// --- spec ---
|
||||
name: overrides.name ?? `${overrides.firstName ?? ''} ${overrides.lastName ?? ''}`.trim(),
|
||||
email: overrides.email ?? '',
|
||||
phone: overrides.phone ?? null,
|
||||
@@ -52,16 +7,70 @@ const makeTeacherSnapshot = (overrides) => ({
|
||||
avatarUrl: overrides.avatarUrl ?? null,
|
||||
avatarDownloadUrl: overrides.avatarDownloadUrl ?? null,
|
||||
createdAt: overrides.createdAt ?? '',
|
||||
// --- ui-only ---
|
||||
firstName: overrides.firstName ?? '',
|
||||
lastName: overrides.lastName ?? '',
|
||||
fullName:
|
||||
overrides.fullName ?? `${overrides.firstName ?? ''} ${overrides.lastName ?? ''}`.trim(),
|
||||
fullName: overrides.fullName ?? `${overrides.firstName ?? ''} ${overrides.lastName ?? ''}`.trim(),
|
||||
})
|
||||
|
||||
export const adminOfferedCourses = [
|
||||
export const adminCourses = [
|
||||
{
|
||||
id: 1,
|
||||
termId: null,
|
||||
teacherId: 5,
|
||||
title: 'اصول اخلاق اسلامی',
|
||||
description: 'دوره مقدماتی برای آشنایی با اخلاق اسلامی.',
|
||||
capacity: 30,
|
||||
isActive: true,
|
||||
coverUrl: 'https://picsum.photos/seed/course1/200/200',
|
||||
image: 'https://picsum.photos/seed/course1/200/200',
|
||||
teacher: makeTeacherSnapshot({ id: 5, firstName: 'علی', lastName: 'حسنی' }),
|
||||
term: null,
|
||||
sessionsCount: 0,
|
||||
prerequisitesCount: 0,
|
||||
prerequisites: [],
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
createdAt: '2025-08-10T08:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
termId: null,
|
||||
teacherId: 6,
|
||||
title: 'مفاهیم قرآنی',
|
||||
description: 'بررسی مفاهیم قرآنی به همراه تفسیر مختصر.',
|
||||
capacity: 25,
|
||||
isActive: true,
|
||||
coverUrl: 'https://picsum.photos/seed/course2/200/200',
|
||||
image: 'https://picsum.photos/seed/course2/200/200',
|
||||
teacher: makeTeacherSnapshot({ id: 6, firstName: 'حسین', lastName: 'مرادی' }),
|
||||
term: null,
|
||||
sessionsCount: 0,
|
||||
prerequisitesCount: 1,
|
||||
prerequisites: [{ courseId: 1, course: { id: 1, title: 'اصول اخلاق اسلامی' } }],
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
createdAt: '2025-09-12T08:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
termId: null,
|
||||
teacherId: 7,
|
||||
title: 'فقه عبادات',
|
||||
description: 'مرور احکام عملی نماز و روزه.',
|
||||
capacity: 20,
|
||||
isActive: false,
|
||||
coverUrl: '',
|
||||
image: '',
|
||||
teacher: makeTeacherSnapshot({ id: 7, firstName: 'مهدی', lastName: 'سهرابی' }),
|
||||
term: null,
|
||||
sessionsCount: 0,
|
||||
prerequisitesCount: 0,
|
||||
prerequisites: [],
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
createdAt: '2026-01-05T08:00:00.000Z',
|
||||
},
|
||||
{
|
||||
// --- spec ---
|
||||
id: 11,
|
||||
termId: 1,
|
||||
teacherId: 5,
|
||||
@@ -70,20 +79,17 @@ export const adminOfferedCourses = [
|
||||
capacity: 30,
|
||||
isActive: true,
|
||||
coverUrl: 'https://picsum.photos/seed/offered1/200/200',
|
||||
|
||||
// --- ui-only ---
|
||||
image: 'https://picsum.photos/seed/offered1/200/200',
|
||||
template: { id: 1, title: 'اصول اخلاق اسلامی' },
|
||||
templateId: 1,
|
||||
term: { id: 1, title: 'ترم پاییز ۱۴۰۴' },
|
||||
teacher: makeTeacherSnapshot({ id: 5, firstName: 'علی', lastName: 'حسنی' }),
|
||||
term: { id: 1, title: 'ترم پاییز ۱۴۰۴' },
|
||||
sessionsCount: 0,
|
||||
prerequisitesCount: 0,
|
||||
prerequisites: [],
|
||||
startDate: '2025-09-23T00:00:00.000Z',
|
||||
endDate: '2025-11-20T00:00:00.000Z',
|
||||
createdAt: '2025-09-01T08:00:00.000Z',
|
||||
},
|
||||
{
|
||||
// --- spec ---
|
||||
id: 12,
|
||||
termId: 1,
|
||||
teacherId: 6,
|
||||
@@ -92,20 +98,17 @@ export const adminOfferedCourses = [
|
||||
capacity: 25,
|
||||
isActive: true,
|
||||
coverUrl: 'https://picsum.photos/seed/offered2/200/200',
|
||||
|
||||
// --- ui-only ---
|
||||
image: 'https://picsum.photos/seed/offered2/200/200',
|
||||
template: { id: 2, title: 'مفاهیم قرآنی' },
|
||||
templateId: 2,
|
||||
term: { id: 1, title: 'ترم پاییز ۱۴۰۴' },
|
||||
teacher: makeTeacherSnapshot({ id: 6, firstName: 'حسین', lastName: 'مرادی' }),
|
||||
term: { id: 1, title: 'ترم پاییز ۱۴۰۴' },
|
||||
sessionsCount: 0,
|
||||
prerequisitesCount: 1,
|
||||
prerequisites: [{ courseId: 11, course: { id: 11, title: 'اصول اخلاق اسلامی - پاییز' } }],
|
||||
startDate: '2025-10-01T00:00:00.000Z',
|
||||
endDate: '2025-12-15T00:00:00.000Z',
|
||||
createdAt: '2025-09-10T08:00:00.000Z',
|
||||
},
|
||||
{
|
||||
// --- spec ---
|
||||
id: 13,
|
||||
termId: 2,
|
||||
teacherId: 7,
|
||||
@@ -114,14 +117,12 @@ export const adminOfferedCourses = [
|
||||
capacity: 20,
|
||||
isActive: false,
|
||||
coverUrl: '',
|
||||
|
||||
// --- ui-only ---
|
||||
image: '',
|
||||
template: { id: 3, title: 'فقه عبادات' },
|
||||
templateId: 3,
|
||||
term: { id: 2, title: 'ترم زمستان ۱۴۰۴' },
|
||||
teacher: makeTeacherSnapshot({ id: 7, firstName: 'مهدی', lastName: 'سهرابی' }),
|
||||
term: { id: 2, title: 'ترم زمستان ۱۴۰۴' },
|
||||
sessionsCount: 0,
|
||||
prerequisitesCount: 0,
|
||||
prerequisites: [],
|
||||
startDate: '2026-01-22T00:00:00.000Z',
|
||||
endDate: '2026-03-15T00:00:00.000Z',
|
||||
createdAt: '2025-12-10T08:00:00.000Z',
|
||||
|
||||
@@ -1,67 +1,83 @@
|
||||
const sampleQuestions = (seed) => [
|
||||
{
|
||||
id: `${seed}-q1`,
|
||||
title: 'کدام گزینه به مفهوم تقوا نزدیکتر است؟',
|
||||
score: 5,
|
||||
correctAnswerId: `${seed}-q1-a2`,
|
||||
answers: [
|
||||
{ id: `${seed}-q1-a1`, title: 'پرهیز از خطا و دوری از گناه' },
|
||||
{ id: `${seed}-q1-a2`, title: 'خودنگهداری در محضر خداوند' },
|
||||
{ id: `${seed}-q1-a3`, title: 'پرهیز از خوراکیهای مضر' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: `${seed}-q2`,
|
||||
title: 'منبع اصلی احکام شیعه چیست؟',
|
||||
score: 5,
|
||||
correctAnswerId: `${seed}-q2-a1`,
|
||||
answers: [
|
||||
{ id: `${seed}-q2-a1`, title: 'قرآن و سنت اهل بیت(ع)' },
|
||||
{ id: `${seed}-q2-a2`, title: 'فقط قرآن کریم' },
|
||||
{ id: `${seed}-q2-a3`, title: 'اجماع علما' },
|
||||
],
|
||||
},
|
||||
// Shape mirrors backend Postman doc for /exams:
|
||||
// exam: { id, sessionId, title, description, passScore, isActive,
|
||||
// questions: [{ id, questionText, position,
|
||||
// options: [{ id, optionText, isCorrect }] }] }
|
||||
// (Backend hides `is_correct` from non-admin reads; the admin mock shows it.)
|
||||
|
||||
const buildQuestion = (id, questionText, position, options, correctIndex) => ({
|
||||
id,
|
||||
questionText,
|
||||
position,
|
||||
options: options.map((optionText, idx) => ({
|
||||
id: id * 100 + idx + 1,
|
||||
optionText,
|
||||
isCorrect: idx === correctIndex,
|
||||
})),
|
||||
})
|
||||
|
||||
const ethicsQuestions = [
|
||||
buildQuestion(
|
||||
1,
|
||||
'کدام گزینه به مفهوم تقوا نزدیکتر است؟',
|
||||
1,
|
||||
['پرهیز از خطا و دوری از گناه', 'خودنگهداری در محضر خداوند', 'پرهیز از خوراکیهای مضر'],
|
||||
1
|
||||
),
|
||||
buildQuestion(
|
||||
2,
|
||||
'منبع اصلی احکام شیعه چیست؟',
|
||||
2,
|
||||
['قرآن و سنت اهل بیت(ع)', 'فقط قرآن کریم', 'اجماع علما'],
|
||||
0
|
||||
),
|
||||
]
|
||||
|
||||
const quranQuestions = [
|
||||
buildQuestion(3, 'سوره حمد چند آیه دارد؟', 1, ['۵ آیه', '۶ آیه', '۷ آیه', '۸ آیه'], 2),
|
||||
buildQuestion(
|
||||
4,
|
||||
'کدام آیه به نام آیةالکرسی شناخته میشود؟',
|
||||
2,
|
||||
['آیه ۲۵۵ سوره بقره', 'آیه اول سوره فاتحه', 'آیه ۱۸ سوره آل عمران'],
|
||||
0
|
||||
),
|
||||
]
|
||||
|
||||
export const adminExams = [
|
||||
{
|
||||
id: 201,
|
||||
sessionId: 101,
|
||||
title: 'آزمون پایان فصل اول اخلاق',
|
||||
description: 'آزمون چهار گزینهای از مفاهیم درسهای ۱ تا ۳.',
|
||||
courseTemplate: { id: 1, title: 'اصول اخلاق اسلامی' },
|
||||
courseTemplateTitle: 'اصول اخلاق اسلامی',
|
||||
passScore: 12,
|
||||
isActive: true,
|
||||
questions: ethicsQuestions,
|
||||
// ── UI-only fields for ExamItem / ExamDetailsModal (not in backend). ──
|
||||
course: { id: 1, title: 'اصول اخلاق اسلامی' },
|
||||
courseTitle: 'اصول اخلاق اسلامی',
|
||||
session: { id: 101, title: 'مقدمهای بر اخلاق اسلامی' },
|
||||
sessionTitle: 'مقدمهای بر اخلاق اسلامی',
|
||||
durationMinutes: 25,
|
||||
passingScore: 12,
|
||||
questionsCount: 2,
|
||||
randomize: true,
|
||||
endDate: '2026-05-15T00:00:00.000Z',
|
||||
startDate: '2026-05-01T00:00:00.000Z',
|
||||
questionsCount: ethicsQuestions.length,
|
||||
createdAt: '2026-04-20T08:00:00.000Z',
|
||||
questions: sampleQuestions(201),
|
||||
usedInTerms: [{ termId: 1 }],
|
||||
},
|
||||
{
|
||||
id: 202,
|
||||
sessionId: 102,
|
||||
title: 'آزمون مفاهیم قرآنی - میانترم',
|
||||
description: 'آزمون میانترم برای مرور آیات کلیدی.',
|
||||
courseTemplate: { id: 2, title: 'مفاهیم قرآنی' },
|
||||
courseTemplateTitle: 'مفاهیم قرآنی',
|
||||
passScore: 14,
|
||||
isActive: true,
|
||||
questions: quranQuestions,
|
||||
course: { id: 2, title: 'مفاهیم قرآنی' },
|
||||
courseTitle: 'مفاهیم قرآنی',
|
||||
session: { id: 102, title: 'تفسیر سوره حمد' },
|
||||
sessionTitle: 'تفسیر سوره حمد',
|
||||
durationMinutes: 30,
|
||||
passingScore: 14,
|
||||
questionsCount: 2,
|
||||
randomize: false,
|
||||
endDate: '2026-06-10T00:00:00.000Z',
|
||||
startDate: '2026-05-25T00:00:00.000Z',
|
||||
questionsCount: quranQuestions.length,
|
||||
createdAt: '2026-05-10T08:00:00.000Z',
|
||||
questions: sampleQuestions(202),
|
||||
usedInTerms: [{ termId: 1 }],
|
||||
},
|
||||
]
|
||||
|
||||
// FE-mock-only — no backend endpoint for exam participants.
|
||||
export const examParticipants = new Map([
|
||||
[
|
||||
201,
|
||||
@@ -85,10 +101,6 @@ export const examParticipants = new Map([
|
||||
date: '2026-05-05T10:30:00.000Z',
|
||||
score: 17,
|
||||
scoreTone: 'good',
|
||||
questions: sampleQuestions(201).map((q) => ({
|
||||
...q,
|
||||
userAnswerId: q.correctAnswerId,
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -111,10 +123,6 @@ export const examParticipants = new Map([
|
||||
date: '2026-05-06T11:00:00.000Z',
|
||||
score: 9,
|
||||
scoreTone: 'bad',
|
||||
questions: sampleQuestions(201).map((q, i) => ({
|
||||
...q,
|
||||
userAnswerId: i === 0 ? q.answers[0].id : q.correctAnswerId,
|
||||
})),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// Maps the FE's 7-value `sessionType` to the spec's 3-value `type`.
|
||||
const SESSION_TYPE_TO_SPEC = {
|
||||
in_person: 'offline',
|
||||
online: 'online',
|
||||
@@ -13,29 +12,26 @@ const makeSession = (overrides) => {
|
||||
const sessionType = overrides.sessionType ?? ''
|
||||
const sessionConfig = overrides.sessionConfig ?? {}
|
||||
const startsAt = overrides.startsAt ?? sessionConfig.startTime ?? null
|
||||
const location = overrides.location ?? sessionConfig.location ?? null
|
||||
const endsAt = overrides.endsAt ?? sessionConfig.endTime ?? null
|
||||
const link = overrides.link ?? sessionConfig.meetingLink ?? null
|
||||
return {
|
||||
// --- spec ---
|
||||
id: overrides.id,
|
||||
courseId: overrides.courseId ?? null,
|
||||
title: overrides.title ?? '',
|
||||
description: overrides.description ?? '',
|
||||
type: overrides.type ?? SESSION_TYPE_TO_SPEC[sessionType] ?? null,
|
||||
startsAt,
|
||||
location,
|
||||
endsAt,
|
||||
link,
|
||||
media: overrides.media ?? [],
|
||||
|
||||
// --- ui-only ---
|
||||
image: overrides.image ?? '',
|
||||
courseTemplate: overrides.courseTemplate ?? null,
|
||||
course: overrides.course ?? null,
|
||||
sessionType,
|
||||
sessionTypeFa: overrides.sessionTypeFa ?? '',
|
||||
contentType: overrides.contentType ?? '',
|
||||
contentMediaId: overrides.contentMediaId ?? null,
|
||||
durationMinutes: overrides.durationMinutes ?? 0,
|
||||
order: overrides.order ?? 1,
|
||||
sessionConfig,
|
||||
materials: overrides.materials ?? [],
|
||||
usedInTerms: overrides.usedInTerms ?? [],
|
||||
createdAt: overrides.createdAt ?? '',
|
||||
}
|
||||
@@ -48,7 +44,7 @@ export const adminSessions = [
|
||||
title: 'مقدمهای بر اخلاق اسلامی',
|
||||
description: 'جلسه نخست؛ تعاریف و چارچوب دوره.',
|
||||
image: 'https://picsum.photos/seed/session1/200/200',
|
||||
courseTemplate: { id: 1, title: 'اصول اخلاق اسلامی' },
|
||||
course: { id: 11, title: 'اصول اخلاق اسلامی - پاییز' },
|
||||
sessionType: 'in_person',
|
||||
sessionTypeFa: 'حضوری',
|
||||
durationMinutes: 90,
|
||||
@@ -65,7 +61,7 @@ export const adminSessions = [
|
||||
courseId: 12,
|
||||
title: 'تفسیر سوره حمد',
|
||||
description: 'تحلیل آیات سوره حمد.',
|
||||
courseTemplate: { id: 2, title: 'مفاهیم قرآنی' },
|
||||
course: { id: 12, title: 'مفاهیم قرآنی - پاییز' },
|
||||
sessionType: 'online',
|
||||
sessionTypeFa: 'آنلاین',
|
||||
durationMinutes: 75,
|
||||
@@ -83,7 +79,7 @@ export const adminSessions = [
|
||||
courseId: 13,
|
||||
title: 'احکام نماز جماعت',
|
||||
description: 'مرور احکام و شرایط نماز جماعت.',
|
||||
courseTemplate: { id: 3, title: 'فقه عبادات' },
|
||||
course: { id: 13, title: 'فقه عبادات - زمستان' },
|
||||
sessionType: 'video',
|
||||
sessionTypeFa: 'ویدئو',
|
||||
durationMinutes: 50,
|
||||
|
||||
@@ -1,92 +1,148 @@
|
||||
// Shape mirrors backend Postman doc for /admin/tickets:
|
||||
// ticket: { id, student_id, assigned_to_user_id, target_role, status, subject,
|
||||
// created_at, student, assignee, messages: [{ id, ticket_id,
|
||||
// sender_id, message, created_at, sender }] }
|
||||
// Field names use camelCase here (snake_case bridge happens at the HTTP layer
|
||||
// when we swap the mock for the real backend).
|
||||
|
||||
const makeUser = (overrides) => ({
|
||||
id: overrides.id,
|
||||
name: overrides.name ?? '',
|
||||
email: overrides.email ?? '',
|
||||
phone: overrides.phone ?? null,
|
||||
roles: overrides.roles ?? ['student'],
|
||||
avatarUrl: overrides.avatarUrl ?? null,
|
||||
avatarDownloadUrl: overrides.avatarDownloadUrl ?? null,
|
||||
createdAt: overrides.createdAt ?? '',
|
||||
})
|
||||
|
||||
const studentJane = makeUser({
|
||||
id: 100,
|
||||
name: 'فاطمه رضایی',
|
||||
email: 'fateme@example.com',
|
||||
phone: '+989121111111',
|
||||
roles: ['student'],
|
||||
avatarUrl: '',
|
||||
createdAt: '2026-02-01T10:00:00.000Z',
|
||||
})
|
||||
|
||||
const studentMaryam = makeUser({
|
||||
id: 102,
|
||||
name: 'مریم احمدی',
|
||||
email: 'maryam@example.com',
|
||||
phone: '+989122222222',
|
||||
roles: ['student'],
|
||||
avatarUrl: '',
|
||||
createdAt: '2026-02-05T10:00:00.000Z',
|
||||
})
|
||||
|
||||
const studentAli = makeUser({
|
||||
id: 103,
|
||||
name: 'علی علوی',
|
||||
email: 'ali@example.com',
|
||||
phone: '+989123333333',
|
||||
roles: ['student'],
|
||||
avatarUrl: '',
|
||||
createdAt: '2026-02-10T10:00:00.000Z',
|
||||
})
|
||||
|
||||
const adminUser = makeUser({
|
||||
id: 1,
|
||||
name: 'مدیر سامانه',
|
||||
email: 'admin@example.com',
|
||||
roles: ['admin'],
|
||||
createdAt: '2026-01-01T00:00:00.000Z',
|
||||
})
|
||||
|
||||
export const adminTickets = [
|
||||
{
|
||||
id: 401,
|
||||
title: 'پیگیری وضعیت ثبتنام',
|
||||
user: {
|
||||
id: 100,
|
||||
firstName: 'فاطمه',
|
||||
lastName: 'رضایی',
|
||||
fullName: 'فاطمه رضایی',
|
||||
avatarUrl: '',
|
||||
},
|
||||
studentId: studentJane.id,
|
||||
assignedToUserId: adminUser.id,
|
||||
targetRole: 'admin',
|
||||
status: 'answered',
|
||||
statusLabel: 'پاسخ داده شده',
|
||||
createdAt: '2026-05-09T20:28:00.000Z',
|
||||
faCreatedAt: '۱۴۰۵/۰۲/۱۹',
|
||||
faCreatedTime: '۱۰:۳۰',
|
||||
subject: 'پیگیری وضعیت ثبتنام',
|
||||
createdAt: '2026-05-09T10:30:00.000Z',
|
||||
student: studentJane,
|
||||
assignee: adminUser,
|
||||
messages: [
|
||||
{
|
||||
id: 1,
|
||||
sender: 'user',
|
||||
text: 'سلام وقت بخیر، در فرایند ثبتنام به مشکل برخوردم.',
|
||||
time: '۱۲:۳۵',
|
||||
ticketId: 401,
|
||||
senderId: studentJane.id,
|
||||
message: 'سلام وقت بخیر، در فرایند ثبتنام به مشکل برخوردم.',
|
||||
createdAt: '2026-05-09T10:35:00.000Z',
|
||||
sender: studentJane,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
sender: 'admin',
|
||||
text: 'سلام و عرض ادب. لطفا کد پیگیری ثبتنام را ارسال کنید.',
|
||||
time: '۱۲:۴۰',
|
||||
ticketId: 401,
|
||||
senderId: adminUser.id,
|
||||
message: 'سلام و عرض ادب. لطفا کد پیگیری ثبتنام را ارسال کنید.',
|
||||
createdAt: '2026-05-09T10:40:00.000Z',
|
||||
sender: adminUser,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
sender: 'user',
|
||||
text: 'کد ثبتنام: ۱۲۳۴۵۶۷۸',
|
||||
time: '۱۲:۴۲',
|
||||
ticketId: 401,
|
||||
senderId: studentJane.id,
|
||||
message: 'کد ثبتنام: ۱۲۳۴۵۶۷۸',
|
||||
createdAt: '2026-05-09T10:42:00.000Z',
|
||||
sender: studentJane,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 402,
|
||||
title: 'سوال درباره جلسه آموزشی',
|
||||
user: {
|
||||
id: 102,
|
||||
firstName: 'مریم',
|
||||
lastName: 'احمدی',
|
||||
fullName: 'مریم احمدی',
|
||||
avatarUrl: '',
|
||||
},
|
||||
status: 'pending',
|
||||
statusLabel: 'در انتظار پاسخ',
|
||||
studentId: studentMaryam.id,
|
||||
assignedToUserId: null,
|
||||
targetRole: 'admin',
|
||||
status: 'open',
|
||||
subject: 'سوال درباره جلسه آموزشی',
|
||||
createdAt: '2026-05-08T09:15:00.000Z',
|
||||
faCreatedAt: '۱۴۰۵/۰۲/۱۸',
|
||||
faCreatedTime: '۱۲:۴۵',
|
||||
student: studentMaryam,
|
||||
assignee: null,
|
||||
messages: [
|
||||
{
|
||||
id: 1,
|
||||
sender: 'user',
|
||||
text: 'سلام، درباره ساعت برگزاری جلسه چهارم سوال داشتم.',
|
||||
time: '۱۲:۴۵',
|
||||
ticketId: 402,
|
||||
senderId: studentMaryam.id,
|
||||
message: 'سلام، درباره ساعت برگزاری جلسه چهارم سوال داشتم.',
|
||||
createdAt: '2026-05-08T09:15:00.000Z',
|
||||
sender: studentMaryam,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 403,
|
||||
title: 'درخواست بستن تیکت',
|
||||
user: {
|
||||
id: 103,
|
||||
firstName: 'علی',
|
||||
lastName: 'علوی',
|
||||
fullName: 'علی علوی',
|
||||
avatarUrl: '',
|
||||
},
|
||||
studentId: studentAli.id,
|
||||
assignedToUserId: adminUser.id,
|
||||
targetRole: 'admin',
|
||||
status: 'closed',
|
||||
statusLabel: 'بسته شده',
|
||||
subject: 'درخواست بستن تیکت',
|
||||
createdAt: '2026-05-07T14:10:00.000Z',
|
||||
faCreatedAt: '۱۴۰۵/۰۲/۱۷',
|
||||
faCreatedTime: '۱۷:۴۰',
|
||||
student: studentAli,
|
||||
assignee: adminUser,
|
||||
messages: [
|
||||
{
|
||||
id: 1,
|
||||
sender: 'user',
|
||||
text: 'مشکل برطرف شد. لطفا تیکت بسته شود.',
|
||||
time: '۱۷:۴۰',
|
||||
ticketId: 403,
|
||||
senderId: studentAli.id,
|
||||
message: 'مشکل برطرف شد. لطفا تیکت بسته شود.',
|
||||
createdAt: '2026-05-07T17:40:00.000Z',
|
||||
sender: studentAli,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
sender: 'admin',
|
||||
text: 'با تشکر از شما. تیکت بسته شد.',
|
||||
time: '۱۷:۴۲',
|
||||
ticketId: 403,
|
||||
senderId: adminUser.id,
|
||||
message: 'با تشکر از شما. تیکت بسته شد.',
|
||||
createdAt: '2026-05-07T17:42:00.000Z',
|
||||
sender: adminUser,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
// The signed-in admin used as `sender` when a message is posted from the FE.
|
||||
export const currentAdminUser = adminUser
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { register } from '@/services/mock/registry'
|
||||
import { endpoints } from '@/services/api/endpoints'
|
||||
import { adminCourses } from '@/services/mock/fixtures/admin-courses'
|
||||
import { adminSessions } from '@/services/mock/fixtures/admin-sessions'
|
||||
import { adminCourseTemplates } from '@/services/mock/fixtures/admin-courses'
|
||||
import { adminAssignments, assignmentSubmissions } from '@/services/mock/fixtures/admin-assignments'
|
||||
import {
|
||||
filterDateRange,
|
||||
@@ -23,7 +23,7 @@ const computeDurationDays = (start, end) => {
|
||||
register('GET', endpoints.getAssignmentsList, ({ query }) => {
|
||||
let list = filterItems(adminAssignments, query, {
|
||||
title: 'includes',
|
||||
courseTemplateId: (item, v) => String(item.courseTemplate?.id) === String(v),
|
||||
courseId: (item, v) => String(item.course?.id) === String(v),
|
||||
sessionId: (item, v) => String(item.session?.id) === String(v),
|
||||
})
|
||||
list = filterDateRange(list, query)
|
||||
@@ -35,14 +35,14 @@ register('GET', endpoints.showAssignment, ({ params }) => ({
|
||||
}))
|
||||
|
||||
register('POST', endpoints.addNewAssignment, ({ data }) => {
|
||||
const tpl = adminCourseTemplates.find((c) => c.id === Number(data.courseTemplateId))
|
||||
const course = adminCourses.find((c) => c.id === Number(data.courseId))
|
||||
const sess = adminSessions.find((s) => s.id === Number(data.sessionId))
|
||||
const item = {
|
||||
id: makeId(),
|
||||
title: data.title || '',
|
||||
description: data.description || '',
|
||||
courseTemplate: tpl ? { id: tpl.id, title: tpl.title } : null,
|
||||
courseTemplateTitle: tpl?.title || '',
|
||||
course: course ? { id: course.id, title: course.title } : null,
|
||||
courseTitle: course?.title || '',
|
||||
session: sess ? { id: sess.id, title: sess.title } : null,
|
||||
sessionTitle: sess?.title || '',
|
||||
startDate: data.startDate || '',
|
||||
@@ -57,15 +57,15 @@ register('POST', endpoints.addNewAssignment, ({ data }) => {
|
||||
return { data: item }
|
||||
})
|
||||
|
||||
register('PUT', endpoints.updateAssignment, ({ params, data }) => {
|
||||
const tpl = adminCourseTemplates.find((c) => c.id === Number(data.courseTemplateId))
|
||||
register('PATCH', endpoints.updateAssignment, ({ params, data }) => {
|
||||
const course = adminCourses.find((c) => c.id === Number(data.courseId))
|
||||
const sess = adminSessions.find((s) => s.id === Number(data.sessionId))
|
||||
return {
|
||||
data: updateById(adminAssignments, params.id, {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
courseTemplate: tpl ? { id: tpl.id, title: tpl.title } : undefined,
|
||||
courseTemplateTitle: tpl?.title,
|
||||
course: course ? { id: course.id, title: course.title } : undefined,
|
||||
courseTitle: course?.title,
|
||||
session: sess ? { id: sess.id, title: sess.title } : undefined,
|
||||
sessionTitle: sess?.title,
|
||||
startDate: data.startDate,
|
||||
@@ -83,19 +83,26 @@ register('DELETE', endpoints.deleteAssignment, ({ params }) => {
|
||||
})
|
||||
|
||||
register('GET', endpoints.getAssignmentSubmissions, ({ params, query }) => {
|
||||
const list = assignmentSubmissions.get(Number(params.assignmentId)) || []
|
||||
const list = assignmentSubmissions.get(Number(params.homeworkId)) || []
|
||||
return paginate(list, query)
|
||||
})
|
||||
|
||||
register('GET', endpoints.showAssignmentSubmission, ({ params }) => {
|
||||
const list = assignmentSubmissions.get(Number(params.assignmentId)) || []
|
||||
const found = list.find((s) => String(s.id) === String(params.submissionId))
|
||||
return { data: found || null }
|
||||
})
|
||||
// Backend `/homework-submissions/:submissionId` — submissions live flat, so we
|
||||
// scan every homework's submission list to find the matching id.
|
||||
const findSubmission = (submissionId) => {
|
||||
for (const list of assignmentSubmissions.values()) {
|
||||
const found = list.find((s) => String(s.id) === String(submissionId))
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
register('POST', endpoints.reviewAssignmentSubmission, ({ params, data }) => {
|
||||
const list = assignmentSubmissions.get(Number(params.assignmentId)) || []
|
||||
const sub = list.find((s) => String(s.id) === String(params.submissionId))
|
||||
register('GET', endpoints.showAssignmentSubmission, ({ params }) => ({
|
||||
data: findSubmission(params.submissionId),
|
||||
}))
|
||||
|
||||
register('PATCH', endpoints.reviewAssignmentSubmission, ({ params, data }) => {
|
||||
const sub = findSubmission(params.submissionId)
|
||||
if (sub) {
|
||||
Object.assign(sub, {
|
||||
score: data.score ?? sub.score,
|
||||
|
||||
@@ -2,11 +2,7 @@ import { register } from '@/services/mock/registry'
|
||||
import { endpoints } from '@/services/api/endpoints'
|
||||
import { adminTerms } from '@/services/mock/fixtures/admin-terms'
|
||||
import { adminUsers } from '@/services/mock/fixtures/admin-users'
|
||||
import {
|
||||
adminCourseTemplates,
|
||||
adminOfferedCourses,
|
||||
makeTeacherSnapshot,
|
||||
} from '@/services/mock/fixtures/admin-courses'
|
||||
import { adminCourses, makeTeacherSnapshot } from '@/services/mock/fixtures/admin-courses'
|
||||
import {
|
||||
filterDateRange,
|
||||
filterItems,
|
||||
@@ -18,92 +14,48 @@ import {
|
||||
updateById,
|
||||
} from '@/services/mock/helpers'
|
||||
|
||||
register('GET', endpoints.getCourseTemplatesList, ({ query }) => {
|
||||
let list = filterItems(adminCourseTemplates, query, {
|
||||
title: 'includes',
|
||||
status: (item, v) => String(item.isActiveByDefault ? 1 : 0) === String(v),
|
||||
const resolveTermId = (raw) => {
|
||||
if (raw === undefined || raw === null || raw === '') return null
|
||||
if (raw === 'null') return null
|
||||
const n = Number(raw)
|
||||
return Number.isFinite(n) ? n : null
|
||||
}
|
||||
|
||||
const resolveTeacher = (rawId) => {
|
||||
const id = Number(rawId)
|
||||
const source = adminUsers.find((u) => u.id === id)
|
||||
if (!source) return null
|
||||
return makeTeacherSnapshot({
|
||||
id: source.id,
|
||||
firstName: source.firstName,
|
||||
lastName: source.lastName,
|
||||
name: source.name,
|
||||
email: source.email,
|
||||
phone: source.phone,
|
||||
roles: source.roles,
|
||||
avatarUrl: source.avatarUrl,
|
||||
avatarDownloadUrl: source.avatarDownloadUrl,
|
||||
createdAt: source.createdAt,
|
||||
})
|
||||
list = filterDateRange(list, query)
|
||||
return paginate(list, query)
|
||||
})
|
||||
|
||||
register('GET', endpoints.showCourseTemplate, ({ params }) => ({
|
||||
data: findOrThrow(adminCourseTemplates, params.id),
|
||||
}))
|
||||
|
||||
register('POST', endpoints.addNewCourseTemplate, ({ data }) => {
|
||||
const teacher = adminUsers.find((u) => u.id === Number(data.defaultTeacherId))
|
||||
const item = {
|
||||
id: makeId(),
|
||||
title: data.title || '',
|
||||
description: data.description || '',
|
||||
image: data.imageId ? `https://picsum.photos/seed/course-${data.imageId}/200/200` : '',
|
||||
defaultTeacher: teacher
|
||||
? {
|
||||
id: teacher.id,
|
||||
firstName: teacher.firstName,
|
||||
lastName: teacher.lastName,
|
||||
fullName: `${teacher.firstName} ${teacher.lastName}`,
|
||||
}
|
||||
: null,
|
||||
defaultCapacity: Number(data.defaultCapacity) || 0,
|
||||
isActiveByDefault: !!data.isActiveByDefault,
|
||||
prerequisitesCount: (data.prerequisites || []).length,
|
||||
prerequisites: (data.prerequisites || []).map((id) => ({
|
||||
courseId: id,
|
||||
course: adminCourseTemplates.find((c) => c.id === Number(id)) || { id },
|
||||
})),
|
||||
createdAt: isoNow(),
|
||||
}
|
||||
adminCourseTemplates.unshift(item)
|
||||
return { data: item }
|
||||
})
|
||||
|
||||
register('PUT', endpoints.updateCourseTemplate, ({ params, data }) => {
|
||||
const teacher = adminUsers.find((u) => u.id === Number(data.defaultTeacherId))
|
||||
return {
|
||||
data: updateById(adminCourseTemplates, params.id, {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
image: data.imageId ? `https://picsum.photos/seed/course-${data.imageId}/200/200` : undefined,
|
||||
defaultTeacher: teacher
|
||||
? {
|
||||
id: teacher.id,
|
||||
firstName: teacher.firstName,
|
||||
lastName: teacher.lastName,
|
||||
fullName: `${teacher.firstName} ${teacher.lastName}`,
|
||||
}
|
||||
: undefined,
|
||||
defaultCapacity: Number(data.defaultCapacity) || 0,
|
||||
isActiveByDefault: !!data.isActiveByDefault,
|
||||
prerequisites: (data.prerequisites || []).map((id) => ({
|
||||
courseId: id,
|
||||
course: adminCourseTemplates.find((c) => c.id === Number(id)) || { id },
|
||||
})),
|
||||
prerequisitesCount: (data.prerequisites || []).length,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
register('DELETE', endpoints.deleteCourseTemplate, ({ params }) => {
|
||||
removeById(adminCourseTemplates, params.id)
|
||||
return { data: { message: 'حذف موفق' } }
|
||||
})
|
||||
|
||||
register('POST', endpoints.changeStatusCourseTemplate, ({ params, data }) => ({
|
||||
data: updateById(adminCourseTemplates, params.id, {
|
||||
isActiveByDefault: !!data.isActiveByDefault,
|
||||
}),
|
||||
}))
|
||||
}
|
||||
|
||||
register('GET', endpoints.getCoursesList, ({ query }) => {
|
||||
let list = filterItems(adminOfferedCourses, query, {
|
||||
let list = adminCourses
|
||||
if (query?.termId !== undefined && query?.termId !== '') {
|
||||
if (String(query.termId) === 'null') {
|
||||
list = list.filter((c) => c.termId == null)
|
||||
} else {
|
||||
const tid = Number(query.termId)
|
||||
list = list.filter((c) => c.termId === tid)
|
||||
}
|
||||
}
|
||||
const { termId: _omitted, ...rest } = query || {}
|
||||
list = filterItems(list, rest, {
|
||||
title: 'includes',
|
||||
termId: 'eq',
|
||||
status: (item, v) => String(item.isActive ? 1 : 0) === String(v),
|
||||
})
|
||||
list = filterDateRange(list, query, 'startDate')
|
||||
const { data: items, meta } = paginate(list, query)
|
||||
list = filterDateRange(list, rest, 'startDate')
|
||||
const { data: items, meta } = paginate(list, rest)
|
||||
return {
|
||||
success: true,
|
||||
message: 'OK',
|
||||
@@ -114,54 +66,40 @@ register('GET', endpoints.getCoursesList, ({ query }) => {
|
||||
register('GET', endpoints.showCourse, ({ params }) => ({
|
||||
success: true,
|
||||
message: 'OK',
|
||||
data: findOrThrow(adminOfferedCourses, params.id),
|
||||
data: findOrThrow(adminCourses, params.id),
|
||||
}))
|
||||
|
||||
register('POST', endpoints.addNewCourse, ({ data }) => {
|
||||
const template = adminCourseTemplates.find((t) => t.id === Number(data.templateId))
|
||||
const term = adminTerms.find((t) => t.id === Number(data.termId))
|
||||
const teacherSource = adminUsers.find((u) => u.id === Number(data.teacherId))
|
||||
const teacher = teacherSource
|
||||
? makeTeacherSnapshot({
|
||||
id: teacherSource.id,
|
||||
firstName: teacherSource.firstName,
|
||||
lastName: teacherSource.lastName,
|
||||
name: teacherSource.name,
|
||||
email: teacherSource.email,
|
||||
phone: teacherSource.phone,
|
||||
roles: teacherSource.roles,
|
||||
avatarUrl: teacherSource.avatarUrl,
|
||||
avatarDownloadUrl: teacherSource.avatarDownloadUrl,
|
||||
createdAt: teacherSource.createdAt,
|
||||
})
|
||||
: null
|
||||
const termId = resolveTermId(data.termId)
|
||||
const term = termId == null ? null : adminTerms.find((t) => t.id === termId)
|
||||
const teacher = resolveTeacher(data.teacherId)
|
||||
const coverFromUpload = data.imageId
|
||||
? `https://picsum.photos/seed/offered-${data.imageId}/200/200`
|
||||
? `https://picsum.photos/seed/course-${data.imageId}/200/200`
|
||||
: ''
|
||||
const coverUrl = data.coverUrl ?? coverFromUpload
|
||||
const item = {
|
||||
// --- spec ---
|
||||
id: makeId(),
|
||||
termId: term?.id ?? Number(data.termId) ?? null,
|
||||
termId: term?.id ?? termId ?? null,
|
||||
teacherId: teacher?.id ?? (Number(data.teacherId) || null),
|
||||
title: data.title || template?.title || '',
|
||||
title: data.title || '',
|
||||
description: data.description || '',
|
||||
capacity: Number(data.capacity) || 0,
|
||||
isActive: data.isActive !== undefined ? !!data.isActive : true,
|
||||
isActive: data.isActive === undefined ? true : !!data.isActive,
|
||||
coverUrl,
|
||||
|
||||
// --- ui-only ---
|
||||
image: coverUrl,
|
||||
template: template ? { id: template.id, title: template.title } : null,
|
||||
templateId: template?.id,
|
||||
term: term ? { id: term.id, title: term.title } : null,
|
||||
teacher,
|
||||
prerequisitesCount: 0,
|
||||
term: term ? { id: term.id, title: term.title } : null,
|
||||
sessionsCount: Number(data.sessionsCount) || 0,
|
||||
prerequisitesCount: (data.prerequisites || []).length,
|
||||
prerequisites: (data.prerequisites || []).map((id) => ({
|
||||
courseId: id,
|
||||
course: adminCourses.find((c) => c.id === Number(id)) || { id },
|
||||
})),
|
||||
startDate: term?.startDate || '',
|
||||
endDate: term?.endDate || '',
|
||||
createdAt: isoNow(),
|
||||
}
|
||||
adminOfferedCourses.unshift(item)
|
||||
adminCourses.unshift(item)
|
||||
return {
|
||||
success: true,
|
||||
message: 'Course created.',
|
||||
@@ -174,43 +112,34 @@ register('PATCH', endpoints.updateCourse, ({ params, data }) => {
|
||||
if (data.title !== undefined) patch.title = data.title
|
||||
if (data.description !== undefined) patch.description = data.description
|
||||
if (data.capacity !== undefined) patch.capacity = Number(data.capacity) || 0
|
||||
if (data.sessionsCount !== undefined) patch.sessionsCount = Number(data.sessionsCount) || 0
|
||||
if (data.isActive !== undefined) patch.isActive = !!data.isActive
|
||||
if (data.termId !== undefined) {
|
||||
patch.termId = Number(data.termId) || null
|
||||
const term = adminTerms.find((t) => t.id === Number(data.termId))
|
||||
if (term) patch.term = { id: term.id, title: term.title }
|
||||
const termId = resolveTermId(data.termId)
|
||||
patch.termId = termId
|
||||
const term = termId == null ? null : adminTerms.find((t) => t.id === termId)
|
||||
patch.term = term ? { id: term.id, title: term.title } : null
|
||||
}
|
||||
if (data.teacherId !== undefined) {
|
||||
patch.teacherId = Number(data.teacherId) || null
|
||||
const t = adminUsers.find((u) => u.id === Number(data.teacherId))
|
||||
if (t) {
|
||||
patch.teacher = makeTeacherSnapshot({
|
||||
id: t.id,
|
||||
firstName: t.firstName,
|
||||
lastName: t.lastName,
|
||||
name: t.name,
|
||||
email: t.email,
|
||||
phone: t.phone,
|
||||
roles: t.roles,
|
||||
avatarUrl: t.avatarUrl,
|
||||
avatarDownloadUrl: t.avatarDownloadUrl,
|
||||
createdAt: t.createdAt,
|
||||
})
|
||||
}
|
||||
const teacher = resolveTeacher(data.teacherId)
|
||||
patch.teacherId = teacher?.id ?? null
|
||||
patch.teacher = teacher
|
||||
}
|
||||
if (data.templateId !== undefined) {
|
||||
const template = adminCourseTemplates.find((t) => t.id === Number(data.templateId))
|
||||
patch.templateId = template?.id
|
||||
if (template) patch.template = { id: template.id, title: template.title }
|
||||
if (data.prerequisites !== undefined) {
|
||||
patch.prerequisites = (data.prerequisites || []).map((id) => ({
|
||||
courseId: id,
|
||||
course: adminCourses.find((c) => c.id === Number(id)) || { id },
|
||||
}))
|
||||
patch.prerequisitesCount = (data.prerequisites || []).length
|
||||
}
|
||||
if (data.coverUrl !== undefined || data.imageId !== undefined) {
|
||||
const url =
|
||||
data.coverUrl ??
|
||||
(data.imageId ? `https://picsum.photos/seed/offered-${data.imageId}/200/200` : '')
|
||||
(data.imageId ? `https://picsum.photos/seed/course-${data.imageId}/200/200` : '')
|
||||
patch.coverUrl = url
|
||||
patch.image = url
|
||||
}
|
||||
const updated = updateById(adminOfferedCourses, params.id, patch)
|
||||
const updated = updateById(adminCourses, params.id, patch)
|
||||
return {
|
||||
success: true,
|
||||
message: 'Course updated.',
|
||||
@@ -219,14 +148,10 @@ register('PATCH', endpoints.updateCourse, ({ params, data }) => {
|
||||
})
|
||||
|
||||
register('DELETE', endpoints.deleteCourse, ({ params }) => {
|
||||
removeById(adminOfferedCourses, params.id)
|
||||
removeById(adminCourses, params.id)
|
||||
return {
|
||||
success: true,
|
||||
message: 'Course deleted.',
|
||||
data: null,
|
||||
}
|
||||
})
|
||||
|
||||
register('POST', endpoints.changeStatusCourse, ({ params, data }) => ({
|
||||
data: updateById(adminOfferedCourses, params.id, { isActive: !!data.isActive }),
|
||||
}))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { register } from '@/services/mock/registry'
|
||||
import { endpoints } from '@/services/api/endpoints'
|
||||
import { adminCourses } from '@/services/mock/fixtures/admin-courses'
|
||||
import { adminSessions } from '@/services/mock/fixtures/admin-sessions'
|
||||
import { adminCourseTemplates } from '@/services/mock/fixtures/admin-courses'
|
||||
import { adminExams, examParticipants } from '@/services/mock/fixtures/admin-exams'
|
||||
import {
|
||||
filterDateRange,
|
||||
@@ -14,74 +14,139 @@ import {
|
||||
updateById,
|
||||
} from '@/services/mock/helpers'
|
||||
|
||||
const resolveCourseFromSession = (session) => {
|
||||
const courseId = session?.course?.id ?? session?.courseId
|
||||
if (courseId == null) return null
|
||||
return adminCourses.find((c) => c.id === Number(courseId)) || null
|
||||
}
|
||||
|
||||
const sessionSnapshot = (session) => (session ? { id: session.id, title: session.title } : null)
|
||||
|
||||
const courseSnapshot = (course) => (course ? { id: course.id, title: course.title } : null)
|
||||
|
||||
register('GET', endpoints.getExamsList, ({ query }) => {
|
||||
let list = filterItems(adminExams, query, {
|
||||
title: 'includes',
|
||||
courseTemplateId: (item, v) => String(item.courseTemplate?.id) === String(v),
|
||||
courseId: (item, v) => String(item.course?.id) === String(v),
|
||||
sessionId: (item, v) => String(item.sessionId) === String(v),
|
||||
})
|
||||
list = filterDateRange(list, query)
|
||||
return paginate(list, query)
|
||||
const { data: items, meta } = paginate(list, query)
|
||||
return {
|
||||
success: true,
|
||||
message: 'OK',
|
||||
data: items,
|
||||
meta,
|
||||
}
|
||||
})
|
||||
|
||||
register('GET', endpoints.showExam, ({ params }) => ({
|
||||
success: true,
|
||||
message: 'OK',
|
||||
data: findOrThrow(adminExams, params.id),
|
||||
}))
|
||||
|
||||
register('POST', endpoints.addNewExam, ({ data }) => {
|
||||
const session = adminSessions.find((s) => s.id === Number(data.sessionId))
|
||||
const template = session?.courseTemplate
|
||||
? adminCourseTemplates.find((c) => c.id === Number(session.courseTemplate.id))
|
||||
: null
|
||||
const course = resolveCourseFromSession(session)
|
||||
const item = {
|
||||
id: makeId(),
|
||||
sessionId: session?.id ?? Number(data.sessionId) ?? null,
|
||||
title: data.title || '',
|
||||
description: data.description || '',
|
||||
session: session ? { id: session.id, title: session.title } : null,
|
||||
passScore: Number(data.passingScore ?? data.passScore) || 0,
|
||||
isActive: data.isActive ?? true,
|
||||
questions: [],
|
||||
// UI-only fallbacks for ExamItem / ExamDetailsModal.
|
||||
session: sessionSnapshot(session),
|
||||
sessionTitle: session?.title || '',
|
||||
courseTemplate: template ? { id: template.id, title: template.title } : null,
|
||||
courseTemplateTitle: template?.title || '',
|
||||
durationMinutes: Number(data.durationMinutes) || 0,
|
||||
passingScore: Number(data.passingScore) || 0,
|
||||
questionsCount: data.questions?.length || 0,
|
||||
randomize: !!data.randomize,
|
||||
endDate: data.endDate || '',
|
||||
startDate: isoNow(),
|
||||
course: courseSnapshot(course),
|
||||
courseTitle: course?.title || '',
|
||||
questionsCount: 0,
|
||||
createdAt: isoNow(),
|
||||
questions: data.questions || [],
|
||||
usedInTerms: [],
|
||||
}
|
||||
adminExams.unshift(item)
|
||||
return { data: item }
|
||||
return {
|
||||
success: true,
|
||||
message: 'Exam created.',
|
||||
data: item,
|
||||
}
|
||||
})
|
||||
|
||||
register('PUT', endpoints.updateExam, ({ params, data }) => {
|
||||
const session = adminSessions.find((s) => s.id === Number(data.sessionId))
|
||||
const template = session?.courseTemplate
|
||||
? adminCourseTemplates.find((c) => c.id === Number(session.courseTemplate.id))
|
||||
: null
|
||||
register('PATCH', endpoints.updateExam, ({ params, data }) => {
|
||||
const patch = {}
|
||||
if (data.title !== undefined) patch.title = data.title
|
||||
if (data.description !== undefined) patch.description = data.description
|
||||
if (data.passingScore !== undefined) patch.passScore = Number(data.passingScore) || 0
|
||||
if (data.passScore !== undefined) patch.passScore = Number(data.passScore) || 0
|
||||
if (data.isActive !== undefined) patch.isActive = !!data.isActive
|
||||
if (data.sessionId !== undefined) {
|
||||
const session = adminSessions.find((s) => s.id === Number(data.sessionId))
|
||||
const course = resolveCourseFromSession(session)
|
||||
patch.sessionId = session?.id ?? Number(data.sessionId) ?? null
|
||||
patch.session = sessionSnapshot(session)
|
||||
patch.sessionTitle = session?.title ?? ''
|
||||
patch.course = courseSnapshot(course)
|
||||
patch.courseTitle = course?.title ?? ''
|
||||
}
|
||||
return {
|
||||
data: updateById(adminExams, params.id, {
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
session: session ? { id: session.id, title: session.title } : undefined,
|
||||
sessionTitle: session?.title,
|
||||
courseTemplate: template ? { id: template.id, title: template.title } : undefined,
|
||||
courseTemplateTitle: template?.title,
|
||||
durationMinutes: Number(data.durationMinutes) || 0,
|
||||
passingScore: Number(data.passingScore) || 0,
|
||||
questionsCount: data.questions?.length || 0,
|
||||
randomize: !!data.randomize,
|
||||
endDate: data.endDate,
|
||||
questions: data.questions || [],
|
||||
}),
|
||||
success: true,
|
||||
message: 'Exam updated.',
|
||||
data: updateById(adminExams, params.id, patch),
|
||||
}
|
||||
})
|
||||
|
||||
register('DELETE', endpoints.deleteExam, ({ params }) => {
|
||||
removeById(adminExams, params.id)
|
||||
return { data: { message: 'حذف موفق' } }
|
||||
return { success: true, message: 'Exam deleted.', data: null }
|
||||
})
|
||||
|
||||
// POST /exams/:examId/questions — atomic question + options create.
|
||||
register('POST', endpoints.addExamQuestion, ({ params, data }) => {
|
||||
const exam = findOrThrow(adminExams, params.examId)
|
||||
const questionId = makeId()
|
||||
const options = Array.isArray(data.options) ? data.options : []
|
||||
const question = {
|
||||
id: questionId,
|
||||
questionText: data.questionText || '',
|
||||
position: data.position ?? (exam.questions?.length ?? 0) + 1,
|
||||
options: options.map((opt, idx) => ({
|
||||
id: questionId * 100 + idx + 1,
|
||||
optionText: opt.optionText || '',
|
||||
isCorrect: !!opt.isCorrect,
|
||||
})),
|
||||
}
|
||||
exam.questions = [...(exam.questions || []), question]
|
||||
exam.questionsCount = exam.questions.length
|
||||
return {
|
||||
success: true,
|
||||
message: 'Question added.',
|
||||
data: question,
|
||||
}
|
||||
})
|
||||
|
||||
// POST /questions/:questionId/options — single option append.
|
||||
register('POST', endpoints.addQuestionOption, ({ params, data }) => {
|
||||
for (const exam of adminExams) {
|
||||
const question = (exam.questions || []).find((q) => String(q.id) === String(params.questionId))
|
||||
if (question) {
|
||||
const option = {
|
||||
id: makeId(),
|
||||
optionText: data.optionText || '',
|
||||
isCorrect: !!data.isCorrect,
|
||||
}
|
||||
question.options = [...(question.options || []), option]
|
||||
return {
|
||||
success: true,
|
||||
message: 'Option added.',
|
||||
data: option,
|
||||
}
|
||||
}
|
||||
}
|
||||
return { success: false, message: 'Question not found.', data: null }
|
||||
})
|
||||
|
||||
// FE-mock-only.
|
||||
register('GET', endpoints.getExamParticipants, ({ params, query }) => {
|
||||
const list = examParticipants.get(Number(params.examId)) || []
|
||||
return paginate(list, query)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { SESSION_TYPE } from '@/enums'
|
||||
import { register } from '@/services/mock/registry'
|
||||
import { endpoints } from '@/services/api/endpoints'
|
||||
import { adminCourseTemplates, adminOfferedCourses } from '@/services/mock/fixtures/admin-courses'
|
||||
import { adminCourses } from '@/services/mock/fixtures/admin-courses'
|
||||
import {
|
||||
adminSessions,
|
||||
makeSession,
|
||||
@@ -23,25 +23,10 @@ const enrich = (session) => ({
|
||||
sessionTypeFa: SESSION_TYPE[session.sessionType] || session.sessionTypeFa || '',
|
||||
})
|
||||
|
||||
const courseSnapshot = (offered) =>
|
||||
offered
|
||||
? {
|
||||
id: offered.id,
|
||||
termId: offered.termId,
|
||||
teacherId: offered.teacherId,
|
||||
title: offered.title,
|
||||
description: offered.description,
|
||||
capacity: offered.capacity,
|
||||
isActive: offered.isActive,
|
||||
coverUrl: offered.coverUrl,
|
||||
}
|
||||
: null
|
||||
|
||||
register('GET', endpoints.getSessionsList, ({ query }) => {
|
||||
let list = filterItems(adminSessions, query, {
|
||||
title: 'includes',
|
||||
courseTemplateId: (item, v) => String(item.courseTemplate?.id) === String(v),
|
||||
courseId: 'eq',
|
||||
courseId: (item, v) => String(item.course?.id ?? item.courseId) === String(v),
|
||||
sessionType: 'eq',
|
||||
type: 'eq',
|
||||
})
|
||||
@@ -57,35 +42,41 @@ register('GET', endpoints.getSessionsList, ({ query }) => {
|
||||
|
||||
register('GET', endpoints.showSession, ({ params }) => {
|
||||
const session = enrich(findOrThrow(adminSessions, params.id))
|
||||
const offered = adminOfferedCourses.find((c) => c.id === session.courseId)
|
||||
const course = adminCourses.find((c) => c.id === session.courseId)
|
||||
return {
|
||||
success: true,
|
||||
message: 'OK',
|
||||
data: { ...session, course: courseSnapshot(offered) },
|
||||
data: { ...session, course: course ? { id: course.id, title: course.title } : session.course },
|
||||
}
|
||||
})
|
||||
|
||||
// Form sends a flat payload (startTime, endTime, meetingLink, contentType, etc.).
|
||||
// We mirror those into the legacy `sessionConfig` shape too so SessionDetailsModal
|
||||
// (which reads `sessionConfig.startTime` / `sessionConfig.meetingLink`) keeps working.
|
||||
const buildSessionConfig = (data) => ({
|
||||
startTime: data.startTime ?? data.sessionConfig?.startTime ?? null,
|
||||
endTime: data.endTime ?? data.sessionConfig?.endTime ?? null,
|
||||
meetingLink: data.meetingLink ?? data.sessionConfig?.meetingLink ?? null,
|
||||
})
|
||||
|
||||
register('POST', endpoints.addNewSession, ({ data }) => {
|
||||
const tpl = adminCourseTemplates.find((c) => c.id === Number(data.courseTemplateId))
|
||||
const offered =
|
||||
adminOfferedCourses.find((c) => c.id === Number(data.courseId)) ||
|
||||
(tpl ? adminOfferedCourses.find((c) => c.templateId === tpl.id) : null)
|
||||
const course = adminCourses.find((c) => c.id === Number(data.courseId))
|
||||
const sessionConfig = buildSessionConfig(data)
|
||||
const item = makeSession({
|
||||
id: makeId(),
|
||||
courseId: data.courseId ?? offered?.id ?? null,
|
||||
courseId: course?.id ?? (data.courseId ? Number(data.courseId) : null),
|
||||
title: data.title || '',
|
||||
description: data.description || '',
|
||||
type: data.type,
|
||||
startsAt: data.startsAt ?? data.sessionConfig?.startTime ?? null,
|
||||
location: data.location ?? data.sessionConfig?.location ?? null,
|
||||
link: data.link ?? data.sessionConfig?.meetingLink ?? null,
|
||||
image: data.imageId ? `https://picsum.photos/seed/session-${data.imageId}/200/200` : '',
|
||||
courseTemplate: tpl ? { id: tpl.id, title: tpl.title } : null,
|
||||
type: data.sessionType === 'online' ? 'online' : 'offline',
|
||||
startsAt: sessionConfig.startTime,
|
||||
endsAt: sessionConfig.endTime,
|
||||
link: sessionConfig.meetingLink,
|
||||
course: course ? { id: course.id, title: course.title } : null,
|
||||
sessionType: data.sessionType || 'in_person',
|
||||
contentType: data.contentType || '',
|
||||
contentMediaId: data.contentMediaId ?? null,
|
||||
durationMinutes: Number(data.durationMinutes) || 0,
|
||||
order: Number(data.order) || 1,
|
||||
sessionConfig: data.sessionConfig || {},
|
||||
materials: data.materials || [],
|
||||
sessionConfig,
|
||||
createdAt: isoNow(),
|
||||
})
|
||||
adminSessions.unshift(item)
|
||||
@@ -100,35 +91,34 @@ register('PATCH', endpoints.updateSession, ({ params, data }) => {
|
||||
const patch = {}
|
||||
if (data.title !== undefined) patch.title = data.title
|
||||
if (data.description !== undefined) patch.description = data.description
|
||||
if (data.type !== undefined) patch.type = data.type
|
||||
if (data.startsAt !== undefined) patch.startsAt = data.startsAt
|
||||
if (data.location !== undefined) patch.location = data.location
|
||||
if (data.link !== undefined) patch.link = data.link
|
||||
if (data.courseId !== undefined) patch.courseId = Number(data.courseId) || null
|
||||
if (data.courseTemplateId !== undefined) {
|
||||
const tpl = adminCourseTemplates.find((c) => c.id === Number(data.courseTemplateId))
|
||||
if (tpl) patch.courseTemplate = { id: tpl.id, title: tpl.title }
|
||||
if (data.startTime !== undefined) patch.startsAt = data.startTime
|
||||
if (data.endTime !== undefined) patch.endsAt = data.endTime
|
||||
if (data.meetingLink !== undefined) patch.link = data.meetingLink
|
||||
if (data.courseId !== undefined) {
|
||||
patch.courseId = Number(data.courseId) || null
|
||||
const course = adminCourses.find((c) => c.id === Number(data.courseId))
|
||||
patch.course = course ? { id: course.id, title: course.title } : null
|
||||
}
|
||||
if (data.sessionType !== undefined) patch.sessionType = data.sessionType
|
||||
if (data.sessionType !== undefined) {
|
||||
patch.sessionType = data.sessionType
|
||||
patch.type = data.sessionType === 'online' ? 'online' : 'offline'
|
||||
}
|
||||
if (data.contentType !== undefined) patch.contentType = data.contentType
|
||||
if (data.contentMediaId !== undefined) patch.contentMediaId = data.contentMediaId
|
||||
if (data.durationMinutes !== undefined) patch.durationMinutes = Number(data.durationMinutes) || 0
|
||||
if (data.order !== undefined) patch.order = Number(data.order) || 1
|
||||
if (data.sessionConfig !== undefined) {
|
||||
patch.sessionConfig = data.sessionConfig
|
||||
if (data.startsAt === undefined && data.sessionConfig.startTime !== undefined) {
|
||||
patch.startsAt = data.sessionConfig.startTime
|
||||
// Keep sessionConfig in sync for SessionDetailsModal display fallback.
|
||||
if (
|
||||
data.startTime !== undefined ||
|
||||
data.endTime !== undefined ||
|
||||
data.meetingLink !== undefined
|
||||
) {
|
||||
const existing = findOrThrow(adminSessions, params.id)?.sessionConfig || {}
|
||||
patch.sessionConfig = {
|
||||
...existing,
|
||||
startTime: data.startTime ?? existing.startTime,
|
||||
endTime: data.endTime ?? existing.endTime,
|
||||
meetingLink: data.meetingLink ?? existing.meetingLink,
|
||||
}
|
||||
if (data.location === undefined && data.sessionConfig.location !== undefined) {
|
||||
patch.location = data.sessionConfig.location
|
||||
}
|
||||
if (data.link === undefined && data.sessionConfig.meetingLink !== undefined) {
|
||||
patch.link = data.sessionConfig.meetingLink
|
||||
}
|
||||
}
|
||||
if (data.materials !== undefined) patch.materials = data.materials
|
||||
if (data.imageId !== undefined) {
|
||||
patch.image = data.imageId
|
||||
? `https://picsum.photos/seed/session-${data.imageId}/200/200`
|
||||
: ''
|
||||
}
|
||||
const updated = updateById(adminSessions, params.id, patch)
|
||||
return {
|
||||
@@ -147,11 +137,6 @@ register('DELETE', endpoints.deleteSession, ({ params }) => {
|
||||
}
|
||||
})
|
||||
|
||||
register('POST', endpoints.changeStatusSession, ({ params, data }) => {
|
||||
const updated = updateById(adminSessions, params.id, { isActive: !!data.isActive })
|
||||
return { data: enrich(updated) }
|
||||
})
|
||||
|
||||
register('GET', endpoints.getSessionsAttendance, ({ params, query }) => {
|
||||
const list = sessionAttendance.get(Number(params.sessionId)) || []
|
||||
return paginate(list, query)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { register } from '@/services/mock/registry'
|
||||
import { endpoints } from '@/services/api/endpoints'
|
||||
import { adminUsers } from '@/services/mock/fixtures/admin-users'
|
||||
import { adminOfferedCourses } from '@/services/mock/fixtures/admin-courses'
|
||||
import { adminCourses } from '@/services/mock/fixtures/admin-courses'
|
||||
import { adminTerms, makeTerm, termStudentLinks } from '@/services/mock/fixtures/admin-terms'
|
||||
import {
|
||||
filterDateRange,
|
||||
@@ -106,10 +106,6 @@ register('POST', endpoints.cloneTerm, ({ params }) => {
|
||||
return { data: clone }
|
||||
})
|
||||
|
||||
register('POST', endpoints.changeStatusTerm, ({ params, data }) => ({
|
||||
data: updateById(adminTerms, params.id, { isActive: !!data.isActive }),
|
||||
}))
|
||||
|
||||
const studentSnapshot = (link) => {
|
||||
const user = adminUsers.find((u) => u.id === link.userId)
|
||||
return user ? { ...user, isOnLeave: link.isOnLeave } : null
|
||||
@@ -161,7 +157,7 @@ register('POST', endpoints.changeLeaveStatus, ({ params, data }) => {
|
||||
|
||||
register('GET', endpoints.listCourseTerm, ({ params, query }) => {
|
||||
const termId = Number(params.termId)
|
||||
const list = adminOfferedCourses.filter((c) => Number(c.termId) === termId)
|
||||
const list = adminCourses.filter((c) => Number(c.termId) === termId)
|
||||
return paginate(list, query)
|
||||
})
|
||||
|
||||
@@ -170,7 +166,7 @@ register('POST', endpoints.addCourseTerm, ({ params, data }) => {
|
||||
const ids = Array.isArray(data.courseIds) ? data.courseIds : []
|
||||
const linked = []
|
||||
ids.forEach((id) => {
|
||||
const course = adminOfferedCourses.find((c) => Number(c.id) === Number(id))
|
||||
const course = adminCourses.find((c) => Number(c.id) === Number(id))
|
||||
if (course) {
|
||||
course.termId = termId
|
||||
course.term = adminTerms.some((t) => t.id === termId)
|
||||
@@ -184,7 +180,7 @@ register('POST', endpoints.addCourseTerm, ({ params, data }) => {
|
||||
|
||||
register('DELETE', endpoints.removeCourseTerm, ({ params }) => {
|
||||
const courseId = Number(params.courseId)
|
||||
const course = adminOfferedCourses.find((c) => c.id === courseId)
|
||||
const course = adminCourses.find((c) => c.id === courseId)
|
||||
if (course) {
|
||||
course.termId = null
|
||||
course.term = null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { register } from '@/services/mock/registry'
|
||||
import { endpoints } from '@/services/api/endpoints'
|
||||
import { adminTickets } from '@/services/mock/fixtures/admin-tickets'
|
||||
import { adminTickets, currentAdminUser } from '@/services/mock/fixtures/admin-tickets'
|
||||
import {
|
||||
filterDateRange,
|
||||
filterItems,
|
||||
@@ -14,16 +14,28 @@ import {
|
||||
register('GET', endpoints.getTicketsList, ({ query }) => {
|
||||
let list = filterItems(adminTickets, query, {
|
||||
status: 'eq',
|
||||
subject: (item, v) =>
|
||||
String(item.subject || '')
|
||||
.toLowerCase()
|
||||
.includes(String(v).toLowerCase()),
|
||||
userName: (item, v) =>
|
||||
`${item.user?.firstName || ''} ${item.user?.lastName || ''}`
|
||||
String(item.student?.name || '')
|
||||
.toLowerCase()
|
||||
.includes(String(v).toLowerCase()),
|
||||
})
|
||||
list = filterDateRange(list, query)
|
||||
return paginate(list, query)
|
||||
const { data: items, meta } = paginate(list, query)
|
||||
return {
|
||||
success: true,
|
||||
message: 'OK',
|
||||
data: items,
|
||||
meta,
|
||||
}
|
||||
})
|
||||
|
||||
register('GET', endpoints.showTicket, ({ params }) => ({
|
||||
success: true,
|
||||
message: 'OK',
|
||||
data: findOrThrow(adminTickets, params.id),
|
||||
}))
|
||||
|
||||
@@ -31,25 +43,25 @@ register('POST', endpoints.sendTicketMessage, ({ params, data }) => {
|
||||
const ticket = findOrThrow(adminTickets, params.id)
|
||||
const message = {
|
||||
id: makeId(),
|
||||
sender: 'admin',
|
||||
text: data.text || '',
|
||||
time: 'همین الان',
|
||||
sentAt: isoNow(),
|
||||
ticketId: ticket.id,
|
||||
senderId: currentAdminUser.id,
|
||||
message: data.message || '',
|
||||
createdAt: isoNow(),
|
||||
sender: currentAdminUser,
|
||||
}
|
||||
ticket.messages = [...(ticket.messages || []), message]
|
||||
ticket.status = 'answered'
|
||||
ticket.statusLabel = 'پاسخ داده شده'
|
||||
return { data: ticket }
|
||||
ticket.assigneeId = currentAdminUser.id
|
||||
ticket.assignee = currentAdminUser
|
||||
return {
|
||||
success: true,
|
||||
message: 'Message posted.',
|
||||
data: message,
|
||||
}
|
||||
})
|
||||
|
||||
register('POST', endpoints.changeTicketStatus, ({ params, data }) => ({
|
||||
data: updateById(adminTickets, params.id, {
|
||||
status: data.status,
|
||||
statusLabel:
|
||||
data.status === 'closed'
|
||||
? 'بسته شده'
|
||||
: data.status === 'answered'
|
||||
? 'پاسخ داده شده'
|
||||
: 'در انتظار پاسخ',
|
||||
}),
|
||||
register('PATCH', endpoints.changeTicketStatus, ({ params, data }) => ({
|
||||
success: true,
|
||||
message: 'Ticket status updated.',
|
||||
data: updateById(adminTickets, params.id, { status: data.status }),
|
||||
}))
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
import { cleanFilters } from '@/utils/clean-filters'
|
||||
import { useMutation, useQuery } from '@tanstack/vue-query'
|
||||
import {
|
||||
apiAddAdminCourseTemplate,
|
||||
apiAddAdminTemplateStudent,
|
||||
apiAttachAdminTemplateSession,
|
||||
apiChangeAdminCourseTemplateStatus,
|
||||
apiDeleteAdminCourseTemplate,
|
||||
apiDetachAdminTemplateSession,
|
||||
apiGetAdminCourseTemplates,
|
||||
apiGetAdminTemplateSessions,
|
||||
apiGetAdminTemplateStudents,
|
||||
apiRemoveAdminTemplateStudent,
|
||||
apiShowAdminCourseTemplate,
|
||||
apiUpdateAdminCourseTemplate,
|
||||
} from '@/services/api/admin-course-templates'
|
||||
|
||||
export const adminCourseTemplatesKeys = {
|
||||
all: ['admin', 'course-templates'],
|
||||
list: (filters, pagination) => ['admin', 'course-templates', 'list', filters, pagination],
|
||||
detail: (id) => ['admin', 'course-templates', 'detail', id],
|
||||
students: (templateId, filters, pagination) => [
|
||||
'admin',
|
||||
'course-templates',
|
||||
'students',
|
||||
templateId,
|
||||
filters,
|
||||
pagination,
|
||||
],
|
||||
sessions: (templateId, filters, pagination) => [
|
||||
'admin',
|
||||
'course-templates',
|
||||
'sessions',
|
||||
templateId,
|
||||
filters,
|
||||
pagination,
|
||||
],
|
||||
}
|
||||
|
||||
export const useAdminCourseTemplatesListQuery = (filtersRef, paginationRef, options = {}) =>
|
||||
useQuery({
|
||||
queryKey: ['admin', 'course-templates', 'list', filtersRef, paginationRef],
|
||||
queryFn: () =>
|
||||
apiGetAdminCourseTemplates({
|
||||
...cleanFilters(filtersRef.value),
|
||||
...paginationRef.value,
|
||||
}),
|
||||
...options,
|
||||
})
|
||||
|
||||
export const useAdminCourseTemplateQuery = (idRef, options = {}) =>
|
||||
useQuery({
|
||||
queryKey: ['admin', 'course-templates', 'detail', idRef],
|
||||
queryFn: () => apiShowAdminCourseTemplate(idRef.value),
|
||||
select: (response) => response?.data ?? response,
|
||||
...options,
|
||||
})
|
||||
|
||||
export const useAddAdminCourseTemplateMutation = () =>
|
||||
useMutation({ mutationFn: (payload) => apiAddAdminCourseTemplate(payload) })
|
||||
|
||||
export const useUpdateAdminCourseTemplateMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ id, payload }) => apiUpdateAdminCourseTemplate(id, payload),
|
||||
})
|
||||
|
||||
export const useDeleteAdminCourseTemplateMutation = () =>
|
||||
useMutation({ mutationFn: (id) => apiDeleteAdminCourseTemplate(id) })
|
||||
|
||||
export const useChangeAdminCourseTemplateStatusMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ id, payload }) => apiChangeAdminCourseTemplateStatus(id, payload),
|
||||
})
|
||||
|
||||
export const useAdminTemplateStudentsQuery = (
|
||||
templateIdRef,
|
||||
filtersRef,
|
||||
paginationRef,
|
||||
options = {}
|
||||
) =>
|
||||
useQuery({
|
||||
queryKey: ['admin', 'course-templates', 'students', templateIdRef, filtersRef, paginationRef],
|
||||
queryFn: () =>
|
||||
apiGetAdminTemplateStudents(templateIdRef.value, {
|
||||
...cleanFilters(filtersRef?.value || {}),
|
||||
...paginationRef?.value,
|
||||
}),
|
||||
...options,
|
||||
})
|
||||
|
||||
export const useAddAdminTemplateStudentMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ templateId, payload }) => apiAddAdminTemplateStudent(templateId, payload),
|
||||
})
|
||||
|
||||
export const useRemoveAdminTemplateStudentMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ templateId, userId }) => apiRemoveAdminTemplateStudent(templateId, userId),
|
||||
})
|
||||
|
||||
export const useAdminTemplateSessionsQuery = (
|
||||
templateIdRef,
|
||||
filtersRef,
|
||||
paginationRef,
|
||||
options = {}
|
||||
) =>
|
||||
useQuery({
|
||||
queryKey: ['admin', 'course-templates', 'sessions', templateIdRef, filtersRef, paginationRef],
|
||||
queryFn: () =>
|
||||
apiGetAdminTemplateSessions(templateIdRef.value, {
|
||||
...cleanFilters(filtersRef?.value || {}),
|
||||
...paginationRef?.value,
|
||||
}),
|
||||
...options,
|
||||
})
|
||||
|
||||
export const useAttachAdminTemplateSessionMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ templateId, payload }) => apiAttachAdminTemplateSession(templateId, payload),
|
||||
})
|
||||
|
||||
export const useDetachAdminTemplateSessionMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ templateId, sessionId }) => apiDetachAdminTemplateSession(templateId, sessionId),
|
||||
})
|
||||
@@ -2,9 +2,14 @@ import { cleanFilters } from '@/utils/clean-filters'
|
||||
import { useMutation, useQuery } from '@tanstack/vue-query'
|
||||
import {
|
||||
apiAddAdminCourse,
|
||||
apiChangeAdminCourseStatus,
|
||||
apiAddAdminCourseStudent,
|
||||
apiAttachAdminCourseSession,
|
||||
apiDeleteAdminCourse,
|
||||
apiDetachAdminCourseSession,
|
||||
apiGetAdminCourseSessions,
|
||||
apiGetAdminCourseStudents,
|
||||
apiGetAdminCourses,
|
||||
apiRemoveAdminCourseStudent,
|
||||
apiShowAdminCourse,
|
||||
apiUpdateAdminCourse,
|
||||
} from '@/services/api/admin-courses'
|
||||
@@ -13,6 +18,22 @@ export const adminCoursesKeys = {
|
||||
all: ['admin', 'courses'],
|
||||
list: (filters, pagination) => ['admin', 'courses', 'list', filters, pagination],
|
||||
detail: (id) => ['admin', 'courses', 'detail', id],
|
||||
students: (courseId, filters, pagination) => [
|
||||
'admin',
|
||||
'courses',
|
||||
'students',
|
||||
courseId,
|
||||
filters,
|
||||
pagination,
|
||||
],
|
||||
sessions: (courseId, filters, pagination) => [
|
||||
'admin',
|
||||
'courses',
|
||||
'sessions',
|
||||
courseId,
|
||||
filters,
|
||||
pagination,
|
||||
],
|
||||
}
|
||||
|
||||
export const useAdminCoursesListQuery = (filtersRef, paginationRef, options = {}) =>
|
||||
@@ -46,7 +67,44 @@ export const useUpdateAdminCourseMutation = () =>
|
||||
export const useDeleteAdminCourseMutation = () =>
|
||||
useMutation({ mutationFn: (id) => apiDeleteAdminCourse(id) })
|
||||
|
||||
export const useChangeAdminCourseStatusMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ id, payload }) => apiChangeAdminCourseStatus(id, payload),
|
||||
export const useAdminCourseStudentsQuery = (courseIdRef, filtersRef, paginationRef, options = {}) =>
|
||||
useQuery({
|
||||
queryKey: ['admin', 'courses', 'students', courseIdRef, filtersRef, paginationRef],
|
||||
queryFn: () =>
|
||||
apiGetAdminCourseStudents(courseIdRef.value, {
|
||||
...cleanFilters(filtersRef?.value || {}),
|
||||
...paginationRef?.value,
|
||||
}),
|
||||
...options,
|
||||
})
|
||||
|
||||
export const useAddAdminCourseStudentMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ courseId, payload }) => apiAddAdminCourseStudent(courseId, payload),
|
||||
})
|
||||
|
||||
export const useRemoveAdminCourseStudentMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ courseId, userId }) => apiRemoveAdminCourseStudent(courseId, userId),
|
||||
})
|
||||
|
||||
export const useAdminCourseSessionsQuery = (courseIdRef, filtersRef, paginationRef, options = {}) =>
|
||||
useQuery({
|
||||
queryKey: ['admin', 'courses', 'sessions', courseIdRef, filtersRef, paginationRef],
|
||||
queryFn: () =>
|
||||
apiGetAdminCourseSessions(courseIdRef.value, {
|
||||
...cleanFilters(filtersRef?.value || {}),
|
||||
...paginationRef?.value,
|
||||
}),
|
||||
...options,
|
||||
})
|
||||
|
||||
export const useAttachAdminCourseSessionMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ courseId, payload }) => apiAttachAdminCourseSession(courseId, payload),
|
||||
})
|
||||
|
||||
export const useDetachAdminCourseSessionMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ courseId, sessionId }) => apiDetachAdminCourseSession(courseId, sessionId),
|
||||
})
|
||||
|
||||
@@ -2,6 +2,8 @@ import { cleanFilters } from '@/utils/clean-filters'
|
||||
import { useMutation, useQuery } from '@tanstack/vue-query'
|
||||
import {
|
||||
apiAddAdminExam,
|
||||
apiAddAdminExamQuestion,
|
||||
apiAddAdminQuestionOption,
|
||||
apiDeleteAdminExam,
|
||||
apiGetAdminExams,
|
||||
apiGetAdminExamParticipants,
|
||||
@@ -36,6 +38,10 @@ export const useAdminExamsListQuery = (filtersRef, paginationRef, options = {})
|
||||
useQuery({
|
||||
queryKey: ['admin', 'exams', 'list', filtersRef, paginationRef],
|
||||
queryFn: () => apiGetAdminExams({ ...cleanFilters(filtersRef.value), ...paginationRef.value }),
|
||||
select: (response) => ({
|
||||
data: response?.data?.items ?? response?.data ?? [],
|
||||
meta: response?.data?.meta ?? response?.meta,
|
||||
}),
|
||||
...options,
|
||||
})
|
||||
|
||||
@@ -74,3 +80,11 @@ export const useUpdateAdminExamMutation = () =>
|
||||
|
||||
export const useDeleteAdminExamMutation = () =>
|
||||
useMutation({ mutationFn: (id) => apiDeleteAdminExam(id) })
|
||||
|
||||
export const useAddAdminExamQuestionMutation = () =>
|
||||
useMutation({ mutationFn: ({ examId, payload }) => apiAddAdminExamQuestion(examId, payload) })
|
||||
|
||||
export const useAddAdminQuestionOptionMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ questionId, payload }) => apiAddAdminQuestionOption(questionId, payload),
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ import { cleanFilters } from '@/utils/clean-filters'
|
||||
import { useMutation, useQuery } from '@tanstack/vue-query'
|
||||
import {
|
||||
apiAddAdminSession,
|
||||
apiChangeAdminSessionStatus,
|
||||
apiDeleteAdminSession,
|
||||
apiGetAdminSessions,
|
||||
apiGetSessionAttendance,
|
||||
@@ -70,8 +69,3 @@ export const useUpdateAdminSessionMutation = () =>
|
||||
|
||||
export const useDeleteAdminSessionMutation = () =>
|
||||
useMutation({ mutationFn: (id) => apiDeleteAdminSession(id) })
|
||||
|
||||
export const useChangeAdminSessionStatusMutation = () =>
|
||||
useMutation({
|
||||
mutationFn: ({ id, payload }) => apiChangeAdminSessionStatus(id, payload),
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
apiAddAdminTerm,
|
||||
apiAddAdminTermCourses,
|
||||
apiAddAdminTermStudents,
|
||||
apiChangeAdminTermStatus,
|
||||
apiCloneAdminTerm,
|
||||
apiDeleteAdminTerm,
|
||||
apiGetAdminTerms,
|
||||
@@ -70,9 +69,6 @@ export const useDeleteAdminTermMutation = () =>
|
||||
export const useCloneAdminTermMutation = () =>
|
||||
useMutation({ mutationFn: (id) => apiCloneAdminTerm(id) })
|
||||
|
||||
export const useChangeAdminTermStatusMutation = () =>
|
||||
useMutation({ mutationFn: ({ id, payload }) => apiChangeAdminTermStatus(id, payload) })
|
||||
|
||||
export const useAdminTermStudentsQuery = (termIdRef, filtersRef, paginationRef, options = {}) =>
|
||||
useQuery({
|
||||
queryKey: ['admin', 'terms', 'students', termIdRef, filtersRef, paginationRef],
|
||||
|
||||
@@ -18,6 +18,10 @@ export const useAdminTicketsListQuery = (filtersRef, paginationRef, options = {}
|
||||
queryKey: ['admin', 'tickets', 'list', filtersRef, paginationRef],
|
||||
queryFn: () =>
|
||||
apiGetAdminTickets({ ...cleanFilters(filtersRef.value), ...paginationRef.value }),
|
||||
select: (response) => ({
|
||||
data: response?.data ?? [],
|
||||
meta: response?.meta,
|
||||
}),
|
||||
...options,
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user