Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
673 changes: 0 additions & 673 deletions src/demo/mockApi.ts

This file was deleted.

44 changes: 44 additions & 0 deletions src/demo/mockApi/analytics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { groupRoster } from '../mockData'
import { db, json } from './state'

export function handleAnalytics(path: string, method: string): Response | null {
if (path.startsWith('/analytics/') && method === 'GET') {
const category = path.slice('/analytics/'.length)
switch (category) {
case 'student':
return json({
studentCount: db.students.length,
studentsAddedInMonth: 3,
})
case 'teacher':
return json({
teacherCount: db.teachers.length,
teachersAddedInMonth: 1,
})
case 'lead':
return json({
leadCount: db.leads.length,
leadCountInAMonth: 5,
})
case 'invoice': {
const totalAmount = db.invoices.reduce((sum, inv) => sum + (inv.amount ?? 0), 0)
return json({
invoiceAmount: totalAmount,
invoiceAmountInAMonth: 1050000,
})
}
case 'enrollment':
return json({
enrollmentCount: Object.values(groupRoster).reduce((sum, ids) => sum + ids.length, 0),
enrollmentCountInAMonth: 4,
})
case 'branch':
return json({
branchCount: db.branches.length,
})
default:
return json({ message: `Unknown analytics category: ${category}` }, 404)
}
}
return null
}
59 changes: 59 additions & 0 deletions src/demo/mockApi/attendance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { AttendanceDto } from '@/shared/types'
import { db, json, nextId } from './state'

export function handleAttendance(
path: string,
method: string,
body: Record<string, unknown>
): Response | null {
/**
* Guruhning oylik davomati.
*
* Demo'da oy bo'yicha filtrlash yo'q: mock ma'lumot bitta oyga tegishli,
* shuning uchun `previousMonths` qabul qilinadi-yu, natijani
* o'zgartirmaydi — ekranni ko'rsatish uchun shu yetarli.
*/
if (path.startsWith('/attendance/monthly/') && method === 'GET') {
const groupId = path.slice('/attendance/monthly/'.length)
const groupLessons = db.lessons.filter((lesson) => lesson.group?.id === groupId)
return json(
groupLessons.map((lesson) => {
const record = db.attendance.find((item) => item.lessonId === lesson.id)
const attendanceStudentMap: Record<string, { status: string; reason?: string }> = {}
for (const entry of record?.attendanceStudents ?? []) {
if (entry.studentId && entry.status) {
attendanceStudentMap[entry.studentId] = { status: entry.status, reason: entry.reason }
}
}
return {
// `id` — davomat yozuvining o'zi (PUT shu yerga boradi), dars emas.
id: record?.id ?? lesson.id,
lessonTitle: lesson.topic ?? lesson.title ?? '',
date: lesson.lessonDate?.slice(0, 10) ?? '',
attendanceStudentMap,
}
})
)
}
if (path === '/attendance' && method === 'GET') return json(db.attendance)
if (path === '/attendance' && method === 'POST') {
const record: AttendanceDto = {
id: nextId('a'),
lessonId: String(body.lessonId),
createdAt: new Date().toISOString(),
attendanceStudents: body.students as AttendanceDto['attendanceStudents'],
}
db.attendance = [...db.attendance, record]
return json(record)
}
if (path.startsWith('/attendance/') && method === 'PUT') {
const id = path.slice('/attendance/'.length)
const students = body.attendanceStudents as AttendanceDto['attendanceStudents']
db.attendance = db.attendance.map((item) =>
item.id === id ? { ...item, attendanceStudents: students } : item
)
const updated = db.attendance.find((item) => item.id === id)
return updated ? json(updated) : json({ message: 'Attendance not found' }, 404)
}
return null
}
15 changes: 15 additions & 0 deletions src/demo/mockApi/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { demoUser, getDemoRole, json, makeToken } from './state'

export function handleAuth(path: string): Response | null {
if (path === '/auth/refresh-token' || path === '/auth/login') {
return json({ token: makeToken(getDemoRole()), expiry: '2099-01-01T00:00:00Z' })
}
if (path === '/auth/me') {
return json(demoUser)
}
if (path === '/auth/change-password') {
// Demo'da har doim muvaffaqiyatli — haqiqiy tekshiruv backendda.
return json({ response: 'Password changed successfully' })
}
return null
}
34 changes: 34 additions & 0 deletions src/demo/mockApi/enrollments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { groupRoster } from '../mockData'
import { json, noContent } from './state'

export function handleEnrollments(
path: string,
method: string,
url: URL,
body: Record<string, unknown>
): Response | null {
if (path === '/enrollments' && method === 'GET') {
const groupId = url.searchParams.get('groupId') ?? ''
const ids = groupRoster[groupId] ?? []
return json({
// Enrollment id si demo'da guruh+o'quvchidan yasaladi —
// haqiqiy backendda u alohida yozuvning id si.
content: ids.map((studentId) => ({ id: `e-${groupId}-${studentId}`, studentId, groupId })),
totalPages: 1,
totalElements: ids.length,
})
}
if (path === '/enrollments' && method === 'POST') {
const groupId = String(body.groupId)
const studentId = String(body.studentId)
groupRoster[groupId] = [...(groupRoster[groupId] ?? []), studentId]
return json({ id: `e-${groupId}-${studentId}`, studentId, groupId })
}
if (path.startsWith('/enrollments/') && method === 'DELETE') {
// `e-<groupId>-<studentId>` ni teskari yechamiz.
const [, groupId, studentId] = path.split('/')[2].split('-')
groupRoster[groupId] = (groupRoster[groupId] ?? []).filter((id) => id !== studentId)
return noContent()
}
return null
}
73 changes: 73 additions & 0 deletions src/demo/mockApi/generic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import type { LessonDto } from '@/shared/types'
import { db, demoUser, flatten, json, nextId, page, type Row } from './state'

export function handleGeneric(
path: string,
method: string,
url: URL,
body: Record<string, unknown>
): Response | null {
// Profil saqlash `PUT /user/{id}` orqali ketadi — demo'da shunchaki
// yangi qiymatni qaytaramiz.
const [, resource, tail] = path.split('/')
if (resource === 'user' && method === 'PUT') {
Object.assign(demoUser, body)
return json(demoUser)
}

// --- generik CRUD: /student, /teacher, /group, /lesson ---
const table = {
student: 'students',
teacher: 'teachers',
group: 'groups',
lesson: 'lessons',
}[resource] as 'students' | 'teachers' | 'groups' | 'lessons' | undefined

if (!table) return json({ message: `No mock for ${path}` }, 404)

if (tail === 'count') return json(db[table].length)

if (method === 'GET') {
const rows = db[table] as unknown as Row[]
const status = url.searchParams.get('status')
const filtered =
table === 'groups' && status ? rows.filter((row) => row.status === status) : rows
return page(filtered, url)
}

if (method === 'POST') {
// Dars boshlash: o'qituvchi paneli LessonDto kutadi.
if (table === 'lessons') {
const group = db.groups.find((item) => item.id === String(body.groupId))
const lesson: LessonDto = {
id: nextId('l'),
title: String(db.lessons.length + 12),
lessonDate: new Date().toISOString().slice(0, 19),
isComplete: false,
group,
teacherDto: group?.teacher,
}
db.lessons = [...db.lessons, lesson]
return json(lesson)
}
const created = { id: nextId(resource[0]), ...flatten(body) } as Row
;(db[table] as unknown as Row[]).push(created)
return json(created)
}

if (method === 'PUT' && tail) {
const rows = db[table] as unknown as Row[]
const index = rows.findIndex((row) => row.id === tail)
if (index >= 0) rows[index] = { ...rows[index], ...flatten(body), id: tail }
return json(rows[index] ?? null)
}

if (method === 'DELETE' && tail) {
db[table] = (db[table] as unknown as Row[]).filter(
(row) => row.id !== tail
) as never
return new Response('', { status: 204 })
}

return json({ message: `No mock for ${method} ${path}` }, 405)
}
59 changes: 59 additions & 0 deletions src/demo/mockApi/groupLevels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { GroupLevelDto } from '@/shared/types'
import { db, json, nextId, noContent } from './state'

export function handleGroupLevels(
path: string,
method: string,
body: Record<string, unknown>
): Response | null {
if (path === '/group-level/names' && method === 'GET') {
return json(db.groupLevels.map((gl) => ({ id: gl.id, name: gl.name })))
}
if (path === '/group-level' && method === 'GET') {
return json(db.groupLevels)
}
if (path === '/group-level' && method === 'POST') {
const level: GroupLevelDto = {
id: nextId('lvl'),
name: String(body.name ?? ''),
lessonCount: Number(body.lessonCount ?? 0),
orderNumber: db.groupLevels.length + 1,
durationInMonths: Number(body.durationInMonths ?? 0),
monthlyFee: Number(body.monthlyFee ?? 0),
}
db.groupLevels = [...db.groupLevels, level]
return json(level)
}
if (path === '/group-level' && method === 'PUT') {
// Tartibni yangilash: { levels: [{ id, orderNumber }] }
const levels = body.levels as Array<{ id: string; orderNumber: number }> | undefined
if (Array.isArray(levels)) {
const orderMap = new Map(levels.map((item) => [item.id, item.orderNumber]))
db.groupLevels = db.groupLevels
.map((gl) => (orderMap.has(gl.id) ? { ...gl, orderNumber: orderMap.get(gl.id)! } : gl))
.sort((a, b) => a.orderNumber - b.orderNumber)
}
return json(db.groupLevels)
}
if (path.startsWith('/group-level/') && method === 'PUT') {
const id = path.slice('/group-level/'.length)
db.groupLevels = db.groupLevels.map((gl) => {
if (gl.id !== id) return gl
return {
...gl,
...(body.name !== undefined ? { name: String(body.name) } : {}),
...(body.lessonCount !== undefined ? { lessonCount: Number(body.lessonCount) } : {}),
...(body.durationInMonths !== undefined ? { durationInMonths: Number(body.durationInMonths) } : {}),
...(body.monthlyFee !== undefined ? { monthlyFee: Number(body.monthlyFee) } : {}),
}
})
const updated = db.groupLevels.find((gl) => gl.id === id)
return updated ? json(updated) : json({ message: 'Group level not found' }, 404)
}
if (path.startsWith('/group-level/') && method === 'DELETE') {
const id = path.slice('/group-level/'.length)
db.groupLevels = db.groupLevels.filter((gl) => gl.id !== id)
return noContent()
}
return null
}
81 changes: 81 additions & 0 deletions src/demo/mockApi/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { handleAnalytics } from './analytics'
import { handleAttendance } from './attendance'
import { handleAuth } from './auth'
import { handleEnrollments } from './enrollments'
import { handleGeneric } from './generic'
import { handleGroupLevels } from './groupLevels'
import { handleInvoices } from './invoices'
import { handleLeads } from './leads'
import { handleOrganizations } from './organizations'
import { isInstalled, resetMockApiInstalledFlag, setDemoRole, setInstalled } from './state'
import { handleStudents } from './students'
import { handleTeacher } from './teacher'

export { setDemoRole, resetMockApiInstalledFlag }

/** `fetch` ni bir marta almashtiradi (qayta chaqirilsa hech narsa qilmaydi). */
export function installMockApi() {
if (isInstalled()) return
setInstalled(true)

const fetchImpl = typeof window !== 'undefined' ? window.fetch : globalThis.fetch
const original = fetchImpl.bind(typeof window !== 'undefined' ? window : globalThis)

const mockFetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
const raw = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url
if (!raw.includes('/api/v1/')) return original(input as RequestInfo, init)

const origin =
typeof window !== 'undefined' && window.location && window.location.origin && window.location.origin !== 'null'
? window.location.origin
: 'http://localhost'
const url = new URL(raw, origin)
const path = url.pathname.replace('/api/v1', '')
const method = (init?.method ?? 'GET').toUpperCase()
const body = init?.body ? (JSON.parse(String(init.body)) as Record<string, unknown>) : {}

// Haqiqiy tarmoqqa o'xshasin — spinner'lar ko'rinib qolsin.
await new Promise((resolve) => setTimeout(resolve, 180))

const authRes = handleAuth(path)
if (authRes) return authRes

const enrollmentsRes = handleEnrollments(path, method, url, body)
if (enrollmentsRes) return enrollmentsRes

const studentsRes = handleStudents(path, method, url)
if (studentsRes) return studentsRes

const teacherRes = handleTeacher(path, url)
if (teacherRes) return teacherRes

const attendanceRes = handleAttendance(path, method, body)
if (attendanceRes) return attendanceRes

const leadsRes = handleLeads(path, method, url, body)
if (leadsRes) return leadsRes

const groupLevelsRes = handleGroupLevels(path, method, body)
if (groupLevelsRes) return groupLevelsRes

const analyticsRes = handleAnalytics(path, method)
if (analyticsRes) return analyticsRes

const orgsRes = handleOrganizations(path, method, url, body)
if (orgsRes) return orgsRes

const invoicesRes = handleInvoices(path, method, url, body)
if (invoicesRes) return invoicesRes

const genericRes = handleGeneric(path, method, url, body)
if (genericRes) return genericRes

return new Response(JSON.stringify({ message: `No mock for ${method} ${path}` }), {
status: 405,
headers: { 'Content-Type': 'application/json' },
})
}

if (typeof window !== 'undefined') window.fetch = mockFetch
if (typeof globalThis !== 'undefined') globalThis.fetch = mockFetch
}
Loading
Loading