diff --git a/.env b/.env deleted file mode 100644 index 24829e2..0000000 --- a/.env +++ /dev/null @@ -1,7 +0,0 @@ -# Environment variables declared in this file are automatically made available to Prisma. -# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema - -# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB. -# See the documentation for all the connection string options: https://pris.ly/d/connection-strings - -DATABASE_URL="postgresql://bip:Production_123d@localhost:5433/sistem_desa_mandiri?schema=public" \ No newline at end of file diff --git a/.gitignore b/.gitignore index f3e886e..cdedd88 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# folder foto kandidat +/public/image/ +/public/file/ \ No newline at end of file diff --git a/api.http b/api.http new file mode 100644 index 0000000..464147b --- /dev/null +++ b/api.http @@ -0,0 +1,262 @@ +### +POST http://localhost:3000/api/auth/login/ HTTP/1.1 +Content-Type: application/json + +{ + "phone": "6287701790942" +} + +// GROUP + +### +GET http://localhost:3000/api/group/get?path=get-all-group&villageId=121212&active=true HTTP/1.1 + +### +POST http://localhost:3000/api/group/post?path=create-group HTTP/1.1 +Content-Type: application/json + +{ + "name": "LPD 3", + "idVillage": "121212" +} + +### +POST http://localhost:3000/api/group/post?path=update-group HTTP/1.1 +Content-Type: application/json + +{ + "id": "1", + "name": "LPD2", + "idVillage": "121212" +} + +### +POST http://localhost:3000/api/group/post?path=delete-group HTTP/1.1 +Content-Type: application/json + +{ + "id": "1", + "idVillage": "121212" +} + +### +GET http://localhost:3000/api/group/get?path=get-one-group&groupId=3 HTTP/1.1 + + +// VILLAGE + +### +GET http://localhost:3000/api/village/get?path=get-all-village HTTP/1.1 + +### +GET http://localhost:3000/api/village/get?path=get-one-village HTTP/1.1 + +### +POST http://localhost:3000/api/village/post?path=create-village HTTP/1.1 +Content-Type: application/json + +{ + "name": "nama satu satu ", + "desc": "description data" +} + +### +POST http://localhost:3000/api/village/post?path=update-village HTTP/1.1 +Content-Type: application/json + +{ + "id": "11", + "name": "nama satu new", + "desc": "description data new" +} + +### +POST http://localhost:3000/api/village/post?path=delete-village HTTP/1.1 +Content-Type: application/json + +{ + "id": "11" +} + + +// POSITION + +### +GET http://localhost:3000/api/position/get?path=get-all-position&groupID=2 HTTP/1.1 + +### +GET http://localhost:3000/api/position/get?path=get-one-position&positionId=clz24bff70001w01in64dd9ea HTTP/1.1 + +### +POST http://localhost:3000/api/position/post?path=create-position HTTP/1.1 +Content-Type: application/json + +{ + "name": "Anggota", + "idGroup": "1" +} + +### +POST http://localhost:3000/api/position/post?path=update-position HTTP/1.1 +Content-Type: application/json + +{ + "id": "1", + "name": "Wakil Sekertaris", + "idGroup": "2" +} + +### +POST http://localhost:3000/api/position/post?path=delete-position HTTP/1.1 +Content-Type: application/json + +{ + "id": "1" +} + + +// USERS + +### +GET http://localhost:3000/api/user/get?path=get-all-users&active=true HTTP/1.1 + +### +GET http://localhost:3000/api/user/get?path=get-one-users&userID=devAmalia HTTP/1.1 + +### +POST http://localhost:3000/api/user/post?path=create-users HTTP/1.1 +Content-Type: application/json + +{ + "idUserRole": "user", + "idVillage": "121212", + "idGroup": "2", + "idPosition": "clz24bff70001w01in64dd9ea", + "nik": "53239236727329", + "name": "coba user", + "email": "cobauser@gmail.com", + "phone": "07319031009", + "gender": "M" +} + +### +POST http://localhost:3000/api/user/post?path=update-users HTTP/1.1 +Content-Type: application/json + +{ + "id": "clz6dq88e0001b3mlyl4vjaf8", + "idUserRole": "user", + "idVillage": "121212", + "idGroup": "2", + "idPosition": "clz24bff70001w01in64dd9ea", + "nik": "53239236727329", + "name": "coba user edit", + "email": "cobauser@gmail.com", + "phone": "07319031009", + "gender": "M" +} + +### +POST http://localhost:3000/api/user/post?path=delete-users HTTP/1.1 +Content-Type: application/json + +{ + "id": "clz6dq88e0001b3mlyl4vjaf8" +} + + + // Announcement + +### +POST http://localhost:3000/api/announcement/post?path=create-announcement HTTP/1.1 +Content-Type: application/json + +{ + + "title": "cobaannouncement1 dsdsd", + "desc": "coba announcement sdsdsd", + "idVillage": "desa1", + "createBy": "superAdminLukman", + "groups": [ + { + "idGroup": "group1", + "idDivision": "1" + }, + ] +} + +### +GET http://localhost:3000/api/announcement/get?path=get-all-announcement HTTP/1.1 +### +GET http://localhost:3000/api/announcement/get?path=get-user-announcement HTTP/1.1 + +### +GET http://localhost:3000/api/announcement/get?path=get-all-announcement&divisionI=1 HTTP/1.1 + +### +GET http://localhost:3000/api/announcement/get?path=get-one-announcement&announcementId=1 HTTP/1.1 + +### +POST http://localhost:3000/api/announcement/post?path=update-announcement HTTP/1.1 +Content-Type: application/json + +{ + "id": "clz6kqzvt000eb3mle1nyz6fd", + "title": "cobaannouncement coba coba", + "desc": "coba announcement coba coba", + "idVillage": "121212", + "createBy": "clz6dq88e0001b3mlyl4vjaf8", + "groups": [ + { + "idGroup": "1", + "idDivision": "1" + }, + { + "idGroup": "1", + "idDivision": "2" + } + ] +} + +### +POST http://localhost:3000/api/announcement/post?path=delete-announcement HTTP/1.1 +Content-Type: application/json + +{ + "id": "clz6naf9s000ib3mlf5aujk92" +} + + +### +POST http://localhost:3000/api/announcement HTTP/1.1 +Content-Type: application/json + +{ + "title": "test", + "desc": "test", + "groups": + [ + { + "idAnnouncement": "1", + "idGroup": "group1", + "idDivision": "1", + "isActive": true + }, + { + "idAnnouncement": "1", + "idGroup": "group1", + "idDivision": "clzknx2xy0001bt0wygcwjfph", + "isActive": true + } + ] +} + + + +### +DELETE http://localhost:3000/api/discussion/clzs6b7kb0001aw16g5clh5vw HTTP/1.1 +Content-Type: application/json + +{ + "status":1 +} \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index 4678774..059cc82 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,8 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + devIndicators: { + buildActivityPosition: 'bottom-right', + }, +}; export default nextConfig; diff --git a/package.json b/package.json index 63937c6..2516225 100644 --- a/package.json +++ b/package.json @@ -9,20 +9,21 @@ "lint": "next lint" }, "prisma": { - "seed": "tsx prisma/seed.ts" + "seed": "npx tsx prisma/seed.ts" }, "dependencies": { "@hookstate/core": "^4.0.1", + "@hookstate/localstored": "^4.0.2", "@mantine/carousel": "^7.11.1", "@mantine/charts": "^7.11.0", "@mantine/code-highlight": "^7.11.0", "@mantine/core": "^7.11.0", - "@mantine/dates": "^7.11.0", - "@mantine/dropzone": "^7.11.0", + "@mantine/dates": "^7.11.1", + "@mantine/dropzone": "^7.12.1", "@mantine/form": "^7.11.0", "@mantine/hooks": "^7.11.0", "@mantine/modals": "^7.11.0", - "@mantine/notifications": "^7.11.0", + "@mantine/notifications": "^7.12.2", "@mantine/nprogress": "^7.11.0", "@mantine/spotlight": "^7.11.0", "@mantine/tiptap": "^7.11.0", @@ -33,15 +34,25 @@ "@tiptap/starter-kit": "^2.4.0", "@types/lodash": "^4.17.6", "dayjs": "^1.11.11", + "echarts": "^5.5.1", + "echarts-for-react": "^3.0.2", "embla-carousel-autoplay": "^7.1.0", "embla-carousel-react": "^7.1.0", + "iron-session": "^8.0.2", "lodash": "^4.17.21", + "moment": "^2.30.1", "next": "14.2.4", + "pdfjs-dist": "^4.6.82", + "prettier": "^3.3.2", "react": "^18", "react-dom": "^18", "react-hot-toast": "^2.4.1", "react-icons": "^5.2.1", - "recharts": "2" + "react-simple-toasts": "^5.10.0", + "readdirp": "^3.6.0", + "recharts": "2", + "rrule": "^2.8.1", + "yargs": "^17.7.2" }, "devDependencies": { "@types/node": "^20.14.9", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 06627ab..9a14f90 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -13,6 +13,28 @@ datasource db { url = env("DATABASE_URL") } +model AdminRole { + id String @id @default(cuid()) + name String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + Admin Admin[] +} + +model Admin { + id String @id @default(cuid()) + AdminRole AdminRole @relation(fields: [idAdminRole], references: [id]) + idAdminRole String + name String + phone String @unique + email String? @unique + gender String @default("M") //M= Male, F= Female + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + model UserRole { id String @id @default(cuid()) name String @@ -24,32 +46,34 @@ model UserRole { } model Village { - id String @id @default(cuid()) - name String - desc String @db.Text - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - Group Group[] - User User[] - Annoucement Annoucement[] - Project Project[] - Division Division[] + id String @id @default(cuid()) + idTheme String? + name String + desc String @db.Text + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + Group Group[] + User User[] + Announcement Announcement[] + Project Project[] + Division Division[] + ColorTheme ColorTheme[] } model Group { - id String @id @default(cuid()) - Village Village @relation(fields: [idVillage], references: [id]) - idVillage String - name String - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - Position Position[] - User User[] - Project Project[] - Division Division[] - AnnoucementMember AnnoucementMember[] + id String @id @default(cuid()) + Village Village @relation(fields: [idVillage], references: [id]) + idVillage String + name String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + Position Position[] + User User[] + Project Project[] + Division Division[] + AnnouncementMember AnnouncementMember[] } model Position { @@ -67,21 +91,23 @@ model User { id String @id @default(cuid()) UserRole UserRole @relation(fields: [idUserRole], references: [id]) idUserRole String - Village Village? @relation(fields: [idVillage], references: [id]) - idVillage String? - Group Group? @relation(fields: [idGroup], references: [id]) - idGroup String? - Position Position? @relation(fields: [idPosition], references: [id]) - idPosition String? + Village Village @relation(fields: [idVillage], references: [id]) + idVillage String + Group Group @relation(fields: [idGroup], references: [id]) + idGroup String + Position Position @relation(fields: [idPosition], references: [id]) + idPosition String nik String @unique name String phone String @unique email String? @unique gender String @default("M") //M= Male, F= Female + img String? + isFirstLogin Boolean @default(true) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - Annoucement Annoucement[] + Announcement Announcement[] Project Project[] ProjectMember ProjectMember[] ProjectComment ProjectComment[] @@ -94,6 +120,7 @@ model User { DivisionDisscussionComment DivisionDisscussionComment[] DivisionDocumentFolderFile DivisionDocumentFolderFile[] DivisionCalendar DivisionCalendar[] + DivisionCalendarMember DivisionCalendarMember[] } model UserLog { @@ -109,31 +136,31 @@ model UserLog { updatedAt DateTime @updatedAt } -model Annoucement { - id String @id @default(cuid()) - Village Village @relation(fields: [idVillage], references: [id]) - idVillage String - title String - desc String @db.Text - isActive Boolean @default(true) - User User @relation(fields: [createdBy], references: [id]) - createdBy String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - AnnoucementMember AnnoucementMember[] +model Announcement { + id String @id @default(cuid()) + Village Village @relation(fields: [idVillage], references: [id]) + idVillage String + title String + desc String @db.Text + isActive Boolean @default(true) + User User @relation(fields: [createdBy], references: [id]) + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + AnnouncementMember AnnouncementMember[] } -model AnnoucementMember { - id String @id @default(cuid()) - Annoucement Annoucement @relation(fields: [idAnnoucement], references: [id]) - idAnnoucement String - Group Group @relation(fields: [idGroup], references: [id]) - idGroup String - Division Division @relation(fields: [idDivision], references: [id]) - idDivision String - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt +model AnnouncementMember { + id String @id @default(cuid()) + Announcement Announcement @relation(fields: [idAnnouncement], references: [id]) + idAnnouncement String + Group Group @relation(fields: [idGroup], references: [id]) + idGroup String + Division Division @relation(fields: [idDivision], references: [id]) + idDivision String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Project { @@ -142,8 +169,10 @@ model Project { idVillage String Group Group @relation(fields: [idGroup], references: [id]) idGroup String - name String - desc String @db.Text + title String + status Int @default(0) // 0 = pending, 1 = ongoing, 2 = done, 3 = cancelled + desc String? @db.Text + reason String? @db.Text isActive Boolean @default(true) User User @relation(fields: [createdBy], references: [id]) createdBy String @@ -152,6 +181,7 @@ model Project { ProjectMember ProjectMember[] ProjectFile ProjectFile[] ProjectComment ProjectComment[] + ProjectTask ProjectTask[] } model ProjectMember { @@ -172,6 +202,21 @@ model ProjectFile { idProject String name String extension String + idStorage String? + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model ProjectTask { + id String @id @default(cuid()) + Project Project @relation(fields: [idProject], references: [id]) + idProject String + title String + desc String? + status Int @default(0) // 0 = todo, 1 = done + dateStart DateTime @db.Date + dateEnd DateTime @db.Date isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -203,7 +248,7 @@ model Division { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt DivisionMember DivisionMember[] - AnnoucementMember AnnoucementMember[] + AnnouncementMember AnnouncementMember[] DivisionProject DivisionProject[] DivisionProjectTask DivisionProjectTask[] DivisionProjectMember DivisionProjectMember[] @@ -213,6 +258,7 @@ model Division { DivisionDocumentShare DivisionDocumentShare[] DivisionCalendar DivisionCalendar[] DivisionCalendarReminder DivisionCalendarReminder[] + ContainerFileDivision ContainerFileDivision[] } model DivisionMember { @@ -229,85 +275,98 @@ model DivisionMember { } model DivisionProject { - id String @id @default(cuid()) - Division Division @relation(fields: [idDivision], references: [id]) - idDivision String - title String - desc String @db.Text - status Int @default(0) - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + Division Division @relation(fields: [idDivision], references: [id]) + idDivision String + title String + desc String? @db.Text + reason String? @db.Text + status Int @default(0) // 0 = pending, 1 = ongoing, 2 = done, 3 = cancelled + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + DivisionProjectTask DivisionProjectTask[] + DivisionProjectMember DivisionProjectMember[] + DivisionProjectFile DivisionProjectFile[] } model DivisionProjectTask { - id String @id @default(cuid()) - Division Division @relation(fields: [idDivision], references: [id]) - idDivision String - title String - desc String @db.Text - status Int @default(0) - dateStart DateTime @db.Date - dateEnd DateTime @db.Date - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + Division Division @relation(fields: [idDivision], references: [id]) + idDivision String + DivisionProject DivisionProject @relation(fields: [idProject], references: [id]) + idProject String + title String + desc String? @db.Text + status Int @default(0) // 0 = todo, 1 = done + dateStart DateTime @db.Date + dateEnd DateTime @db.Date + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model DivisionProjectMember { - id String @id @default(cuid()) - Division Division @relation(fields: [idDivision], references: [id]) - idDivision String - User User @relation(fields: [idUser], references: [id]) - idUser String - isLeader Boolean @default(false) - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + Division Division @relation(fields: [idDivision], references: [id]) + idDivision String + DivisionProject DivisionProject @relation(fields: [idProject], references: [id]) + idProject String + User User @relation(fields: [idUser], references: [id]) + idUser String + isLeader Boolean @default(false) + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model DivisionProjectFile { - id String @id @default(cuid()) - Division Division @relation(fields: [idDivision], references: [id]) - idDivision String - name String - extension String - isActive Boolean @default(true) - User User @relation(fields: [createdBy], references: [id]) - createdBy String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + Division Division @relation(fields: [idDivision], references: [id]) + idDivision String + DivisionProject DivisionProject @relation(fields: [idProject], references: [id]) + idProject String + ContainerFileDivision ContainerFileDivision @relation(fields: [idFile], references: [id]) + idFile String + isActive Boolean @default(true) + User User @relation(fields: [createdBy], references: [id]) + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model DivisionDisscussion { - id String @id @default(cuid()) - Division Division @relation(fields: [idDivision], references: [id]) - idDivision String - title String - desc String @db.Text - status Int @default(1) // 1 = open, 2 = close - isActive Boolean @default(true) - User User @relation(fields: [createdBy], references: [id]) - createdBy String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + Division Division @relation(fields: [idDivision], references: [id]) + idDivision String + title String? + desc String @db.Text + status Int @default(1) // 1 = open, 2 = close + isActive Boolean @default(true) + User User @relation(fields: [createdBy], references: [id]) + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + DivisionDisscussionComment DivisionDisscussionComment[] } model DivisionDisscussionComment { - id String @id @default(cuid()) - idDisscussion String - comment String @db.Text - isActive Boolean @default(true) - User User @relation(fields: [createdBy], references: [id]) - createdBy String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + DivisionDisscussion DivisionDisscussion @relation(fields: [idDisscussion], references: [id]) + idDisscussion String + comment String @db.Text + isActive Boolean @default(true) + User User @relation(fields: [createdBy], references: [id]) + createdBy String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model DivisionDocumentFolderFile { id String @id @default(cuid()) Division Division @relation(fields: [idDivision], references: [id]) idDivision String + idStorage String? category String @default("FOLDER") // FOLDER OR FILE name String extension String @@ -337,16 +396,21 @@ model DivisionCalendar { idDivision String title String desc String @db.Text + linkMeet String? @db.Text dateStart DateTime @db.Date - dateEnd DateTime @db.Date - repeatEventTyper String - reminderInterval String + dateEnd DateTime? @db.Date + timeStart DateTime @db.Time() + timeEnd DateTime @db.Time() + repeatEventTyper String // once = Acara 1 Kali, weekdays = hari Kerja (senin - jumat), daily=setiap hari , weekly = setiap minggu, monthly = setiap bulan, yearly = setiap tahun + repeatValue Int @default(1) + reminderInterval String? status Int @default(0) isActive Boolean @default(true) User User @relation(fields: [createdBy], references: [id]) createdBy String createdAt DateTime @default(now()) DivisionCalendarReminder DivisionCalendarReminder[] + DivisionCalendarMember DivisionCalendarMember[] } model DivisionCalendarReminder { @@ -356,9 +420,61 @@ model DivisionCalendarReminder { DivisionCalendar DivisionCalendar @relation(fields: [idCalendar], references: [id]) idCalendar String dateStart DateTime @db.Date - dateEnd DateTime @db.Date + dateEnd DateTime? @db.Date + timeStart DateTime @db.Time() + timeEnd DateTime @db.Time() status Int @default(0) isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model DivisionCalendarMember { + id String @id @default(cuid()) + DivisionCalendar DivisionCalendar @relation(fields: [idCalendar], references: [id]) + idCalendar String + User User @relation(fields: [idUser], references: [id]) + idUser String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model ContainerImage { + id String @id @default(cuid()) + category String + idCategory String + extension String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model ContainerFileDivision { + id String @id @default(cuid()) + Division Division @relation(fields: [idDivision], references: [id]) + idDivision String + idStorage String? + name String + extension String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + DivisionProjectFile DivisionProjectFile[] +} + +model ColorTheme { + id String @id @default(cuid()) + Village Village? @relation(fields: [idVillage], references: [id]) + idVillage String? + name String + utama String + bgUtama String + bgIcon String + bgFiturHome String + bgFiturDivision String + bgTotalKegiatan String + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/prisma/seed.ts b/prisma/seed.ts index f4947af..b55d1ae 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,8 +1,132 @@ -import { seederUser, seederUserRole } from '@/module/seeder'; +import { seederAdmin, seederAdminRole, seederDesa, seederGroup, seederPosition, seederTheme, seederUser, seederUserRole } from '@/module/seeder'; import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient() async function main() { + // ADMIN ROLE + for (let data of seederAdminRole) { + await prisma.adminRole.upsert({ + where: { + id: data.id + }, + update: { + name: data.name + }, + create: { + id: data.id, + name: data.name, + }, + }) + } + + // ADMIN + for (let data of seederAdmin) { + await prisma.admin.upsert({ + where: { + id: data.id + }, + update: { + name: data.name, + idAdminRole: data.idAdminRole, + phone: data.phone, + email: data.email, + gender: data.gender + }, + create: { + id: data.id, + idAdminRole: data.idAdminRole, + phone: data.phone, + email: data.email, + gender: data.gender, + name: data.name + }, + }) + } + + // THEME + for (let data of seederTheme) { + await prisma.colorTheme.upsert({ + where: { + id: data.id + }, + update: { + name: data.name, + utama: data.utama, + bgUtama: data.bgUtama, + bgIcon: data.bgIcon, + bgFiturHome: data.bgFiturHome, + bgFiturDivision: data.bgFiturDivisi, + bgTotalKegiatan: data.bgTotalKegiatan + }, + create: { + id: data.id, + name: data.name, + utama: data.utama, + bgUtama: data.bgUtama, + bgIcon: data.bgIcon, + bgFiturHome: data.bgFiturHome, + bgFiturDivision: data.bgFiturDivisi, + bgTotalKegiatan: data.bgTotalKegiatan + } + }) + } + + // DESA + for (let data of seederDesa) { + await prisma.village.upsert({ + where: { + id: data.id + }, + update: { + name: data.name, + desc: data.desc, + idTheme: "theme1" + }, + create: { + id: data.id, + name: data.name, + desc: data.desc, + idTheme: "theme1" + } + }) + } + + // GROUP + for (let data of seederGroup) { + await prisma.group.upsert({ + where: { + id: data.id + }, + update: { + name: data.name, + idVillage: data.idVillage + }, + create: { + id: data.id, + name: data.name, + idVillage: data.idVillage + } + }) + } + + // POSITION + for (let data of seederPosition) { + await prisma.position.upsert({ + where: { + id: data.id + }, + update: { + name: data.name, + idGroup: data.idGroup + }, + create: { + id: data.id, + name: data.name, + idGroup: data.idGroup + } + }) + } + // USER ROLE for (let data of seederUserRole) { await prisma.userRole.upsert({ @@ -27,10 +151,21 @@ async function main() { id: data.id }, update: { - name: data.name + idVillage: data.idVillage, + idGroup: data.idGroup, + idPosition: data.idPosition, + idUserRole: data.idUserRole, + nik: data.nik, + name: data.name, + // phone: data.phone, + email: data.email, + gender: data.gender }, create: { id: data.id, + idVillage: data.idVillage, + idGroup: data.idGroup, + idPosition: data.idPosition, idUserRole: data.idUserRole, nik: data.nik, name: data.name, @@ -40,6 +175,7 @@ async function main() { }, }) } + } main().then(async () => { diff --git a/public/assets/.gitkeep b/public/assets/file/.gitkeep similarity index 100% rename from public/assets/.gitkeep rename to public/assets/file/.gitkeep diff --git a/src/module/division/calendar/index.ts b/public/file/dokumen/.gitkeep similarity index 100% rename from src/module/division/calendar/index.ts rename to public/file/dokumen/.gitkeep diff --git a/src/module/division/discussion/index.ts b/public/file/project/.gitkeep similarity index 100% rename from src/module/division/discussion/index.ts rename to public/file/project/.gitkeep diff --git a/src/module/division/document/index.ts b/public/file/task/.gitkeep similarity index 100% rename from src/module/division/document/index.ts rename to public/file/task/.gitkeep diff --git a/src/module/division/index.ts b/public/image/user/.gitkeep similarity index 100% rename from src/module/division/index.ts rename to public/image/user/.gitkeep diff --git a/src/module/division/project/index.ts b/public/image/village/.gitkeep similarity index 100% rename from src/module/division/project/index.ts rename to public/image/village/.gitkeep diff --git a/src/app/(application)/announcement/[id]/page.tsx b/src/app/(application)/announcement/[id]/page.tsx index 872826e..8bd5211 100644 --- a/src/app/(application)/announcement/[id]/page.tsx +++ b/src/app/(application)/announcement/[id]/page.tsx @@ -1,8 +1,12 @@ -import { ViewDetailAnnouncement } from "@/module/announcement"; +import { DetailAnnouncement, NavbarDetailAnnouncement } from "@/module/announcement"; +import { Box } from "@mantine/core"; function Page({ params }: { params: { id: string } }) { return ( - + + + + ) } diff --git a/src/app/(application)/announcement/create-user/page.tsx b/src/app/(application)/announcement/create-user/page.tsx new file mode 100644 index 0000000..afb2451 --- /dev/null +++ b/src/app/(application)/announcement/create-user/page.tsx @@ -0,0 +1,11 @@ +import CreateUsersAnnouncement from '@/module/announcement/ui/create_users_announcement'; +import React from 'react'; + +function Page() { + return ( + // + <> + ); +} + +export default Page; diff --git a/src/app/(application)/announcement/create/page.tsx b/src/app/(application)/announcement/create/page.tsx index c4f77f1..a5ca3d3 100644 --- a/src/app/(application)/announcement/create/page.tsx +++ b/src/app/(application)/announcement/create/page.tsx @@ -1,8 +1,9 @@ -import { ViewCreateAnnouncement } from "@/module/announcement"; +import { CreateAnnouncement } from "@/module/announcement"; + function Page() { return ( - + ) } diff --git a/src/app/(application)/announcement/edit/[id]/page.tsx b/src/app/(application)/announcement/edit/[id]/page.tsx index b9f348b..31ddb52 100644 --- a/src/app/(application)/announcement/edit/[id]/page.tsx +++ b/src/app/(application)/announcement/edit/[id]/page.tsx @@ -1,8 +1,11 @@ -import { ViewEditAnnouncement } from "@/module/announcement"; +import { EditAnnouncement } from "@/module/announcement"; +import { Box } from "@mantine/core"; function Page({ params }: { params: { id: any } }) { return ( - + + + ) } diff --git a/src/app/(application)/announcement/page.tsx b/src/app/(application)/announcement/page.tsx index 4551abb..25b2f51 100644 --- a/src/app/(application)/announcement/page.tsx +++ b/src/app/(application)/announcement/page.tsx @@ -1,8 +1,15 @@ -import { ViewListAnnouncement } from "@/module/announcement"; +import { ViewFilter } from "@/module/_global"; +import { ListAnnouncement, NavbarAnnouncement } from "@/module/announcement"; +import { Box } from "@mantine/core"; -function Page() { +function Page({ searchParams }: { searchParams: { page: string } }) { + if (searchParams.page == 'filter') + return return ( - + + + + ) } diff --git a/src/app/(application)/color-palette/create/page.tsx b/src/app/(application)/color-palette/create/page.tsx new file mode 100644 index 0000000..747668e --- /dev/null +++ b/src/app/(application)/color-palette/create/page.tsx @@ -0,0 +1,10 @@ +import { CreatePaletteColor } from '@/module/color_palette'; +import React from 'react'; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/color-palette/page.tsx b/src/app/(application)/color-palette/page.tsx new file mode 100644 index 0000000..452f04c --- /dev/null +++ b/src/app/(application)/color-palette/page.tsx @@ -0,0 +1,10 @@ +import { ListColorPalette } from '@/module/color_palette'; +import React from 'react'; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/color-palette/update/[id]/page.tsx b/src/app/(application)/color-palette/update/[id]/page.tsx new file mode 100644 index 0000000..946e8cb --- /dev/null +++ b/src/app/(application)/color-palette/update/[id]/page.tsx @@ -0,0 +1,10 @@ +import { EditPaletteColor } from '@/module/color_palette'; +import React from 'react'; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/detail-feature/page.tsx b/src/app/(application)/detail-feature/page.tsx deleted file mode 100644 index 7838a54..0000000 --- a/src/app/(application)/detail-feature/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { ViewDetailFeature } from '@/module/home'; -import React from 'react'; - -function Page() { - return ( - - ); -} - -export default Page; \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/calender/[detail]/add-member/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/calender/[detail]/add-member/page.tsx new file mode 100644 index 0000000..1f76c6b --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/calender/[detail]/add-member/page.tsx @@ -0,0 +1,10 @@ +import { CreateUserDetailCalender } from '@/module/calender'; +import React from 'react'; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/division/[id]/(fitur-division)/calender/[detail]/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/calender/[detail]/page.tsx new file mode 100644 index 0000000..eca6613 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/calender/[detail]/page.tsx @@ -0,0 +1,12 @@ +import { DetailEventDivision } from '@/module/calender'; +import React from 'react'; + +function Page() { + return ( + <> + + + ); +} + +export default Page; diff --git a/src/app/(application)/division/[id]/(fitur-division)/calender/create/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/calender/create/page.tsx new file mode 100644 index 0000000..d1392f1 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/calender/create/page.tsx @@ -0,0 +1,11 @@ +import { CreateCalenderDivisionCaleder } from '@/module/calender'; +import React from 'react'; + +function Page({ searchParams }: { searchParams: any }) { + + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/division/[id]/(fitur-division)/calender/history/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/calender/history/page.tsx new file mode 100644 index 0000000..26817a2 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/calender/history/page.tsx @@ -0,0 +1,10 @@ +import { HistoryDivisionCalender } from '@/module/calender'; +import React from 'react'; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/division/[id]/(fitur-division)/calender/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/calender/page.tsx new file mode 100644 index 0000000..d071bf1 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/calender/page.tsx @@ -0,0 +1,10 @@ +import { NavbarDivisionCalender } from '@/module/calender'; +import React from 'react'; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/division/[id]/(fitur-division)/calender/update/[detail]/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/calender/update/[detail]/page.tsx new file mode 100644 index 0000000..60b4434 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/calender/update/[detail]/page.tsx @@ -0,0 +1,10 @@ +import { UpdateDivisionCalender } from '@/module/calender'; +import React from 'react'; + +function Page({ searchParams }: { searchParams: any }) { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/page.tsx new file mode 100644 index 0000000..9ab4347 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/discussion/[detail]/page.tsx @@ -0,0 +1,10 @@ +import { DetailDiscussion } from "@/module/discussion" + + +function Page({ params}: { params: { detail: string, id: string } }) { + return ( + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/discussion/create/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/discussion/create/page.tsx new file mode 100644 index 0000000..aba906b --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/discussion/create/page.tsx @@ -0,0 +1,13 @@ +import { LayoutNavbarNew } from "@/module/_global"; +import { FormCreateDiscussion } from "@/module/discussion"; + +function Page({ params }: { params: { id: string } }) { + return ( + <> + } /> + + + ) +} + +export default Page; \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/discussion/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/discussion/page.tsx new file mode 100644 index 0000000..66c768a --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/discussion/page.tsx @@ -0,0 +1,13 @@ +import { ListDiscussion, NavbarListDiscussion } from '@/module/discussion'; +import React from 'react'; + +function Page({ params }: { params: { id: string } }) { + return ( +
+ + +
+ ); +} + +export default Page; diff --git a/src/app/(application)/division/[id]/(fitur-division)/discussion/update/[detail]/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/discussion/update/[detail]/page.tsx new file mode 100644 index 0000000..d66e7f2 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/discussion/update/[detail]/page.tsx @@ -0,0 +1,15 @@ +import { LayoutNavbarNew } from "@/module/_global"; +import { FormEditDiscussion } from "@/module/discussion"; +import { Box } from "@mantine/core"; + +function Page() { + return ( + + } /> + + + + ) +} + +export default Page; \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/document/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/document/page.tsx new file mode 100644 index 0000000..d9e6811 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/document/page.tsx @@ -0,0 +1,13 @@ +import { NavbarDocumentDivision } from '@/module/document'; +import { Box } from '@mantine/core'; +import React from 'react'; + +function Page({ searchParams }: { searchParams: any }) { + return ( + + + + ); +} + +export default Page; diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-file/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-file/page.tsx new file mode 100644 index 0000000..a18e3be --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-file/page.tsx @@ -0,0 +1,7 @@ +import { AddFileDetailTask } from "@/module/task"; + +export default function Page() { + return ( + + ) +} \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-member/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-member/page.tsx new file mode 100644 index 0000000..9f9852e --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-member/page.tsx @@ -0,0 +1,9 @@ +import { AddMemberDetailTask } from "@/module/task" + +function Page() { + return ( + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-task/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-task/page.tsx new file mode 100644 index 0000000..64459c4 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/add-task/page.tsx @@ -0,0 +1,9 @@ +import { AddDetailTask } from "@/module/task" + +function Page() { + return ( + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/cancel/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/cancel/page.tsx new file mode 100644 index 0000000..e63ee85 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/cancel/page.tsx @@ -0,0 +1,9 @@ +import { CancelTask } from "@/module/task" + +function Page() { + return ( + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/edit/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/edit/page.tsx new file mode 100644 index 0000000..290ecb6 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/edit/page.tsx @@ -0,0 +1,9 @@ +import { EditTask } from "@/module/task" + +function Page() { + return ( + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx new file mode 100644 index 0000000..293102f --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/[detail]/page.tsx @@ -0,0 +1,18 @@ +import { NavbarDetailDivisionTask, ProgressDetailTask, ListTugasDetailTask, ListFileDetailTask, ListAnggotaDetailTask } from "@/module/task" +import { Box } from "@mantine/core" + +function Page() { + return ( + + + + + + + + + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/create/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/create/page.tsx new file mode 100644 index 0000000..543ecc5 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/create/page.tsx @@ -0,0 +1,11 @@ +import { CreateTask, FileSave } from "@/module/task"; + +function Page({ searchParams }: { searchParams: any }) { + + // if (searchParams.page == "file-save") + // return + + return +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/edit/[detail]/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/edit/[detail]/page.tsx new file mode 100644 index 0000000..fe6054e --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/edit/[detail]/page.tsx @@ -0,0 +1,9 @@ +import { EditDetailTask } from "@/module/task" + +function Page() { + return ( + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/division/[id]/(fitur-division)/task/page.tsx b/src/app/(application)/division/[id]/(fitur-division)/task/page.tsx new file mode 100644 index 0000000..45b8f15 --- /dev/null +++ b/src/app/(application)/division/[id]/(fitur-division)/task/page.tsx @@ -0,0 +1,13 @@ +import { NavbarDivisionTask, TabsDivisionTask } from '@/module/task'; +import React from 'react'; + +function Page() { + return ( +
+ + +
+ ); +} + +export default Page; diff --git a/src/app/(application)/division/[id]/layout.tsx b/src/app/(application)/division/[id]/layout.tsx new file mode 100644 index 0000000..f6b429c --- /dev/null +++ b/src/app/(application)/division/[id]/layout.tsx @@ -0,0 +1,12 @@ +import { WrapLayoutDivision } from "@/module/division_new"; +import _ from "lodash" + +export default async function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + + {children} + + + ); +} \ No newline at end of file diff --git a/src/app/(application)/division/[id]/page.tsx b/src/app/(application)/division/[id]/page.tsx new file mode 100644 index 0000000..044105b --- /dev/null +++ b/src/app/(application)/division/[id]/page.tsx @@ -0,0 +1,22 @@ +import { NavbarDetailDivision, CarouselDivision, FeatureDetailDivision, ListTaskOnDetailDivision, ListDocumentOnDetailDivision, ListDiscussionOnDetailDivision } from '@/module/division_new'; +import { Box, Stack } from '@mantine/core'; +import React from 'react'; + +function Page({ params }: { params: { id: string } }) { + return ( + + + + + + + + + + + + + ); +} + +export default Page; diff --git a/src/app/(application)/division/add-member/[id]/page.tsx b/src/app/(application)/division/add-member/[id]/page.tsx new file mode 100644 index 0000000..b9180c6 --- /dev/null +++ b/src/app/(application)/division/add-member/[id]/page.tsx @@ -0,0 +1,12 @@ +import { CreateAnggotaDivision } from "@/module/division_new"; +import { Box } from "@mantine/core"; + +function Page({ params }: { params: { id: string } }) { + return ( + + + + ) +} + +export default Page; \ No newline at end of file diff --git a/src/app/(application)/notification/page.tsx b/src/app/(application)/division/create/page.tsx similarity index 50% rename from src/app/(application)/notification/page.tsx rename to src/app/(application)/division/create/page.tsx index 67c880a..ea41ae0 100644 --- a/src/app/(application)/notification/page.tsx +++ b/src/app/(application)/division/create/page.tsx @@ -1,9 +1,9 @@ -import { ViewNotification } from '@/module/notification'; +import { CreateDivision } from '@/module/division_new'; import React from 'react'; function Page() { return ( - + ); } diff --git a/src/app/(application)/division/edit/[id]/page.tsx b/src/app/(application)/division/edit/[id]/page.tsx new file mode 100644 index 0000000..2fa9367 --- /dev/null +++ b/src/app/(application)/division/edit/[id]/page.tsx @@ -0,0 +1,12 @@ +import { EditDivision } from "@/module/division_new" +import { Box } from "@mantine/core" + +function Page({ params }: { params: { id: string } }) { + return ( + + + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/division/info/[id]/page.tsx b/src/app/(application)/division/info/[id]/page.tsx new file mode 100644 index 0000000..a10be7f --- /dev/null +++ b/src/app/(application)/division/info/[id]/page.tsx @@ -0,0 +1,12 @@ +import { InformationDivision } from "@/module/division_new"; +import { Box } from "@mantine/core"; + +function Page({ params }: { params: { id: string } }) { + return ( + + + + ) +} + +export default Page; \ No newline at end of file diff --git a/src/app/(application)/division/page.tsx b/src/app/(application)/division/page.tsx new file mode 100644 index 0000000..2e17ed4 --- /dev/null +++ b/src/app/(application)/division/page.tsx @@ -0,0 +1,15 @@ +import { ViewFilter } from '@/module/_global'; +import { CreateReport, ListDivision } from '@/module/division_new'; +import React from 'react'; + +function Page({ searchParams }: { searchParams: { page: string } }) { + if (searchParams.page == "filter") + return + if (searchParams.page == "report") + return + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/division/report/[id]/page.tsx b/src/app/(application)/division/report/[id]/page.tsx new file mode 100644 index 0000000..00ed1f0 --- /dev/null +++ b/src/app/(application)/division/report/[id]/page.tsx @@ -0,0 +1,9 @@ +import { ReportDivisionId } from "@/module/division_new" + +function Page() { + return ( + + ) +} + +export default Page \ No newline at end of file diff --git a/src/app/(application)/group/page.tsx b/src/app/(application)/group/page.tsx index ec46c75..e583c4e 100644 --- a/src/app/(application)/group/page.tsx +++ b/src/app/(application)/group/page.tsx @@ -1,9 +1,13 @@ -import { ViewGroup } from '@/module/group'; +import { NavbarGroup, TabListGroup } from '@/module/group'; +import { Box } from '@mantine/core'; import React from 'react'; -function Page() { +function Page({ searchParams }: { searchParams: { active: string } }) { return ( - + + + + ); } diff --git a/src/app/(application)/home/page.tsx b/src/app/(application)/home/page.tsx index f1e2ec6..7aa3ca1 100644 --- a/src/app/(application)/home/page.tsx +++ b/src/app/(application)/home/page.tsx @@ -1,9 +1,13 @@ -import LayoutNavbarHome from '@/module/_global/layout/layout_navbar_home'; -import { ViewHome } from '@/module/home'; -import { Flex, Group, Text } from '@mantine/core'; +import { ViewDetailFeature, ViewHome, ViewNotification, ViewSearch } from '@/module/home'; import React from 'react'; -function Page() { +function Page({ searchParams }: { searchParams: { cat: string } }) { + if (searchParams.cat == "notification") + return + if (searchParams.cat == "search") + return + if (searchParams.cat == "fitur") + return return ( <> diff --git a/src/app/(application)/layout.tsx b/src/app/(application)/layout.tsx new file mode 100644 index 0000000..95b566a --- /dev/null +++ b/src/app/(application)/layout.tsx @@ -0,0 +1,18 @@ +import { WrapLayout } from "@/module/_global" +import { funDetectCookies, funGetUserByCookies } from "@/module/auth" +import _ from "lodash" +import { redirect } from "next/navigation" + +export default async function Layout({ children }: { children: React.ReactNode }) { + const cookies = await funDetectCookies() + if (!cookies) return redirect('/') + + const user = await funGetUserByCookies() + return ( + <> + + {children} + + + ); +} \ No newline at end of file diff --git a/src/app/(application)/member/[id]/page.tsx b/src/app/(application)/member/[id]/page.tsx index c05ea29..045f0a0 100644 --- a/src/app/(application)/member/[id]/page.tsx +++ b/src/app/(application)/member/[id]/page.tsx @@ -1,10 +1,12 @@ -import { ViewDetailMember } from "@/module/user/member"; +import { NavbarDetailMember } from "@/module/user/member"; +import { Box } from "@mantine/core"; function Page({ params }: { params: { id: string } }) { - return ( - - ) + return ( + + + + ); } export default Page; - diff --git a/src/app/(application)/member/create/page.tsx b/src/app/(application)/member/create/page.tsx index 1afa674..19f74ff 100644 --- a/src/app/(application)/member/create/page.tsx +++ b/src/app/(application)/member/create/page.tsx @@ -1,9 +1,14 @@ -import { ViewCreateMember } from "@/module/user/member"; +import { LayoutNavbarNew } from "@/module/_global"; +import { CreateMember } from "@/module/user/member"; +import { Box } from "@mantine/core"; function Page() { - return ( - - ); + return ( + + } /> + + + ); } -export default Page; \ No newline at end of file +export default Page; diff --git a/src/app/(application)/member/edit/[id]/page.tsx b/src/app/(application)/member/edit/[id]/page.tsx index 70f69d9..adaef5b 100644 --- a/src/app/(application)/member/edit/[id]/page.tsx +++ b/src/app/(application)/member/edit/[id]/page.tsx @@ -1,9 +1,14 @@ -import { ViewEditMember } from "@/module/user/member"; +import { LayoutNavbarNew } from "@/module/_global"; +import { EditMember } from "@/module/user/member"; +import { Box } from "@mantine/core"; -function Page() { - return ( - - ) +function Page({ params }: { params: { id: string } }) { + return ( + + } /> + + + ); } -export default Page; \ No newline at end of file +export default Page; diff --git a/src/app/(application)/member/page.tsx b/src/app/(application)/member/page.tsx index 8b49914..9191ff4 100644 --- a/src/app/(application)/member/page.tsx +++ b/src/app/(application)/member/page.tsx @@ -1,9 +1,16 @@ -import { ViewListMember } from "@/module/user/member"; +import { ViewFilter } from "@/module/_global"; +import { ListMember, NavbarListMember } from "@/module/user/member"; +import { Box } from "@mantine/core"; -function Page() { - return ( - - ) +function Page({ searchParams }: { searchParams: { page: string } }) { + if (searchParams.page == "filter") return ; + + return ( + + + + + ); } -export default Page; \ No newline at end of file +export default Page; diff --git a/src/app/(application)/position/page.tsx b/src/app/(application)/position/page.tsx index 9dd2f1a..d9fcb85 100644 --- a/src/app/(application)/position/page.tsx +++ b/src/app/(application)/position/page.tsx @@ -1,9 +1,17 @@ -import { ViewListPosition } from '@/module/position'; +import { ViewFilter } from '@/module/_global'; +import { NavbarListPosition, TabListPosition } from '@/module/position'; +import { Box } from '@mantine/core'; import React from 'react'; -function Page() { +function Page({ searchParams }: { searchParams: { page: string } }) { + if (searchParams.page == "filter") + return + return ( - + + + + ); } diff --git a/src/app/(application)/profile/edit/page.tsx b/src/app/(application)/profile/edit/page.tsx index 6ca9661..da3e588 100644 --- a/src/app/(application)/profile/edit/page.tsx +++ b/src/app/(application)/profile/edit/page.tsx @@ -1,8 +1,8 @@ -import { ViewEditProfile } from "@/module/user" +import { EditProfile } from "@/module/user" function Page() { return ( - + ) } diff --git a/src/app/(application)/profile/page.tsx b/src/app/(application)/profile/page.tsx index 2c27402..b747f4e 100644 --- a/src/app/(application)/profile/page.tsx +++ b/src/app/(application)/profile/page.tsx @@ -1,8 +1,8 @@ -import { ViewProfile } from "@/module/user"; +import { Profile } from "@/module/user"; function Page() { return ( - + ) } diff --git a/src/app/(application)/project/[id]/add-file/page.tsx b/src/app/(application)/project/[id]/add-file/page.tsx new file mode 100644 index 0000000..7286e51 --- /dev/null +++ b/src/app/(application)/project/[id]/add-file/page.tsx @@ -0,0 +1,9 @@ +import { AddFileDetailProject } from "@/module/project"; + +export default function Page() { + return ( + <> + + + ) +} \ No newline at end of file diff --git a/src/app/(application)/project/[id]/add-member/page.tsx b/src/app/(application)/project/[id]/add-member/page.tsx new file mode 100644 index 0000000..fd99420 --- /dev/null +++ b/src/app/(application)/project/[id]/add-member/page.tsx @@ -0,0 +1,10 @@ +import { AddMemberDetailProject } from '@/module/project'; +import React from 'react'; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/project/[id]/add-task/page.tsx b/src/app/(application)/project/[id]/add-task/page.tsx new file mode 100644 index 0000000..246778d --- /dev/null +++ b/src/app/(application)/project/[id]/add-task/page.tsx @@ -0,0 +1,10 @@ +import { AddDetailTaskProject } from '@/module/project'; +import React from 'react'; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/search/page.tsx b/src/app/(application)/project/[id]/cancel/page.tsx similarity index 54% rename from src/app/(application)/search/page.tsx rename to src/app/(application)/project/[id]/cancel/page.tsx index 198a79b..28c0bc9 100644 --- a/src/app/(application)/search/page.tsx +++ b/src/app/(application)/project/[id]/cancel/page.tsx @@ -1,9 +1,9 @@ -import { ViewSearch } from '@/module/search'; +import { CancelProject } from '@/module/project'; import React from 'react'; function Page() { return ( - + ); } diff --git a/src/app/(application)/project/[id]/edit/page.tsx b/src/app/(application)/project/[id]/edit/page.tsx new file mode 100644 index 0000000..cde1d7c --- /dev/null +++ b/src/app/(application)/project/[id]/edit/page.tsx @@ -0,0 +1,10 @@ +import { EditTaskProject } from '@/module/project'; +import React from 'react'; + +function Page() { + return ( + + ); +} + +export default Page; diff --git a/src/app/(application)/project/[id]/page.tsx b/src/app/(application)/project/[id]/page.tsx new file mode 100644 index 0000000..1153f0c --- /dev/null +++ b/src/app/(application)/project/[id]/page.tsx @@ -0,0 +1,19 @@ +import { ListAnggotaDetailProject, ListFileDetailProject, ListTugasDetailProject, NavbarDetailProject, ProgressDetailProject, ViewDetailProject } from '@/module/project'; +import { Box } from '@mantine/core'; +import React from 'react'; + +function Page() { + return ( + + + + + + + + + + ); +} + +export default Page; diff --git a/src/app/(application)/project/create/page.tsx b/src/app/(application)/project/create/page.tsx new file mode 100644 index 0000000..e571503 --- /dev/null +++ b/src/app/(application)/project/create/page.tsx @@ -0,0 +1,12 @@ +import { CreateProject, ViewFileSave } from "@/module/project"; +import React from "react"; + +function Page({ searchParams }: { searchParams: any }) { + + // if (searchParams.page == "file-save") + // return + + return ; +} + +export default Page; diff --git a/src/app/(application)/project/page.tsx b/src/app/(application)/project/page.tsx index af04bfb..65738c4 100644 --- a/src/app/(application)/project/page.tsx +++ b/src/app/(application)/project/page.tsx @@ -1,9 +1,12 @@ -import { ViewProject } from '@/module/project'; +import { ViewFilter } from '@/module/_global'; +import { TabProject, ViewProject } from '@/module/project'; import React from 'react'; -function Page() { +function Page({ searchParams }: { searchParams: { page: string } }) { + if (searchParams.page == 'filter') + return return ( - + ); } diff --git a/src/app/(application)/project/update/[id]/page.tsx b/src/app/(application)/project/update/[id]/page.tsx new file mode 100644 index 0000000..f49558a --- /dev/null +++ b/src/app/(application)/project/update/[id]/page.tsx @@ -0,0 +1,11 @@ +import { EditDetailTaskProject } from "@/module/project"; + +import React from "react"; + +function Page() { + return ( + + ) +} + +export default Page; diff --git a/src/app/(auth)/verification/page.tsx b/src/app/(auth)/verification/page.tsx index de28044..31b693d 100644 --- a/src/app/(auth)/verification/page.tsx +++ b/src/app/(auth)/verification/page.tsx @@ -1,6 +1,7 @@ import { ViewVerification } from "@/module/auth"; +import { IVerification } from "@/types"; import React from "react"; -export default function Verification() { - return ; +export default function Page() { + return ; } diff --git a/src/app/api/announcement/[id]/route.ts b/src/app/api/announcement/[id]/route.ts new file mode 100644 index 0000000..50cde58 --- /dev/null +++ b/src/app/api/announcement/[id]/route.ts @@ -0,0 +1,223 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + + +// GET ONE PENGUMUMAN, UNTUK TAMPIL DETAIL PENGUMUMAN +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { searchParams } = new URL(request.url); + const kategori = searchParams.get('category'); + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await prisma.announcement.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan pengumuman, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const announcement = await prisma.announcement.findUnique({ + where: { + id: id, + }, + select: { + id: true, + title: true, + desc: true, + }, + }); + + const announcementMember = await prisma.announcementMember.findMany({ + where: { + idAnnouncement: id, + }, + select: { + idGroup: true, + idDivision: true, + Group: { + select: { + name: true, + }, + }, + Division: { + select: { + name: true, + }, + }, + }, + }); + + const formatMember = announcementMember.map((v: any) => ({ + ..._.omit(v, ["Group", "Division"]), + idGroup: v.idGroup, + idDivision: v.idDivision, + group: v.Group.name, + division: v.Division.name + })) + + const fixMember = Object.groupBy(formatMember, ({ group }) => group); + + return NextResponse.json( + { + success: true, + message: "Berhasil mendapatkan pengumuman", + data: announcement, + member: fixMember + }, + { status: 200 } + ); + + + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan pengumuman, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// HAPUS PENGUMUMAN +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + const data = await prisma.announcement.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Hapus pengumuman gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.announcement.update({ + where: { + id: id, + }, + data: { + isActive: false, + }, + }); + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus data pengumuman', table: 'announcement', data: id }) + + return NextResponse.json( + { + success: true, + message: "Pengumuman berhasil dihapus", + data, + }, + { status: 200 } + ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan pengumuman, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// EDIT PENGUMUMAN +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies(); + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + + const { title, desc, groups } = (await request.json()); + const { id } = context.params; + + const data = await prisma.announcement.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Edit pengumuman gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.announcement.update({ + where: { + id: id + }, + data: { + title, + desc, + }, + }); + + // hapus semua member divisi pengumuman + const hapus = await prisma.announcementMember.deleteMany({ + where: { + idAnnouncement: id + } + }) + + let memberDivision = [] + + for (var i = 0, l = groups.length; i < l; i++) { + var obj = groups[i].Division; + for (let index = 0; index < obj.length; index++) { + const element = obj[index]; + const fix = { + idAnnouncement: id, + idGroup: groups[i].id, + idDivision: element.id + } + memberDivision.push(fix) + } + } + + const announcementMember = await prisma.announcementMember.createMany({ + data: memberDivision, + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data pengumuman', table: 'announcement', data: id }) + + return NextResponse.json({ success: true, message: "Berhasil mengupdate pengumuman" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengeupdate pengumuman, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + diff --git a/src/app/api/announcement/route.ts b/src/app/api/announcement/route.ts new file mode 100644 index 0000000..8380629 --- /dev/null +++ b/src/app/api/announcement/route.ts @@ -0,0 +1,156 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; +import { createLogUser } from '@/module/user'; + +export const dynamic = 'force-dynamic' + + + +// GET ALL PENGUMUMAN +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies(); + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const villageId = user.idVillage + const roleUser = user.idUserRole + const groupId = user.idGroup + const { searchParams } = new URL(request.url); + const name = searchParams.get('search'); + + let kondisi: any = { + idVillage: String(villageId), + isActive: true, + title: { + contains: (name == undefined || name == null) ? "" : name, + mode: "insensitive" + } + } + + if (roleUser != "supadmin") { + if (roleUser == "cosupadmin" || roleUser == "admin") { + kondisi = { + idVillage: String(villageId), + isActive: true, + title: { + contains: (name == undefined || name == null) ? "" : name, + mode: "insensitive" + }, + AnnouncementMember: { + some: { + idGroup: String(groupId) + } + } + + } + } else { + kondisi = { + idVillage: String(villageId), + isActive: true, + title: { + contains: (name == undefined || name == null) ? "" : name, + mode: "insensitive" + }, + AnnouncementMember: { + some: { + idGroup: String(groupId), + Division: { + DivisionMember: { + some: { + idUser: String(user.id) + } + } + } + } + } + } + } + } + + + const announcements = await prisma.announcement.findMany({ + where: kondisi, + select: { + id: true, + title: true, + desc: true, + createdAt: true, + }, + orderBy: { + createdAt: 'desc' + } + }); + + const allData = announcements.map((v: any) => ({ + ..._.omit(v, ["createdAt"]), + createdAt: moment(v.createdAt).format("LL") + })) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan pengumuman", data: allData, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan pengumuman, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// CREATE PENGUMUMAN +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies(); + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { title, desc, groups } = (await request.json()); + const villaId = user.idVillage + const userId = user.id + + const data = await prisma.announcement.create({ + data: { + title, + desc, + idVillage: String(villaId), + createdBy: String(userId), + }, + select: { + id: true, + } + }); + + let memberDivision = [] + + for (var i = 0, l = groups.length; i < l; i++) { + var obj = groups[i].Division; + for (let index = 0; index < obj.length; index++) { + const element = obj[index]; + const fix = { + idAnnouncement: data.id, + idGroup: groups[i].id, + idDivision: element.id + } + memberDivision.push(fix) + } + } + + const announcementMember = await prisma.announcementMember.createMany({ + data: memberDivision, + }); + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data pengumuman baru', table: 'announcement', data: data.id }) + + return NextResponse.json({ success: true, message: "Berhasil membuat pengumuman" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal membuat pengumuman, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/auth/get-user-by-cookies/route.ts b/src/app/api/auth/get-user-by-cookies/route.ts new file mode 100644 index 0000000..99798fa --- /dev/null +++ b/src/app/api/auth/get-user-by-cookies/route.ts @@ -0,0 +1,18 @@ +import { prisma, pwd_key_config } from "@/module/_global"; +import { unsealData } from "iron-session"; +import { cookies } from "next/headers"; + +export async function GET() { + const sessionCookie = cookies().get("sessionCookieSDM"); + const userId = await unsealData(sessionCookie!.value, { + password: pwd_key_config, + }); + + const user = await prisma.user.findUnique({ + where: { + id: String(userId), + }, + }); + + return Response.json(user); +} diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts new file mode 100644 index 0000000..da75009 --- /dev/null +++ b/src/app/api/auth/login/route.ts @@ -0,0 +1,31 @@ +import { prisma } from "@/module/_global"; +import { ILogin } from "@/types"; +import { NextRequest } from "next/server"; + +export async function POST(req: NextRequest) { + try { + const { phone }: ILogin = await req.json(); + const user = await prisma.user.findUnique({ + where: { phone, isActive: true }, + select: { id: true, phone: true }, + }); + + if (!user) { + return Response.json({ + success: false, + message: "Nomor telepon tidak terdaftar", + }); + } + + return Response.json({ + success: true, + message: "Sukses", + phone: user.phone, + id: user.id, + }); + + } catch (error) { + console.error(error); + return Response.json({ message: "Internal Server Error", success: false }); + } +} diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts new file mode 100644 index 0000000..69c14fe --- /dev/null +++ b/src/app/api/auth/logout/route.ts @@ -0,0 +1,10 @@ +import { createLogUser } from "@/module/user"; +import { cookies } from "next/headers"; + +export async function DELETE() { + const log = await createLogUser({ act: 'LOGOUT', desc: 'User keluar dari program', table: 'user', data: '' }) + + cookies().delete('sessionCookieSDM') + + return Response.json({ success: true }) +} \ No newline at end of file diff --git a/src/app/api/calender/[id]/member/route.ts b/src/app/api/calender/[id]/member/route.ts new file mode 100644 index 0000000..2fe8cc8 --- /dev/null +++ b/src/app/api/calender/[id]/member/route.ts @@ -0,0 +1,176 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + +// GET ONE DATA KALENDER BY ID KALENDER +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + + const cek = await prisma.divisionCalendar.count({ + where: { + id: id + } + }) + + if (cek == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan calender, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const data = await prisma.divisionCalendar.findUnique({ + where: { + id: id + }, + select: { + id: true, + title: true, + desc: true, + timeStart: true, + dateStart: true, + timeEnd: true, + createdAt: true, + linkMeet: true, + repeatValue: true, + repeatEventTyper: true, + } + }); + + const { ...dataCalender } = data + const timeStart = moment.utc(dataCalender?.timeStart).format("HH:mm") + const timeEnd = moment.utc(dataCalender?.timeEnd).format("HH:mm") + + const result = { ...dataCalender, timeStart, timeEnd } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan calender", data: result }, { status: 200 }); + + } catch (error) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan calender, data tidak ditemukan", + }, + { status: 404 } + ); + } +} + + +// TAMBAH MEMBER KALENDER +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + const member = await request.json() + + const cek = await prisma.divisionCalendar.count({ + where: { + id: id + } + }) + + if (cek == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal menambahkan anggota, data tidak ditemukan", + }, + { status: 404 } + ); + } + + + if (member.length > 0) { + const dataMember = member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name", "img"]), + idCalendar: id, + idUser: v.idUser, + })) + + const insertMember = await prisma.divisionCalendarMember.createMany({ + data: dataMember + }) + } + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambah anggota kalender', table: 'divisionCalendar', data: String(id) }) + + return NextResponse.json({ success: true, message: "Berhasil menambahkan anggota", }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambah anggota, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } + + +} + +// MENGELUARKAN ANGGOTA +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { idUser } = (await request.json()); + + const data = await prisma.divisionCalendar.count({ + where: { + id: id, + }, + }); + + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mengeluarkan anggota, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const del = await prisma.divisionCalendarMember.deleteMany({ + where: { + idUser: idUser, + idCalendar: id + } + }) + + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User mengeluarkan anggota acara kalender', table: 'divisionCalendar', data: String(id) }) + + + return NextResponse.json( + { + success: true, + message: "Berhasil mengeluarkan anggota", + }, + { status: 200 } + ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengeluarkan anggota, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/calender/[id]/route.ts b/src/app/api/calender/[id]/route.ts new file mode 100644 index 0000000..96c2d97 --- /dev/null +++ b/src/app/api/calender/[id]/route.ts @@ -0,0 +1,271 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _, { remove } from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; +import { Frequency, RRule } from 'rrule'; + +// GET ONE CALENDER BY ID KALENDER REMINDER +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + + const cek = await prisma.divisionCalendarReminder.count({ + where: { + id: id + } + }) + + if (cek == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan acara, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const data: any = await prisma.divisionCalendarReminder.findUnique({ + where: { + id: id + }, + select: { + id: true, + timeStart: true, + dateStart: true, + timeEnd: true, + createdAt: true, + DivisionCalendar: { + select: { + id: true, + title: true, + desc: true, + linkMeet: true, + repeatEventTyper: true, + repeatValue: true, + } + } + } + }); + const { DivisionCalendar, ...dataCalender } = data + const timeStart = moment.utc(dataCalender?.timeStart).format("HH:mm") + const timeEnd = moment.utc(dataCalender?.timeEnd).format("HH:mm") + const idCalendar = data?.DivisionCalendar.id + const title = data?.DivisionCalendar?.title + const desc = data?.DivisionCalendar?.desc + const linkMeet = data?.DivisionCalendar?.linkMeet + const repeatEventTyper = data?.DivisionCalendar?.repeatEventTyper + const repeatValue = data?.DivisionCalendar?.repeatValue + + + const result = { ...dataCalender, timeStart, timeEnd, idCalendar, title, desc, linkMeet, repeatEventTyper, repeatValue } + + + const member = await prisma.divisionCalendarMember.findMany({ + where: { + idCalendar: data?.DivisionCalendar.id + }, + select: { + id: true, + idUser: true, + User: { + select: { + id: true, + name: true, + email: true, + img: true + } + } + } + }) + const fixMember = member.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + email: v.User.email, + img: v.User.img + })) + + + const dataFix = { + calender: result, + member: fixMember, + total: fixMember.length + } + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan calender", data: dataFix }, { status: 200 }); + + } catch (error) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan calender, data tidak ditemukan", + }, + { status: 404 } + ); + } +} + +// DELETE CALENDER BY ID +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + + const cek = await prisma.divisionCalendar.count({ + where: { + id: id + } + }) + + if (cek == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal menghapus acara kalender, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const data = await prisma.divisionCalendar.update({ + where: { + id: id + }, + data: { + isActive: false + } + }); + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus data acara kalender', table: 'divisionCalendar', data: id }) + + return NextResponse.json({ success: true, message: "Berhasil menghapus acara kalender", data }, { status: 200 }); + + } catch (error) { + return NextResponse.json( + { + success: false, + message: "Gagal menghapus calender, coba lagi nanti", + }, + { status: 500 } + ); + } +} + +// EDIT CALENDER BY IDKALENDER +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + const userId = user.id + const { title, desc, timeStart, dateStart, timeEnd, linkMeet, repeatEventTyper, repeatValue } = await request.json() + + const cek = await prisma.divisionCalendar.count({ + where: { + id: id + } + }) + + if (cek == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mengedit acara, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const y = new Date('1970-01-01 ' + timeStart) + const x = new Date('1970-01-01 ' + timeEnd) + const timeStartFix = new Date(y.getTime() - (y.getTimezoneOffset() * 60000)).toISOString() + const timeEndFix = new Date(x.getTime() - (x.getTimezoneOffset() * 60000)).toISOString() + const statusCalender = 0 + const data = await prisma.divisionCalendar.update({ + where: { + id: id + }, + data: { + title: title, + desc: desc, + createdBy: String(userId), + timeStart: timeStartFix, + dateStart: new Date(dateStart), + timeEnd: timeEndFix, + linkMeet: linkMeet, + repeatEventTyper: repeatEventTyper, + status: statusCalender, + repeatValue: Number(repeatValue) + }, + select: { + idDivision: true + } + }); + + const freq: Frequency = repeatEventTyper === "yearly" ? RRule.YEARLY : + repeatEventTyper === "monthly" ? RRule.MONTHLY : + repeatEventTyper === "weekly" ? RRule.WEEKLY : + repeatEventTyper === "daily" ? RRule.DAILY : + repeatEventTyper === "hourly" ? RRule.HOURLY : + repeatEventTyper === "minutely" ? RRule.MINUTELY : + RRule.SECONDLY; + + const rule = new RRule({ + freq, + interval: 1, + dtstart: new Date(dateStart), + count: repeatValue + }); + + const hasil = rule.all().map(recurrenceDate => ({ + idDivision: data.idDivision, + idCalendar: id, + dateStart: recurrenceDate, + timeStart: timeStartFix, + timeEnd: timeEndFix, + dateEnd: recurrenceDate + })); + + const deleteReminder = await prisma.divisionCalendarReminder.deleteMany({ + where: { + idCalendar: id + } + }) + + const insertReminder = await prisma.divisionCalendarReminder.createMany({ + data: hasil + }) + + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data acara kalender', table: 'divisionCalendar', data: id }) + + return NextResponse.json({ success: true, message: "Berhasil mengedit acara" }, { status: 200 }); + + } catch (error) { + return NextResponse.json( + { + success: false, + message: "Gagal mengedit acara, coba lagi nanti", + }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/calender/history/route.ts b/src/app/api/calender/history/route.ts new file mode 100644 index 0000000..40ce2d1 --- /dev/null +++ b/src/app/api/calender/history/route.ts @@ -0,0 +1,102 @@ +import moment from "moment"; +import { NextResponse } from "next/server"; +import "moment/locale/id"; +import { funGetUserByCookies } from "@/module/auth"; +import { prisma } from "@/module/_global"; +import _ from "lodash"; + +// GET HSITORY +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const idDivision = searchParams.get("division"); + const name = searchParams.get('search'); + + if (idDivision != "null" && idDivision != null && idDivision != undefined) { + const cekDivision = await prisma.division.count({ + where: { + id: idDivision, + isActive: true + } + }) + + if (cekDivision == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + const data = await prisma.divisionCalendarReminder.findMany({ + where: { + isActive: true, + idDivision: idDivision, + DivisionCalendar: { + title: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + }, + isActive: true + } + + }, + select: { + id: true, + timeStart: true, + dateStart: true, + timeEnd: true, + DivisionCalendar: { + select: { + title: true, + } + } + }, + orderBy: [ + { + dateStart: 'asc' + }, + { + timeStart: 'asc' + }, + { + timeEnd: 'asc' + } + ] + }); + + const allOmit = data.map((v: any) => ({ + ..._.omit(v, ["DivisionCalendar"]), + title: v.DivisionCalendar.title + })) + + // groupBy untuk dateStart + const groupByDateStart = _.groupBy(allOmit, 'dateStart'); + + const result = Object.keys(groupByDateStart).map(key => { + const obj = groupByDateStart[key]; + const data = obj.map((v: any) => ({ + id: v.id, + title: v.title, + timeEnd: moment.utc(v.timeEnd).format('HH:mm'), + timeStart: moment.utc(v.timeStart).format('HH:mm') + })) + return { + dateStart: key, + data: data + } + }) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan riwayat acara kalender", data: result }, { status: 200 }); + + } else { + return NextResponse.json({ success: false, message: "Gagal mendapatkan riwayat acara kalender, coba lagi nanti" }, { status: 404 }); + + } + + } catch (error) { + console.error(error) + return NextResponse.json({ success: false, message: "Gagal mendapatkan riwayat acara kalender, coba lagi nanti" }, { status: 404 }); + } +} \ No newline at end of file diff --git a/src/app/api/calender/indicator/route.ts b/src/app/api/calender/indicator/route.ts new file mode 100644 index 0000000..bc85d13 --- /dev/null +++ b/src/app/api/calender/indicator/route.ts @@ -0,0 +1,67 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const idDivision = searchParams.get("division"); + const date = searchParams.get("date"); + + const awalDate = moment(date).format('YYYY-MM') + '-01' + const akhirDate = moment(awalDate).add(1, 'M').format('YYYY-MM-DD') + + + const cekDivision = await prisma.division.count({ + where: { + id: String(idDivision), + isActive: true + } + }) + + if (cekDivision == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + const data = await prisma.divisionCalendarReminder.findMany({ + where: { + isActive: true, + idDivision: String(idDivision), + dateStart: { + gte: new Date(awalDate), + lte: new Date(akhirDate), + }, + DivisionCalendar: { + isActive: true + } + } + }) + + const dataGroup = _.map(_.groupBy(data, "dateStart"), (v: any) => ({ + dateContent: v[0].dateStart + })) + + const result = dataGroup.map(a => moment(a.dateContent).format('YYYY-MM-DD')); + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan list acara", data: result }, { status: 200 }); + } catch (error) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan list acara" }, { status: 401 }); + } +} + + + + + + + + diff --git a/src/app/api/calender/route.ts b/src/app/api/calender/route.ts new file mode 100644 index 0000000..8e08d38 --- /dev/null +++ b/src/app/api/calender/route.ts @@ -0,0 +1,196 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; +import "moment/locale/id"; +import { createLogUser } from '@/module/user'; +import { Frequency, RRule } from 'rrule'; + +//GET ALL CALENDER +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const idDivision = searchParams.get("division"); + const isDate = searchParams.get("date") + + + if (idDivision != "null" && idDivision != null && idDivision != undefined) { + const cekDivision = await prisma.division.count({ + where: { + id: idDivision, + isActive: true + } + }) + + if (cekDivision == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + const data = await prisma.divisionCalendarReminder.findMany({ + where: { + isActive: true, + idDivision: idDivision, + dateStart: new Date(String(isDate)), + DivisionCalendar: { + isActive: true + } + }, + select: { + id: true, + idCalendar: true, + timeStart: true, + dateStart: true, + timeEnd: true, + dateEnd: true, + createdAt: true, + status: true, + DivisionCalendar: { + select: { + title: true, + desc: true, + User: { + select: { + name: true + } + } + } + } + }, + orderBy: [ + { + dateStart: 'asc' + }, + { + timeStart: 'asc' + }, + { + timeEnd: 'asc' + } + ] + }); + + const allOmit = data.map((v: any) => ({ + ..._.omit(v, ["DivisionCalendar", "User"]), + title: v.DivisionCalendar.title, + desc: v.DivisionCalendar.desc, + user_name: v.DivisionCalendar.User.name, + timeStart: moment.utc(v.timeStart).format('HH:mm'), + timeEnd: moment.utc(v.timeEnd).format('HH:mm') + })) + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan calender", data: allOmit }, { status: 200 }); + + } else { + return NextResponse.json({ success: false, message: "Gagal mendapatkan calender, data tidak ditemukan" }, { status: 404 }); + } + + } catch (error) { + console.error(error) + return NextResponse.json({ success: false, message: "Gagal mendapatkan calender, data tidak ditemukan" }, { status: 404 }); + } +} + + +//CREATE CALENDER +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies(); + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { idDivision, title, desc, timeStart, timeEnd, dateStart, dateEnd, repeatEventTyper, member, linkMeet, repeatValue } = (await request.json()); + + + const userId = user.id + const cekDivision = await prisma.division.count({ + where: { + id: idDivision, + isActive: true + } + }) + + if (cekDivision == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + const y = new Date('1970-01-01 ' + timeStart) + const x = new Date('1970-01-01 ' + timeEnd) + const timeStartFix = new Date(y.getTime() - (y.getTimezoneOffset() * 60000)).toISOString() + const timeEndFix = new Date(x.getTime() - (x.getTimezoneOffset() * 60000)).toISOString() + + const data = await prisma.divisionCalendar.create({ + data: { + idDivision, + createdBy: String(userId), + title, + dateStart: new Date(dateStart), + timeStart: timeStartFix, + timeEnd: timeEndFix, + linkMeet, + repeatEventTyper, + desc, + repeatValue: Number(repeatValue) + }, + select: { + id: true, + } + }); + + + const freq: Frequency = repeatEventTyper === "yearly" ? RRule.YEARLY : + repeatEventTyper === "monthly" ? RRule.MONTHLY : + repeatEventTyper === "weekly" ? RRule.WEEKLY : + repeatEventTyper === "daily" ? RRule.DAILY : + repeatEventTyper === "hourly" ? RRule.HOURLY : + repeatEventTyper === "minutely" ? RRule.MINUTELY : + RRule.SECONDLY; + + const rule = new RRule({ + freq, + interval: 1, + dtstart: new Date(dateStart), + count: repeatValue + }); + + const hasil = rule.all().map(recurrenceDate => ({ + idDivision: idDivision, + idCalendar: data.id, + dateStart: recurrenceDate, + timeStart: timeStartFix, + timeEnd: timeEndFix, + dateEnd: recurrenceDate + })); + + const insertReminder = await prisma.divisionCalendarReminder.createMany({ + data: hasil + }) + + const omitMember = member.map((v: any) => ({ + ..._.omit(v, ["name", "idUser", "img"]), + idCalendar: data.id, + idUser: v.idUser + })) + + const insertMember = await prisma.divisionCalendarMember.createMany({ + data: omitMember + }); + + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data acara kalender', table: 'divisionCalendar', data: data.id }) + + return NextResponse.json({ success: true, message: "Berhasil membuat acara kalender" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal membuat acara kalender, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/discussion/[id]/comment/route.ts b/src/app/api/discussion/[id]/comment/route.ts new file mode 100644 index 0000000..338ef78 --- /dev/null +++ b/src/app/api/discussion/[id]/comment/route.ts @@ -0,0 +1,53 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import { NextResponse } from "next/server"; + +// CREATE COMENT BY ID KOMENTAR +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + const { comment } = (await request.json()); + + const cek = await prisma.divisionDisscussion.count({ + where: { + id: id + } + }) + + if (cek == 0) { + return NextResponse.json( + { + success: false, + message: "Tambah komentar gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const data = await prisma.divisionDisscussionComment.create({ + data: { + comment: comment, + idDisscussion: id, + createdBy: user.id + }, + select: { + id: true + } + }) + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambah komentar pada diskusi', table: 'divisionDisscussionComment', data: data.id }) + + return NextResponse.json({ success: true, message: "Berhasil menambah komentar", data: data, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambah komentar, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/discussion/[id]/route.ts b/src/app/api/discussion/[id]/route.ts new file mode 100644 index 0000000..a87bcd3 --- /dev/null +++ b/src/app/api/discussion/[id]/route.ts @@ -0,0 +1,223 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; + +// GET ONE DISCUSSION BY ID +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params + + const cek = await prisma.divisionDisscussion.count({ + where: { + id: id + } + }) + + if (cek == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan diskusi, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const data = await prisma.divisionDisscussion.findUnique({ + where: { + id: id + }, + select: { + id: true, + title: true, + desc: true, + status: true, + createdAt: true, + createdBy: true, + User: { + select: { + name: true, + img: true + } + }, + DivisionDisscussionComment: { + select: { + id: true, + comment: true, + createdAt: true, + User: { + select: { + name: true, + img: true + } + } + } + }, + } + }); + + const { ...userMember } = data + const username = data?.User.name + const user_img = data?.User.img + const createdAt = moment(data?.createdAt).format("ll") + const isCreator = data?.createdBy == user.id + + + + const result = { ...userMember, username, createdAt, user_img, isCreator } + + + const omitData = _.omit(result, ["User"]) + const comments = omitData.DivisionDisscussionComment.map((comment: any) => { + return { ...comment, username: comment.User.name, img: comment.User.img }; + }); + + + omitData.DivisionDisscussionComment = comments; + const response = { + ...omitData, + totalComments: comments.length, + }; + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: response }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// OPEN OR CLOSE DISCUSSION +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params + const { status } = (await request.json()); + let newStatus; + if (status === 1) { + newStatus = 2; + } else if (status === 2) { + newStatus = 1; + } else { + return NextResponse.json({ success: false, message: "Invalid status" }, { status: 400 }); + } + + const data = await prisma.divisionDisscussion.count({ + where: { + id: id + }, + }); + + if (data == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, data tidak ditemukan" }, { status: 404 }); + } + + const result = await prisma.divisionDisscussion.update({ + where: { + id: id + }, + data: { + status: newStatus + } + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate status diskusi', table: 'divisionDisscussion', data: id }) + + return NextResponse.json({ success: true, message: "Berhasil mengedit diskusi" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengedit diskusi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +// DELETE DISCUSSION +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params + + const cek = await prisma.divisionDisscussion.count({ + where: { + id: id + }, + }); + + if (cek == 0) { + return NextResponse.json({ success: false, message: "Gagal menghapus diskusi, data tidak ditemukan" }, { status: 404 }); + } + + + const data = await prisma.divisionDisscussion.update({ + where: { + id: id + }, + data: { + isActive: false + } + }); + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus data diskusi', table: 'divisionDisscussion', data: id }) + + return NextResponse.json({ success: true, message: "Berhasil menghapus diskusi" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus diskusi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// EDIT DISCUSSION +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params + const { title, desc } = (await request.json()); + + const data = await prisma.divisionDisscussion.count({ + where: { + id: id + }, + }); + + if (data == 0) { + return NextResponse.json({ success: false, message: "Gagal mengedit diskusi, data tidak ditemukan" }, { status: 404 }); + } + + const update = await prisma.divisionDisscussion.update({ + where: { + id: id + }, + data: { + desc: desc + } + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data diskusi', table: 'divisionDisscussion', data: id }) + return NextResponse.json({ success: true, message: "Berhasil mengedit diskusi" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengedit diskusi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} diff --git a/src/app/api/discussion/route.ts b/src/app/api/discussion/route.ts new file mode 100644 index 0000000..c00992d --- /dev/null +++ b/src/app/api/discussion/route.ts @@ -0,0 +1,133 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; +import "moment/locale/id"; +import { createLogUser } from "@/module/user"; + + +// GET ALL DISCUSSION DIVISION ACTIVE = TRUE +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const idDivision = searchParams.get("division"); + const name = searchParams.get('search'); + + + if (idDivision != "null" && idDivision != null && idDivision != undefined) { + const cekDivision = await prisma.division.count({ + where: { + id: idDivision, + isActive: true + } + }) + + if (cekDivision == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + const data = await prisma.divisionDisscussion.findMany({ + where: { + isActive: true, + idDivision: idDivision, + User: { + name: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + } + } + }, + orderBy: { + createdAt: 'desc' + }, + select: { + id: true, + title: true, + desc: true, + status: true, + createdAt: true, + User: { + select: { + name: true, + img: true + } + }, + DivisionDisscussionComment: { + select: { + id: true, + } + } + } + }); + + + + const fixData = data.map((v: any) => ({ + ..._.omit(v, ["User", "DivisionDisscussionComment", "createdAt"]), + user_name: v.User.name, + img: v.User.img, + total_komentar: v.DivisionDisscussionComment.length, + createdAt: moment(v.createdAt).format("ll") + })) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: fixData, }, { status: 200 }); + + } else { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// CREATE DISCUSSION +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { idDivision, desc } = (await request.json()); + + const cekDivision = await prisma.division.count({ + where: { + id: idDivision, + isActive: true + } + }) + + if (cekDivision == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + const data = await prisma.divisionDisscussion.create({ + data: { + idDivision, + desc, + createdBy: user.id + }, + select: { + id: true + } + }); + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data diskusi', table: 'divisionDisscussion', data: data.id }) + + return NextResponse.json({ success: true, message: "Berhasil menambahkan diskusi", data, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambahkan diskusi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/src/app/api/division/[id]/detail/route.ts b/src/app/api/division/[id]/detail/route.ts new file mode 100644 index 0000000..af4fd71 --- /dev/null +++ b/src/app/api/division/[id]/detail/route.ts @@ -0,0 +1,321 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; + + +// GET ONE DATA DIVISI :: UNTUK TAMPIL DETAIL DIVISI (FITUR DIVISI) PADA HALAMAN DETAIL +export async function GET(request: Request, context: { params: { id: string } }) { + try { + let allData + const { id } = context.params; + const user = await funGetUserByCookies() + const { searchParams } = new URL(request.url); + const kategori = searchParams.get("cat"); + + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await prisma.division.findUnique({ + where: { + id: String(id), + isActive: true + } + }); + + if (!data) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan", }, { status: 404 }); + } + + if (kategori == "jumlah") { + const tugas = await prisma.divisionProject.count({ + where: { + idDivision: String(id), + status: { + lte: 1 + }, + isActive: true + } + }) + + const dokumen = await prisma.divisionDocumentFolderFile.count({ + where: { + idDivision: String(id), + isActive: true, + category: "FILE" + } + }) + + const diskusi = await prisma.divisionDisscussion.count({ + where: { + idDivision: String(id), + isActive: true, + status: 1 + } + }) + + const kalender = await prisma.divisionCalendarReminder.count({ + where: { + idDivision: String(id), + isActive: true, + dateStart: { + lte: new Date() + }, + DivisionCalendar: { + isActive: true + } + } + }) + + + allData = { + tugas: tugas, + dokumen: dokumen, + diskusi: diskusi, + kalender: kalender + } + } else if (kategori == "today-task") { + const tugas = await prisma.divisionProjectTask.findMany({ + skip: 0, + take: 5, + where: { + idDivision: String(id), + status: 0, + isActive: true, + dateStart: new Date() + }, + select: { + id: true, + title: true, + dateStart: true, + dateEnd: true, + } + }) + + allData = tugas.map((v: any) => ({ + ..._.omit(v, ["dateStart", "dateEnd"]), + dateStart: moment(v.dateStart).format("LL"), + dateEnd: moment(v.dateEnd).format("LL") + })) + } else if (kategori == "new-file") { + allData = await prisma.divisionDocumentFolderFile.findMany({ + skip: 0, + take: 5, + where: { + idDivision: String(id), + isActive: true, + category: "FILE" + }, + select: { + id: true, + name: true, + extension: true, + }, + orderBy: { + createdAt: "desc" + } + }) + } else if (kategori == "new-discussion") { + const diskusi = await prisma.divisionDisscussion.findMany({ + skip: 0, + take: 5, + where: { + idDivision: String(id), + isActive: true, + status: 1 + }, + select: { + id: true, + title: true, + desc: true, + createdAt: true, + User: { + select: { + name: true + } + } + }, + orderBy: { + createdAt: "desc" + } + }) + + allData = diskusi.map((v: any) => ({ + ..._.omit(v, ["createdAt", "User"]), + date: moment(v.dateStart).format("ll"), + user: v.User.name + })) + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: allData }, { status: 200 }); + } + + + + catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + + +// MENGELUARKAN ANGGOTA DARI DIVISI +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const idDivision = context.params.id; + const { id } = (await request.json()); + + const data = await prisma.division.count({ + where: { + id: idDivision, + isActive: true + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Hapus anggota divisi gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionMember.delete({ + where: { + id: id, + }, + }); + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User mengeluarkan anggota divisi', table: 'division', data: idDivision }) + + return NextResponse.json( + { + success: true, + message: "Anggota divisi berhasil dihapus", + }, + { status: 200 } + ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengeluarkan anggota divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// MENGGANTI STATUS ADMIN DIVISI +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const idDivision = context.params.id; + const { id, isAdmin } = (await request.json()); + + const data = await prisma.division.count({ + where: { + id: idDivision, + isActive: true + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Perubahan status admin gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionMember.update({ + where: { + id: id, + }, + data: { + isAdmin: !isAdmin + } + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate status anggota divisi', table: 'division', data: idDivision }) + + return NextResponse.json( + { + success: true, + message: "Status admin berhasil diupdate", + }, + { status: 200 } + ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengubah status admin divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// TAMBAH ANGGOTA DIVISI +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const member = await request.json(); + const idDivision = context.params.id; + + + const data = await prisma.division.count({ + where: { + id: idDivision, + isActive: true + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Tambah anggota divisi gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + + const dataMember = member.map((v: any) => ({ + ..._.omit(v, ["name", "img"]), + idUser: v.idUser, + idDivision: idDivision, + })) + + const insertMember = await prisma.divisionMember.createMany({ + data: dataMember + }) + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambah anggota divisi', table: 'division', data: idDivision }) + + return NextResponse.json({ success: true, message: "Berhasil menambahkan anggota divisi" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambahkan anggota divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/src/app/api/division/[id]/member/route.ts b/src/app/api/division/[id]/member/route.ts new file mode 100644 index 0000000..b396f89 --- /dev/null +++ b/src/app/api/division/[id]/member/route.ts @@ -0,0 +1,70 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + +// GET MEMBER BY ID +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { searchParams } = new URL(request.url); + const user = await funGetUserByCookies() + const name = searchParams.get('search') + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await prisma.division.findUnique({ + where: { + id: String(id), + isActive: true, + } + }); + + if (!data) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan", }, { status: 404 }); + } + + const member = await prisma.divisionMember.findMany({ + where: { + idDivision: String(id), + isActive: true, + User: { + name: { + contains: (name == undefined || name == null) ? "" : name, + mode: "insensitive", + } + } + }, + select: { + id: true, + isAdmin: true, + isLeader: true, + idUser: true, + User: { + select: { + name: true, + img: true + } + } + }, + orderBy: { + isAdmin: 'desc', + } + }) + + const fixMember = member.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + img: v.User.img + })) + + + + return NextResponse.json({ success: true, data: fixMember }) + + } catch (error) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan member, data tidak ditemukan", }, { status: 404 }); + } +} \ No newline at end of file diff --git a/src/app/api/division/[id]/route.ts b/src/app/api/division/[id]/route.ts new file mode 100644 index 0000000..eb81f1c --- /dev/null +++ b/src/app/api/division/[id]/route.ts @@ -0,0 +1,122 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + +// GET ONE DATA DIVISI :: UNTUK TAMPIL DATA DI HALAMAN EDIT DAN INFO +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await prisma.division.findUnique({ + where: { + id: String(id), + isActive: true + } + }); + + if (!data) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan", }, { status: 404 }); + } + + const member = await prisma.divisionMember.findMany({ + where: { + idDivision: String(id), + isActive: true, + }, + select: { + id: true, + isAdmin: true, + isLeader: true, + idUser: true, + User: { + select: { + name: true, + img: true + } + } + }, + orderBy: { + isAdmin: 'desc', + } + }) + + const fixMember = member.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + img: v.User.img + })) + + const dataFix = { + division: data, + member: fixMember + } + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: dataFix, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// EDIT DATA DIVISI +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { name, desc } = (await request.json()); + const data = await prisma.division.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Edit divisi gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.division.update({ + where: { + id: id, + }, + data: { + name: name, + desc: desc + }, + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data divisi', table: 'division', data: id }) + + return NextResponse.json( + { + success: true, + message: "Divisi berhasil diedit", + }, + { status: 200 } + ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengedit divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/division/more/route.ts b/src/app/api/division/more/route.ts new file mode 100644 index 0000000..fdf5394 --- /dev/null +++ b/src/app/api/division/more/route.ts @@ -0,0 +1,50 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + +// GET LIST DIVISI BY ID DIVISI (CONTOH : UNTUK SHARE DOKUMEN) +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const idDivision = searchParams.get("division"); + const name = searchParams.get('search'); + + const dataDivision = await prisma.division.findUnique({ + where: { + id: String(idDivision), + isActive: true + } + }) + + if (!dataDivision) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan", }, { status: 404 }); + } + + const data = await prisma.division.findMany({ + where: { + isActive: true, + idGroup: dataDivision.idGroup, + name: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + } + }, + select: { + id: true, + name: true, + } + }); + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/division/report/route.ts b/src/app/api/division/report/route.ts new file mode 100644 index 0000000..0645bd9 --- /dev/null +++ b/src/app/api/division/report/route.ts @@ -0,0 +1,198 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _, { ceil } from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + try { + + const user = await funGetUserByCookies() + const { searchParams } = new URL(request.url) + const group = searchParams.get("group") + const division = searchParams.get("division") + const date = searchParams.get("date") + + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }) + } + + + // CHART PROGRESS + let kondisiProgress + if (division == "undefined") { + kondisiProgress = { + isActive: true, + updatedAt: { + lte: new Date(String(date)) + }, + Division: { + idGroup: String(group) + } + } + } else { + kondisiProgress = { + isActive: true, + idDivision: String(division), + updatedAt: { + lte: new Date(String(date)) + }, + } + } + + const data = await prisma.divisionProject.groupBy({ + where: kondisiProgress, + by: ["status"], + _count: true + }) + + const dataStatus = [{ name: 'Segera dikerjakan', status: 0 }, { name: 'Dikerjakan', status: 1 }, { name: 'Selesai dikerjakan', status: 2 }, { name: 'Dibatalkan', status: 3 }] + const hasilProgres: any[] = [] + let input + for (let index = 0; index < dataStatus.length; index++) { + const cek = data.some((i: any) => i.status == dataStatus[index].status) + if (cek) { + const find = ((Number(data.find((i: any) => i.status == dataStatus[index].status)?._count) * 100) / data.reduce((n, { _count }) => n + _count, 0)).toFixed(2) + input = { + name: dataStatus[index].name, + value: find + } + } else { + input = { + name: dataStatus[index].name, + value: 0 + } + } + hasilProgres.push(input) + } + + + + + // CHART DOKUMEN + let kondisi + if (division == "undefined") { + kondisi = { + isActive: true, + category: 'FILE', + Division: { + idGroup: String(group) + }, + createdAt: { + lte: new Date(String(date)) + }, + } + } else { + kondisi = { + isActive: true, + category: 'FILE', + idDivision: String(division), + createdAt: { + lte: new Date(String(date)) + }, + } + } + + const dataDokumen = await prisma.divisionDocumentFolderFile.findMany({ + where: kondisi, + }) + + const groupData = _.map(_.groupBy(dataDokumen, "extension"), (v: any) => ({ + file: v[0].extension, + jumlah: v.length, + })) + + const image = ['jpg', 'jpeg', 'png', 'heic'] + + let hasilImage = { + name: 'Gambar', + value: 0 + } + + let hasilFile = { + name: 'Dokumen', + value: 0 + } + + groupData.map((v: any) => { + if (image.some((i: any) => i == v.file)) { + hasilImage = { + name: 'Gambar', + value: hasilImage.value + v.jumlah + } + } else { + hasilFile = { + name: 'Dokumen', + value: hasilFile.value + v.jumlah + } + } + }) + + const hasilDokumen = [hasilImage, hasilFile] + + + + // CHART EVENT + let kondisiEvent + if (division == "undefined") { + kondisiEvent = { + isActive: true, + Division: { + idGroup: String(group) + }, + dateStart: new Date(String(date)) + } + } else { + kondisiEvent = { + isActive: true, + idDivision: String(division), + dateStart: new Date(String(date)) + } + } + + const dataEvent = await prisma.divisionCalendar.findMany({ + where: kondisiEvent, + select: { + id: true, + idDivision: true, + title: true, + desc: true, + status: true, + timeStart: true, + dateStart: true, + timeEnd: true, + dateEnd: true, + createdAt: true, + User: { + select: { + name: true + } + } + }, + orderBy: { + createdAt: 'desc' + } + }) + + const hasilEvent = dataEvent.map((v: any) => ({ + ..._.omit(v, ["User"]), + user_name: v.User.name, + timeStart: moment.utc(v.timeStart).format('HH:mm'), + timeEnd: moment.utc(v.timeEnd).format('HH:mm') + })) + + + const allData = { + progress: hasilProgres, + dokumen: hasilDokumen, + event: hasilEvent + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: allData }, { status: 200 }); + + } + catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan data, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/division/route.ts b/src/app/api/division/route.ts new file mode 100644 index 0000000..cc52a2d --- /dev/null +++ b/src/app/api/division/route.ts @@ -0,0 +1,152 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import { revalidatePath, revalidateTag } from "next/cache"; +import { NextResponse } from "next/server"; + + +// GET ALL DATA DIVISI == LIST DATA DIVISI +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + let grup + const villaId = user.idVillage + const roleUser = user.idUserRole + const { searchParams } = new URL(request.url); + const idGroup = searchParams.get("group"); + const name = searchParams.get('search'); + + if (idGroup == "null" || idGroup == undefined) { + grup = user.idGroup + } else { + grup = idGroup + } + + + let kondisi: any = { + isActive: true, + idVillage: String(villaId), + idGroup: grup, + name: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + } + } + + if (roleUser != "supadmin" && roleUser != "cosupadmin" && roleUser != "admin") { + kondisi = { + isActive: true, + idVillage: String(villaId), + idGroup: grup, + name: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + }, + DivisionMember: { + some: { + isActive: true, + idUser: String(user.id) + } + } + } + } + + const data = await prisma.division.findMany({ + where: kondisi, + select: { + id: true, + name: true, + desc: true, + DivisionMember: { + where: { + isActive: true + }, + select: { + idUser: true + } + } + } + }); + + const allData = data.map((v: any) => ({ + ..._.omit(v, ["DivisionMember"]), + jumlah_member: v.DivisionMember.length + })) + + + const filter = await prisma.group.findUnique({ + where: { + id: grup + }, + select: { + id: true, + name: true + } + }) + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: allData, filter }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + + + +// CREATE DATA DIVISI +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const sent = (await request.json()); + const villaId = user.idVillage + const data = await prisma.division.create({ + data: { + name: sent.data.name, + idVillage: String(user.idVillage), + idGroup: sent.data.idGroup, + desc: sent.data.desc, + createdBy: String(user.id) + }, + select: { + id: true + } + }) + + + const dataMember = sent.member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name", "img"]), + idUser: v.idUser, + idDivision: data.id, + isAdmin: sent.admin.some((i: any) => i == v.idUser) + })) + + const insertMember = await prisma.divisionMember.createMany({ + data: dataMember + }) + + revalidatePath('/api/divisi/', "page") + revalidatePath('/divisi', 'page') + revalidateTag('divisi') + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data divisi', table: 'division', data: data.id }) + + return NextResponse.json({ success: true, message: "Berhasil menambahkan divisi", data, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambahkan divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/src/app/api/document/more/route.ts b/src/app/api/document/more/route.ts new file mode 100644 index 0000000..20a697c --- /dev/null +++ b/src/app/api/document/more/route.ts @@ -0,0 +1,202 @@ +import { DIR, funCopyFile, prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + +// MOVE ITEM +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { path, dataItem } = (await request.json()); + + + if (path != "home") { + const cekPath = await prisma.divisionDocumentFolderFile.count({ + where: { + isActive: true, + id: path + } + }) + + if (cekPath == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan path, data tidak ditemukan" }, { status: 404 }); + } + } + + for (let i = 0; i < dataItem.length; i++) { + + let status = false + let numb = 1 + let name = dataItem[i].name + do { + const cekName = await prisma.divisionDocumentFolderFile.count({ + where: { + path: path, + isActive: true, + extension: dataItem[i].extension, + name + } + }) + + if (cekName > 0) { + name = dataItem[i].name + " (" + numb + ")" + numb++ + status = false + } else { + status = true + } + } while (status == false); + + + const id = dataItem[i].id; + const update = await prisma.divisionDocumentFolderFile.update({ + where: { + id + }, + data: { + path, + name + } + }) + } + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User memindahkan file atau folder', table: 'divisionDocumentFolderFile', data: '' }) + + + return NextResponse.json({ success: true, message: "Berhasil memindahkan item" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal memindahkan item, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; + + +// COPY ITEM +export async function PUT(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { idDivision, path, dataItem } = (await request.json()); + + + if (path != "home") { + const cekPath = await prisma.divisionDocumentFolderFile.count({ + where: { + isActive: true, + id: path + } + }) + + if (cekPath == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan path, data tidak ditemukan" }, { status: 404 }); + } + } + + for (let i = 0; i < dataItem.length; i++) { + let name = dataItem[i].name; + const category = dataItem[i].category; + const extension = dataItem[i].extension; + const idStorage = dataItem[i].idStorage; + + const copyOnStorage = await funCopyFile({ fileId: idStorage, dirId: DIR.document }) + if (copyOnStorage.success) { + let status = false + let numb = 1 + do { + const cekName = await prisma.divisionDocumentFolderFile.count({ + where: { + path: path, + isActive: true, + extension, + name + } + }) + + if (cekName > 0) { + name = dataItem[i].name + " (" + numb + ")" + numb++ + status = false + } else { + status = true + } + } while (status == false); + + + const create = await prisma.divisionDocumentFolderFile.create({ + data: { + name, + path, + idDivision, + category, + extension, + idStorage: copyOnStorage.data.id, + createdBy: user.id + }, + select: { + id: true + } + }) + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menyalin file', table: 'divisionDocumentFolderFile', data: create.id }) + } + } + + return NextResponse.json({ success: true, message: "Berhasil salin item" }, { status: 200 }); + + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal salin item, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; + + +// SHARE ITEM +export async function DELETE(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { dataDivision, dataItem } = (await request.json()); + + + for (let i = 0; i < dataItem.length; i++) { + const del = await prisma.divisionDocumentShare.deleteMany({ + where: { + idDocument: dataItem[i].id + } + }) + + const omitData = dataDivision.map((v: any) => ({ + ..._.omit(v, ["name", "id"]), + idDivision: v.id, + idDocument: dataItem[i].id + })) + + const insert = await prisma.divisionDocumentShare.createMany({ + data: omitData + }) + + } + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membagikan item', table: 'divisionDocumentShare', data: '' }) + return NextResponse.json({ success: true, message: "Berhasil membagikan item" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal membagikan item, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/src/app/api/document/route.ts b/src/app/api/document/route.ts new file mode 100644 index 0000000..f45bf14 --- /dev/null +++ b/src/app/api/document/route.ts @@ -0,0 +1,394 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + +// GET ALL DOCUMENT +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + + const { searchParams } = new URL(request.url); + const idDivision = searchParams.get("division"); + const path = searchParams.get("path"); + const category = searchParams.get("category"); + + const cekDivision = await prisma.division.count({ + where: { + id: String(idDivision), + isActive: true + } + }) + + if (cekDivision == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + let statusAkses = false + let aksesPath = String(path) + if (path != "home" && path != "null" && path != "undefined" && path != "") { + const cekPath = await prisma.divisionDocumentFolderFile.count({ + where: { + isActive: true, + id: String(path), + idDivision: String(idDivision) + } + }) + + const cekSharePath = await prisma.divisionDocumentShare.count({ + where: { + isActive: true, + idDivision: String(idDivision), + idDocument: String(path) + } + }) + + if (cekPath == 0 && cekSharePath == 0) { + do { + const dataPath = await prisma.divisionDocumentFolderFile.findUnique({ + where: { + id: String(aksesPath) + } + }) + + if (dataPath) { + const cekShare = await prisma.divisionDocumentShare.count({ + where: { + isActive: true, + idDivision: String(idDivision), + idDocument: String(aksesPath) + } + }) + if (cekShare == 0) { + statusAkses = false + aksesPath = dataPath.path + } else { + statusAkses = true + } + + } else { + aksesPath = "home" + } + } while (aksesPath != "home" && statusAkses == false); + + if (statusAkses == false) { + return NextResponse.json({ success: false, message: "Data tidak ditemukan / tidak memilik hak akses" }, { status: 404 }); + } + } + } + + + let kondisi: any = { + isActive: true, + idDivision: String(idDivision), + path: (path == "undefined" || path == "null" || path == "" || path == null) ? "home" : path + } + + let formatDataShare: any[] = []; + + if (category == "folder") { + kondisi = { + isActive: true, + idDivision: String(idDivision), + path: (path == "undefined" || path == "null" || path == "" || path == null) ? "home" : path, + category: "FOLDER" + } + } else { + if (path == "home" || path == "null" || path == "undefined") { + const dataShare = await prisma.divisionDocumentShare.findMany({ + where: { + isActive: true, + idDivision: String(idDivision), + }, + select: { + DivisionDocumentFolderFile: { + select: { + id: true, + category: true, + name: true, + extension: true, + path: true, + User: { + select: { + name: true + } + }, + createdAt: true, + updatedAt: true + } + } + } + }) + + formatDataShare = dataShare.map((v: any) => ({ + ..._.omit(v, ["DivisionDocumentFolderFile"]), + idStorage: '', + id: v.DivisionDocumentFolderFile.id, + category: v.DivisionDocumentFolderFile.category, + name: v.DivisionDocumentFolderFile.name, + extension: v.DivisionDocumentFolderFile.extension, + path: v.DivisionDocumentFolderFile.path, + createdBy: v.DivisionDocumentFolderFile.User.name, + createdAt: moment(v.DivisionDocumentFolderFile.createdAt).format("DD-MM-YYYY HH:mm"), + updatedAt: moment(v.DivisionDocumentFolderFile.updatedAt).format("DD-MM-YYYY HH:mm"), + share: true + })) + + } else { + kondisi = { + isActive: true, + path: (path == "undefined" || path == "null" || path == null) ? "home" : path + } + } + } + + + const data = await prisma.divisionDocumentFolderFile.findMany({ + where: kondisi, + select: { + id: true, + category: true, + name: true, + extension: true, + idStorage: true, + path: true, + User: { + select: { + name: true + } + }, + createdAt: true, + updatedAt: true + }, + orderBy: { + name: 'asc' + } + }) + + const allData = data.map((v: any) => ({ + ..._.omit(v, ["User", "createdAt", "updatedAt"]), + createdBy: v.User.name, + createdAt: moment(v.createdAt).format("DD-MM-YYYY HH:mm"), + updatedAt: moment(v.updatedAt).format("DD-MM-YYYY HH:mm"), + share: false + })) + + if (formatDataShare.length > 0) { + allData.push(...formatDataShare) + } + + const formatData = _.orderBy(allData, ['category', 'name'], ['desc', 'asc']); + + let pathNow = path + let jalur = [] + + if (path != "home" && path != "null" && path != "undefined" && path != "") { + do { + const dataPath = await prisma.divisionDocumentFolderFile.findUnique({ + where: { + id: String(pathNow) + } + }) + + if (dataPath && (dataPath.idDivision == idDivision || path == pathNow || pathNow == aksesPath)) { + const fix = { + id: String(pathNow), + name: dataPath.name, + } + jalur.push(fix) + pathNow = dataPath.path + + } else { + pathNow = "home" + } + } while (pathNow != "home"); + + } + + jalur.push({ id: 'home', name: 'home' }) + jalur = [...jalur].reverse() + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan item", data: formatData, jalur }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan item, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// CREATE FOLDER +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { name, path, idDivision } = (await request.json()); + + const cekDivision = await prisma.division.count({ + where: { + id: String(idDivision), + isActive: true + } + }) + + if (cekDivision == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + if (path != "home") { + const cekPath = await prisma.divisionDocumentFolderFile.count({ + where: { + isActive: true, + id: path + } + }) + + if (cekPath == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan path, data tidak ditemukan" }, { status: 404 }); + } + } + + const nameFile = await prisma.divisionDocumentFolderFile.count({ + where: { + name, + idDivision, + path, + extension: "folder", + category: "FOLDER", + isActive: true + } + }) + + + if (nameFile > 0) { + return NextResponse.json({ success: false, message: "Gagal membuat folder baru, folder sudah ada" }, { status: 400 }); + } + + const data = await prisma.divisionDocumentFolderFile.create({ + data: { + name, + path, + idDivision, + category: "FOLDER", + extension: "folder", + createdBy: user.id, + }, + select: { + id: true + } + }); + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat folder baru', table: 'divisionDocumentFolderFile', data: data.id }) + + return NextResponse.json({ success: true, message: "Berhasil membuat folder baru" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal membuat folder, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; + + +// RENAME ITEM +export async function PUT(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { name, id, path, idDivision, extension } = (await request.json()); + const cekFile = await prisma.divisionDocumentFolderFile.count({ + where: { + id: id, + isActive: true + } + }) + + if (cekFile == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan item, data tidak ditemukan" }, { status: 404 }); + } + + const nameFile = await prisma.divisionDocumentFolderFile.count({ + where: { + name, + idDivision, + path, + extension, + isActive: true, + NOT: { + id: id + }, + } + }) + + + if (nameFile > 0) { + return NextResponse.json({ success: false, message: "Gagal mengubah nama item, item sudah ada" }, { status: 400 }); + } + + const update = await prisma.divisionDocumentFolderFile.update({ + where: { + id: id + }, + data: { + name, + } + }) + + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengubah nama file atau folder', table: 'divisionDocumentFolderFile', data: id }) + + + return NextResponse.json({ success: true, message: "Berhasil mengubah nama item" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengubah nama item, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; + + +// DELETE ITEM +export async function DELETE(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await request.json() + + for (let i = 0; i < data.length; i++) { + const id = data[i].id; + const cekFile = await prisma.divisionDocumentFolderFile.update({ + where: { + id: id + }, + data: { + isActive: false + } + }) + } + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus file atau folder', table: 'divisionDocumentFolderFile', data: '' }) + + + return NextResponse.json({ success: true, message: "Berhasil menghapus item" }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus item, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/src/app/api/document/upload/route.ts b/src/app/api/document/upload/route.ts new file mode 100644 index 0000000..6dd776f --- /dev/null +++ b/src/app/api/document/upload/route.ts @@ -0,0 +1,100 @@ +import { DIR, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import { NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; +import { createLogUser } from "@/module/user"; + + +// UPLOAD FILE +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const body = await request.formData() + const dataBody = body.get("data") + const file = body.get("file") as File + const fileName = file.name + + + const { idPath, idDivision } = JSON.parse(dataBody as string) + + const cekDivision = await prisma.division.count({ + where: { + id: String(idDivision), + isActive: true + } + }) + + if (cekDivision == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan" }, { status: 404 }); + } + + if (idPath != "home") { + const cekPath = await prisma.divisionDocumentFolderFile.count({ + where: { + isActive: true, + id: idPath + } + }) + + if (cekPath == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan path, data tidak ditemukan" }, { status: 404 }); + } + } + + const nameFile = await prisma.divisionDocumentFolderFile.findMany({ + where: { + idDivision, + path: idPath, + category: "FILE", + isActive: true + } + }) + + const dataOmit = nameFile.map((v: any) => ({ + ..._.omit(v, [""]), + file: v.name + '.' + v.extension, + })) + + const cek = dataOmit.some((i: any) => i.file == fileName) + + if (cek) { + return NextResponse.json({ success: false, message: "Terdapat file dengan nama yang sama" }, { status: 400 }); + } + + + const fExt = file.name.split(".").pop() + const fName = file.name.replace("." + fExt, "") + const upload = await funUploadFile({ file: file, dirId: DIR.document }) + if (upload.success) { + const dataInsert = await prisma.divisionDocumentFolderFile.create({ + data: { + name: fName, + path: idPath, + idDivision, + category: "FILE", + extension: String(fExt), + createdBy: user.id, + idStorage: upload.data.id + }, + select: { + id: true + } + }); + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User mengupload file baru', table: 'divisionDocumentFolderFile', data: dataInsert.id }) + return NextResponse.json({ success: true, message: "Berhasil upload file" }, { status: 200 }); + } else { + return NextResponse.json({ success: false, message: "Gagal upload file, coba lagi nanti" }, { status: 400 }); + } + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal upload file, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/src/app/api/file/img/route.ts b/src/app/api/file/img/route.ts new file mode 100644 index 0000000..51a987a --- /dev/null +++ b/src/app/api/file/img/route.ts @@ -0,0 +1,23 @@ +import { NextRequest, NextResponse } from "next/server" +import fs from 'fs' + +export async function GET(request: Request) { + let fl; + + try { + const { searchParams } = new URL(request.url); + const kategori = searchParams.get('cat'); + const file = searchParams.get('file'); + const jenis = searchParams.get('jenis'); + fl = fs.readFileSync(`./public/${jenis}/${kategori}/${file}`) + } catch (err: any) { + throw err; + } + + return new NextResponse(fl, { + headers: { + "Content-Type": "image/png" + } + }) + +} \ No newline at end of file diff --git a/src/app/api/group/[id]/route.ts b/src/app/api/group/[id]/route.ts new file mode 100644 index 0000000..2c9652f --- /dev/null +++ b/src/app/api/group/[id]/route.ts @@ -0,0 +1,133 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import { NextResponse } from "next/server"; + +export const dynamic = 'force-dynamic' +export const revalidate = true +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await prisma.group.findUnique({ + where: { + id: id, + }, + }); + + if (!data) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan grup, data tidak ditemukan", + }, + { status: 404 } + ); + } + + return NextResponse.json( + { + success: true, + message: "Berhasil mendapatkan grup", + data, + }, + { status: 200 } + ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan grup, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + const { isActive } = (await request.json()); + const data = await prisma.group.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Edit grup gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.group.update({ + where: { + id: id, + }, + data: { + isActive: !isActive, + }, + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengedit status data grup', table: 'group', data: id }) + + return NextResponse.json( { success: true, message: "Grup berhasil diedit", data, }, { status: 200 } ); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengedit grup, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { name } = (await request.json()); + const data = await prisma.group.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Edit grup gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.group.update({ + where: { + id: id, + }, + data: { + name: name, + }, + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengedit data grup', table: 'group', data: id }) + + return NextResponse.json( { success: true, message: "Grup berhasil diedit", data, }, { status: 200 } ); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengedit grup, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/group/get-division/route.ts b/src/app/api/group/get-division/route.ts new file mode 100644 index 0000000..ec52351 --- /dev/null +++ b/src/app/api/group/get-division/route.ts @@ -0,0 +1,49 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { NextResponse } from "next/server"; + +export const dynamic = 'force-dynamic' +export const revalidate = true +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const role = user.idUserRole + const villaId = user.idVillage + const group = user.idGroup + let kondisi: any = { + isActive: true, + idVillage: String(villaId) + } + + if (role != "supadmin") { + kondisi = { + isActive: true, + idVillage: String(villaId), + id: String(group) + } + } + + const data = await prisma.group.findMany({ + where: kondisi, + select: { + id: true, + name: true, + Division: { + select: { + id: true, + name: true + } + } + } + }); + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan grup", data, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan grup, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/group/route.ts b/src/app/api/group/route.ts new file mode 100644 index 0000000..7a6c886 --- /dev/null +++ b/src/app/api/group/route.ts @@ -0,0 +1,79 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import { revalidatePath, revalidateTag } from "next/cache"; +import { NextResponse } from "next/server"; + +export const dynamic = 'force-dynamic' +export const revalidate = true +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const villaId = user.idVillage + const { searchParams } = new URL(request.url); + const isActive = searchParams.get("active"); + const name = searchParams.get('search'); + + const data = await prisma.group.findMany({ + where: { + isActive: isActive == 'false' ? false : true, + idVillage: String(villaId), + name: { + contains: (name == undefined || name == null) ? "" : name, + mode: "insensitive" + } + }, + select: { + id: true, + name: true, + isActive: true + } + }); + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan grup", data, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan grup, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { name } = (await request.json()); + const villaId = user.idVillage + const data = await prisma.group.create({ + data: { + name, + idVillage: String(villaId) + }, + select: { + id: true + } + }); + + revalidatePath('/api/group?active=true', "page") + revalidatePath('/api/group?active=false', 'page') + revalidatePath('/group?active=true', 'page') + revalidateTag('group') + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data grup', table: 'group', data: data.id }) + + + return NextResponse.json({ success: true, message: "Berhasil menambahkan grup", data, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambahkan grup, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +}; \ No newline at end of file diff --git a/src/app/api/home/route.ts b/src/app/api/home/route.ts new file mode 100644 index 0000000..918c174 --- /dev/null +++ b/src/app/api/home/route.ts @@ -0,0 +1,370 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _, { ceil } from "lodash"; +import moment from "moment"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; + + +// HOME +export async function GET(request: Request) { + try { + let allData + const user = await funGetUserByCookies() + const { searchParams } = new URL(request.url); + const kategori = searchParams.get("cat"); + + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const roleUser = user.idUserRole + const idVillage = user.idVillage + const idGroup = user.idGroup + + + if (kategori == "kegiatan") { + let kondisi + + // klo perbekel == semua grup + if (roleUser == "supadmin") { + kondisi = { + isActive: true, + idVillage: idVillage, + Group: { + isActive: true, + } + } + } else { + kondisi = { + isActive: true, + idGroup: idGroup + } + } + + const data = await prisma.project.findMany({ + skip: 0, + take: 5, + where: kondisi, + select: { + id: true, + title: true, + desc: true, + status: true, + createdAt: true, + ProjectTask: { + where: { + isActive: true + }, + select: { + title: true, + status: true + } + } + }, + orderBy: { + createdAt: "desc" + } + }) + + allData = data.map((v: any) => ({ + ..._.omit(v, ["ProjectTask", "createdAt"]), + progress: ceil((v.ProjectTask.filter((i: any) => i.status == 1).length * 100) / v.ProjectTask.length), + createdAt: moment(v.createdAt).format("LL") + })) + + } else if (kategori == "division") { + let kondisi + + // klo perbekel == semua grup + if (roleUser == "supadmin") { + kondisi = { + isActive: true, + idVillage: idVillage, + Group: { + isActive: true, + } + } + } else { + kondisi = { + isActive: true, + idGroup: idGroup + } + } + + const data = await prisma.division.findMany({ + where: kondisi, + select: { + id: true, + name: true, + DivisionProject: { + where: { + isActive: true, + NOT: { + status: 3 + } + } + } + }, + }) + + const format = data.map((v: any) => ({ + ..._.omit(v, ["DivisionProject"]), + jumlah: v.DivisionProject.length, + })) + + allData = _.orderBy(format, 'jumlah', 'desc').slice(0, 5) + + } else if (kategori == "progress") { + let kondisi + + // klo perbekel == semua grup + if (roleUser == "supadmin") { + kondisi = { + isActive: true, + Division: { + idVillage: idVillage, + Group: { + isActive: true, + } + } + } + } else { + kondisi = { + isActive: true, + Division: { + idGroup: idGroup + } + } + } + + const data = await prisma.divisionProject.groupBy({ + where: kondisi, + by: ["status"], + _count: true + }) + + const dataStatus = [{ name: 'Segera dikerjakan', status: 0 }, { name: 'Dikerjakan', status: 1 }, { name: 'Selesai dikerjakan', status: 2 }, { name: 'Dibatalkan', status: 3 }] + const hasil: any[] = [] + let input + for (let index = 0; index < dataStatus.length; index++) { + const cek = data.some((i: any) => i.status == dataStatus[index].status) + if (cek) { + const find = ((Number(data.find((i: any) => i.status == dataStatus[index].status)?._count) * 100) / data.reduce((n, { _count }) => n + _count, 0)).toFixed(2) + input = { + name: dataStatus[index].name, + value: find + } + } else { + input = { + name: dataStatus[index].name, + value: 0 + } + } + hasil.push(input) + } + + allData = hasil + + } else if (kategori == "dokumen") { + let kondisi + + // klo perbekel == semua grup + if (roleUser == "supadmin") { + kondisi = { + isActive: true, + category: 'FILE', + Division: { + idVillage: idVillage, + Group: { + isActive: true, + } + } + } + } else { + kondisi = { + isActive: true, + category: 'FILE', + Division: { + idGroup: idGroup + } + } + } + + const data = await prisma.divisionDocumentFolderFile.findMany({ + where: kondisi, + }) + + const groupData = _.map(_.groupBy(data, "extension"), (v: any) => ({ + file: v[0].extension, + jumlah: v.length, + })) + + const image = ['jpg', 'jpeg', 'png', 'heic'] + + + let hasilImage = { + name: 'Gambar', + value: 0 + } + + let hasilFile = { + name: 'Dokumen', + value: 0 + } + + groupData.map((v: any) => { + if (image.some((i: any) => i == v.file)) { + hasilImage = { + name: 'Gambar', + value: hasilImage.value + v.jumlah + } + } else { + hasilFile = { + name: 'Dokumen', + value: hasilFile.value + v.jumlah + } + } + }) + + allData = [hasilImage, hasilFile] + + } else if (kategori == "event") { + let kondisi + + // klo perbekel == semua grup + if (roleUser == "supadmin") { + kondisi = { + isActive: true, + dateStart: new Date(), + Division: { + idVillage: idVillage, + Group: { + isActive: true, + } + }, + DivisionCalendar: { + isActive: true + }, + } + } else { + kondisi = { + isActive: true, + dateStart: new Date(), + Division: { + idGroup: idGroup + }, + DivisionCalendar: { + isActive: true + }, + } + } + + + const data = await prisma.divisionCalendarReminder.findMany({ + skip: 0, + take: 5, + where: kondisi, + select: { + id: true, + idCalendar: true, + timeStart: true, + dateStart: true, + timeEnd: true, + dateEnd: true, + createdAt: true, + status: true, + DivisionCalendar: { + select: { + title: true, + desc: true, + User: { + select: { + name: true + } + } + } + } + }, + orderBy: [ + { + dateStart: 'asc' + }, + { + timeStart: 'asc' + }, + { + timeEnd: 'asc' + } + ] + }) + + allData = data.map((v: any) => ({ + ..._.omit(v, ["DivisionCalendar", "User"]), + user_name: v.DivisionCalendar.User.name, + title: v.DivisionCalendar.title, + timeStart: moment.utc(v.timeStart).format('HH:mm'), + timeEnd: moment.utc(v.timeEnd).format('HH:mm') + })) + + } else if (kategori == "discussion") { + let kondisi + + // klo perbekel == semua grup + if (roleUser == "supadmin") { + kondisi = { + isActive: true, + status: 1, + Division: { + idVillage: idVillage, + Group: { + isActive: true, + } + } + } + } else { + kondisi = { + isActive: true, + status: 1, + Division: { + idGroup: idGroup + } + } + } + + const data = await prisma.divisionDisscussion.findMany({ + skip: 0, + take: 5, + where: kondisi, + select: { + id: true, + idDivision: true, + title: true, + desc: true, + createdAt: true, + User: { + select: { + name: true + } + } + }, + orderBy: { + createdAt: "desc" + } + }) + + allData = data.map((v: any) => ({ + ..._.omit(v, ["createdAt", "User"]), + date: moment(v.dateStart).format("ll"), + user: v.User.name + })) + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: allData }, { status: 200 }); + + } + catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan data, coba lagi nanti 99", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/home/search/route.ts b/src/app/api/home/search/route.ts new file mode 100644 index 0000000..103220d --- /dev/null +++ b/src/app/api/home/search/route.ts @@ -0,0 +1,141 @@ + + +// SEARCH USER, DIVISION, PROJECT + +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url) + const search = searchParams.get("search") + const userId = await funGetUserByCookies() + if (userId.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + let kondisi: any, kondisiProject: any + + // klo perbekel == semua grup + if (userId.idUserRole == "supadmin") { + kondisi = { + isActive: true, + idVillage: userId.idVillage, + Group: { + isActive: true, + }, + name: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + } + } + + kondisiProject = { + isActive: true, + idVillage: userId.idVillage, + Group: { + isActive: true, + }, + title: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + } + } + } else { + kondisi = { + idVillage: userId.idVillage, + isActive: true, + idGroup: userId.idGroup, + name: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + } + } + + kondisiProject = { + idVillage: userId.idVillage, + isActive: true, + idGroup: userId.idGroup, + title: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + } + } + } + + const user = await prisma.user.findMany({ + where: kondisi, + select: { + id: true, + name: true, + email: true, + img: true, + Position: { + select: { + name: true + } + }, + Group: { + select: { + name: true + } + } + } + }) + + const userOmit = user.map((v: any) => ({ + ..._.omit(v, ["Position", "Group"]), + position: v.Position.name, + group: v.Group.name + })) + + const divisions = await prisma.division.findMany({ + where: kondisi, + select: { + id: true, + name: true, + desc: true, + Group: { + select: { + name: true + } + } + } + }) + + const divisionOmit = divisions.map((v: any) => ({ + ..._.omit(v, ["Group"]), + group: v.Group.name + })) + + const projects = await prisma.project.findMany({ + where: kondisiProject, + select: { + id: true, + title: true, + Group: { + select: { + name: true + } + } + } + }) + + const projectOmit = projects.map((v: any) => ({ + ..._.omit(v, ["Group"]), + group: v.Group.name + })) + + const allDataSearch = { + user: userOmit, + division: divisionOmit, + project: projectOmit + } + return NextResponse.json({ success: true, data: allDataSearch }, { status: 200 }); + + } catch (error) { + return NextResponse.json({ success: false, message: error }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/position/[id]/route.ts b/src/app/api/position/[id]/route.ts new file mode 100644 index 0000000..5d0d165 --- /dev/null +++ b/src/app/api/position/[id]/route.ts @@ -0,0 +1,141 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import { revalidatePath } from "next/cache"; +import { NextResponse } from "next/server"; + +// GET ONE JABATAN +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const data = await prisma.position.findUnique({ + where: { + id: id, + }, + select: { + id: true, + name: true, + idGroup: true, + }, + }); + if (!data) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan jabatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + return NextResponse.json( + { + success: true, + message: "Berhasil mendapatkan jabatan", + data, + }, + { status: 200 } + ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan jabatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +// DELETE / ACTIVE & NON ACTIVE POSITION +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + const { isActive } = (await request.json()); + const data = await prisma.position.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mengubah status jabatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + + const update = await prisma.position.update({ + where: { + id: id, + }, + data: { + isActive: !isActive, + }, + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate status data jabatan', table: 'position', data: id }) + return NextResponse.json( + { success: true, message: "Berhasil mengubah status jabatan" }, + { status: 200 } + ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengubah status jabatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// UPDATE POSITION +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + const data = await request.json(); + const cek = await prisma.position.count({ + where: { + name: data.name, + idGroup: data.idGroup, + NOT: { + id: id + } + }, + }); + + if (cek == 0) { + const positions = await prisma.position.update({ + where: { + id: id, + }, + data: { + name: data.name, + // idGroup: data.idGroup, + }, + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data jabatan', table: 'position', data: id }) + return NextResponse.json({ success: true, message: "Berhasil mengedit jabatan", }, { status: 200 }); + } else { + return NextResponse.json( + { success: false, message: "Jabatan sudah ada" }, + { status: 400 } + ); + } + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengedit jabatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/position/route.ts b/src/app/api/position/route.ts new file mode 100644 index 0000000..9112a5f --- /dev/null +++ b/src/app/api/position/route.ts @@ -0,0 +1,139 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import { revalidatePath, revalidateTag } from "next/cache"; +import { NextResponse } from "next/server"; + + +// GET ALL POSITION +export async function GET(request: Request) { + try { + + let grup + const { searchParams } = new URL(request.url); + const idGroup = searchParams.get("group"); + const active = searchParams.get('active'); + const name = searchParams.get('search') + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + if (idGroup == "null" || idGroup == undefined) { + grup = user.idGroup + } else { + grup = idGroup + } + + const cek = await prisma.group.count({ + where: { + id: grup, + isActive: true + } + }) + + if (cek == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan jabatan, data tidak ditemukan", }, { status: 404 }); + } + + const filter = await prisma.group.findUnique({ + where: { + id: grup + }, + select: { + id: true, + name: true + } + }) + + const positions = await prisma.position.findMany({ + where: { + idGroup: grup, + isActive: active == 'false' ? false : true, + name: { + contains: (name == undefined || name == null) ? "" : name, + mode: "insensitive" + } + }, + select: { + id: true, + name: true, + isActive: true, + Group: { + select: { + name: true + } + } + }, + }); + + const allData = positions.map((v: any) => ({ + ..._.omit(v, ["Group"]), + group: v.Group.name + })) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan jabatan", data: allData, filter }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan jabatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// CREATE POSITION +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { name, idGroup } = await request.json(); + + let groupFix = idGroup + + if (groupFix == null || groupFix == undefined || groupFix == "") { + groupFix = user.idGroup + } + + + const cek = await prisma.position.count({ + where: { + name: name, + idGroup: groupFix, + }, + }); + if (cek == 0) { + const positions = await prisma.position.create({ + data: { + name: name, + idGroup: groupFix, + }, + select: { + id: true, + name: true, + }, + }); + + revalidatePath('/api/position?active=true', "page") + revalidatePath('/api/position?active=false', 'page') + revalidatePath('/position?active=true', 'page') + revalidateTag('position') + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data jabatan baru', table: 'position', data: positions.id }) + + return NextResponse.json({ success: true, message: "Berhasil menambahkan jabatan", positions, }, { status: 200 }); + } else { + return NextResponse.json( + { success: false, message: "Jabatan sudah ada" }, + { status: 400 } + ); + } + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambahkan jabatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} diff --git a/src/app/api/project/[id]/member/route.ts b/src/app/api/project/[id]/member/route.ts new file mode 100644 index 0000000..a99aad7 --- /dev/null +++ b/src/app/api/project/[id]/member/route.ts @@ -0,0 +1,172 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + +// ADD MEMBER PROJECT +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + const { member } = (await request.json()) + + const data = await prisma.project.count({ + where: { + id: id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + if (member.length > 0) { + const dataMember = member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name", "img"]), + idProject: id, + idUser: v.idUser + })) + + const insertMember = await prisma.projectMember.createMany({ + data: dataMember + }) + } + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambah anggota kegiatan', table: 'project', data: String(id) }) + return NextResponse.json({ success: true, message: "Berhasil menambahkan anggota kegiatan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambah anggota kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +// MENGELUARKAN ANGGOTA +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + const { idUser } = (await request.json()); + + const data = await prisma.project.count({ + where: { + id: id, + }, + }); + + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const deleteMember = await prisma.projectMember.deleteMany({ + where: { + idProject: id, + idUser: idUser + } + }) + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User mengeluarkan anggota kegiatan', table: 'project', data: String(id) }) + + return NextResponse.json({ success: true, message: "Berhasil mengeluarkan anggota kegiatan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengeluarkan anggota kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +//GET MEMBER ALL USER ID GROUP +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + const groupId = user.idGroup + const userId = user.id + const { searchParams } = new URL(request.url); + const name = searchParams.get('search'); + + const data = await prisma.project.findUnique({ + where: { + id: String(id), + isActive: true + } + }) + + if (data == null) { + return NextResponse.json( + { + success: false, + message: "Gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const member = await prisma.user.findMany({ + where: { + idGroup: String(groupId), + id: { + not: String(userId) + }, + isActive: true, + name: { + contains: (name == undefined || name == "null") ? "" : name, + mode: 'insensitive' + } + + }, + select: { + id: true, + name: true, + email: true, + img: true + } + }) + + const fixMember = member.map((v: any) => ({ + idUser: v.id, + name: v.name, + email: v.email, + img: v.img + })) + + const dataFix = { + project: data, + member: fixMember + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan project", data: dataFix, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan project, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/project/[id]/route.ts b/src/app/api/project/[id]/route.ts new file mode 100644 index 0000000..2d5a51e --- /dev/null +++ b/src/app/api/project/[id]/route.ts @@ -0,0 +1,283 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + +// GET DETAIL PROJECT / GET ONE PROJECT +export async function GET(request: Request, context: { params: { id: string } }) { + try { + let allData + const { id } = context.params; + const user = await funGetUserByCookies() + const { searchParams } = new URL(request.url); + const kategori = searchParams.get("cat"); + + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await prisma.project.findUnique({ + where: { + id: String(id), + isActive: true + } + }); + + if (!data) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan project, data tidak ditemukan", }, { status: 404 }); + } + + + if (kategori == "data") { + allData = data + } else if (kategori == "progress") { + const dataProgress = await prisma.projectTask.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + updatedAt: 'desc' + } + }) + + const semua = dataProgress.length + const selesai = _.filter(dataProgress, { status: 1 }).length + const progress = Math.ceil((selesai / semua) * 100) + + allData = { + progress: progress, + lastUpdate: moment(dataProgress[0].updatedAt).format("DD MMMM YYYY"), + } + } else if (kategori == "task") { + const dataProgress = await prisma.projectTask.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + select: { + id: true, + title: true, + desc: true, + status: true, + dateStart: true, + dateEnd: true, + }, + orderBy: { + status: 'desc' + } + }) + + const formatData = dataProgress.map((v: any) => ({ + ..._.omit(v, ["dateStart", "dateEnd"]), + dateStart: moment(v.dateStart).format("DD MMMM YYYY"), + dateEnd: moment(v.dateEnd).format("DD MMMM YYYY"), + })) + + allData = formatData + + } else if (kategori == "file") { + const dataFile = await prisma.projectFile.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + createdAt: 'desc' + }, + select: { + id: true, + name: true, + extension: true, + idStorage: true + } + }) + + allData = dataFile + } else if (kategori == "member") { + + const dataMember = await prisma.projectMember.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + select: { + id: true, + idUser: true, + User: { + select: { + name: true, + email: true, + img: true, + Position: { + select: { + name: true + } + } + } + }, + } + }) + + const fix = dataMember.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + email: v.User.email, + img: v.User.img, + position: v.User.Position.name + })) + + allData = fix + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan project", data: allData, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan project, coba lagi nantiiiiii", reason: (error as Error).message, }, { status: 500 }); + } +} + +//CREATE NEW DETAIL TASK PROJECT +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + const { name, dateStart, dateEnd, } = await request.json() + + const data = await prisma.project.count({ + where: { + id: String(id) + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataCreate = await prisma.projectTask.create({ + data: { + title: name, + idProject: id, + dateStart: new Date(moment(dateStart).format('YYYY-MM-DD')), + dateEnd: new Date(moment(dateEnd).format('YYYY-MM-DD')), + }, + select: { + id: true + } + }) + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data tahapan kegiatan', table: 'projectTask', data: String(dataCreate.id) }) + + return NextResponse.json({ success: true, message: "Detail tahapan kegiatan berhasil ditambahkan", data: dataCreate, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal tambah tahapan kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// PEMBATALAN TASK PROJECT +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + const { reason } = await request.json() + const data = await prisma.project.count({ + where: { + id: id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan project, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataCreate = await prisma.project.update({ + where: { + id + }, + data: { + status: 3, + reason: reason + } + }) + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User membatalkan data kegiatan', table: 'project', data: String(id) }) + return NextResponse.json({ success: true, message: "Kegiatan berhasil dibatalkan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal membatalkan kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +// EDIT PROJECT +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params + const { name } = await request.json() + + const data = await prisma.project.count({ + where: { + id: id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataCreate = await prisma.project.update({ + where: { + id + }, + data: { + title: name + } + }) + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data kegiatan', table: 'project', data: String(id) }) + + return NextResponse.json({ success: true, message: "Kegiatan berhasil diupdate" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengupdate kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/project/detail/[id]/route.ts b/src/app/api/project/detail/[id]/route.ts new file mode 100644 index 0000000..52f7b22 --- /dev/null +++ b/src/app/api/project/detail/[id]/route.ts @@ -0,0 +1,246 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + +// HAPUS DETAIL PROJECT +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + const { idProject } = (await request.json()); + + const data = await prisma.projectTask.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Hapus tahapan kegiatan gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.projectTask.update({ + where: { + id: id, + }, + data: { + isActive: false, + }, + }); + + const dataTask = await prisma.projectTask.findMany({ + where: { + isActive: true, + idProject: idProject, + } + }) + + const semua = dataTask.length + const selesai = dataTask.filter((item) => item.status == 1).length + const prosess = Math.ceil((selesai / semua) * 100) + let statusProject = 1 + + if (prosess == 100) { + statusProject = 2 + } else if (prosess == 0) { + statusProject = 0 + } + + + const updProject = await prisma.project.update({ + where: { + id: idProject + }, + data: { + status: statusProject + } + }) + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus tahapan kegiatan', table: 'projectTask', data: String(id) }) + + + return NextResponse.json( + { + success: true, + message: "Tahapan kegiatan berhasil dihapus", + data, + }, + { status: 200 } + ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus tahapan kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// EDIT STATUS DETAIL PROJECT +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { status, idProject } = (await request.json()); + + const data = await prisma.projectTask.count({ + where: { + id + } + }) + + if (data == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataCreate = await prisma.projectTask.update({ + where: { + id + }, + data: { + status: status + } + }) + + + // const cek progress + const dataTask = await prisma.projectTask.findMany({ + where: { + isActive: true, + idProject: idProject, + } + }) + + const semua = dataTask.length + const selesai = dataTask.filter((item) => item.status == 1).length + const prosess = Math.ceil((selesai / semua) * 100) + let statusProject = 1 + + if (prosess == 100) { + statusProject = 2 + } else if (prosess == 0) { + statusProject = 0 + } + + + const update = await prisma.project.update({ + where: { + id: idProject + }, + data: { + status: statusProject + } + }) + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate status tahapan kegiatan', table: 'projectTask', data: String(id) }) + + return NextResponse.json({ success: true, message: "Status tahapan kegiatan berhasil diupdate", data }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengupdate status tahapan kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// GET ONE DETAIL PROJECT +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const data = await prisma.projectTask.findUnique({ + where: { + id: String(id), + isActive: true + } + }) + + if (!data) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan project, data tidak ditemukan", + }, + { status: 404 } + ); + } + + return NextResponse.json({ success: true, message: "Detail project berhasil ditemukan", data }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan project, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// EDIT DETAIL PROJECT +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { name, dateStart, dateEnd } = (await request.json()); + + const dataTask = await prisma.projectTask.count({ + where: { + id + } + }) + + if (dataTask == 0) { + return NextResponse.json( + { + success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const data = await prisma.projectTask.update({ + where: { + id + }, + data: { + title: name, + dateStart: new Date(moment(dateStart).format('YYYY-MM-DD')), + dateEnd: new Date(moment(dateEnd).format('YYYY-MM-DD')), + } + }) + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate tahapan kegiatan', table: 'projectTask', data: String(id) }) + + return NextResponse.json({ success: true, message: "Detail tahapan kegiatan berhasil diupdate", data }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengupdate detail tahapan kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/project/file/[id]/route.ts b/src/app/api/project/file/[id]/route.ts new file mode 100644 index 0000000..0d51d27 --- /dev/null +++ b/src/app/api/project/file/[id]/route.ts @@ -0,0 +1,201 @@ +import { DIR, funDeleteFile, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; +import _ from "lodash"; +import { createLogUser } from "@/module/user"; + +// HAPUS FILE PROJECT BUKAN PAKE ISACTIVE +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + const data = await prisma.projectFile.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Hapus file gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataRelasi = await prisma.projectFile.findUnique({ + where: { + id: id, + } + }) + + const delStorage = await funDeleteFile({ fileId: String(dataRelasi?.idStorage) }) + + const deleteRelasi = await prisma.projectFile.delete({ + where: { + id: id, + }, + }); + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus file kegiatan', table: 'project', data: String(dataRelasi?.idProject) }) + + + return NextResponse.json( + { + success: true, + message: "File berhasil dihapus", + data, + }, + { status: 200 } + ); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus file, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// CEK FILE PROJECT APAKAH PERNAH DIUPLOAD PADA PROJECT YG SAMA +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + + const { id } = context.params; + const body = await request.formData() + const file = body.get("file") as File + const fileName = file.name + + const dataCek = await prisma.project.count({ + where: { + id: id, + }, + }); + + if (dataCek == 0) { + return NextResponse.json( + { + success: false, + message: "Upload file gagal, data kegiatan tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataTaskFile = await prisma.projectFile.findMany({ + where: { + idProject: id, + isActive: true + }, + select: { + id: true, + name: true, + extension: true + } + }) + + const dataOmit = dataTaskFile.map((v: any) => ({ + ..._.omit(v, [""]), + file: v.name + '.' + v.extension, + })) + + const cek = dataOmit.some((i: any) => i.file == fileName) + + + if (cek) { + return NextResponse.json({ success: false, message: "File sudah pernah diupload" }, { status: 400 }); + } else { + return NextResponse.json({ success: true, message: "Cek berhasil" }, { status: 200 }); + } + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Upload file gagal, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// TAMBAH FILE PROJECT +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + + const { id } = context.params; + const body = await request.formData() + const cekFile = body.has("file0") + + const dataCek = await prisma.project.count({ + where: { + id: id, + }, + }); + + if (dataCek == 0) { + return NextResponse.json( + { + success: false, + message: "Tambah file kegiatan gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataProject = await prisma.project.findUnique({ + where: { + id: id, + }, + }); + + + + if (cekFile) { + for (var pair of body.entries()) { + if (String(pair[0]).substring(0, 4) == "file") { + const file = body.get(pair[0]) as File + const fExt = file.name.split(".").pop() + const fName = file.name.replace("." + fExt, "") + + const upload = await funUploadFile({ file: file, dirId: DIR.project }) + if (upload.success) { + const insertToTable = await prisma.projectFile.create({ + data: { + idStorage: upload.data.id, + idProject: id, + name: fName, + extension: String(fExt), + + }, + select: { + id: true + } + }) + } + } + } + } + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambah file kegiatan', table: 'project', data: String(id) }) + return NextResponse.json({ success: true, message: "Berhasil mengupload file kegiatan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengupload file, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/project/route.ts b/src/app/api/project/route.ts new file mode 100644 index 0000000..feec0b7 --- /dev/null +++ b/src/app/api/project/route.ts @@ -0,0 +1,201 @@ +import { DIR, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; +import path from "path"; +import fs from "fs"; +import { createLogUser } from "@/module/user"; + + +// GET ALL DATA PROJECT +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const roleUser = user.idUserRole + const { searchParams } = new URL(request.url); + + let grup + const name = searchParams.get('search'); + const status = searchParams.get('status'); + const idGroup = searchParams.get("group"); + const villageId = user.idVillage + const userId = user.id + + if (idGroup == "null" || idGroup == undefined) { + grup = user.idGroup + } else { + grup = idGroup + } + + const cek = await prisma.group.count({ + where: { + id: grup, + isActive: true + } + }) + + if (cek == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan data kegiatan, data tidak ditemukan", }, { status: 404 }); + } + + let kondisi: any = { + isActive: true, + idVillage: String(villageId), + idGroup: grup, + title: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + }, + status: (status == "0" || status == "1" || status == "2" || status == "3") ? Number(status) : 0 + } + + if (roleUser != "supadmin" && roleUser != "cosupadmin" && roleUser != "admin") { + kondisi = { + isActive: true, + idVillage: String(villageId), + idGroup: grup, + title: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + }, + status: (status == "0" || status == "1" || status == "2" || status == "3") ? Number(status) : 0, + ProjectMember: { + some: { + idUser: String(userId) + } + } + } + } + + + const data = await prisma.project.findMany({ + where: kondisi, + select: { + id: true, + title: true, + desc: true, + status: true, + ProjectMember: { + where: { + isActive: true + }, + select: { + idUser: true + } + } + } + }) + + const omitData = data.map((v: any) => ({ + ..._.omit(v, ["ProjectMember"]), + member: v.ProjectMember.length + })) + + const filter = await prisma.group.findUnique({ + where: { + id: grup + }, + select: { + id: true, + name: true + } + }) + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: omitData, filter }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +// CREATE PROJECT +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const body = await request.formData() + const dataBody = body.get("data") + const cekFile = body.has("file0") + + const { idGroup, title, task, member } = JSON.parse(dataBody as string) + const userId = user.id + + + const data = await prisma.project.create({ + data: { + idVillage: String(user.idVillage), + idGroup: String(idGroup), + title, + createdBy: String(userId) + }, + select: { + id: true + } + }) + + if (task.length > 0) { + const dataProject = task.map((v: any) => ({ + ..._.omit(v, ["dateStart", "dateEnd", "name"]), + idProject: data.id, + title: v.title, + dateStart: new Date(moment(v.dateStart).format('YYYY-MM-DD')), + dateEnd: new Date(moment(v.dateEnd).format('YYYY-MM-DD')), + })) + + const insertTask = await prisma.projectTask.createMany({ + data: dataProject + }) + } + + if (member.length > 0) { + const dataMember = member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name", "img"]), + idProject: data.id, + idUser: v.idUser, + })) + + const insertMember = await prisma.projectMember.createMany({ + data: dataMember + }) + } + + if (cekFile) { + for (var pair of body.entries()) { + if (String(pair[0]).substring(0, 4) == "file") { + const file = body.get(pair[0]) as File + const fExt = file.name.split(".").pop() + const fName = file.name.replace("." + fExt, "") + const upload = await funUploadFile({ file: file, dirId: DIR.project }) + if (upload.success) { + await prisma.projectFile.create({ + data: { + idStorage: upload.data.id, + idProject: data.id, + name: fName, + extension: String(fExt) + } + }) + } + } + } + } + + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data kegiatan', table: 'project', data: data.id }) + return NextResponse.json({ success: true, message: "Berhasil membuat kegiatan" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal membuat kegiatan, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/role-user/route.ts b/src/app/api/role-user/route.ts new file mode 100644 index 0000000..c38698b --- /dev/null +++ b/src/app/api/role-user/route.ts @@ -0,0 +1,26 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const res = await prisma.userRole.findMany({ + where: { + isActive: true + }, + select: { + id: true, + name: true, + }, + }); + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan role user", data: res, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan role user, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/task/[id]/member/route.ts b/src/app/api/task/[id]/member/route.ts new file mode 100644 index 0000000..caf5f77 --- /dev/null +++ b/src/app/api/task/[id]/member/route.ts @@ -0,0 +1,101 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + +// ADD MEMBER TASK DIVISI +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { member, idDivision } = (await request.json()); + + const data = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Tambah anggota tugas gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + if (member.length > 0) { + const dataMember = member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name", "img"]), + idDivision: idDivision, + idProject: id, + idUser: v.idUser, + })) + + const insertMember = await prisma.divisionProjectMember.createMany({ + data: dataMember + }) + } + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambahkan anggota tugas divisi', table: 'divisionProject', data: id }) + + + return NextResponse.json( { success: true, message: "Berhasil menambahkan anggota tugas", }, { status: 200 } ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambah anggota tugas, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + +// MENGELUARKAN ANGGOTA +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { idUser } = (await request.json()); + + const data = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const del = await prisma.divisionProjectMember.deleteMany({ + where: { + idUser: idUser, + idProject: id + } + }) + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User mengeluarkan anggota dari tugas divisi', table: 'divisionProject', data: id }) + + return NextResponse.json( { success: true, message: "Berhasil mengeluarkan anggota", }, { status: 200 } ); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengeluarkan anggota, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} diff --git a/src/app/api/task/[id]/route.ts b/src/app/api/task/[id]/route.ts new file mode 100644 index 0000000..1ae287f --- /dev/null +++ b/src/app/api/task/[id]/route.ts @@ -0,0 +1,298 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id" +import { NextResponse } from "next/server"; + + +// GET DETAIL TASK DIVISI / GET ONE +export async function GET(request: Request, context: { params: { id: string } }) { + try { + let allData + const { id } = context.params; + const user = await funGetUserByCookies() + const { searchParams } = new URL(request.url); + const kategori = searchParams.get("cat"); + + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await prisma.divisionProject.findUnique({ + where: { + id: String(id), + isActive: true + } + }); + + if (!data) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan tugas, data tidak ditemukan", }, { status: 404 }); + } + + if (kategori == "data") { + allData = data + } else if (kategori == "progress") { + const dataProgress = await prisma.divisionProjectTask.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + updatedAt: 'desc' + } + }) + + const semua = dataProgress.length + const selesai = _.filter(dataProgress, { status: 1 }).length + const progress = Math.ceil((selesai / semua) * 100) + + allData = { + progress: progress, + lastUpdate: moment(dataProgress[0].updatedAt).format("DD MMMM YYYY"), + } + } else if (kategori == "task") { + const dataProgress = await prisma.divisionProjectTask.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + select: { + id: true, + title: true, + status: true, + dateStart: true, + dateEnd: true, + }, + orderBy: { + status: 'desc' + } + }) + + const fix = dataProgress.map((v: any) => ({ + ..._.omit(v, ["dateStart", "dateEnd"]), + dateStart: moment(v.dateStart).format("DD MMMM YYYY"), + dateEnd: moment(v.dateEnd).format("DD MMMM YYYY"), + })) + + allData = fix + + } else if (kategori == "file") { + const dataFile = await prisma.divisionProjectFile.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + select: { + id: true, + ContainerFileDivision: { + select: { + id: true, + name: true, + extension: true, + idStorage: true + } + } + } + }) + + const fix = dataFile.map((v: any) => ({ + ..._.omit(v, ["ContainerFileDivision"]), + nameInStorage: v.ContainerFileDivision.id, + name: v.ContainerFileDivision.name, + extension: v.ContainerFileDivision.extension, + idStorage: v.ContainerFileDivision.idStorage, + })) + + allData = fix + + } else if (kategori == "member") { + const dataMember = await prisma.divisionProjectMember.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + select: { + id: true, + idUser: true, + User: { + select: { + name: true, + email: true, + img: true, + Position: { + select: { + name: true + } + } + } + } + } + }) + + + const fix = dataMember.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + email: v.User.email, + img: v.User.img, + position: v.User.Position.name + })) + + allData = fix + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan tugas divisi", data: allData }, { status: 200 }); + + } + catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan tugas divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// CREATE NEW DETAIL TASK DIVISI +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { title, dateStart, dateEnd, idDivision } = (await request.json()); + const data = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Tambah detail tugas gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const create = await prisma.divisionProjectTask.create({ + data: { + idProject: id, + idDivision, + title, + dateStart: new Date(moment(dateStart).format('YYYY-MM-DD')), + dateEnd: new Date(moment(dateEnd).format('YYYY-MM-DD')), + }, + select: { + id: true + } + }); + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambahkan detail tugas divisi', table: 'divisionProjectTask', data: create.id }) + + return NextResponse.json({ success: true, message: "Detail tugas berhasil ditambahkan", data, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengedit detail tugas, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// PEMBATALAN TASK DIVISI +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { reason } = (await request.json()); + const data = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Pembatalan tugas gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionProject.update({ + where: { + id + }, + data: { + reason: reason, + status: 3 + } + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User membatalkan tugas divisi', table: 'divisionProject', data: id }) + + return NextResponse.json({ success: true, message: "Tugas berhasil dibatalkan", }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal membatalkan tugas, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// EDIT TASK DIVISI +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { title } = (await request.json()); + const data = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Tugas gagal diedit, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionProject.update({ + where: { + id + }, + data: { + title + } + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data tugas divisi', table: 'divisionProject', data: id }) + + return NextResponse.json({ success: true, message: "Tugas berhasil diedit", }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengedit tugas, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + diff --git a/src/app/api/task/detail/[id]/route.ts b/src/app/api/task/detail/[id]/route.ts new file mode 100644 index 0000000..4989183 --- /dev/null +++ b/src/app/api/task/detail/[id]/route.ts @@ -0,0 +1,234 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + +// HAPUS DETAIL TASK +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + const { idProject } = (await request.json()); + const data = await prisma.divisionProjectTask.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Hapus tugas gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionProjectTask.update({ + where: { + id: id, + }, + data: { + isActive: false, + }, + }); + + // const cek progress + const dataTask = await prisma.divisionProjectTask.findMany({ + where: { + isActive: true, + idProject: idProject + } + }) + + const semua = dataTask.length + const selesai = _.filter(dataTask, { status: 1 }).length + const progress = Math.ceil((selesai / semua) * 100) + let statusProject = 1 + + if (progress == 100) { + statusProject = 2 + } else if (progress == 0) { + statusProject = 0 + } + + const updProject = await prisma.divisionProject.update({ + where: { + id: idProject + }, + data: { + status: statusProject + } + }) + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus detail task divisi', table: 'divisionProjectTask', data: id }) + + return NextResponse.json({ success: true, message: "Tugas berhasil dihapus", data, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus tugas divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// EDIT STATUS DETAIL TASK +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { status, idProject } = (await request.json()); + const data = await prisma.divisionProjectTask.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Update status detail tugas gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionProjectTask.update({ + where: { + id: id, + }, + data: { + status: status, + }, + }); + + // const cek progress + const dataTask = await prisma.divisionProjectTask.findMany({ + where: { + isActive: true, + idProject: idProject + } + }) + + const semua = dataTask.length + const selesai = _.filter(dataTask, { status: 1 }).length + const progress = Math.ceil((selesai / semua) * 100) + let statusProject = 1 + + if (progress == 100) { + statusProject = 2 + } else if (progress == 0) { + statusProject = 0 + } + + const updProject = await prisma.divisionProject.update({ + where: { + id: idProject + }, + data: { + status: statusProject + } + }) + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate status detail task divisi', table: 'divisionProjectTask', data: id }) + + return NextResponse.json({ success: true, message: "Status detail tugas berhasil diupdate", data, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengupdate status detail tugas, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// GET ONE DETAIL TASK +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const user = await funGetUserByCookies() + + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const data = await prisma.divisionProjectTask.findUnique({ + where: { + id: String(id), + isActive: true + } + }); + + if (!data) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan detail tugas, data tidak ditemukan", }, { status: 404 }); + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan detail tugas divisi", data }, { status: 200 }); + + } + catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan detail tugas divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// EDIT DETAIL TASK +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { id } = context.params; + const { title, dateStart, dateEnd } = (await request.json()); + const data = await prisma.divisionProjectTask.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Edit detail tugas gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const update = await prisma.divisionProjectTask.update({ + where: { + id: id, + }, + data: { + title, + dateStart: new Date(moment(dateStart).format('YYYY-MM-DD')), + dateEnd: new Date(moment(dateEnd).format('YYYY-MM-DD')), + }, + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data detail task divisi', table: 'divisionProjectTask', data: id }) + + return NextResponse.json({ success: true, message: "Detail tugas berhasil diedit", data, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengedit detail tugas, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/task/file/[id]/route.ts b/src/app/api/task/file/[id]/route.ts new file mode 100644 index 0000000..7796b3f --- /dev/null +++ b/src/app/api/task/file/[id]/route.ts @@ -0,0 +1,224 @@ +import { DIR, funDeleteFile, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import { NextResponse } from "next/server"; +import fs from "fs"; +import path from "path"; +import { createLogUser } from "@/module/user"; + +// HAPUS DETAIL FILE, HAPUS FILE DI ASSETS DAN DATABASE (BUKAN PAKE ISACTIVE) +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + const data = await prisma.divisionProjectFile.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Hapus file gagal, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataRelasi = await prisma.divisionProjectFile.findUnique({ + where: { + id: id, + } + }) + + const dataFile = await prisma.containerFileDivision.findUnique({ + where: { + id: dataRelasi?.idFile + } + }) + + await funDeleteFile({ fileId: String(dataFile?.idStorage) }) + + const deleteRelasi = await prisma.divisionProjectFile.delete({ + where: { + id: id, + }, + }); + + const deleteFile = await prisma.containerFileDivision.delete({ + where: { + id: dataRelasi?.idFile, + }, + }); + + // create log user + const log = await createLogUser({ act: 'DELETE', desc: 'User menghapus file tugas divisi', table: 'divisionProject', data: String(dataRelasi?.idProject) }) + + return NextResponse.json({ success: true, message: "File berhasil dihapus", data, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menghapus file, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// TAMBAH FILE TASK DIVISI +export async function POST(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + + const { id } = context.params; + const body = await request.formData() + const cekFile = body.has("file0") + + const dataCek = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (dataCek == 0) { + return NextResponse.json( + { + success: false, + message: "Tambah file tugas gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataProject = await prisma.divisionProject.findUnique({ + where: { + id: id, + }, + }); + + + let fileFix: any[] = [] + + if (cekFile) { + const root = path.join(process.cwd(), "./public/file/task/"); + for (var pair of body.entries()) { + if (String(pair[0]).substring(0, 4) == "file") { + const file = body.get(pair[0]) as File + const fExt = file.name.split(".").pop() + const fName = file.name.replace("." + fExt, "") + + + const upload = await funUploadFile({ file: file, dirId: DIR.task }) + if (upload.success) { + const insertToContainer = await prisma.containerFileDivision.create({ + data: { + idDivision: String(dataProject?.idDivision), + name: fName, + extension: String(fExt), + idStorage: upload.data.id + }, + select: { + id: true + } + }) + + const dataFile = { + idProject: id, + idDivision: dataProject?.idDivision, + idFile: insertToContainer.id, + createdBy: user.id, + } + + fileFix.push(dataFile) + } + } + } + + const insertFile = await prisma.divisionProjectFile.createMany({ + data: fileFix + }) + } + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User menambahkan file tugas divisi baru', table: 'divisionProject', data: id }) + return NextResponse.json({ success: true, message: "Berhasil menambahkan file" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal menambahkan filae, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + + +// CEK FILE TASK DIVISI APAKAH PERNAH DIUPLOAD PADA TASK YG SAMA +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + + const { id } = context.params; + const body = await request.formData() + const file = body.get("file") as File + const fileName = file.name + + const dataCek = await prisma.divisionProject.count({ + where: { + id: id, + }, + }); + + if (dataCek == 0) { + return NextResponse.json( + { + success: false, + message: "Upload file gagal, data tugas tidak ditemukan", + }, + { status: 404 } + ); + } + + const dataTaskFile = await prisma.divisionProjectFile.findMany({ + where: { + idProject: id, + isActive: true + }, + select: { + ContainerFileDivision: { + select: { + name: true, + extension: true + } + } + } + }) + + const dataOmit = dataTaskFile.map((v: any) => ({ + ..._.omit(v, ["ContainerFileDivision"]), + file: v.ContainerFileDivision.name + '.' + v.ContainerFileDivision.extension, + })) + + const cek = dataOmit.some((i: any) => i.file == fileName) + + + if (cek) { + return NextResponse.json({ success: false, message: "File sudah pernah diupload" }, { status: 400 }); + } else { + return NextResponse.json({ success: true, message: "Cek berhasil" }, { status: 200 }); + } + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Upload file gagal, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/task/route.ts b/src/app/api/task/route.ts new file mode 100644 index 0000000..3093cf6 --- /dev/null +++ b/src/app/api/task/route.ts @@ -0,0 +1,203 @@ +import { DIR, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _, { ceil } from "lodash"; +import { NextResponse } from "next/server"; +import path from "path"; +import fs from "fs"; +import moment from "moment"; +import { createLogUser } from "@/module/user"; + + +// GET ALL DATA TUGAS DIVISI +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const name = searchParams.get('search'); + const divisi = searchParams.get('division'); + const status = searchParams.get('status'); + + const cek = await prisma.division.count({ + where: { + isActive: true, + id: String(divisi) + } + }) + + if (cek == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan", }, { status: 404 }); + } + + const data = await prisma.divisionProject.findMany({ + where: { + isActive: true, + idDivision: String(divisi), + status: (status == "0" || status == "1" || status == "2" || status == "3") ? Number(status) : 0, + title: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + } + }, + select: { + id: true, + title: true, + desc: true, + status: true, + DivisionProjectTask: { + where: { + isActive: true + }, + select: { + title: true, + status: true + } + }, + DivisionProjectMember: { + where: { + isActive: true + }, + select: { + idUser: true + } + } + } + }); + + const formatData = data.map((v: any) => ({ + ..._.omit(v, ["DivisionProjectTask", "DivisionProjectMember"]), + progress: ceil((v.DivisionProjectTask.filter((i: any) => i.status == 1).length * 100) / v.DivisionProjectTask.length), + member: v.DivisionProjectMember.length + })) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: formatData, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// CREATE PROJECT TASK DIVISION +export async function POST(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + + const body = await request.formData() + const dataBody = body.get("data") + const cekFile = body.has("file0") + + const { title, task, member, idDivision } = JSON.parse(dataBody as string) + + const cek = await prisma.division.count({ + where: { + isActive: true, + id: idDivision + } + }) + + if (cek == 0) { + return NextResponse.json({ success: false, message: "Gagal, data divisi tidak ditemukan", }, { status: 404 }); + } + + const data = await prisma.divisionProject.create({ + data: { + idDivision: idDivision, + title: title, + desc: '' + }, + select: { + id: true + } + }) + + + if (task.length > 0) { + const dataTask = task.map((v: any) => ({ + ..._.omit(v, ["dateStart", "dateEnd", "title"]), + idDivision: idDivision, + idProject: data.id, + title: v.title, + dateStart: new Date(moment(v.dateStart).format('YYYY-MM-DD')), + dateEnd: new Date(moment(v.dateEnd).format('YYYY-MM-DD')), + })) + + const insertTask = await prisma.divisionProjectTask.createMany({ + data: dataTask + }) + } + + if (member.length > 0) { + const dataMember = member.map((v: any) => ({ + ..._.omit(v, ["idUser", "name", "img"]), + idDivision: idDivision, + idProject: data.id, + idUser: v.idUser, + })) + + const insertMember = await prisma.divisionProjectMember.createMany({ + data: dataMember + }) + } + + + + let fileFix: any[] = [] + + if (cekFile) { + for (var pair of body.entries()) { + if (String(pair[0]).substring(0, 4) == "file") { + const file = body.get(pair[0]) as File + const fExt = file.name.split(".").pop() + const fName = file.name.replace("." + fExt, "") + + const upload = await funUploadFile({ file: file, dirId: DIR.task }) + if (upload.success) { + const insertToContainer = await prisma.containerFileDivision.create({ + data: { + idDivision: idDivision, + name: fName, + extension: String(fExt), + idStorage: upload.data.id + }, + select: { + id: true + } + }) + + const dataFile = { + idProject: data.id, + idDivision: idDivision, + idFile: insertToContainer.id, + createdBy: user.id, + } + + fileFix.push(dataFile) + } + } + } + + const insertFile = await prisma.divisionProjectFile.createMany({ + data: fileFix + }) + } + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat tugas divisi baru', table: 'divisionProject', data: data.id }) + + + return NextResponse.json({ success: true, message: "Berhasil membuat tugas divisi" }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal membuat tugas divisi, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/user/[id]/route.ts b/src/app/api/user/[id]/route.ts new file mode 100644 index 0000000..7936848 --- /dev/null +++ b/src/app/api/user/[id]/route.ts @@ -0,0 +1,235 @@ +import { DIR, funDeleteFile, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _, { update } from "lodash"; +import { NextResponse } from "next/server"; +import path from "path"; +import fs from "fs"; + +// GET ONE MEMBER / USER +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const users = await prisma.user.findUnique({ + where: { + id: id, + }, + select: { + id: true, + nik: true, + name: true, + phone: true, + email: true, + gender: true, + img: true, + idGroup: true, + isActive: true, + idPosition: true, + UserRole: { + select: { + name: true, + id: true + } + }, + Position: { + select: { + name: true, + id: true + }, + }, + Group: { + select: { + name: true, + id: true + }, + }, + }, + }); + + const { ...userData } = users; + const group = users?.Group.name + const position = users?.Position.name + const idUserRole = users?.UserRole.id + const phone = users?.phone.substr(2) + + const result = { ...userData, group, position, idUserRole, phone }; + + const omitData = _.omit(result, ["Group", "Position", "UserRole"]); + + + + return NextResponse.json( + { + success: true, + message: "Berhasil mendapatkan anggota", + data: omitData, + }, + { status: 200 } + ); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan member, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// DELETE / ACTIVE & NON ACTIVE MEMBER / USER +export async function DELETE(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + const { isActive } = (await request.json()); + const data = await prisma.user.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan anggota, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const result = await prisma.user.update({ + where: { + id: id, + }, + data: { + isActive: !isActive, + }, + }); + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate status anggota', table: 'user', data: id }) + + return NextResponse.json( + { + success: true, + message: "Berhasil mengupdate status anggota", + result, + }, + { status: 200 } + ); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengupdate status anggota, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// UPDATE MEMBER +export async function PUT(request: Request, context: { params: { id: string } }) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const { id } = context.params; + + const body = await request.formData() + const file = body.get("file") as File + const data = body.get("data") + const { + name, + email, + phone, + nik, + gender, + idGroup, + idPosition, + idUserRole + } = JSON.parse(data as string) + + const cekNIK = await prisma.user.count({ + where: { + nik: nik, + NOT: { + id: id + } + }, + }); + + const cekEmail = await prisma.user.count({ + where: { + email: email, + NOT: { + id: id + } + }, + }); + + const cekPhone = await prisma.user.count({ + where: { + phone: "62" + phone, + NOT: { + id: id + } + }, + }); + + if (cekNIK == 0 && cekEmail == 0 && cekPhone == 0) { + const updates = await prisma.user.update({ + where: { + id: id + }, + data: { + nik: nik, + name: name, + phone: "62" + phone, + email: email, + gender: gender, + idGroup: idGroup, + idPosition: idPosition, + idUserRole: idUserRole, + }, + select: { + img: true + } + }); + + if (String(file) != "undefined" && String(file) != "null") { + const fExt = file.name.split(".").pop() + const fileName = id + '.' + fExt; + const newFile = new File([file], fileName, { type: file.type }); + await funDeleteFile({ fileId: String(updates.img) }) + const upload = await funUploadFile({ file: newFile, dirId: DIR.user }) + await prisma.user.update({ + where: { + id: id + }, + data: { + img: upload.data.id + } + }) + } + + // create log user + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data anggota', table: 'user', data: user.id }) + + return Response.json( + { success: true, message: "Sukses update anggota" }, + { status: 200 } + ); + } else { + return Response.json({ success: false, message: "Anggota sudah ada" }, { status: 400 }); + } + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mengupdate data anggota, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/user/profile/route.ts b/src/app/api/user/profile/route.ts new file mode 100644 index 0000000..2a0ad66 --- /dev/null +++ b/src/app/api/user/profile/route.ts @@ -0,0 +1,146 @@ +import { DIR, funDeleteFile, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import _ from "lodash"; +import { NextResponse } from "next/server"; +import path from "path"; +import fs from "fs"; +import { createLogUser } from "@/module/user"; + + +// GET PROFILE BY COOKIES +export async function GET(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const data = await prisma.user.findUnique({ + where: { + id: user.id + }, + select: { + id: true, + name: true, + email: true, + phone: true, + nik: true, + gender: true, + idGroup: true, + idPosition: true, + img: true, + Group: { + select: { + name: true + } + }, + Position: { + select: { + name: true + } + } + } + }) + const { ...userData } = data; + const group = data?.Group.name + const position = data?.Position.name + const phone = data?.phone.substr(2) + + const omitData = _.omit(data, ["Group", "Position", "phone"]) + + const result = { ...userData, group, position, phone }; + + return NextResponse.json({ success: true, data: result }); + } catch (error) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + +} + +// UPDATE PROFILE BY COOKIES +export async function PUT(request: Request) { + try { + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + + const body = await request.formData() + const file = body.get("file") as File; + const data = body.get("data"); + + const { name, email, phone, nik, gender } = JSON.parse(data as string) + + const cekNIK = await prisma.user.count({ + where: { + nik: nik, + NOT: { + id: user.id + } + }, + }); + + const cekEmail = await prisma.user.count({ + where: { + email: email, + NOT: { + id: user.id + } + }, + }); + + const cekPhone = await prisma.user.count({ + where: { + phone: "62" + phone, + NOT: { + id: user.id + } + }, + }); + + if (cekNIK > 0 || cekEmail > 0 || cekPhone > 0) { + return NextResponse.json({ success: false, message: "Gagal ubah profile, NIK/email/phone sudah terdaftar" }, { status: 401 }); + } + + const update = await prisma.user.update({ + where: { + id: user.id + }, + data: { + name: name, + email: email, + phone: "62" + phone, + nik: nik, + gender: gender + }, + select: { + img: true + } + }) + + if (String(file) != "undefined" && String(file) != "null") { + const fExt = file.name.split(".").pop() + const fileName = user.id + '.' + fExt; + const newFile = new File([file], fileName, { type: file.type }); + await funDeleteFile({ fileId: String(update.img) }) + const upload = await funUploadFile({ file: newFile, dirId: DIR.user }) + if (upload.success) { + await prisma.user.update({ + where: { + id: user.id + }, + data: { + img: upload.data.id + } + }) + } + + } + + const log = await createLogUser({ act: 'UPDATE', desc: 'User mengupdate data profile', table: 'user', data: user.id }) + + return NextResponse.json({ success: true, message: "Berhasil ubah profile" }); + } catch (error) { + return NextResponse.json({ success: false, message: "Gagal ubah profile" }, { status: 500 }); + } +} + diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts new file mode 100644 index 0000000..ee9c44e --- /dev/null +++ b/src/app/api/user/route.ts @@ -0,0 +1,169 @@ +import { DIR, funUploadFile, prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import { NextResponse } from "next/server"; +import path from "path"; +import fs from "fs"; + +// GET ALL MEMBER / USER +export async function GET(request: Request) { + try { + let fixGroup + const { searchParams } = new URL(request.url); + const name = searchParams.get('search') + const idGroup = searchParams.get("group"); + const active = searchParams.get("active"); + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + if (idGroup == "null" || idGroup == undefined) { + fixGroup = user.idGroup + } else { + fixGroup = idGroup + } + + const filter = await prisma.group.findUnique({ + where: { + id: fixGroup + }, + select: { + id: true, + name: true + } + }) + + + const users = await prisma.user.findMany({ + where: { + isActive: active == 'false' ? false : true, + idGroup: String(fixGroup), + name: { + contains: (name == undefined || name == null) ? "" : name, + mode: "insensitive", + } + }, + select: { + id: true, + isActive: true, + nik: true, + name: true, + phone: true, + email: true, + gender: true, + img: true, + Position: { + select: { + name: true, + }, + }, + Group: { + select: { + name: true, + }, + }, + }, + }); + + const allData = users.map((v: any) => ({ + ..._.omit(v, ["Group", "Position"]), + group: v.Group.name, + position: v.Position.name + })) + + return NextResponse.json({ success: true, message: "Berhasil member", data: allData, filter }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan member, coba lagi nanti", reason: (error as Error).message, }, { status: 500 }); + } +} + + +// CREATE MEMBER / USER +export async function POST(request: Request) { + try { + + const user = await funGetUserByCookies() + if (user.id == undefined) { + return NextResponse.json({ success: false, message: "Anda harus login untuk mengakses ini" }, { status: 401 }); + } + const body = await request.formData() + const data = JSON.parse(body.get("data") as string) + const file = body.get("file") as File + const village = String(user.idVillage) + + let groupFix = data.idGroup + + if (groupFix == null || groupFix == undefined || groupFix == "") { + groupFix = user.idGroup + } + + const cekNIK = await prisma.user.count({ + where: { + nik: data.nik + }, + }); + + const cekEmail = await prisma.user.count({ + where: { + email: data.email + }, + }); + + const cekPhone = await prisma.user.count({ + where: { + phone: data.phone + }, + }); + + + if (cekNIK == 0 && cekEmail == 0 && cekPhone == 0) { + const users = await prisma.user.create({ + data: { + nik: data.nik, + name: data.name, + phone: data.phone, + email: data.email, + gender: data.gender, + idGroup: groupFix, + idVillage: village, + idPosition: data.idPosition, + idUserRole: data.idUserRole, + }, + select: { + id: true + }, + }); + + if (String(file) != "undefined" && String(file) != "null") { + + const fExt = file.name.split(".").pop() + const fileName = user.id + '.' + fExt; + const newFile = new File([file], fileName, { type: file.type }); + const upload = await funUploadFile({ file: newFile, dirId: DIR.user }) + if (upload.success) { + await prisma.user.update({ + where: { + id: users.id + }, + data: { + img: upload.data.id + } + }) + } + } + + // create log user + const log = await createLogUser({ act: 'CREATE', desc: 'User membuat data user baru', table: 'user', data: users.id }) + + return Response.json({ success: true, message: 'Sukses membuat user' }, { status: 200 }); + } else { + return Response.json({ success: false, message: "User sudah ada" }, { status: 400 }); + } + + } catch (error) { + console.error(error); + return Response.json({ success: false, message: "Internal Server Error" }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/village/get/route.ts b/src/app/api/village/get/route.ts new file mode 100644 index 0000000..e95887e --- /dev/null +++ b/src/app/api/village/get/route.ts @@ -0,0 +1,5 @@ +import { apiViilage } from "@/module/village"; + +export async function GET(req: Request) { + return apiViilage(req, "GET"); +} diff --git a/src/app/api/village/post/route.ts b/src/app/api/village/post/route.ts new file mode 100644 index 0000000..1c1e481 --- /dev/null +++ b/src/app/api/village/post/route.ts @@ -0,0 +1,5 @@ +import { apiViilage } from "@/module/village"; + +export async function POST(req: Request) { + return apiViilage(req, "POST"); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 65c0815..04459c5 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -10,6 +10,9 @@ import { WARNA } from "@/module/_global"; import { Lato } from "next/font/google"; import '@mantine/carousel/styles.css'; import { Toaster } from 'react-hot-toast'; +import '@mantine/dates/styles.css'; +import '@mantine/notifications/styles.css'; +import { Notifications } from '@mantine/notifications' export const metadata = { title: "SISTEM DESA MANDIRI", @@ -29,12 +32,14 @@ export default function RootLayout({ return ( + + diff --git a/src/app/page.tsx b/src/app/page.tsx index fcd135f..4bec541 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,10 +1,16 @@ -import { ViewLogin } from "@/module/auth"; -import { Box, Image, rem, Stack, Text } from "@mantine/core"; +import { pwd_key_config } from "@/module/_global"; +import { funDetectCookies, ViewLogin } from "@/module/auth"; +import { unsealData } from "iron-session"; +import _ from "lodash"; +import { cookies } from "next/headers"; +import { redirect } from "next/navigation"; -export default function Home() { +export default async function Home() { + const cookies = await funDetectCookies() + if (cookies) return redirect('/home') return ( <> - + ); } diff --git a/src/module/_global/bin/prisma.ts b/src/module/_global/bin/prisma.ts new file mode 100644 index 0000000..b9e55ff --- /dev/null +++ b/src/module/_global/bin/prisma.ts @@ -0,0 +1,17 @@ +import { PrismaClient } from '@prisma/client' + +const prismaClientSingleton = () => { + return new PrismaClient() +} + +type PrismaClientSingleton = ReturnType + +const globalForPrisma = globalThis as unknown as { + prisma: PrismaClientSingleton | undefined +} + +const prisma = globalForPrisma.prisma ?? prismaClientSingleton() + +export default prisma + +if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma \ No newline at end of file diff --git a/src/module/_global/bin/type_global.ts b/src/module/_global/bin/type_global.ts new file mode 100644 index 0000000..c85b9ea --- /dev/null +++ b/src/module/_global/bin/type_global.ts @@ -0,0 +1,8 @@ +export interface IGlobalTema { + utama: string + bgUtama: string + bgIcon: string + bgFiturHome: string + bgFiturDivisi: string + bgTotalKegiatan: string +} \ No newline at end of file diff --git a/src/module/_global/bin/val_global.ts b/src/module/_global/bin/val_global.ts new file mode 100644 index 0000000..b6752cc --- /dev/null +++ b/src/module/_global/bin/val_global.ts @@ -0,0 +1,22 @@ +import { hookstate } from "@hookstate/core" +import { IGlobalTema } from './type_global'; + +export const pwd_key_config = "fchgvjknlmdfnbvghhujlaknsdvjbhknlkmsdbdyu567t8y9u30r4587638y9uipkoeghjvuyi89ipkoefmnrjbhtiu4or9ipkoemnjfbhjiuoijdklnjhbviufojkejnshbiuojijknehgruyu" +export const globalRole = hookstate('') +export const DIR = { + task: "cm0xhcqf0000dacbbixjb09yn", + project: "cm0xhc9sv000bacbb7rfikw1k", + document: "cm0xhbkf50009acbbtw03qo4l", + village: "cm0xhb91o0007acbbkx8rk8hj", + user: "cm0x8dbwn0005bp5tgmfcthzw", + +} + +export const TEMA = hookstate({ + utama: "#19345E", + bgUtama: "#F4F9FD", + bgIcon: "#384288", + bgFiturHome: "#FCAA4B", + bgFiturDivisi: "#FCAA4B", + bgTotalKegiatan: "#DCEED8" +}) \ No newline at end of file diff --git a/src/module/_global/components/pdf_viewer.tsx b/src/module/_global/components/pdf_viewer.tsx new file mode 100644 index 0000000..d1f0c3d --- /dev/null +++ b/src/module/_global/components/pdf_viewer.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist'; +import { Image, Skeleton, Stack } from '@mantine/core'; + +GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.6.82/pdf.worker.min.mjs'; + +const PdfToImage = ({ md }: { md: string }) => { + const [images, setImages] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (typeof window !== 'undefined') { + const renderPages = async () => { + try { + const loadingTask = getDocument(md); // Menggunakan md sebagai URL PDF + const pdf = await loadingTask.promise; + const numPages = pdf.numPages; + const imagePromises: Promise[] = []; + + for (let pageNum = 1; pageNum <= numPages; pageNum++) { + const renderPage = async (pageNum: number): Promise => { + const page = await pdf.getPage(pageNum); + const viewport = page.getViewport({ scale: 2.0 }); + + // Buat elemen canvas + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + if (context) { + canvas.width = viewport.width; + canvas.height = viewport.height; + + // Render halaman PDF ke dalam canvas + const renderContext = { + canvasContext: context, + viewport: viewport, + }; + await page.render(renderContext).promise; + + // Konversi canvas ke gambar (data URL) + return canvas.toDataURL('image/png'); + } + return ''; + }; + + imagePromises.push(renderPage(pageNum)); + } + + const imageSrcs = await Promise.all(imagePromises); + setImages(imageSrcs); + } catch (error) { + console.error('Error rendering PDF to images:', error); + } finally { + setLoading(false); + } + }; + + renderPages(); + } + }, [md]); + + return ( + + {loading + ? + : images.map((src, index) => ( + {`Page + ))} + + ); +}; + +function CustomLoading() { + return ( + + {[...Array(3)].map((_, index) => ( + + ))} + + ); +} + +export default PdfToImage \ No newline at end of file diff --git a/src/module/_global/components/skeleton_detail_discussion_comment.tsx b/src/module/_global/components/skeleton_detail_discussion_comment.tsx new file mode 100644 index 0000000..fdc1b79 --- /dev/null +++ b/src/module/_global/components/skeleton_detail_discussion_comment.tsx @@ -0,0 +1,32 @@ +import { useHookstate } from "@hookstate/core"; +import { ActionIcon, Box, Divider, Group, Skeleton } from "@mantine/core"; +import { TEMA } from "../bin/val_global"; + +export default function SkeletonDetailDiscussionComment() { + const tema = useHookstate(TEMA) + return ( + <> + + + + + + + + + + + + + + + + + ); +} diff --git a/src/module/_global/components/skeleton_detail_discussion_member.tsx b/src/module/_global/components/skeleton_detail_discussion_member.tsx new file mode 100644 index 0000000..ba93282 --- /dev/null +++ b/src/module/_global/components/skeleton_detail_discussion_member.tsx @@ -0,0 +1,41 @@ +import { useHookstate } from '@hookstate/core'; +import { ActionIcon, Box, Flex, Group, Skeleton } from '@mantine/core'; +import React from 'react'; +import { TEMA } from '../bin/val_global'; + +export default function SkeletonDetailDiscussionMember() { + const tema = useHookstate(TEMA) + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + ); +} + diff --git a/src/module/_global/components/skeleton_detail_list_tugas_task.tsx b/src/module/_global/components/skeleton_detail_list_tugas_task.tsx new file mode 100644 index 0000000..a6eee3f --- /dev/null +++ b/src/module/_global/components/skeleton_detail_list_tugas_task.tsx @@ -0,0 +1,63 @@ +import { useHookstate } from '@hookstate/core'; +import { Box, Center, Grid, Group, SimpleGrid, Skeleton } from '@mantine/core'; +import React from 'react'; +import { TEMA } from '../bin/val_global'; + +export default function SkeletonDetailListTugasTask() { + const tema = useHookstate(TEMA) + return ( + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/module/_global/components/skeleton_detail_profile.tsx b/src/module/_global/components/skeleton_detail_profile.tsx new file mode 100644 index 0000000..097cccd --- /dev/null +++ b/src/module/_global/components/skeleton_detail_profile.tsx @@ -0,0 +1,89 @@ +import { useHookstate } from '@hookstate/core'; +import { ActionIcon, Box, Group, Skeleton } from '@mantine/core'; +import React from 'react'; +import { TEMA } from '../bin/val_global'; + +export default function SkeletonDetailProfile() { + const tema = useHookstate(TEMA) + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + diff --git a/src/module/_global/components/skeleton_single.tsx b/src/module/_global/components/skeleton_single.tsx new file mode 100644 index 0000000..6486998 --- /dev/null +++ b/src/module/_global/components/skeleton_single.tsx @@ -0,0 +1,36 @@ +import { useHookstate } from '@hookstate/core'; +import { ActionIcon, Box, Group, Skeleton } from '@mantine/core'; +import React from 'react'; +import { TEMA } from '../bin/val_global'; + +export default function SkeletonSingle() { + const tema = useHookstate(TEMA) + return ( + + + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/module/_global/components/wrap_layout.tsx b/src/module/_global/components/wrap_layout.tsx new file mode 100644 index 0000000..ab53be2 --- /dev/null +++ b/src/module/_global/components/wrap_layout.tsx @@ -0,0 +1,19 @@ +'use client' +import { useHookstate } from "@hookstate/core"; +import { globalRole } from "../bin/val_global"; +import { useShallowEffect } from "@mantine/hooks"; +import { useEffect } from "react"; + +export default function WrapLayout({ children, role }: { children: React.ReactNode, role: any }) { + const roleLogin = useHookstate(globalRole) + + useEffect(() => { + roleLogin.set(role) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [role]) + return ( + <> + {children} + + ); +} \ No newline at end of file diff --git a/src/module/_global/fun/WARNA.ts b/src/module/_global/fun/WARNA.ts index 38580ae..be463fc 100644 --- a/src/module/_global/fun/WARNA.ts +++ b/src/module/_global/fun/WARNA.ts @@ -1,6 +1,10 @@ export const WARNA = { bgWhite: "#F4F9FD", - biruTua: "#19345E", - bgIcon: "#384288", - borderOrange: "#FCAA4B" + // biruTua: "#19345E", + biruTua: "#508D4E", + // bgIcon: "#384288", + bgIcon: "#3C8754", + borderOrange: "#FCAA4B", + bgHijauMuda: "#DCEED8", + borderBiruMuda: "#9EBDED" } \ No newline at end of file diff --git a/src/module/_global/fun/copy_file.ts b/src/module/_global/fun/copy_file.ts new file mode 100644 index 0000000..eabce8f --- /dev/null +++ b/src/module/_global/fun/copy_file.ts @@ -0,0 +1,22 @@ +export async function funCopyFile({ fileId, dirId }: { fileId: string, dirId: string }) { + try { + const res = await fetch(`https://wibu-storage.wibudev.com/api/files/copy/${dirId}/${dirId}`, { + method: "POST", + body: JSON.stringify({ fileId: fileId }), + headers: { + Authorization: `Bearer ${process.env.WS_APIKEY}` + } + }); + + if (res.ok) { + const hasil = await res.json() + return { success: true, data: hasil.data } + } else { + const errorText = await res.json(); + return { success: false, data: {} } + } + } catch (error) { + console.error("Copy error:", error); + return { success: false, data: {} } + } +} \ No newline at end of file diff --git a/src/module/_global/fun/delete_file.ts b/src/module/_global/fun/delete_file.ts new file mode 100644 index 0000000..a4ec2c6 --- /dev/null +++ b/src/module/_global/fun/delete_file.ts @@ -0,0 +1,21 @@ +export async function funDeleteFile({ fileId }: { fileId: string }) { + try { + const res = await fetch(`https://wibu-storage.wibudev.com/api/files/${fileId}/delete`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${process.env.WS_APIKEY}` + } + }); + + if (res.ok) { + const hasil = await res.json() + return { success: true } + } else { + const errorText = await res.json(); + return { success: false } + } + } catch (error) { + return { success: false } + console.error("Upload error:", error); + } +} \ No newline at end of file diff --git a/src/module/_global/fun/upload_file.ts b/src/module/_global/fun/upload_file.ts new file mode 100644 index 0000000..49ff249 --- /dev/null +++ b/src/module/_global/fun/upload_file.ts @@ -0,0 +1,26 @@ +export async function funUploadFile({ file, dirId }: { file: File, dirId: string }) { + const formData = new FormData(); + formData.append("file", file); + formData.append("dirId", dirId); + + try { + const res = await fetch("https://wibu-storage.wibudev.com/api/upload", { + method: "POST", + body: formData, + headers: { + Authorization: `Bearer ${process.env.WS_APIKEY}` + } + }); + + if (res.ok) { + const hasil = await res.json() + return { success: true, data: hasil.data } + } else { + const errorText = await res.text(); + return { success: false, data: {} } + } + } catch (error) { + console.error("Upload error:", error); + return { success: false, data: {} } + } +} \ No newline at end of file diff --git a/src/module/_global/index.ts b/src/module/_global/index.ts index 9a9f104..bcdf19e 100644 --- a/src/module/_global/index.ts +++ b/src/module/_global/index.ts @@ -1,17 +1,46 @@ +import prisma from "./bin/prisma"; +import { DIR, globalRole, pwd_key_config, TEMA } from "./bin/val_global"; +import SkeletonDetailDiscussionComment from "./components/skeleton_detail_discussion_comment"; +import SkeletonDetailDiscussionMember from "./components/skeleton_detail_discussion_member"; +import SkeletonDetailListTugasTask from "./components/skeleton_detail_list_tugas_task"; +import SkeletonDetailProfile from "./components/skeleton_detail_profile"; +import SkeletonSingle from "./components/skeleton_single"; +import WrapLayout from "./components/wrap_layout"; +import { funCopyFile } from "./fun/copy_file"; +import { funDeleteFile } from "./fun/delete_file"; +import { funUploadFile } from "./fun/upload_file"; import { WARNA } from "./fun/WARNA"; import LayoutDrawer from "./layout/layout_drawer"; import LayoutIconBack from "./layout/layout_icon_back"; import LoadingPage from "./layout/layout_loading_page"; import LayoutLogin from "./layout/layout_login"; +import LayoutModalViewFile from "./layout/layout_modal_view_file"; import LayoutNavbarHome from "./layout/layout_navbar_home"; -import { isDrawer } from "./val/isDrawer"; -import { isModal } from "./val/isModal"; +import LayoutNavbarNew from "./layout/layout_navbar_new"; +import NoZoom from "./layout/no_zoom"; +import ViewFilter from "./view/view_filter"; -export { WARNA } -export { LayoutLogin } -export { LayoutNavbarHome } -export { LayoutIconBack } -export { LoadingPage } -export { LayoutDrawer } -export { isDrawer } -export { isModal } \ No newline at end of file +export { WARNA }; +export { LayoutLogin }; +export { LayoutNavbarHome }; +export { LayoutIconBack }; +export { LoadingPage }; +export { LayoutDrawer }; +export { LayoutNavbarNew }; +export { ViewFilter }; +export { prisma }; +export { pwd_key_config }; +export { SkeletonSingle } +export { SkeletonDetailDiscussionComment } +export { SkeletonDetailDiscussionMember } +export { SkeletonDetailProfile } +export { SkeletonDetailListTugasTask } +export { LayoutModalViewFile } +export { globalRole } +export { WrapLayout } +export { NoZoom } +export { funUploadFile } +export { funDeleteFile } +export { DIR } +export { TEMA } +export { funCopyFile } diff --git a/src/module/_global/layout/layout_drawer.tsx b/src/module/_global/layout/layout_drawer.tsx index df7b558..31a31f2 100644 --- a/src/module/_global/layout/layout_drawer.tsx +++ b/src/module/_global/layout/layout_drawer.tsx @@ -1,15 +1,20 @@ import { Box, Drawer, Text } from '@mantine/core'; import React from 'react'; import { WARNA } from '../fun/WARNA'; +import { useHookstate } from '@hookstate/core'; +import { TEMA } from '../bin/val_global'; export default function LayoutDrawer({ opened, onClose, title, children, size }: { children: React.ReactNode, opened: boolean, size?: string, onClose: () => void, title: React.ReactNode }) { + const tema = useHookstate(TEMA) return ( - {title}} onClose={onClose} position={"bottom"} size={(size == 'lg') ? '80%' : '30%'} + {title}} onClose={onClose} position={"bottom"} size={(size == 'lg') ? '80%' : '40%'} styles={{ content: { backgroundColor: "white", borderRadius: "20px 20px 0px 0px", + maxWidth: 550, + margin: "0 auto", }, }} > diff --git a/src/module/_global/layout/layout_icon_back.tsx b/src/module/_global/layout/layout_icon_back.tsx index f3a6dc3..d4658fa 100644 --- a/src/module/_global/layout/layout_icon_back.tsx +++ b/src/module/_global/layout/layout_icon_back.tsx @@ -5,19 +5,22 @@ import React from 'react'; import { HiChevronLeft } from 'react-icons/hi2'; import { WARNA } from '../fun/WARNA'; import _ from 'lodash'; +import { useHookstate } from '@hookstate/core'; +import { TEMA } from '../bin/val_global'; function LayoutIconBack({ back }: { back?: string }) { const router = useRouter() + const tema = useHookstate(TEMA) return ( { - if (!_.isUndefined(back) && !_.isNull(back)) { + if (!_.isUndefined(back) && !_.isNull(back) && !_.isEmpty(back)) { return router.push(back) } else { return router.back() } - }} bg={WARNA.bgIcon} size="lg" radius="lg" aria-label="Settings"> + }} bg={tema.get().bgIcon} size="lg" radius="lg" aria-label="Settings"> diff --git a/src/module/_global/layout/layout_login.tsx b/src/module/_global/layout/layout_login.tsx index 806768f..127541e 100644 --- a/src/module/_global/layout/layout_login.tsx +++ b/src/module/_global/layout/layout_login.tsx @@ -1,18 +1,21 @@ import { Image, rem, Stack, Title } from "@mantine/core"; import React from "react"; import { WARNA } from "../fun/WARNA"; +import { useHookstate } from "@hookstate/core"; +import { TEMA } from "../bin/val_global"; export default function LayoutLogin({ children, }: { children: React.ReactNode; -}) { + }) { + const tema = useHookstate(TEMA) return ( <> logo - - PERBEKAL DARMASABA + <Title c={tema.get().utama} order={4}> + PERBEKEL DARMASABA {children} diff --git a/src/module/_global/layout/layout_modal.tsx b/src/module/_global/layout/layout_modal.tsx index 2c60bed..d364584 100644 --- a/src/module/_global/layout/layout_modal.tsx +++ b/src/module/_global/layout/layout_modal.tsx @@ -1,11 +1,10 @@ -import { useHookstate } from '@hookstate/core'; -import { Box, Button, Flex, Modal, Text } from '@mantine/core'; -import React from 'react'; +import { Box, Button, Flex, Group, Modal, SimpleGrid, Text } from '@mantine/core'; +import React, { useState } from 'react'; import { BsQuestionCircleFill } from 'react-icons/bs'; -import { isModal } from '../val/isModal'; +import { WARNA } from '../fun/WARNA'; export default function LayoutModal({ opened, onClose, description, onYes }: { opened: boolean, onClose: () => void, description: string, onYes: (val: boolean) => void }) { - const openModal = useHookstate(isModal) + const [isValModal, setValModal] = useState(opened) return ( {description} - - - - + + + + ); } diff --git a/src/module/_global/layout/layout_modal_view_file.tsx b/src/module/_global/layout/layout_modal_view_file.tsx new file mode 100644 index 0000000..4565f7f --- /dev/null +++ b/src/module/_global/layout/layout_modal_view_file.tsx @@ -0,0 +1,75 @@ +'use client' +import { Box, Button, Flex, Image, Modal, rem } from '@mantine/core'; +import dynamic from 'next/dynamic'; +import React, { useState } from 'react'; +const PdfToImage = dynamic(() => import('./../components/pdf_viewer').then((mod) => mod.default), { ssr: false }); + +export default function LayoutModal({ opened, onClose, extension, fitur, file }: { opened: boolean, onClose: () => void, extension: string, fitur: string, file: string }) { + const [isValModal, setValModal] = useState(opened) + const [zoom, setZoom] = useState(1) + const filePdf = '/file/' + fitur + '/' + file + + const handleZoomIn = () => { + setZoom(zoom + 0.1) + } + + const handleZoomOut = () => { + setZoom(zoom - 0.1) + } + + return ( + + + + + + + + +
+ { + extension === 'pdf' ? : + {file} + } +
+
+
+ ); +} + diff --git a/src/module/_global/layout/layout_navbar_home.tsx b/src/module/_global/layout/layout_navbar_home.tsx index fa35cd1..35d401b 100644 --- a/src/module/_global/layout/layout_navbar_home.tsx +++ b/src/module/_global/layout/layout_navbar_home.tsx @@ -1,14 +1,19 @@ +"use client" import { Box, Grid, Group } from '@mantine/core'; import React from 'react'; import { WARNA } from '../fun/WARNA'; +import { useHookstate } from '@hookstate/core'; +import { TEMA } from '../bin/val_global'; export const LayoutNavbarHome = ({ children }: { children: React.ReactNode }) => { + const tema = useHookstate(TEMA) return ( - {children} diff --git a/src/module/_global/layout/layout_navbar_new.tsx b/src/module/_global/layout/layout_navbar_new.tsx new file mode 100644 index 0000000..bd2059a --- /dev/null +++ b/src/module/_global/layout/layout_navbar_new.tsx @@ -0,0 +1,46 @@ +'use client' +import { ActionIcon, Box, Grid, Group, Text, Title } from '@mantine/core'; +import React from 'react'; +import { WARNA } from '../fun/WARNA'; +import LayoutIconBack from './layout_icon_back'; +import _ from 'lodash'; +import { useHookstate } from '@hookstate/core'; +import { TEMA } from '../bin/val_global'; + +export const LayoutNavbarNew = ({ back, state, title, menu }: { back?: string, title: string, menu: React.ReactNode, state?: React.ReactNode }) => { + const tema = useHookstate(TEMA) + return ( + + + { + state != undefined && + + {state} + + } + { + back != undefined && + + + + } + + {_.startCase(title)} + + + + {menu} + + + + + ); +} +export default LayoutNavbarNew \ No newline at end of file diff --git a/src/module/_global/layout/no_zoom.tsx b/src/module/_global/layout/no_zoom.tsx new file mode 100644 index 0000000..88fcfd3 --- /dev/null +++ b/src/module/_global/layout/no_zoom.tsx @@ -0,0 +1,10 @@ +// components/NoZoom.js +import Head from 'next/head'; + +export default function NoZoom() { + return ( + + + + ); +} diff --git a/src/module/_global/val/isDrawer.ts b/src/module/_global/val/isDrawer.ts deleted file mode 100644 index e8d4ad7..0000000 --- a/src/module/_global/val/isDrawer.ts +++ /dev/null @@ -1,4 +0,0 @@ -'use client' -import { hookstate } from '@hookstate/core'; - -export const isDrawer = hookstate(false) diff --git a/src/module/_global/val/isModal.ts b/src/module/_global/val/isModal.ts deleted file mode 100644 index df233bc..0000000 --- a/src/module/_global/val/isModal.ts +++ /dev/null @@ -1,4 +0,0 @@ -'use client' -import { hookstate } from '@hookstate/core'; - -export const isModal = hookstate(false) \ No newline at end of file diff --git a/src/module/_global/view/view_filter.tsx b/src/module/_global/view/view_filter.tsx new file mode 100644 index 0000000..b15fb72 --- /dev/null +++ b/src/module/_global/view/view_filter.tsx @@ -0,0 +1,106 @@ +'use client' +import { Box, Group, Divider, Button, Text, Skeleton, rem } from "@mantine/core"; +import { useState } from "react"; +import { FaCheck } from "react-icons/fa6"; +import { WARNA } from "../fun/WARNA"; +import LayoutNavbarNew from "../layout/layout_navbar_new"; +import { useRouter, useSearchParams } from "next/navigation"; +import { funGetAllGroup, IDataGroup } from "@/module/group"; +import { useShallowEffect } from "@mantine/hooks"; +import toast from "react-hot-toast"; +import { useHookstate } from "@hookstate/core"; +import { TEMA } from "../bin/val_global"; + +export default function ViewFilter({ linkFilter }: { linkFilter: string }) { + const [selectedFilter, setSelectedFilter] = useState(''); + const [checked, setChecked] = useState([]); + const [loading, setLoading] = useState(true) + const searchParams = useSearchParams() + const group = searchParams.get('group') + const tema = useHookstate(TEMA) + + + async function getAllGroupFilter() { + try { + setLoading(true) + const response = await funGetAllGroup('?active=true') + if (response.success) { + setChecked(response.data); + setLoading(false) + } else { + toast.error(response.message); + } + } catch (error) { + console.error(error); + toast.error("Gagal mendapatkan grup, coba lagi nanti"); + } finally { + setLoading(false); + } + } + + useShallowEffect(() => { + setSelectedFilter(group) + }, [group]); + + + useShallowEffect(() => { + getAllGroupFilter(); + }, []); + + const router = useRouter() + + return ( + + + + {loading ? ( + Array(5) + .fill(null) + .map((_, i) => ( + + + + + + )) + ) : + ( + checked.map((filter) => ( + + setSelectedFilter(filter.id)} + > + + {filter.name} + + {selectedFilter === filter.id && } + + + + )) + ) + } + + + + + + ); +} \ No newline at end of file diff --git a/src/module/announcement/component/detail_announcement.tsx b/src/module/announcement/component/detail_announcement.tsx deleted file mode 100644 index 47b74ea..0000000 --- a/src/module/announcement/component/detail_announcement.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Box, Group, Stack, Text } from "@mantine/core"; -import { BsCardText } from "react-icons/bs"; -import { TfiAnnouncement } from "react-icons/tfi"; - -export default function DetailAnnouncement() { - return ( - - - - - - Pengumuman Dinas - - - - Pengumuman agar menggunakan - - - - - - Anggota - - LPD - - - PKK - - - Karang Taruna - - - - - - ) -} \ No newline at end of file diff --git a/src/module/announcement/component/list_announcement.tsx b/src/module/announcement/component/list_announcement.tsx deleted file mode 100644 index c1317a6..0000000 --- a/src/module/announcement/component/list_announcement.tsx +++ /dev/null @@ -1,92 +0,0 @@ -'use client' -import { isDrawer, LayoutDrawer, WARNA } from '@/module/_global'; -import { ActionIcon, Anchor, Box, Group, Text, TextInput } from '@mantine/core'; -import React from 'react'; -import { TfiAnnouncement } from "react-icons/tfi"; -import { HiMagnifyingGlass } from 'react-icons/hi2'; -import { useRouter } from 'next/navigation'; - -const dataPengumuman = [ - { - id: 1, - name: 'Lembaga Pengkreditan Desa', - desc: 'Diharapkan semua untuk melakukan upacara ben...' - }, - { - id: 2, - name: 'Lembaga Pengkreditan Desa', - desc: 'Diharapkan semua untuk melakukan upacara ben...' - }, - { - id: 3, - name: 'Lembaga Pengkreditan Desa', - desc: 'Diharapkan semua untuk melakukan upacara ben...' - }, - { - id: 4, - name: 'Lembaga Pengkreditan Desa', - desc: 'Diharapkan semua untuk melakukan upacara ben...' - }, - { - id: 5, - name: 'Lembaga Pengkreditan Desa', - desc: 'Diharapkan semua untuk melakukan upacara ben...' - }, - { - id: 6, - name: 'Lembaga Pengkreditan Desa', - desc: 'Diharapkan semua untuk melakukan upacara ben...' - }, - { - id: 7, - name: 'Lembaga Pengkreditan Desa', - desc: 'Diharapkan semua untuk melakukan upacara ben...' - }, - { - id: 8, - name: 'Lembaga Pengkreditan Desa', - desc: 'Diharapkan semua untuk melakukan upacara ben...' - }, -] - -export default function ListAnnouncement() { - const router = useRouter() - return ( - - } - placeholder="Pencarian" - /> - {dataPengumuman.map((v, i) => { - return ( - { - router.push(`/announcement/${v.id}`) - }}> - - - - - - - - {v.name} - - - - ) - })} - - ); -} diff --git a/src/module/announcement/component/ui/drawer_detail_announcement.tsx b/src/module/announcement/component/ui/drawer_detail_announcement.tsx deleted file mode 100644 index 71824e5..0000000 --- a/src/module/announcement/component/ui/drawer_detail_announcement.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { isDrawer, LayoutDrawer, WARNA } from '@/module/_global'; -import { useHookstate } from '@hookstate/core'; -import { Box, Button, Center, Flex, Group, SimpleGrid, Stack, Text, TextInput } from '@mantine/core'; -import { useRouter } from 'next/navigation'; -import React, { useState } from 'react'; -import { FaPencil, FaTrash } from 'react-icons/fa6'; - -export default function DrawerDetailAnnouncement() { - const openDrawer = useHookstate(isDrawer) - const router = useRouter() - - function onCLose() { - openDrawer.set(false) - } - return ( - - - - - - - - - Hapus - - - - { - router.push('edit/123') - onCLose() - }}> - - - - - Edit - - - - - - ); -} diff --git a/src/module/announcement/component/ui/navbar_announcement.tsx b/src/module/announcement/component/ui/navbar_announcement.tsx deleted file mode 100644 index 782be67..0000000 --- a/src/module/announcement/component/ui/navbar_announcement.tsx +++ /dev/null @@ -1,37 +0,0 @@ -"use client" -import { isDrawer, LayoutDrawer, LayoutIconBack, LayoutNavbarHome, WARNA } from '@/module/_global'; -import { ActionIcon, Box, Drawer, Grid, Group, Text } from '@mantine/core'; -import { useRouter } from 'next/navigation'; -import React from 'react'; -import { HiMenu } from "react-icons/hi"; -import { useHookstate } from '@hookstate/core'; -import DrawerAnnouncement from './drawer_announcement'; - -export default function NavbarAnnouncement() { - const openDrawer = useHookstate(isDrawer) - return ( - <> - - - - - - - PENGUMUMAN - - - - openDrawer.set(true)} variant="light" bg={WARNA.bgIcon} size="lg" radius="lg" aria-label="Settings"> - - - - - - - openDrawer.set(false)}> - - - - ); -} - diff --git a/src/module/announcement/component/ui/navbar_create_announcement.tsx b/src/module/announcement/component/ui/navbar_create_announcement.tsx deleted file mode 100644 index cb25c3e..0000000 --- a/src/module/announcement/component/ui/navbar_create_announcement.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client' -import { LayoutIconBack, LayoutNavbarHome } from "@/module/_global"; -import { Box, Grid, Text } from "@mantine/core"; - -export default function NavbarCreateAnnouncement() { - return ( - - - - - - - - Tambah Pengumuman - - - - - - ) -} \ No newline at end of file diff --git a/src/module/announcement/component/ui/navbar_detail_announcement.tsx b/src/module/announcement/component/ui/navbar_detail_announcement.tsx deleted file mode 100644 index a2584ae..0000000 --- a/src/module/announcement/component/ui/navbar_detail_announcement.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client' -import { isDrawer, LayoutDrawer, LayoutIconBack, LayoutNavbarHome, WARNA } from "@/module/_global"; -import { useHookstate } from "@hookstate/core"; -import { ActionIcon, Box, Grid, Group, Text } from "@mantine/core"; -import { HiMenu } from "react-icons/hi"; -import DrawerDetailAnnouncement from "./drawer_detail_announcement"; - -export default function NavbarDetailAnnouncement() { - const openDrawer = useHookstate(isDrawer) - return ( - - - - - - - - PENGUMUMAN - - - - openDrawer.set(true)} variant="light" bg={WARNA.bgIcon} size="lg" radius="lg" aria-label="Settings"> - - - - - - - openDrawer.set(false)}> - - - - ) -} \ No newline at end of file diff --git a/src/module/announcement/component/ui/navbar_edit_announcement.tsx b/src/module/announcement/component/ui/navbar_edit_announcement.tsx deleted file mode 100644 index 3add416..0000000 --- a/src/module/announcement/component/ui/navbar_edit_announcement.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client' -import { LayoutIconBack, LayoutNavbarHome } from "@/module/_global"; -import { Box, Grid, Text } from "@mantine/core"; - -export default function NavbarEditAnnouncement() { - return ( - - - - - - - - Edit Pengumuman - - - - - - ) -} \ No newline at end of file diff --git a/src/module/announcement/index.ts b/src/module/announcement/index.ts index 297f4d5..bae423b 100644 --- a/src/module/announcement/index.ts +++ b/src/module/announcement/index.ts @@ -1,9 +1,14 @@ -import ViewCreateAnnouncement from "./view/view_create_announcement"; -import ViewDetailAnnouncement from "./view/view_detail_anouncement"; -import ViewEditAnnouncement from "./view/view_edit_announcement"; -import ViewListAnnouncement from "./view/view_list_announcement"; +import CreateAnnouncement from "./ui/create_announcement"; +import DetailAnnouncement from "./ui/detail_announcement"; +import EditAnnouncement from "./ui/edit_announcement"; +import ListAnnouncement from "./ui/list_announcement"; +import NavbarAnnouncement from "./ui/navbar_announcement"; +import NavbarDetailAnnouncement from "./ui/navbar_detail_announcement"; -export { ViewListAnnouncement } -export { ViewCreateAnnouncement } -export { ViewDetailAnnouncement } -export { ViewEditAnnouncement } \ No newline at end of file + +export { ListAnnouncement } +export { NavbarAnnouncement } +export { CreateAnnouncement } +export { NavbarDetailAnnouncement } +export { DetailAnnouncement } +export { EditAnnouncement } \ No newline at end of file diff --git a/src/module/announcement/lib/api_announcement.ts b/src/module/announcement/lib/api_announcement.ts new file mode 100644 index 0000000..58e95f5 --- /dev/null +++ b/src/module/announcement/lib/api_announcement.ts @@ -0,0 +1,55 @@ +import { ICreateData, IFormCreateAnnouncement } from "./type_announcement"; + +export const funGetAllAnnouncement = async (path?: string) => { + const response = await fetch(`/api/announcement${(path) ? path : ''}`, { next: { tags: ['announcement'] } }); + return await response.json().catch(() => null); +} + +export const funGetAnnouncementById = async (path: string) => { + const response = await fetch(`/api/announcement/${path}`); + return await response.json().catch(() => null); +} + +export const funCreateAnnouncement = async (data: IFormCreateAnnouncement) => { + if (data.title == "" || data.desc == "") + return { success: false, message: 'Silahkan lengkapi form tambah pengumuman' } + + if (data.groups.length == 0) + return { success: false, message: 'Silahkan pilih divisi penerima pengumuman' } + + const response = await fetch("/api/announcement", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); +} + +export const funDeleteAnnouncement = async (path: string) => { + const response = await fetch(`/api/announcement/${path}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); + return await response.json().catch(() => null); +}; + +export const funEditAnnouncement = async (path: string, data: IFormCreateAnnouncement) => { + if (data.title == "" || data.desc == "") + return { success: false, message: 'Silahkan lengkapi form edit pengumuman' } + + if (data.groups.length == 0) + return { success: false, message: 'Silahkan pilih divisi penerima pengumuman' } + + const response = await fetch(`/api/announcement/${path}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + return await response.json().catch(() => null); +} \ No newline at end of file diff --git a/src/module/announcement/lib/type_announcement.ts b/src/module/announcement/lib/type_announcement.ts new file mode 100644 index 0000000..0e96345 --- /dev/null +++ b/src/module/announcement/lib/type_announcement.ts @@ -0,0 +1,64 @@ +export interface IListDataAnnouncement { + id: string, + title: string, + desc: string, + createdAt: string +} + +export interface IRootAllAnnouncement { + data: IAnnouncement + member: IAllAnnouncementMember +} + +export interface IAnnouncement { + id: string + title: string + desc: string +} + +export interface IAllAnnouncementMember { + group: { + idGroup: string + idDivision: string + group: string + division: string + }[] +} + +export interface GroupData { + id: string; + name: string; + Division: { + id: string; + name: string; + }[]; +} + +export interface GroupDataEditAnnouncement { + id: string; + name: string; + Division: { + idGroup: string; + idDivision: string; + group: string; + division: string; + }[]; +} + +export interface ICreateData { + title: string + desc: string +} + +export interface IFormCreateAnnouncement { + title: string + desc: string + groups: GroupData[] +} + +export interface IGroupData { + idAnnouncement: string + idGroup: string + idDivision: string + isActive: boolean +} diff --git a/src/module/announcement/lib/val_announcement.ts b/src/module/announcement/lib/val_announcement.ts new file mode 100644 index 0000000..912ac66 --- /dev/null +++ b/src/module/announcement/lib/val_announcement.ts @@ -0,0 +1,6 @@ +import { hookstate } from "@hookstate/core"; +import { GroupData, GroupDataEditAnnouncement } from "./type_announcement"; + + +export const globalMemberAnnouncement = hookstate([]) +export const globalMemberEditAnnouncement = hookstate([]) \ No newline at end of file diff --git a/src/module/announcement/ui/create_announcement.tsx b/src/module/announcement/ui/create_announcement.tsx new file mode 100644 index 0000000..128f393 --- /dev/null +++ b/src/module/announcement/ui/create_announcement.tsx @@ -0,0 +1,203 @@ +'use client' +import { LayoutNavbarNew, TEMA, WARNA } from "@/module/_global"; +import LayoutModal from "@/module/_global/layout/layout_modal"; +import { useHookstate } from "@hookstate/core"; +import { Avatar, Box, Button, Flex, Group, rem, Stack, Text, Textarea, TextInput } from "@mantine/core"; +import { useShallowEffect } from "@mantine/hooks"; +import { useState } from "react"; +import toast from "react-hot-toast"; +import { IoIosArrowForward } from "react-icons/io"; +import CreateUsersAnnouncement from "./create_users_announcement"; +import { globalMemberAnnouncement } from "../lib/val_announcement"; +import { funCreateAnnouncement } from "../lib/api_announcement"; +import { GroupData, ICreateData, IGroupData } from "../lib/type_announcement"; +import { useRouter } from "next/navigation"; + + + +export default function CreateAnnouncement() { + const [isOpen, setOpen] = useState(false) + const memberGroup = useHookstate(globalMemberAnnouncement) + const memberValue = memberGroup.get() as GroupData[] + const [selectedFiles, setSelectedFiles] = useState([]) + const router = useRouter() + const tema = useHookstate(TEMA) + + + const [isChooseMember, setIsChooseMember] = useState(false) + const [isData, setisData] = useState({ + title: "", + desc: "", + }) + const [touched, setTouched] = useState({ + title: false, + desc: false + }); + + + async function onSubmit() { + try { + const response = await funCreateAnnouncement({ + title: isData.title, + desc: isData.desc, + groups: memberValue + }) + + if (response.success) { + toast.success(response.message) + // setisData({ + // ...isData, + // title: "", + // desc: "", + // }) + memberGroup.set([]) + router.push('/announcement') + } else { + toast.error(response.message) + } + } catch (error) { + console.error(error) + toast.error("Gagal menambahkan pengumuman, coba lagi nanti"); + } + + setOpen(false) + } + + async function loadData() { + setSelectedFiles(JSON.parse(JSON.stringify(memberGroup.get()))) + } + + useShallowEffect(() => { + loadData() + }, []) + + function onToChooseMember() { + setIsChooseMember(true) + } + + if (isChooseMember) return { setIsChooseMember(false) }} /> + + return ( + + } /> + + { + setisData({ ...isData, title: e.target.value }) + setTouched({ ...touched, title: false }) + }} + onBlur={() => setTouched({ ...touched, title: true })} + error={ + touched.title && ( + isData.title == "" ? "Judul Tidak Boleh Kosong" : null + ) + } + /> +