This commit is contained in:
sajjadtalkhabi
2026-05-21 15:51:32 +03:30
parent 8990ceaddd
commit 74227d5021
71 changed files with 2014 additions and 1666 deletions
@@ -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: [],
+68 -67
View File
@@ -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',
+59 -51
View File
@@ -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,
})),
},
],
},
+8 -12
View File
@@ -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,
+109 -53
View File
@@ -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
+25 -18
View File
@@ -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,
+70 -145
View File
@@ -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 }),
}))
+103 -38
View File
@@ -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)
+49 -64
View File
@@ -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)
+4 -8
View File
@@ -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
+31 -19
View File
@@ -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 }),
}))