diff --git a/src/controllers/schedule.ts b/src/controllers/schedule.ts index c7b4032..23c6722 100644 --- a/src/controllers/schedule.ts +++ b/src/controllers/schedule.ts @@ -120,9 +120,13 @@ export class ScheduleController { /* #swagger.tags = ['Schedule'] #swagger.security = [{ "bearerAuth": [] }] - #swagger.parameters['start'] = { in: 'query', required: true, type: 'string' } - #swagger.parameters['end'] = { in: 'query', required: true, type: 'string' } + #swagger.parameters['start_date'] = { in: 'query', required: true, type: 'string' } + #swagger.parameters['end_date'] = { in: 'query', required: true, type: 'string' } #swagger.parameters['filter'] = { in: 'query', required: false, type: 'string' } + #swagger.parameters['limit'] = { in: 'query', required: false, type: 'number' } + #swagger.parameters['page'] = { in: 'query', required: false, type: 'number' } + #swagger.parameters['order_field'] = { in: 'query', required: false, type: 'string' } + #swagger.parameters['order_direction'] = { in: 'query', required: false, schema: { '@enum': ['ASC', 'DESC'] } } #swagger.responses[200] = { description: 'Schedule calendar resources and events', schema: { @@ -147,21 +151,39 @@ export class ScheduleController { isRecurring: true, isException: false, status: 'confirmed' - }] + }], + meta: { total_count: 24, page: 1, limit: 20, order_field: 'startAt', order_direction: 'ASC' } } } } */ try { const schema = Joi.object().keys({ - start: Joi.string().required().label("Start"), - end: Joi.string().required().label("End"), + start_date: Joi.string().required().label("Start Date"), + end_date: Joi.string().required().label("End Date"), filter: Joi.string().allow("").optional().label("Filter"), + limit: Joi.number().min(1).optional().label("Limit"), + page: Joi.number().min(1).optional().label("Page"), + order_field: Joi.string().allow("startAt", "endAt", "title", "seriesId", "status").optional().label("Order Field"), + order_direction: Joi.string().allow("asc", "desc").optional().label("Order Direction"), }); - const param: { start: string; end: string; filter?: string } = await schema.validateAsync(req.query); + const param: { + start_date: string; + end_date: string; + filter?: string; + limit?: number; + page?: number; + order_field?: "startAt" | "endAt" | "title" | "seriesId" | "status"; + order_direction?: "asc" | "desc"; + } = await schema.validateAsync(req.query); const filterObj = param.filter && param.filter !== "" ? JSON.parse(param.filter) : {}; - const result = await ScheduleModel.getScheduleCalendarRange(param.start, param.end, filterObj); + const result = await ScheduleModel.getScheduleCalendarRange(param.start_date, param.end_date, filterObj, { + limit: param.limit, + page: param.page, + order_field: param.order_field, + order_direction: param.order_direction, + }); return ReturnHelper.successResponseAny(res, 200, Language.lang.success_view, result); } catch (e: unknown) { diff --git a/src/model/schedule.ts b/src/model/schedule.ts index b0dfa83..d574f6f 100644 --- a/src/model/schedule.ts +++ b/src/model/schedule.ts @@ -31,6 +31,13 @@ type ScheduleExceptionPayload = { overrideEndAt?: string | null; }; +type CalendarQueryOptions = { + limit?: number; + page?: number; + order_field?: "startAt" | "endAt" | "title" | "seriesId" | "status"; + order_direction?: "asc" | "desc"; +}; + export class ScheduleModel { static async listSeries(filterObj = {}): Promise> { const repo = OrmHelper.DB.getRepository(ScheduleSeries); @@ -52,7 +59,7 @@ export class ScheduleModel { return query; } - static async getScheduleCalendarRange(start: string, end: string, filterObj = {}): Promise { + static async getScheduleCalendarRange(start: string, end: string, filterObj = {}, options: CalendarQueryOptions = {}): Promise { const startDate = new Date(start); const endDate = new Date(end); if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { @@ -96,8 +103,7 @@ export class ScheduleModel { exceptionMap.set(key, ex); } - const resourceMap = new Map(); - const events: any[] = []; + const allEvents: any[] = []; for (const series of seriesList) { const occurrenceDates = ScheduleModel.expandSeriesOccurrences(series, rangeStart, rangeEnd); @@ -121,15 +127,7 @@ export class ScheduleModel { const roomName = (room as any)?.room || (room as any)?.name || (room as any)?.code || ""; const doctorName = (doctor as any)?.name || (doctor as any)?.fullname || (doctor as any)?.username || ""; - resourceMap.set(resourceId, { - id: resourceId, - roomId, - roomName, - doctorId, - doctorName, - }); - - events.push({ + allEvents.push({ id: `occ:${series.id}:${occurrenceYmd}`, seriesId: series.id, resourceId, @@ -139,11 +137,52 @@ export class ScheduleModel { isRecurring: !!series.recurrence_rule, isException: !!exception, status: "confirmed", + _resource: { + id: resourceId, + roomId, + roomName, + doctorId, + doctorName, + }, }); } } - return { resources: [...resourceMap.values()], events }; + const orderField = options.order_field || "startAt"; + const orderDirection = (options.order_direction || "asc").toUpperCase() as "ASC" | "DESC"; + const sortFactor = orderDirection === "ASC" ? 1 : -1; + + allEvents.sort((a, b) => { + const av = a[orderField]; + const bv = b[orderField]; + if (av === bv) return 0; + return av > bv ? sortFactor : -sortFactor; + }); + + const totalCount = allEvents.length; + const page = options.page ?? 1; + const limit = options.limit ?? totalCount; + const offset = (page - 1) * limit; + const pagedEventsRaw = allEvents.slice(offset, offset + limit); + + const resourceMap = new Map(); + const pagedEvents = pagedEventsRaw.map((event) => { + resourceMap.set(event._resource.id, event._resource); + const { _resource, ...cleanEvent } = event; + return cleanEvent; + }); + + return { + resources: [...resourceMap.values()], + events: pagedEvents, + meta: { + total_count: totalCount, + page, + limit, + order_field: orderField, + order_direction: orderDirection, + }, + }; } static async getScheduleOptions(filterObj = {}): Promise { diff --git a/src/swagger/builder.js b/src/swagger/builder.js index 697017d..6a35f95 100644 --- a/src/swagger/builder.js +++ b/src/swagger/builder.js @@ -410,6 +410,13 @@ const doc = { $isException: false, $status: "confirmed" }, + scheduleCalendarMeta: { + $total_count: 24, + $page: 1, + $limit: 20, + $order_field: "startAt", + $order_direction: "ASC" + }, }, parameters: {