fix: design

This commit is contained in:
sajjadtalkhabi
2026-05-19 22:53:05 +03:30
parent c82701888a
commit 8990ceaddd
44 changed files with 2678 additions and 435 deletions
+54 -12
View File
@@ -37,53 +37,95 @@ export const adminCourseTemplates = [
},
]
// 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,
roles: overrides.roles ?? ['teacher'],
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(),
})
export const adminOfferedCourses = [
{
// --- spec ---
id: 11,
termId: 1,
teacherId: 5,
title: 'اصول اخلاق اسلامی - پاییز',
description: 'دوره مقدماتی اخلاق اسلامی برای ترم پاییز.',
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: 'ترم پاییز ۱۴۰۴' },
termId: 1,
teacher: { id: 5, firstName: 'علی', lastName: 'حسنی', fullName: 'علی حسنی' },
capacity: 30,
isActive: true,
teacher: makeTeacherSnapshot({ id: 5, firstName: 'علی', lastName: 'حسنی' }),
prerequisitesCount: 0,
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,
title: 'مفاهیم قرآنی - پاییز',
description: 'مرور مفاهیم قرآنی به همراه تفسیر مختصر.',
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: 'ترم پاییز ۱۴۰۴' },
termId: 1,
teacher: { id: 6, firstName: 'حسین', lastName: 'مرادی', fullName: 'حسین مرادی' },
capacity: 25,
isActive: true,
teacher: makeTeacherSnapshot({ id: 6, firstName: 'حسین', lastName: 'مرادی' }),
prerequisitesCount: 1,
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,
title: 'فقه عبادات - زمستان',
description: 'مرور احکام عملی نماز و روزه برای ترم زمستان.',
capacity: 20,
isActive: false,
coverUrl: '',
// --- ui-only ---
image: '',
template: { id: 3, title: 'فقه عبادات' },
templateId: 3,
term: { id: 2, title: 'ترم زمستان ۱۴۰۴' },
termId: 2,
teacher: { id: 7, firstName: 'مهدی', lastName: 'سهرابی', fullName: 'مهدی سهرابی' },
capacity: 20,
isActive: false,
teacher: makeTeacherSnapshot({ id: 7, firstName: 'مهدی', lastName: 'سهرابی' }),
prerequisitesCount: 0,
startDate: '2026-01-22T00:00:00.000Z',
endDate: '2026-03-15T00:00:00.000Z',
createdAt: '2025-12-10T08:00:00.000Z',
},
]
export { makeTeacherSnapshot }
+54 -12
View File
@@ -1,6 +1,50 @@
// Maps the FE's 7-value `sessionType` to the spec's 3-value `type`.
const SESSION_TYPE_TO_SPEC = {
in_person: 'offline',
online: 'online',
video: 'content',
audio: 'content',
text: 'content',
slide: 'content',
pdf: 'content',
}
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 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,
link,
media: overrides.media ?? [],
// --- ui-only ---
image: overrides.image ?? '',
courseTemplate: overrides.courseTemplate ?? null,
sessionType,
sessionTypeFa: overrides.sessionTypeFa ?? '',
durationMinutes: overrides.durationMinutes ?? 0,
order: overrides.order ?? 1,
sessionConfig,
materials: overrides.materials ?? [],
usedInTerms: overrides.usedInTerms ?? [],
createdAt: overrides.createdAt ?? '',
}
}
export const adminSessions = [
{
makeSession({
id: 101,
courseId: 11,
title: 'مقدمه‌ای بر اخلاق اسلامی',
description: 'جلسه نخست؛ تعاریف و چارچوب دوره.',
image: 'https://picsum.photos/seed/session1/200/200',
@@ -13,15 +57,14 @@ export const adminSessions = [
startTime: '2025-09-25T16:00:00.000Z',
location: 'سالن آمفی‌تئاتر ۲ - مدرسه قم',
},
materials: [],
usedInTerms: [{ termId: 1 }],
createdAt: '2025-09-10T08:00:00.000Z',
},
{
}),
makeSession({
id: 102,
courseId: 12,
title: 'تفسیر سوره حمد',
description: 'تحلیل آیات سوره حمد.',
image: '',
courseTemplate: { id: 2, title: 'مفاهیم قرآنی' },
sessionType: 'online',
sessionTypeFa: 'آنلاین',
@@ -32,15 +75,14 @@ export const adminSessions = [
platform: 'google_meet',
startTime: '2025-10-04T19:00:00.000Z',
},
materials: [],
usedInTerms: [{ termId: 1 }],
createdAt: '2025-09-25T08:00:00.000Z',
},
{
}),
makeSession({
id: 103,
courseId: 13,
title: 'احکام نماز جماعت',
description: 'مرور احکام و شرایط نماز جماعت.',
image: '',
courseTemplate: { id: 3, title: 'فقه عبادات' },
sessionType: 'video',
sessionTypeFa: 'ویدئو',
@@ -50,12 +92,12 @@ export const adminSessions = [
minWatchedPercent: 80,
mustCompleteBeforeNext: true,
},
materials: [],
usedInTerms: [],
createdAt: '2026-01-12T08:00:00.000Z',
},
}),
]
export { makeSession, SESSION_TYPE_TO_SPEC }
export const sessionAttendance = new Map([
[
101,
+45 -15
View File
@@ -1,42 +1,72 @@
// Each term carries both the spec keys (title/description/is_active/
// starts_at/ends_at/cover_url/created_at — camelized) and the UI-only
// keys the current frontend reads (image, startDate, endDate,
// studentsCount, coursesCount). See docs/backend-api-todo.md.
const makeTerm = (overrides) => {
const startsAt = overrides.startsAt ?? overrides.startDate ?? ''
const endsAt = overrides.endsAt ?? overrides.endDate ?? ''
const coverUrl = overrides.coverUrl ?? overrides.image ?? ''
return {
// --- spec ---
id: overrides.id,
title: overrides.title ?? '',
description: overrides.description ?? '',
isActive: overrides.isActive ?? true,
startsAt,
endsAt,
coverUrl,
createdAt: overrides.createdAt ?? '',
// --- ui-only ---
image: coverUrl,
startDate: startsAt,
endDate: endsAt,
studentsCount: overrides.studentsCount ?? 0,
coursesCount: overrides.coursesCount ?? 0,
}
}
export const adminTerms = [
{
makeTerm({
id: 1,
title: 'ترم پاییز ۱۴۰۴',
description: 'ترم پاییز با محوریت آموزش معارف اسلامی.',
image: 'https://picsum.photos/seed/term1/200/200',
startDate: '2025-09-23T00:00:00.000Z',
endDate: '2026-01-20T00:00:00.000Z',
coverUrl: 'https://picsum.photos/seed/term1/200/200',
startsAt: '2025-09-23T00:00:00.000Z',
endsAt: '2026-01-20T00:00:00.000Z',
isActive: true,
studentsCount: 18,
coursesCount: 4,
createdAt: '2025-08-01T08:00:00.000Z',
},
{
}),
makeTerm({
id: 2,
title: 'ترم زمستان ۱۴۰۴',
description: 'ترم زمستان با تمرکز بر فقه و اصول.',
image: 'https://picsum.photos/seed/term2/200/200',
startDate: '2026-01-21T00:00:00.000Z',
endDate: '2026-04-20T00:00:00.000Z',
coverUrl: 'https://picsum.photos/seed/term2/200/200',
startsAt: '2026-01-21T00:00:00.000Z',
endsAt: '2026-04-20T00:00:00.000Z',
isActive: true,
studentsCount: 12,
coursesCount: 3,
createdAt: '2025-11-10T08:00:00.000Z',
},
{
}),
makeTerm({
id: 3,
title: 'ترم بهار ۱۴۰۵',
description: 'ترم بهار ویژه دوره‌های تخصصی.',
image: '',
startDate: '2026-04-21T00:00:00.000Z',
endDate: '2026-08-21T00:00:00.000Z',
coverUrl: '',
startsAt: '2026-04-21T00:00:00.000Z',
endsAt: '2026-08-21T00:00:00.000Z',
isActive: false,
studentsCount: 0,
coursesCount: 2,
createdAt: '2026-02-15T08:00:00.000Z',
},
}),
]
export { makeTerm }
export const termStudentLinks = new Map([
[
1,
+14 -5
View File
@@ -66,16 +66,26 @@ const makeUser = (i, overrides = {}) => {
const province = provinceById((i % 4) + 1)
const city = cityForProvince(province.id)
const role = roles[(i % 4) + 1]?.name || 'student'
const id = 100 + i
const phoneNumber = `0912${String(1_000_000 + i).padStart(7, '0')}`
return {
id: 100 + i,
// --- spec (GET /admin/users) ---
id,
name: `${firstName} ${lastName}`,
email: `user${id}@example.com`,
phone: phoneNumber.replace(/^0/, '+98'),
roles: [role],
avatarUrl: '',
avatarDownloadUrl: '',
createdAt: new Date(Date.now() - i * 86_400_000).toISOString(),
// --- ui-only (pending backend) ---
firstName,
lastName,
fullName: `${firstName} ${lastName}`,
phoneNumber: `0912${String(1_000_000 + i).padStart(7, '0')}`,
phoneNumber,
nationalCode: String(1_000_000_000 + i * 7).padStart(10, '0'),
avatarUrl: '',
status: i % 5 === 0 ? 'pending' : 'approved',
roles: [role],
roleId: roles.find((r) => r.name === role)?.id,
address: {
address: 'آدرس نمونه',
@@ -88,7 +98,6 @@ const makeUser = (i, overrides = {}) => {
maritalStatus: i % 2 ? 'single' : 'married',
gender: i % 2 ? 'female' : 'male',
},
createdAt: new Date(Date.now() - i * 86_400_000).toISOString(),
...overrides,
}
}
@@ -1,13 +1,25 @@
export const currentProfile = {
id: 1,
// Backend `/auth/me` currently returns the keys marked "spec" below.
// The "ui-only" keys are extras the UI already depends on (edit-profile form,
// avatars, address, etc.) — they must be preserved in the mock until the
// backend completes the response model. See docs/backend-api-todo.md.
export const currentMe = {
// --- spec (GET /auth/me) ---
id: 5,
name: 'سجاد محمدی',
email: 'sajjad@example.com',
phone: '+989121234567',
roles: ['student'],
avatarUrl: 'http://localhost:8080/storage/users/5/avatar.jpg',
avatarDownloadUrl: 'http://localhost:8080/api/media/9/download',
createdAt: '2026-02-01T10:00:00+00:00',
// --- ui-only (pending backend) ---
firstName: 'سجاد',
lastName: 'محمدی',
fullName: 'سجاد محمدی',
phoneNumber: '09123456789',
nationalCode: '0079827498',
avatarUrl: '',
status: 'approved',
roles: ['user'],
address: {
address: 'خیابان آزادی، پلاک ۱۲',
province: { id: 1, name: 'تهران' },
+98 -32
View File
@@ -2,7 +2,11 @@ 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 } from '@/services/mock/fixtures/admin-courses'
import {
adminCourseTemplates,
adminOfferedCourses,
makeTeacherSnapshot,
} from '@/services/mock/fixtures/admin-courses'
import {
filterDateRange,
filterItems,
@@ -99,66 +103,128 @@ register('GET', endpoints.getCoursesList, ({ query }) => {
status: (item, v) => String(item.isActive ? 1 : 0) === String(v),
})
list = filterDateRange(list, query, 'startDate')
return paginate(list, query)
const { data: items, meta } = paginate(list, query)
return {
success: true,
message: 'OK',
data: { items, meta },
}
})
register('GET', endpoints.showCourse, ({ params }) => ({
success: true,
message: 'OK',
data: findOrThrow(adminOfferedCourses, 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 teacher = adminUsers.find((u) => u.id === Number(data.teacherId))
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 coverFromUpload = data.imageId
? `https://picsum.photos/seed/offered-${data.imageId}/200/200`
: ''
const coverUrl = data.coverUrl ?? coverFromUpload
const item = {
// --- spec ---
id: makeId(),
termId: term?.id ?? Number(data.termId) ?? null,
teacherId: teacher?.id ?? (Number(data.teacherId) || null),
title: data.title || template?.title || '',
image: data.imageId ? `https://picsum.photos/seed/offered-${data.imageId}/200/200` : '',
description: data.description || '',
capacity: Number(data.capacity) || 0,
isActive: data.isActive !== undefined ? !!data.isActive : true,
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,
termId: term?.id,
teacher: teacher
? {
id: teacher.id,
firstName: teacher.firstName,
lastName: teacher.lastName,
fullName: `${teacher.firstName} ${teacher.lastName}`,
}
: null,
capacity: Number(data.capacity) || 0,
isActive: !!data.isActive,
teacher,
prerequisitesCount: 0,
startDate: term?.startDate || '',
endDate: term?.endDate || '',
createdAt: isoNow(),
}
adminOfferedCourses.unshift(item)
return { data: item }
return {
success: true,
message: 'Course created.',
data: item,
}
})
register('PUT', endpoints.updateCourse, ({ params, data }) => {
const template = adminCourseTemplates.find((t) => t.id === Number(data.templateId))
const term = adminTerms.find((t) => t.id === Number(data.termId))
register('PATCH', endpoints.updateCourse, ({ params, data }) => {
const patch = {}
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.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 }
}
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,
})
}
}
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.coverUrl !== undefined || data.imageId !== undefined) {
const url =
data.coverUrl ??
(data.imageId ? `https://picsum.photos/seed/offered-${data.imageId}/200/200` : '')
patch.coverUrl = url
patch.image = url
}
const updated = updateById(adminOfferedCourses, params.id, patch)
return {
data: updateById(adminOfferedCourses, params.id, {
title: data.title,
image: data.imageId
? `https://picsum.photos/seed/offered-${data.imageId}/200/200`
: undefined,
template: template ? { id: template.id, title: template.title } : undefined,
templateId: template?.id,
term: term ? { id: term.id, title: term.title } : undefined,
termId: term?.id,
capacity: Number(data.capacity) || 0,
isActive: !!data.isActive,
}),
success: true,
message: 'Course updated.',
data: updated,
}
})
register('DELETE', endpoints.deleteCourse, ({ params }) => {
removeById(adminOfferedCourses, params.id)
return { data: { message: 'حذف موفق' } }
return {
success: true,
message: 'Course deleted.',
data: null,
}
})
register('POST', endpoints.changeStatusCourse, ({ params, data }) => ({
+97 -25
View File
@@ -1,8 +1,12 @@
import { SESSION_TYPE } from '@/enums'
import { register } from '@/services/mock/registry'
import { endpoints } from '@/services/api/endpoints'
import { adminCourseTemplates } from '@/services/mock/fixtures/admin-courses'
import { adminSessions, sessionAttendance } from '@/services/mock/fixtures/admin-sessions'
import { adminCourseTemplates, adminOfferedCourses } from '@/services/mock/fixtures/admin-courses'
import {
adminSessions,
makeSession,
sessionAttendance,
} from '@/services/mock/fixtures/admin-sessions'
import {
filterDateRange,
filterItems,
@@ -19,27 +23,62 @@ 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',
sessionType: 'eq',
type: 'eq',
})
list = filterDateRange(list, query)
// eslint-disable-next-line unicorn/no-array-callback-reference
return paginate(list.map(enrich), query)
const { data: items, meta } = paginate(list.map(enrich), query)
return {
success: true,
message: 'OK',
data: { items, meta },
}
})
register('GET', endpoints.showSession, ({ params }) => ({
data: enrich(findOrThrow(adminSessions, params.id)),
}))
register('GET', endpoints.showSession, ({ params }) => {
const session = enrich(findOrThrow(adminSessions, params.id))
const offered = adminOfferedCourses.find((c) => c.id === session.courseId)
return {
success: true,
message: 'OK',
data: { ...session, course: courseSnapshot(offered) },
}
})
register('POST', endpoints.addNewSession, ({ data }) => {
const tpl = adminCourseTemplates.find((c) => c.id === Number(data.courseTemplateId))
const item = {
const offered =
adminOfferedCourses.find((c) => c.id === Number(data.courseId)) ||
(tpl ? adminOfferedCourses.find((c) => c.templateId === tpl.id) : null)
const item = makeSession({
id: makeId(),
courseId: data.courseId ?? offered?.id ?? 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,
sessionType: data.sessionType || 'in_person',
@@ -47,32 +86,65 @@ register('POST', endpoints.addNewSession, ({ data }) => {
order: Number(data.order) || 1,
sessionConfig: data.sessionConfig || {},
materials: data.materials || [],
usedInTerms: [],
createdAt: isoNow(),
}
})
adminSessions.unshift(item)
return { data: enrich(item) }
return {
success: true,
message: 'Session created.',
data: enrich(item),
}
})
register('PUT', endpoints.updateSession, ({ params, data }) => {
const tpl = adminCourseTemplates.find((c) => c.id === Number(data.courseTemplateId))
const updated = updateById(adminSessions, params.id, {
title: data.title,
description: data.description,
image: data.imageId ? `https://picsum.photos/seed/session-${data.imageId}/200/200` : undefined,
courseTemplate: tpl ? { id: tpl.id, title: tpl.title } : undefined,
sessionType: data.sessionType,
durationMinutes: Number(data.durationMinutes) || 0,
order: Number(data.order) || 1,
sessionConfig: data.sessionConfig ?? undefined,
materials: data.materials ?? undefined,
})
return { data: enrich(updated) }
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.sessionType !== undefined) patch.sessionType = data.sessionType
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
}
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 {
success: true,
message: 'Session updated.',
data: enrich(updated),
}
})
register('DELETE', endpoints.deleteSession, ({ params }) => {
removeById(adminSessions, params.id)
return { data: { message: 'حذف موفق' } }
return {
success: true,
message: 'Session deleted.',
data: null,
}
})
register('POST', endpoints.changeStatusSession, ({ params, data }) => {
+59 -19
View File
@@ -2,7 +2,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 { adminTerms, termStudentLinks } from '@/services/mock/fixtures/admin-terms'
import { adminTerms, makeTerm, termStudentLinks } from '@/services/mock/fixtures/admin-terms'
import {
filterDateRange,
filterItems,
@@ -18,45 +18,85 @@ register('GET', endpoints.getTermsList, ({ query }) => {
let list = filterItems(adminTerms, query, {
title: 'includes',
status: (item, v) => String(item.isActive ? 1 : 0) === String(v),
activeOnly: (item, v) => (String(v) === '1' ? !!item.isActive : true),
})
list = filterDateRange(list, query, 'startDate')
return paginate(list, query)
const { data: items, meta } = paginate(list, query)
return {
success: true,
message: 'OK',
data: { items, meta },
}
})
register('GET', endpoints.showTerm, ({ params }) => ({
success: true,
message: 'OK',
data: findOrThrow(adminTerms, params.id),
}))
register('POST', endpoints.addNewTerm, ({ data }) => {
const term = {
const coverFromUpload = data.imageId
? `https://picsum.photos/seed/term-${data.imageId}/200/200`
: ''
const term = makeTerm({
id: makeId(),
title: data.title || '',
description: data.description || '',
image: data.imageId ? `https://picsum.photos/seed/term-${data.imageId}/200/200` : '',
startDate: data.startDate || '',
endDate: data.endDate || '',
isActive: true,
isActive: data.isActive ?? true,
startsAt: data.startsAt ?? data.startDate ?? '',
endsAt: data.endsAt ?? data.endDate ?? '',
coverUrl: data.coverUrl ?? coverFromUpload,
studentsCount: 0,
coursesCount: 0,
createdAt: isoNow(),
}
})
adminTerms.unshift(term)
return { data: term }
return {
success: true,
message: 'Term created.',
data: term,
}
})
register('PUT', endpoints.updateTerm, ({ params, data }) => ({
data: updateById(adminTerms, params.id, {
title: data.title,
description: data.description,
startDate: data.startDate,
endDate: data.endDate,
image: data.imageId ? `https://picsum.photos/seed/term-${data.imageId}/200/200` : undefined,
}),
}))
register('PATCH', endpoints.updateTerm, ({ params, data }) => {
const coverFromUpload = data.imageId
? `https://picsum.photos/seed/term-${data.imageId}/200/200`
: undefined
const startsAt = data.startsAt ?? data.startDate
const endsAt = data.endsAt ?? data.endDate
const coverUrl = data.coverUrl ?? coverFromUpload
const patch = {}
if (data.title !== undefined) patch.title = data.title
if (data.description !== undefined) patch.description = data.description
if (data.isActive !== undefined) patch.isActive = data.isActive
if (startsAt !== undefined) {
patch.startsAt = startsAt
patch.startDate = startsAt
}
if (endsAt !== undefined) {
patch.endsAt = endsAt
patch.endDate = endsAt
}
if (coverUrl !== undefined) {
patch.coverUrl = coverUrl
patch.image = coverUrl
}
const updated = updateById(adminTerms, params.id, patch)
return {
success: true,
message: 'Term updated.',
data: updated,
}
})
register('DELETE', endpoints.deleteTerm, ({ params }) => {
removeById(adminTerms, params.id)
return { data: { message: 'حذف موفق' } }
return {
success: true,
message: 'Term deleted.',
data: null,
}
})
register('POST', endpoints.cloneTerm, ({ params }) => {
+86 -23
View File
@@ -21,28 +21,51 @@ register('GET', endpoints.getApprovedUsers, ({ query }) => {
roleId: (item, v) => String(item.roleId) === 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.showUserDetails, ({ params }) => ({
success: true,
message: 'OK',
data: findOrThrow(adminUsers, params.id),
}))
register('POST', endpoints.addNewUser, ({ data }) => {
const id = makeId()
const role = roles.find((r) => r.id === Number(data.roleId)) || null
const roleByName = (n) => roles.find((r) => r.name === n)
const role =
roles.find((r) => r.id === Number(data.roleId)) ||
(Array.isArray(data.roles) && data.roles[0] ? roleByName(data.roles[0]) : null) ||
null
const province = data.provinceId ? { id: data.provinceId, name: '' } : null
const city = data.cityId ? { id: data.cityId, name: '' } : null
const firstName = data.firstName || ''
const lastName = data.lastName || ''
const fullName = `${firstName} ${lastName}`.trim()
const phoneNumber = data.phoneNumber || ''
const user = {
// --- spec ---
id,
firstName: data.firstName || '',
lastName: data.lastName || '',
fullName: `${data.firstName || ''} ${data.lastName || ''}`.trim(),
phoneNumber: data.phoneNumber || '',
nationalCode: data.nationalCode || '',
name: data.name || fullName,
email: data.email || '',
phone: data.phone || (phoneNumber ? phoneNumber.replace(/^0/, '+98') : null),
roles: Array.isArray(data.roles) ? data.roles : role ? [role.name] : [],
avatarUrl: '',
avatarDownloadUrl: null,
createdAt: isoNow(),
// --- ui-only ---
firstName,
lastName,
fullName,
phoneNumber,
nationalCode: data.nationalCode || '',
status: 'approved',
roles: role ? [role.name] : [],
roleId: role?.id,
address: { address: data.address || '', province, city },
profile: {
@@ -52,22 +75,39 @@ register('POST', endpoints.addNewUser, ({ data }) => {
gender: data.gender || '',
avatarId: data.avatarId || null,
},
createdAt: isoNow(),
}
adminUsers.unshift(user)
return { data: user }
return {
success: true,
message: 'User created.',
data: user,
}
})
register('PUT', endpoints.updateUser, ({ params, data }) => {
const role = roles.find((r) => r.id === Number(data.roleId))
register('PATCH', endpoints.updateUser, ({ params, data }) => {
const roleByName = (n) => roles.find((r) => r.name === n)
const role =
roles.find((r) => r.id === Number(data.roleId)) ||
(Array.isArray(data.roles) && data.roles[0] ? roleByName(data.roles[0]) : null) ||
null
const firstName = data.firstName ?? ''
const lastName = data.lastName ?? ''
const fullName = `${firstName} ${lastName}`.trim()
const phoneNumber = data.phoneNumber ?? ''
const patch = {
firstName: data.firstName,
lastName: data.lastName,
fullName: `${data.firstName || ''} ${data.lastName || ''}`.trim(),
phoneNumber: data.phoneNumber,
// --- spec ---
name: data.name ?? fullName,
email: data.email,
phone: data.phone ?? (phoneNumber ? phoneNumber.replace(/^0/, '+98') : undefined),
roles: Array.isArray(data.roles) ? data.roles : role ? [role.name] : [],
// --- ui-only ---
firstName,
lastName,
fullName,
phoneNumber,
nationalCode: data.nationalCode,
roleId: role?.id,
roles: role ? [role.name] : [],
address: {
address: data.address || '',
province: data.provinceId ? { id: data.provinceId, name: '' } : null,
@@ -82,13 +122,32 @@ register('PUT', endpoints.updateUser, ({ params, data }) => {
},
}
const updated = updateById(adminUsers, params.id, patch)
return { data: updated }
return {
success: true,
message: 'User updated.',
data: updated,
}
})
register('POST', endpoints.updateUserRole, ({ params, data }) => {
const role = roles.find((r) => r.id === Number(data.roleId))
const patch = role ? { roleId: role.id, roles: [role.name] } : {}
return { data: updateById(adminUsers, params.id, patch) }
register('PATCH', endpoints.updateUserRole, ({ params, data }) => {
const byName = (n) => roles.find((r) => r.name === n)
const nextRoleNames = Array.isArray(data.roles)
? data.roles.filter(Boolean)
: data.role
? [data.role]
: data.roleId
? [roles.find((r) => r.id === Number(data.roleId))?.name].filter(Boolean)
: []
const primary = nextRoleNames[0] ? byName(nextRoleNames[0]) : null
const patch = nextRoleNames.length
? { roles: nextRoleNames, roleId: primary?.id }
: {}
const updated = updateById(adminUsers, params.id, patch)
return {
success: true,
message: 'Roles updated.',
data: updated,
}
})
register('POST', endpoints.changeUserStatus, ({ params, data }) => ({
@@ -97,7 +156,11 @@ register('POST', endpoints.changeUserStatus, ({ params, data }) => ({
register('DELETE', endpoints.deleteUser, ({ params }) => {
removeById(adminUsers, params.id)
return { data: { message: 'حذف موفق' } }
return {
success: true,
message: 'User deleted.',
data: null,
}
})
register('GET', endpoints.getRolesList, () => ({ data: roles }))
+34 -25
View File
@@ -1,24 +1,24 @@
import { makeId } from '@/services/mock/helpers'
import { register } from '@/services/mock/registry'
import { endpoints } from '@/services/api/endpoints'
import { currentProfile } from '@/services/mock/fixtures/profile'
import { currentMe } from '@/services/mock/fixtures/me'
const fakeToken = 'mock-token-1234567890'
register('POST', endpoints.login, () => ({
data: { user: currentProfile, token: fakeToken },
data: { user: currentMe, token: fakeToken },
}))
register('POST', endpoints.loginWithCode2Step, () => ({
data: { user: currentProfile, token: fakeToken },
data: { user: currentMe, token: fakeToken },
}))
register('POST', endpoints.verifyCode, () => ({
data: { user: currentProfile, token: fakeToken },
data: { user: currentMe, token: fakeToken },
}))
register('POST', endpoints.register, ({ data }) => ({
data: { user: { ...currentProfile, ...data }, token: fakeToken },
data: { user: { ...currentMe, ...data }, token: fakeToken },
}))
register('POST', endpoints.resendVerificationCodeForRegister, () => ({
@@ -39,47 +39,56 @@ register('POST', endpoints.resetPassword, () => ({
register('POST', endpoints.logout, () => ({ data: { message: 'خروج موفق' } }))
register('GET', endpoints.getProfile, () => ({ data: currentProfile }))
register('GET', endpoints.me, () => ({
success: true,
message: 'OK',
data: currentMe,
}))
register('PUT', endpoints.updateProfile, ({ data }) => {
Object.assign(currentProfile, {
firstName: data.firstName ?? currentProfile.firstName,
lastName: data.lastName ?? currentProfile.lastName,
phoneNumber: data.phoneNumber ?? currentProfile.phoneNumber,
nationalCode: data.nationalCode ?? currentProfile.nationalCode,
Object.assign(currentMe, {
name: data.name ?? currentMe.name,
email: data.email ?? currentMe.email,
phone: data.phone ?? currentMe.phone,
firstName: data.firstName ?? currentMe.firstName,
lastName: data.lastName ?? currentMe.lastName,
phoneNumber: data.phoneNumber ?? currentMe.phoneNumber,
nationalCode: data.nationalCode ?? currentMe.nationalCode,
})
if (data.provinceId) {
currentProfile.address = currentProfile.address || {}
currentProfile.address.province = { id: data.provinceId, name: '' }
currentMe.address = currentMe.address || {}
currentMe.address.province = { id: data.provinceId, name: '' }
}
if (data.cityId) {
currentProfile.address = currentProfile.address || {}
currentProfile.address.city = { id: data.cityId, name: '' }
currentMe.address = currentMe.address || {}
currentMe.address.city = { id: data.cityId, name: '' }
}
if (data.address) {
currentProfile.address = { ...currentProfile.address, address: data.address }
currentMe.address = { ...currentMe.address, address: data.address }
}
currentProfile.profile = {
...currentProfile.profile,
bio: data.bio ?? currentProfile.profile?.bio,
birthDate: data.birthDate ?? currentProfile.profile?.birthDate,
maritalStatus: data.maritalStatus ?? currentProfile.profile?.maritalStatus,
gender: data.gender ?? currentProfile.profile?.gender,
currentMe.profile = {
...currentMe.profile,
bio: data.bio ?? currentMe.profile?.bio,
birthDate: data.birthDate ?? currentMe.profile?.birthDate,
maritalStatus: data.maritalStatus ?? currentMe.profile?.maritalStatus,
gender: data.gender ?? currentMe.profile?.gender,
}
return { data: currentProfile }
return { data: currentMe }
})
register('POST', endpoints.completeProfile, ({ data }) => ({
data: { user: { ...currentProfile, ...data } },
data: { user: { ...currentMe, ...data } },
}))
register('GET', endpoints.getRegistrationQuestionVideo, () => ({
data: { url: '', id: 0 },
}))
register('POST', endpoints.uploadMediaTemporary, () => {
register('POST', endpoints.uploadMedia, () => {
const id = makeId()
return {
success: true,
message: 'Media uploaded.',
data: {
id,
uploadId: id,