From 88a0dbac5907fc7600a32cd330951d15accee8ac Mon Sep 17 00:00:00 2001
From: Rinaldy Adin <16521390@mahasiswa.itb.ac.id>
Date: Tue, 30 Apr 2024 18:25:30 +0700
Subject: [PATCH] feat: dashboard tim tesis endpoint

---
 src/dashboard/dashboard.controller.ts |  34 +++--
 src/dashboard/dashboard.dto.ts        |  57 +++++++-
 src/dashboard/dashboard.service.ts    | 191 ++++++++++++++++++++++++--
 src/entities/topik.entity.ts          |   4 +-
 4 files changed, 257 insertions(+), 29 deletions(-)

diff --git a/src/dashboard/dashboard.controller.ts b/src/dashboard/dashboard.controller.ts
index 31a3a4c..5631e9d 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 8d27993..d49ba0f 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 c2bfcb1..a4360e8 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 7523412..127540c 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;
-- 
GitLab