diff --git a/src/dashboard/dashboard.controller.ts b/src/dashboard/dashboard.controller.ts index bb16aebd0a8cb044554d293950f8056baa3b6c4b..8cec5c50418c02eac5f35279a28b7582647e149e 100644 --- a/src/dashboard/dashboard.controller.ts +++ b/src/dashboard/dashboard.controller.ts @@ -1,22 +1,24 @@ import { Controller, Get, Query, Req, UseGuards } from "@nestjs/common"; -import { DashboardService } from "./dashboard.service"; -import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; -import { RolesGuard } from "src/middlewares/roles.guard"; +import { + ApiBearerAuth, + ApiCookieAuth, + ApiOkResponse, + ApiTags, +} from "@nestjs/swagger"; +import { Request } from "express"; +import { AuthDto } from "src/auth/auth.dto"; import { RoleEnum } from "src/entities/pengguna.entity"; +import { CustomAuthGuard } from "src/middlewares/custom-auth.guard"; import { Roles } from "src/middlewares/roles.decorator"; -import { AuthDto } from "src/auth/auth.dto"; -import { Request } from "express"; +import { RolesGuard } from "src/middlewares/roles.guard"; import { DashboardDto, GetDashboardDosbimQueryDto, + GetDashboardTimTesisReqQueryDto, + GetDashboardTimTesisRespDto, JalurStatisticDto, } from "./dashboard.dto"; -import { - ApiBearerAuth, - ApiCookieAuth, - ApiOkResponse, - ApiTags, -} from "@nestjs/swagger"; +import { DashboardService } from "./dashboard.service"; @ApiTags("Dashboard") @ApiCookieAuth() @@ -47,4 +49,14 @@ export class DashboardController { (request.user as AuthDto).id, ); } + + @UseGuards(CustomAuthGuard, RolesGuard) + @Roles(RoleEnum.S2_TIM_TESIS, RoleEnum.ADMIN) + @ApiOkResponse({ type: GetDashboardTimTesisRespDto }) + @Get("/tim-tesis") + async getDashboardTimTesis( + @Query() query: GetDashboardTimTesisReqQueryDto, + ): Promise<GetDashboardTimTesisRespDto> { + return this.dashboardService.getDashboardTimTesis(query); + } } diff --git a/src/dashboard/dashboard.dto.ts b/src/dashboard/dashboard.dto.ts index b11d1e6155273b64a4aaf73064f813eada47abd1..22d667de53d46fd14fd84d98baa2caf73e92644e 100644 --- a/src/dashboard/dashboard.dto.ts +++ b/src/dashboard/dashboard.dto.ts @@ -4,11 +4,11 @@ import { OmitType, PickType, } from "@nestjs/swagger"; -import { JalurEnum } from "../entities/pendaftaranTesis.entity"; -import { Topik } from "src/entities/topik.entity"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { IsOptional } from "class-validator"; +import { IsNumberString, IsOptional, IsString } from "class-validator"; import { BimbinganStatus } from "src/entities/bimbingan.entity"; +import { Pengguna } from "src/entities/pengguna.entity"; +import { Topik } from "src/entities/topik.entity"; +import { JalurEnum } from "../entities/pendaftaranTesis.entity"; class PickedTopikDashboard extends PickType(Topik, ["id", "judul"] as const) {} class PickedMhsDashboard extends PickType(Pengguna, [ @@ -52,3 +52,49 @@ export class GetDashboardDosbimQueryDto { @IsOptional() search: string; } + +export class GetDashboardTimTesisReqQueryDto { + @IsOptional() + @IsNumberString() + @ApiPropertyOptional({ description: "default: 1" }) + page?: number; + + @IsOptional() + @IsNumberString() + @ApiPropertyOptional({ description: "default: no limit" }) + limit?: number; + + @IsOptional() + @IsString() + @ApiPropertyOptional() + search?: string; +} + +export enum DashboardTimTesisStatusEnum { + PENGAJUAN_TOPIK = "PENGAJUAN_TOPIK", + SEMINAR_1 = "SEMINAR_1", + SEMINAR_2 = "SEMINAR_2", + SIDANG = "SIDANG", +} + +class GetDashboardTimTesisDataDto { + @ApiProperty() + nim_mahasiswa: string; + + @ApiProperty() + nama_mahasiswa: string; + + @ApiProperty({ isArray: true }) + dosen_pembimbing: string[]; + + @ApiProperty({ isArray: true, enum: DashboardTimTesisStatusEnum }) + status: DashboardTimTesisStatusEnum[]; +} + +export class GetDashboardTimTesisRespDto { + @ApiProperty() + maxPage: number; + + @ApiProperty({ type: GetDashboardTimTesisDataDto }) + data: GetDashboardTimTesisDataDto[]; +} diff --git a/src/dashboard/dashboard.service.ts b/src/dashboard/dashboard.service.ts index f168aa396a89aaf5a062d1c061baffa0ad988f45..80fe742036d2aab8a6c953993b43e6ce2feb43ec 100644 --- a/src/dashboard/dashboard.service.ts +++ b/src/dashboard/dashboard.service.ts @@ -1,13 +1,24 @@ import { BadRequestException, Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; -import { Brackets, Repository } from "typeorm"; +import { BimbinganService } from "src/bimbingan/bimbingan.service"; +import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; +import { + PendaftaranSidsem, + TipeSidsemEnum, +} from "src/entities/pendaftaranSidsem"; +import { ArrayContains, Brackets, In, Like, Repository } from "typeorm"; import { PendaftaranTesis, RegStatus, } from "../entities/pendaftaranTesis.entity"; -import { Pengguna } from "../entities/pengguna.entity"; -import { DashboardDto, JalurStatisticDto } from "./dashboard.dto"; -import { BimbinganService } from "src/bimbingan/bimbingan.service"; +import { Pengguna, RoleEnum } from "../entities/pengguna.entity"; +import { + DashboardDto, + DashboardTimTesisStatusEnum, + GetDashboardTimTesisReqQueryDto, + GetDashboardTimTesisRespDto, + JalurStatisticDto, +} from "./dashboard.dto"; @Injectable() export class DashboardService { @@ -16,6 +27,10 @@ export class DashboardService { private pendaftaranTesisRepository: Repository<PendaftaranTesis>, @InjectRepository(Pengguna) private penggunaRepository: Repository<Pengguna>, + @InjectRepository(DosenBimbingan) + private dosenBimbinganRepository: Repository<DosenBimbingan>, + @InjectRepository(PendaftaranSidsem) + private pendaftaranSidsemRepository: Repository<PendaftaranSidsem>, private bimbinganService: BimbinganService, ) {} @@ -113,4 +128,172 @@ export class DashboardService { return statistics as JalurStatisticDto[]; } + + async getDashboardTimTesis( + query: GetDashboardTimTesisReqQueryDto, + ): Promise<GetDashboardTimTesisRespDto> { + const [foundMahasiswa, total] = await this.penggunaRepository.findAndCount({ + select: { + id: true, + nama: true, + nim: true, + }, + take: query.limit || undefined, + skip: (query.page - 1) * query.limit || undefined, + where: [ + { + nim: Like(`%${query.search ?? ""}%`), + roles: ArrayContains([RoleEnum.S2_MAHASISWA]), + }, + { + nama: Like(`%${query.search ?? ""}%`), + roles: ArrayContains([RoleEnum.S2_MAHASISWA]), + }, + ], + order: { + nim: "ASC", + }, + }); + + const dosbimQuery = this.dosenBimbinganRepository.find({ + select: { + pendaftaran: { + id: true, + mahasiswaId: true, + }, + dosen: { + id: true, + nama: true, + }, + }, + relations: { + pendaftaran: true, + dosen: true, + }, + where: { + pendaftaran: { + mahasiswaId: In(foundMahasiswa.map(({ id }) => id)), + }, + }, + }); + + const topicAcceptedQuery = this.pendaftaranTesisRepository.find({ + select: { + id: true, + mahasiswaId: true, + }, + where: { + status: RegStatus.APPROVED, + mahasiswaId: In(foundMahasiswa.map(({ id }) => id)), + }, + }); + + const mhsSemProAcceptedQuery = this.pendaftaranSidsemRepository.find({ + select: { + pendaftaranTesis: { + mahasiswaId: true, + }, + }, + relations: { + pendaftaranTesis: true, + }, + where: { + tipe: TipeSidsemEnum.SEMINAR_1, + ditolak: false, + pendaftaranTesis: { + mahasiswaId: In(foundMahasiswa.map(({ id }) => id)), + }, + }, + }); + + const mhsSemTesAcceptedQuery = this.pendaftaranSidsemRepository.find({ + select: { + pendaftaranTesis: { + mahasiswaId: true, + }, + }, + relations: { + pendaftaranTesis: true, + }, + where: { + tipe: TipeSidsemEnum.SEMINAR_2, + ditolak: false, + pendaftaranTesis: { + mahasiswaId: In(foundMahasiswa.map(({ id }) => id)), + }, + }, + }); + + const mhsSidangAcceptedQuery = this.pendaftaranSidsemRepository.find({ + select: { + pendaftaranTesis: { + mahasiswaId: true, + }, + }, + relations: { + pendaftaranTesis: true, + }, + where: { + tipe: TipeSidsemEnum.SIDANG, + ditolak: false, + pendaftaranTesis: { + mahasiswaId: In(foundMahasiswa.map(({ id }) => id)), + }, + }, + }); + + const [ + foundDosbim, + topicAccepted, + mhsSemProAccepted, + mhsSemTesAccepted, + mhsSidangAccepted, + ] = await Promise.all([ + dosbimQuery, + topicAcceptedQuery, + mhsSemProAcceptedQuery, + mhsSemTesAcceptedQuery, + mhsSidangAcceptedQuery, + ]); + + const mhsStatusMap: Record<string, DashboardTimTesisStatusEnum[]> = {}; + const dosbimMap: Record<string, string[]> = {}; + + foundMahasiswa.forEach(({ id }) => { + mhsStatusMap[id] = []; + dosbimMap[id] = []; + }); + + foundDosbim.forEach(({ pendaftaran: { mahasiswaId }, dosen: { nama } }) => { + dosbimMap[mahasiswaId].push(nama); + }); + + topicAccepted.forEach(({ mahasiswaId }) => { + mhsStatusMap[mahasiswaId].push( + DashboardTimTesisStatusEnum.PENGAJUAN_TOPIK, + ); + }); + + mhsSemProAccepted.forEach(({ pendaftaranTesis: { mahasiswaId } }) => { + mhsStatusMap[mahasiswaId].push(DashboardTimTesisStatusEnum.SEMINAR_1); + }); + + mhsSemTesAccepted.forEach(({ pendaftaranTesis: { mahasiswaId } }) => { + mhsStatusMap[mahasiswaId].push(DashboardTimTesisStatusEnum.SEMINAR_2); + }); + + mhsSidangAccepted.forEach(({ pendaftaranTesis: { mahasiswaId } }) => { + mhsStatusMap[mahasiswaId].push(DashboardTimTesisStatusEnum.SEMINAR_2); + }); + + return { + maxPage: !!query.limit ? Math.ceil(total / query.limit) : 1, + data: foundMahasiswa.map(({ nim, id, nama }) => ({ + nim_mahasiswa: nim, + nama_mahasiswa: nama, + status: mhsStatusMap[id] ?? [], + dosen_pembimbing: dosbimMap[id] ?? [], + })), + }; + } } diff --git a/src/entities/topik.entity.ts b/src/entities/topik.entity.ts index 17eb3bf158b175d37e93a9a0684a16f943bc5ba1..75bbc9703a7c3c994c434b72b9b392a047ff6300 100644 --- a/src/entities/topik.entity.ts +++ b/src/entities/topik.entity.ts @@ -1,3 +1,4 @@ +import { ApiHideProperty, ApiProperty } from "@nestjs/swagger"; import { Column, Entity, @@ -6,7 +7,6 @@ import { PrimaryGeneratedColumn, } from "typeorm"; import { Pengguna } from "./pengguna.entity"; -import { ApiHideProperty, ApiProperty } from "@nestjs/swagger"; @Entity() export class Topik {