diff --git a/src/dashboard/dashboard.controller.ts b/src/dashboard/dashboard.controller.ts index 31a3a4cc1dc88afe12ba4b05e16bc015fd2a41e9..5631e9daa9da0d30a886abbb1e631faf3e7b03f9 100644 --- a/src/dashboard/dashboard.controller.ts +++ b/src/dashboard/dashboard.controller.ts @@ -1,23 +1,25 @@ 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, DashboardMahasiswaResDto, GetDashboardDosbimQueryDto, + GetDashboardTimTesisReqQueryDto, + GetDashboardTimTesisRespDto, JalurStatisticDto, } from "./dashboard.dto"; -import { - ApiBearerAuth, - ApiCookieAuth, - ApiOkResponse, - ApiTags, -} from "@nestjs/swagger"; +import { DashboardService } from "./dashboard.service"; @ApiTags("Dashboard") @ApiCookieAuth() @@ -60,4 +62,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 8d279937d0616eb007400145e622316c5d4b9012..d49ba0fd308a77aa3f0ea54ef0de78032f1ce200 100644 --- a/src/dashboard/dashboard.dto.ts +++ b/src/dashboard/dashboard.dto.ts @@ -4,16 +4,15 @@ import { OmitType, PickType, } from "@nestjs/swagger"; +import { IsNumberString, IsOptional, IsString } from "class-validator"; +import { Bimbingan, BimbinganStatus } from "src/entities/bimbingan.entity"; +import { PendaftaranSidsem } from "src/entities/pendaftaranSidsem"; +import { Pengguna } from "src/entities/pengguna.entity"; +import { Topik } from "src/entities/topik.entity"; import { JalurEnum, PendaftaranTesis, } from "../entities/pendaftaranTesis.entity"; -import { Topik } from "src/entities/topik.entity"; -import { Pengguna } from "src/entities/pengguna.entity"; -import { Bimbingan } from "src/entities/bimbingan.entity"; -import { PendaftaranSidsem } from "src/entities/pendaftaranSidsem"; -import { IsOptional } from "class-validator"; -import { BimbinganStatus } from "src/entities/bimbingan.entity"; class PickedTopikDashboard extends PickType(Topik, ["id", "judul"] as const) {} class PickedMhsDashboard extends PickType(Pengguna, [ @@ -114,3 +113,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 c2bfcb12e4e7c8bbcbfdfacf6b77a224c475a5b1..a4360e8f1010086dffb050df8315bc57fa92ce2c 100644 --- a/src/dashboard/dashboard.service.ts +++ b/src/dashboard/dashboard.service.ts @@ -1,25 +1,28 @@ import { BadRequestException, Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; -import { Brackets, Repository } from "typeorm"; +import { BimbinganService } from "src/bimbingan/bimbingan.service"; +import { Bimbingan } from "src/entities/bimbingan.entity"; +import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; +import { Konfigurasi } from "src/entities/konfigurasi.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 { Konfigurasi } from "src/entities/konfigurasi.entity"; -import { Bimbingan } from "src/entities/bimbingan.entity"; +import { Pengguna, RoleEnum } from "../entities/pengguna.entity"; import { DashboardDto, DashboardMahasiswaResDto, + DashboardTimTesisStatusEnum, + GetDashboardTimTesisReqQueryDto, + GetDashboardTimTesisRespDto, JalurStatisticDto, NoNIMUserDashboard, } from "./dashboard.dto"; -import { - PendaftaranSidsem, - TipeSidsemEnum, -} from "src/entities/pendaftaranSidsem"; -import { DosenBimbingan } from "src/entities/dosenBimbingan.entity"; -import { BimbinganService } from "src/bimbingan/bimbingan.service"; @Injectable() export class DashboardService { @@ -270,4 +273,172 @@ export class DashboardService { }, }; } + + 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 7523412cc562e04c5c854a3e596195e903ee6d33..127540cc0619fa7863227bf11029f78160287a84 100644 --- a/src/entities/topik.entity.ts +++ b/src/entities/topik.entity.ts @@ -1,3 +1,4 @@ +import { ApiProperty } from "@nestjs/swagger"; import { Column, Entity, @@ -6,7 +7,6 @@ import { PrimaryGeneratedColumn, } from "typeorm"; import { Pengguna } from "./pengguna.entity"; -import { ApiProperty } from "@nestjs/swagger"; @Entity() export class Topik { @@ -22,7 +22,7 @@ export class Topik { @Column({ type: "text" }) deskripsi: string; - @ApiProperty({ type: Pengguna }) + @ApiProperty({ type: () => Pengguna }) @ManyToOne(() => Pengguna, (pengguna) => pengguna.id) @JoinColumn({ name: "idPengaju" }) pengaju: Pengguna;