From fa9601e1267c71f702e43faf179798971657fadd Mon Sep 17 00:00:00 2001 From: nico Date: Tue, 2 Sep 2025 18:08:53 +0800 Subject: [PATCH] Fix UI Admin Menu Pendidikam, Add Menu User & Role --- prisma/data/user/role.json | 6 - prisma/data/user/roles.json | 29 ++ prisma/data/user/users.json | 35 ++ prisma/schema.prisma | 79 +++-- prisma/seed.ts | 44 +++ .../_state/pendidikan/perpustakaan-digital.ts | 34 +- .../(dashboard)/_state/user/user-state.ts | 313 +++++------------- .../desa-anti-korupsi/_lib/layouTabs.tsx | 2 + .../_lib/layoutTab.tsx | 3 +- .../prestasi-desa/_lib/layoutTabs.tsx | 2 + .../landing-page/profile/_lib/layoutTabs.tsx | 4 +- .../beasiswa-desa/_lib/layoutTabs.tsx | 102 ++++-- .../beasiswa-pendaftar/[id]/page.tsx | 213 ++++++------ .../beasiswa-desa/beasiswa-pendaftar/page.tsx | 214 +++++++----- .../keunggulan-program/create/page.tsx | 2 +- .../beasiswa-desa/keunggulan-program/page.tsx | 134 +++++--- .../_lib/layoutTabs.tsx | 108 ++++-- .../fasilitas-yang-disediakan/edit/page.tsx | 179 +++++++--- .../fasilitas-yang-disediakan/page.tsx | 95 ++++-- .../lokasi-dan-jadwal/edit/page.tsx | 181 +++++++--- .../lokasi-dan-jadwal/page.tsx | 95 ++++-- .../tujuan-program/edit/page.tsx | 169 +++++++--- .../tujuan-program/page.tsx | 107 ++++-- .../pendidikan/data-pendidikan/[id]/page.tsx | 121 ++++--- .../data-pendidikan/create/page.tsx | 122 ++++--- .../pendidikan/data-pendidikan/page.tsx | 228 +++++++------ .../info-sekolah/_lib/layoutTabs.tsx | 110 ++++-- .../jenjang-pendidikan/[id]/page.tsx | 70 +++- .../jenjang-pendidikan/create/page.tsx | 97 ++++-- .../info-sekolah/jenjang-pendidikan/page.tsx | 144 +++++--- .../info-sekolah/lembaga/[id]/edit/page.tsx | 85 +++-- .../info-sekolah/lembaga/[id]/page.tsx | 157 +++++---- .../info-sekolah/lembaga/create/page.tsx | 121 ++++--- .../pendidikan/info-sekolah/lembaga/page.tsx | 146 +++++--- .../info-sekolah/pengajar/[id]/edit/page.tsx | 71 +++- .../info-sekolah/pengajar/[id]/page.tsx | 143 ++++---- .../info-sekolah/pengajar/create/page.tsx | 101 ++++-- .../pendidikan/info-sekolah/pengajar/page.tsx | 129 +++++--- .../info-sekolah/siswa/[id]/edit/page.tsx | 114 +++++-- .../info-sekolah/siswa/[id]/page.tsx | 130 +++++--- .../info-sekolah/siswa/create/page.tsx | 86 +++-- .../pendidikan/info-sekolah/siswa/page.tsx | 140 +++++--- .../pendidikan-non-formal/_lib/layoutTabs.tsx | 107 ++++-- .../edit/page.tsx | 167 +++++++--- .../page.tsx | 107 ++++-- .../tempat-kegiatan/edit/page.tsx | 169 +++++++--- .../tempat-kegiatan/page.tsx | 95 ++++-- .../tujuan-program/edit/page.tsx | 162 ++++++--- .../tujuan-program/page.tsx | 109 ++++-- .../perpustakaan-digital/_lib/layoutTabs.tsx | 102 ++++-- .../data-perpustakaan/[id]/edit/page.tsx | 195 +++++------ .../data-perpustakaan/[id]/page.tsx | 137 +++++--- .../data-perpustakaan/create/page.tsx | 166 +++++----- .../data-perpustakaan/page.tsx | 124 ++++--- .../kategori-buku/[id]/page.tsx | 116 ++++--- .../kategori-buku/create/page.tsx | 56 +++- .../kategori-buku/page.tsx | 133 +++++--- .../_lib/layoutTabs.tsx | 102 ++++-- .../program-unggulan/edit/page.tsx | 174 +++++++--- .../program-unggulan/page.tsx | 95 ++++-- .../tujuan-program/edit/page.tsx | 162 ++++++--- .../tujuan-program/page.tsx | 95 ++++-- .../ikm-desa-darmasaba/_lib/layoutTabs.tsx | 3 +- .../ppid/struktur-ppid/_lib/layoutTabs.tsx | 4 +- .../admin/(dashboard)/user&role/layout.tsx | 103 ++++++ .../(dashboard)/user&role/role/[id]/page.tsx | 127 +++++++ .../user&role/role/create/page.tsx | 115 +++++++ .../admin/(dashboard)/user&role/role/page.tsx | 124 +++++++ .../admin/(dashboard)/user&role/user/page.tsx | 154 +++++++++ src/app/admin/_com/list_PageAdmin.tsx | 19 +- .../[[...slugs]]/_lib/{user => auth}/login.ts | 0 src/app/api/[[...slugs]]/_lib/auth/logout.ts | 0 .../_lib/{user => auth}/register.ts | 0 .../info-sekolah-paud/lembaga/findMany.ts | 3 - .../info-sekolah-paud/pengajar/findMany.ts | 2 - .../info-sekolah-paud/siswa/findMany.ts | 2 - .../kategori-buku/findMany.ts | 56 +++- src/app/api/[[...slugs]]/_lib/user/create.ts | 51 --- .../api/[[...slugs]]/_lib/user/findMany.ts | 42 ++- src/app/api/[[...slugs]]/_lib/user/index.ts | 40 --- .../api/[[...slugs]]/_lib/user/role/create.ts | 2 + .../api/[[...slugs]]/_lib/user/role/index.ts | 2 + .../api/[[...slugs]]/_lib/user/role/updt.ts | 2 + src/app/api/[[...slugs]]/_lib/user/updt.ts | 35 -- src/app/login/page.tsx | 33 +- src/app/registrasi/page.tsx | 41 ++- 86 files changed, 5349 insertions(+), 2649 deletions(-) delete mode 100644 prisma/data/user/role.json create mode 100644 prisma/data/user/roles.json create mode 100644 prisma/data/user/users.json create mode 100644 src/app/admin/(dashboard)/user&role/layout.tsx create mode 100644 src/app/admin/(dashboard)/user&role/role/[id]/page.tsx create mode 100644 src/app/admin/(dashboard)/user&role/role/create/page.tsx create mode 100644 src/app/admin/(dashboard)/user&role/role/page.tsx create mode 100644 src/app/admin/(dashboard)/user&role/user/page.tsx rename src/app/api/[[...slugs]]/_lib/{user => auth}/login.ts (100%) create mode 100644 src/app/api/[[...slugs]]/_lib/auth/logout.ts rename src/app/api/[[...slugs]]/_lib/{user => auth}/register.ts (100%) delete mode 100644 src/app/api/[[...slugs]]/_lib/user/create.ts delete mode 100644 src/app/api/[[...slugs]]/_lib/user/updt.ts diff --git a/prisma/data/user/role.json b/prisma/data/user/role.json deleted file mode 100644 index 6091de17..00000000 --- a/prisma/data/user/role.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "id": "cmdpm429r0000vnndkcwslt0h", - "name": "warga" - } -] \ No newline at end of file diff --git a/prisma/data/user/roles.json b/prisma/data/user/roles.json new file mode 100644 index 00000000..1543ba66 --- /dev/null +++ b/prisma/data/user/roles.json @@ -0,0 +1,29 @@ +[ + { + "id": "role_admin_desa", + "name": "ADMIN_DESA", + "description": "Administrator Desa", + "permissions": ["manage_users", "manage_content", "view_reports"], + "isActive": true, + "createdAt": "2025-09-01T00:00:00.000Z", + "updatedAt": "2025-09-01T00:00:00.000Z" + }, + { + "id": "role_admin_kesehatan", + "name": "ADMIN_KESEHATAN", + "description": "Administrator Bidang Kesehatan", + "permissions": ["manage_health_data", "view_reports"], + "isActive": true, + "createdAt": "2025-09-01T00:00:00.000Z", + "updatedAt": "2025-09-01T00:00:00.000Z" + }, + { + "id": "role_admin_sekolah", + "name": "ADMIN_SEKOLAH", + "description": "Administrator Sekolah", + "permissions": ["manage_school_data", "view_reports"], + "isActive": true, + "createdAt": "2025-09-01T00:00:00.000Z", + "updatedAt": "2025-09-01T00:00:00.000Z" + } + ] \ No newline at end of file diff --git a/prisma/data/user/users.json b/prisma/data/user/users.json new file mode 100644 index 00000000..f3852b2b --- /dev/null +++ b/prisma/data/user/users.json @@ -0,0 +1,35 @@ +[ + { + "id": "user_admin_desa", + "nama": "Admin Desa", + "email": "admin.desa@example.com", + "password": "$2b$10$XFDWYOJFxQ7ZM5bA0N4Z0O8u0eKYv58wLsaR7h6XK9bqWJ1YQJQ9q", + "roleId": "role_admin_desa", + "isActive": true, + "lastLogin": "2025-08-31T10:00:00.000Z", + "createdAt": "2025-09-01T00:00:00.000Z", + "updatedAt": "2025-09-01T00:00:00.000Z" + }, + { + "id": "user_admin_puskesmas", + "nama": "Admin Kesehatan", + "email": "admin.kesehatan@example.com", + "password": "$2b$10$XFDWYOJFxQ7ZM5bA0N4Z0O8u0eKYv58wLsaR7h6XK9bqWJ1YQJQ9q", + "roleId": "role_admin_kesehatan", + "isActive": true, + "lastLogin": null, + "createdAt": "2025-09-01T00:00:00.000Z", + "updatedAt": "2025-09-01T00:00:00.000Z" + }, + { + "id": "user_admin_sekolah", + "nama": "Admin Sekolah", + "email": "admin.sekolah@example.com", + "password": "$2b$10$XFDWYOJFxQ7ZM5bA0N4Z0O8u0eKYv58wLsaR7h6XK9bqWJ1YQJQ9q", + "roleId": "role_admin_sekolah", + "isActive": true, + "lastLogin": null, + "createdAt": "2025-09-01T00:00:00.000Z", + "updatedAt": "2025-09-01T00:00:00.000Z" + } + ] \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d2e4cc06..9758e55f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -202,7 +202,7 @@ model PrestasiDesa { deskripsi String @db.Text kategori KategoriPrestasiDesa @relation(fields: [kategoriId], references: [id]) kategoriId String - image FileStorage? @relation(fields: [imageId], references: [id]) + image FileStorage? @relation(fields: [imageId], references: [id]) imageId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@ -293,9 +293,9 @@ model PosisiOrganisasiPPID { pegawai PegawaiPPID[] strukturOrganisasi StrukturPPID[] // Relasi balik parentId String? - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt parent PosisiOrganisasiPPID? @relation("Parent", fields: [parentId], references: [id]) children PosisiOrganisasiPPID[] @relation("Parent") } @@ -1639,28 +1639,29 @@ model ProgramKreatif { // ========================================= KOLABORASI INOVASI ========================================= // model KolaborasiInovasi { - id String @id @default(cuid()) + id String @id @default(cuid()) name String tahun Int - slug String @db.Text //deskripsi singkat - deskripsi String @db.Text //deskripsi panjang + slug String @db.Text //deskripsi singkat + deskripsi String @db.Text //deskripsi panjang kolaborator String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) } model MitraKolaborasi { - id String @id @default(cuid()) + id String @id @default(cuid()) name String image FileStorage? @relation(fields: [imageId], references: [id]) imageId String? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime @default(now()) + isActive Boolean @default(true) } + // ========================================= INFO TEKHNOLOGI TEPAT GUNA ========================================= // model InfoTekno { id String @id @default(cuid()) @@ -2103,25 +2104,43 @@ model KategoriBuku { } model User { - id String @id @default(cuid()) + id String @id @default(cuid()) nama String - email String @unique - password String - role Role @relation(fields: [roleId], references: [id]) + email String @unique + password String? + role Role @relation(fields: [roleId], references: [id]) roleId String - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + instansi String? // Nama instansi (Puskesmas, Sekolah, dll) + isActive Boolean @default(true) + lastLogin DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? } model Role { - id String @id @default(cuid()) - name String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - deletedAt DateTime @default(now()) - isActive Boolean @default(true) - User User[] + id String @id @default(cuid()) + name String @unique // ADMIN_DESA, ADMIN_KESEHATAN, ADMIN_SEKOLAH + description String? + permissions Json // Menyimpan permission dalam format JSON + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + deletedAt DateTime? + users User[] + + @@map("roles") +} + +// Tabel untuk menyimpan permission +model Permission { + id String @id @default(cuid()) + name String @unique + description String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@map("permissions") } // ========================================= DATA PENDIDIKAN ========================================= // diff --git a/prisma/seed.ts b/prisma/seed.ts index 4d3fabc6..0822c90f 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -52,8 +52,52 @@ import tempatKegiatan from "./data/pendidikan/pendidikan-non-formal/tempat-kegia import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-program2.json"; import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json"; import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json"; +import roles from "./data/user/roles.json"; +import users from "./data/user/users.json"; (async () => { + // =========== USER & ROLE =========== + for (const r of roles) { + await prisma.role.upsert({ + where: { id: r.id }, + update: { + name: r.name, + description: r.description, + permissions: r.permissions, + isActive: true, + }, + create: { + id: r.id, + name: r.name, + description: r.description, + permissions: r.permissions, + isActive: true, + }, + }); + } + console.log("✅ Roles seeded"); + //users + for (const u of users) { + await prisma.user.upsert({ + where: { id: u.id }, + update: { + nama: u.nama, + email: u.email, + password: u.password, + roleId: u.roleId, + isActive: true, + }, + create: { + id: u.id, + nama: u.nama, + email: u.email, + password: u.password, + roleId: u.roleId, + isActive: true, + }, + }); + } + console.log("✅ Users seeded"); // =========== LANDING PAGE =========== // =========== SUBMENU PROFILE =========== // =========== PROFILE PEJABAT DESA =========== diff --git a/src/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital.ts b/src/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital.ts index b81127ff..da6411e9 100644 --- a/src/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital.ts +++ b/src/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital.ts @@ -317,14 +317,34 @@ const kategoriBuku = proxy({ isActive: true; }; }>[], + page: 1, + totalPages: 1, loading: false, - async load() { - const res = - await ApiFetch.api.pendidikan.perpustakaandigital.kategoribuku[ - "findMany" - ].get(); - if (res.status === 200) { - kategoriBuku.findMany.data = res.data?.data ?? []; + search: "", + load: async (page = 1, limit = 10, search = "") => { + kategoriBuku.findMany.loading = true; // ✅ Akses langsung via nama path + kategoriBuku.findMany.page = page; + kategoriBuku.findMany.search = search; + + try { + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.pendidikan.perpustakaandigital.kategoribuku["findMany"].get({ query }); + + if (res.status === 200 && res.data?.success) { + kategoriBuku.findMany.data = res.data.data ?? []; + kategoriBuku.findMany.totalPages = res.data.totalPages ?? 1; + } else { + kategoriBuku.findMany.data = []; + kategoriBuku.findMany.totalPages = 1; + } + } catch (err) { + console.error("Gagal fetch data kategori buku paginated:", err); + kategoriBuku.findMany.data = []; + kategoriBuku.findMany.totalPages = 1; + } finally { + kategoriBuku.findMany.loading = false; } }, }, diff --git a/src/app/admin/(dashboard)/_state/user/user-state.ts b/src/app/admin/(dashboard)/_state/user/user-state.ts index f13e46a1..b588088a 100644 --- a/src/app/admin/(dashboard)/_state/user/user-state.ts +++ b/src/app/admin/(dashboard)/_state/user/user-state.ts @@ -1,124 +1,43 @@ -import { proxy } from 'valtio' -import { toast } from 'react-toastify' -import ApiFetch from '@/lib/api-fetch' -import { Prisma } from '@prisma/client' -import { z } from 'zod' +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { proxy } from "valtio"; +import { toast } from "react-toastify"; +import ApiFetch from "@/lib/api-fetch"; +import { Prisma } from "@prisma/client"; +import { z } from "zod"; -// 1. Validasi Zod -const userSchema = z.object({ - nama: z.string().min(1, 'Nama harus diisi'), - email: z.string().email('Email tidak valid'), - password: z.string().min(6, 'Password minimal 6 karakter'), - roleId: z.string().optional(), -}) - -const defaultForm = { nama: '', email: '', password: '', roleId: '' } - -// 2. State Valtio +// State Valtio const userState = proxy({ - // Register - register: { - form: { ...defaultForm }, - loading: false, - async submit() { - const valid = userSchema.omit({ roleId: true }).safeParse(userState.register.form) - if (!valid.success) { - const err = valid.error.issues.map(i => i.message).join(', ') - return toast.error(err) - } - try { - userState.register.loading = true - const res = await ApiFetch.api.user.register.post(userState.register.form) - if (res.status === 200) { - toast.success('Registrasi berhasil, silakan login') - userState.register.form = { ...defaultForm } // reset - } else { - toast.error(res.data?.message || 'Gagal registrasi') - } - } catch (e) { - console.error(e) - toast.error('Terjadi kesalahan saat registrasi') - } finally { - userState.register.loading = false - } - }, - }, - - // Login - login: { - form: { email: '', password: '' }, - loading: false, - async submit() { - try { - userState.login.loading = true - const res = await ApiFetch.api.user.login.post(userState.login.form) - if (res.status === 200) { - toast.success('Login berhasil') - const token = res.data?.data?.token - if (typeof token === 'string') { - localStorage.setItem('token', token) - // Optional: simpan user role untuk otorisasi - const user = res.data?.data?.user - localStorage.setItem('user', JSON.stringify(user)) - } - } else { - toast.error(res.data?.message || 'Login gagal') - } - } catch (e) { - console.error(e) - toast.error('Terjadi kesalahan saat login') - } finally { - userState.login.loading = false - } - }, - }, - - // CRUD User (untuk admin) - create: { - form: { ...defaultForm }, - loading: false, - async create(isAdmin = false) { - const valid = userSchema.safeParse(userState.create.form) - if (!valid.success) { - const err = valid.error.issues.map(i => i.message).join(', ') - return toast.error(err) - } - try { - userState.create.loading = true - const endpoint = isAdmin ? 'create' : 'register' - const res = await ApiFetch.api.user[endpoint].post(userState.create.form) - if (res.status === 200) { - toast.success('User berhasil dibuat') - userState.findMany.load() // refresh list - userState.create.form = { ...defaultForm } // reset form - } else { - toast.error(res.data?.message || 'Gagal membuat user') - } - } catch (e) { - console.error(e) - toast.error('Gagal membuat user') - } finally { - userState.create.loading = false - } - }, - }, - // Find Many findMany: { data: [] as Prisma.UserGetPayload<{ include: { role: true } }>[], + page: 1, + totalPages: 1, loading: false, - async load() { - this.loading = true + search: "", + load: async (page = 1, limit = 10, search = "") => { + userState.findMany.loading = true; // ✅ Akses langsung via nama path + userState.findMany.page = page; + userState.findMany.search = search; + try { - const res = await ApiFetch.api.user.findMany.get() - if (res.status === 200) { - this.data = res.data?.data || [] + const query: any = { page, limit }; + if (search) query.search = search; + + const res = await ApiFetch.api.user["findMany"].get({ query }); + + if (res.status === 200 && res.data?.success) { + userState.findMany.data = res.data.data ?? []; + userState.findMany.totalPages = res.data.totalPages ?? 1; + } else { + userState.findMany.data = []; + userState.findMany.totalPages = 1; } - } catch (e) { - console.error(e) - toast.error('Gagal muat data user') + } catch (err) { + console.error("Gagal fetch user paginated:", err); + userState.findMany.data = []; + userState.findMany.totalPages = 1; } finally { - this.loading = false + userState.findMany.loading = false; } }, }, @@ -128,71 +47,20 @@ const userState = proxy({ data: null as Prisma.UserGetPayload<{ include: { role: true } }> | null, loading: false, async load(id: string) { - this.loading = true + this.loading = true; try { - const res = await fetch(`/api/user/findUnique/${id}`) - const data = await res.json() + const res = await fetch(`/api/user/findUnique/${id}`); + const data = await res.json(); if (res.status === 200) { - this.data = data.data + this.data = data.data; } else { - toast.error(data.message) + toast.error(data.message); } } catch (e) { - console.error(e) - toast.error('Gagal ambil data user') + console.error(e); + toast.error("Gagal ambil data user"); } finally { - this.loading = false - } - }, - }, - - // Update - update: { - id: '', - form: { ...defaultForm }, - loading: false, - async load(id: string) { - this.loading = true - try { - const res = await fetch(`/api/user/findUnique/${id}`) - const data = await res.json() - if (res.status === 200) { - const user = data.data - this.id = user.id - this.form = { - nama: user.nama, - email: user.email, - password: '', // jangan kirim password lama - roleId: user.roleId, - } - } - } catch (e) { - console.error(e) - toast.error('Gagal muat data') - } finally { - this.loading = false - } - }, - async submit() { - this.loading = true - try { - const res = await fetch(`/api/user/update/${this.id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(this.form), - }) - const data = await res.json() - if (res.status === 200) { - toast.success('Update berhasil') - userState.findMany.load() - } else { - toast.error(data.message || 'Gagal update') - } - } catch (e) { - console.error(e) - toast.error('Gagal update user') - } finally { - this.loading = false + this.loading = false; } }, }, @@ -201,35 +69,37 @@ const userState = proxy({ delete: { loading: false, async submit(id: string) { - this.loading = true + this.loading = true; try { const res = await fetch(`/api/user/del/${id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - }) - const data = await res.json() + method: "PUT", + headers: { "Content-Type": "application/json" }, + }); + const data = await res.json(); if (res.status === 200) { - toast.success('User dinonaktifkan') - userState.findMany.load() + toast.success("User dinonaktifkan"); + userState.findMany.load(); } else { - toast.error(data.message || 'Gagal hapus') + toast.error(data.message || "Gagal hapus"); } } catch (e) { - console.error(e) - toast.error('Gagal hapus user') + console.error(e); + toast.error("Gagal hapus user"); } finally { - this.loading = false + this.loading = false; } }, }, -}) +}); const templateRole = z.object({ name: z.string().min(1, "Nama harus diisi"), + permissions: z.array(z.string()).min(1, "Permission harus diisi"), }); const defaultRole = { name: "", + permissions: [] as string[], }; const roleState = proxy({ @@ -247,10 +117,9 @@ const roleState = proxy({ try { roleState.create.loading = true; - const res = - await ApiFetch.api.role[ - "create" - ].post(roleState.create.form); + const res = await ApiFetch.api.role["create"].post( + roleState.create.form + ); if (res.status === 200) { roleState.findMany.load(); return toast.success("Data role Berhasil Dibuat"); @@ -273,10 +142,7 @@ const roleState = proxy({ }>[], loading: false, async load() { - const res = - await ApiFetch.api.role[ - "findMany" - ].get(); + const res = await ApiFetch.api.role["findMany"].get(); if (res.status === 200) { roleState.findMany.data = res.data?.data ?? []; } @@ -291,9 +157,7 @@ const roleState = proxy({ loading: false, async load(id: string) { try { - const res = await fetch( - `/api/role/${id}` - ); + const res = await fetch(`/api/role/${id}`); if (res.ok) { const data = await res.json(); roleState.findUnique.data = data.data ?? null; @@ -315,22 +179,17 @@ const roleState = proxy({ try { roleState.delete.loading = true; - const response = await fetch( - `/api/role/del/${id}`, - { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - } - ); + const response = await fetch(`/api/role/del/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + }); const result = await response.json(); if (response.ok && result?.success) { - toast.success( - result.message || "Data role berhasil dihapus" - ); + toast.success(result.message || "Data role berhasil dihapus"); await roleState.findMany.load(); // refresh list } else { toast.error(result?.message || "Gagal menghapus Data role"); @@ -354,15 +213,12 @@ const roleState = proxy({ } try { - const response = await fetch( - `/api/role/${id}`, - { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - } - ); + const response = await fetch(`/api/role/${id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -374,6 +230,7 @@ const roleState = proxy({ this.id = data.id; this.form = { name: data.name, + permissions: data.permissions, }; return data; // Return the loaded data } else { @@ -400,18 +257,16 @@ const roleState = proxy({ try { roleState.update.loading = true; - const response = await fetch( - `/api/role/${this.id}`, - { - method: "PUT", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: this.form.name, - }), - } - ); + const response = await fetch(`/api/role/${this.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + name: this.form.name, + permissions: this.form.permissions, + }), + }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); @@ -451,6 +306,6 @@ const roleState = proxy({ const user = proxy({ userState, roleState, -}) +}); -export default user \ No newline at end of file +export default user; diff --git a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx index 2f1e92b5..12149248 100644 --- a/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx +++ b/src/app/admin/(dashboard)/landing-page/desa-anti-korupsi/_lib/layouTabs.tsx @@ -4,6 +4,7 @@ import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; import { IconList, IconCategory } from '@tabler/icons-react'; +import colors from '@/con/colors'; function LayoutTabs({ children }: { children: React.ReactNode }) { const router = useRouter(); @@ -50,6 +51,7 @@ function LayoutTabs({ children }: { children: React.ReactNode }) { Desa Anti Korupsi , + tooltip: "Kelola data pendaftar beasiswa desa", }, { label: "Keunggulan Program", value: "keunggulan-program", - href: "/admin/pendidikan/beasiswa-desa/keunggulan-program" + href: "/admin/pendidikan/beasiswa-desa/keunggulan-program", + icon: , + tooltip: "Lihat keunggulan dan detail program beasiswa", }, - ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find(tab => tab.href === pathname); + const [activeTab, setActiveTab] = useState(currentTab?.value || tabs[0].value); const handleTabChange = (value: string | null) => { - const tab = tabs.find(t => t.value === value) + const tab = tabs.find(t => t.value === value); if (tab) { - router.push(tab.href) + router.push(tab.href); } - setActiveTab(value) - } + setActiveTab(value); + }; useEffect(() => { - const match = tabs.find(tab => tab.href === pathname) + const match = tabs.find(tab => tab.href === pathname); if (match) { - setActiveTab(match.value) + setActiveTab(match.value); } - }, [pathname]) + }, [pathname]); return ( - - Beasiswa Desa - - - {tabs.map((e, i) => ( - {e.label} + + + Beasiswa Desa + + + + {tabs.map((tab, i) => ( + + + {tab.label} + + ))} - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + {tabs.map((tab, i) => ( + + {children} ))} - {children} ); } -export default LayoutTabs; \ No newline at end of file +export default LayoutTabs; diff --git a/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/beasiswa-pendaftar/[id]/page.tsx b/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/beasiswa-pendaftar/[id]/page.tsx index 61de1a37..16ffc1c8 100644 --- a/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/beasiswa-pendaftar/[id]/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/beasiswa-pendaftar/[id]/page.tsx @@ -2,133 +2,150 @@ import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa'; import colors from '@/con/colors'; -import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconArrowBack, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconTrash } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; - function DetailBeasiswaPendaftar() { - const state = useProxy(beasiswaDesaState.beasiswaPendaftar) + const state = useProxy(beasiswaDesaState.beasiswaPendaftar); const [modalHapus, setModalHapus] = useState(false); const [selectedId, setSelectedId] = useState(null); - const router = useRouter() - const params = useParams() + const params = useParams(); + const router = useRouter(); useShallowEffect(() => { - state.findUnique.load(params?.id as string) - }, []) + state.findUnique.load(params?.id as string); + }, []); const handleHapus = () => { if (selectedId) { - state.delete.delete(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/pendidikan/beasiswa-desa/beasiswa-pendaftar") + state.delete.delete(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push("/admin/pendidikan/beasiswa-desa/beasiswa-pendaftar"); } - } + }; if (!state.findUnique.data) { return ( - + - ) + ); } + const data = state.findUnique.data; + return ( - - - - - - - - Detail Beasiswa Pendaftar - - - {state.findUnique.data ? ( - - - - Nama Lengkap - {state.findUnique.data?.namaLengkap} - - - NIK - {state.findUnique.data?.nik} - - - Tempat Lahir - {state.findUnique.data?.tempatLahir} - - - Tanggal Lahir - {state.findUnique.data?.tanggalLahir ? new Date(state.findUnique.data.tanggalLahir).toLocaleDateString() : '-'} - - - Jenis Kelamin - {state.findUnique.data?.jenisKelamin} - - - Kewarganegaraan - {state.findUnique.data?.kewarganegaraan} - - - Agama - {state.findUnique.data?.agama} - - - Alamat KTP - {state.findUnique.data?.alamatKTP} - - - Alamat Domisili - {state.findUnique.data?.alamatDomisili} - - - No HP - {state.findUnique.data?.noHp} - - - Email - {state.findUnique.data?.email} - - - Status Pernikahan - {state.findUnique.data?.statusPernikahan} - - - Ukuran Baju - {state.findUnique.data?.ukuranBaju} - - - - ) : null} + + + + + + + Detail Beasiswa Pendaftar + + + + + + Nama Lengkap + {data.namaLengkap || '-'} + + + NIK + {data.nik || '-'} + + + Tempat Lahir + {data.tempatLahir || '-'} + + + Tanggal Lahir + + {data.tanggalLahir ? new Date(data.tanggalLahir).toLocaleDateString() : '-'} + + + + Jenis Kelamin + {data.jenisKelamin || '-'} + + + Kewarganegaraan + {data.kewarganegaraan || '-'} + + + Agama + {data.agama || '-'} + + + Alamat KTP + {data.alamatKTP || '-'} + + + Alamat Domisili + {data.alamatDomisili || '-'} + + + No HP + {data.noHp || '-'} + + + Email + {data.email || '-'} + + + Status Pernikahan + {data.statusPernikahan || '-'} + + + Ukuran Baju + {data.ukuranBaju || '-'} + + + + + + + + + - {/* Modal Konfirmasi Hapus */} setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus beasiswa desa ini?' + text="Apakah Anda yakin ingin menghapus data pendaftar ini?" /> ); diff --git a/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/beasiswa-pendaftar/page.tsx b/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/beasiswa-pendaftar/page.tsx index 2d81dde6..011f4489 100644 --- a/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/beasiswa-pendaftar/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/beasiswa-pendaftar/page.tsx @@ -1,107 +1,149 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Pagination, + Paper, + Skeleton, + Stack, + Table, + TableTbody, + TableTd, + TableTh, + TableThead, + TableTr, + Text, + Title +} from '@mantine/core'; import { IconDeviceImacCog, IconSearch } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; -import HeaderSearch from '../../../_com/header'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; +import HeaderSearch from '../../../_com/header'; import beasiswaDesaState from '../../../_state/pendidikan/beasiswa-desa'; - function BeasiswaPendaftar() { - const [search, setSearch] = useState(''); - return ( - - } - value={search} - onChange={(e) => setSearch(e.currentTarget.value)} - /> - - - ); + const [search, setSearch] = useState(''); + return ( + + } + value={search} + onChange={(e) => setSearch(e.currentTarget.value)} + /> + + + ); } function ListBeasiswaPendaftar({ search }: { search: string }) { - const listDataState = useProxy(beasiswaDesaState.beasiswaPendaftar) - const router = useRouter(); + const state = useProxy(beasiswaDesaState.beasiswaPendaftar); + const router = useRouter(); - useEffect(() => { - listDataState.findMany.load() - }, []) + const { data, page, totalPages, loading, load } = state.findMany; - const filteredData = (listDataState.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.namaLengkap.toLowerCase().includes(keyword) || - item.alamatKTP.toLowerCase().includes(keyword) || - (item.tanggalLahir ? new Date(item.tanggalLahir).toLocaleDateString() : '').toLowerCase().includes(keyword) || - item.jenisKelamin.toLowerCase().includes(keyword) - ); - }); + useEffect(() => { + load(page, 10, search); + }, [page, search]); - if (!listDataState.findMany.data) { - return ( - - - - ) - } + const filteredData = data || []; + + if (loading || !data) { return ( - - - - List Beasiswa Pendaftar - - - - - No - Nama Lengkap - Alamat - Tanggal Lahir - Jenis Kelamin - Detail - - - - {filteredData.map((item, index) => ( - - - - {index + 1} - - - - {item.namaLengkap} - - - {item.alamatKTP} - - - {item.tanggalLahir ? new Date(item.tanggalLahir).toLocaleDateString() : '-'} - - - {item.jenisKelamin} - - - - - - ))} - -
-
-
-
+ + + + ); + } + + return ( + + + + Daftar Beasiswa Pendaftar + + + + + + + No + Nama Lengkap + Alamat + Tanggal Lahir + Jenis Kelamin + Aksi + + + + {filteredData.length > 0 ? ( + filteredData.map((item, index) => ( + + {index + 1} + + {item.namaLengkap} + + + {item.alamatKTP} + + + + {item.tanggalLahir ? new Date(item.tanggalLahir).toLocaleDateString() : '-'} + + + + {item.jenisKelamin} + + + + + + )) + ) : ( + + +
+ Tidak ada data pendaftar yang cocok +
+
+
+ )} +
+
- ) +
+ +
+ { + load(newPage, 10, search); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + mt="md" + mb="md" + color="blue" + radius="md" + /> +
+
+ ); } export default BeasiswaPendaftar; diff --git a/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/keunggulan-program/create/page.tsx b/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/keunggulan-program/create/page.tsx index ae72804d..d0859b4b 100644 --- a/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/keunggulan-program/create/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/beasiswa-desa/keunggulan-program/create/page.tsx @@ -34,7 +34,7 @@ function CreateKeunggulanProgram() { return ( - + @@ -85,10 +99,10 @@ function ListKeunggulanProgram({ search }: { search: string }) { - Nama Keunggulan Program - Deskripsi - Edit - Delete + Nama Keunggulan Program + Deskripsi + Edit + Delete @@ -96,10 +110,17 @@ function ListKeunggulanProgram({ search }: { search: string }) { filteredData.map((item) => ( - {item.judul} + + {item.judul} + - + @@ -107,7 +128,11 @@ function ListKeunggulanProgram({ search }: { search: string }) { variant="light" color="green" size="sm" - onClick={() => router.push(`/admin/pendidikan/beasiswa-desa/keunggulan-program/${item.id}`)} + onClick={() => + router.push( + `/admin/pendidikan/beasiswa-desa/keunggulan-program/${item.id}` + ) + } > @@ -132,34 +157,43 @@ function ListKeunggulanProgram({ search }: { search: string }) { )) ) : ( - - Tidak ada data pengelolaan sampah + +
+ + Tidak ada data keunggulan program yang cocok + +
)}
- - {totalPages > 1 && ( -
- load(newPage)} - total={totalPages} - siblings={1} - boundaries={1} - withEdges - /> -
- )}
+ + {totalPages > 1 && ( +
+ { + load(newPage, 10); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + mt="md" + mb="md" + color="blue" + radius="md" + /> +
+ )} + {/* Modal Konfirmasi Hapus */} setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus pengelolaan sampah bank sampah ini?' + text="Apakah anda yakin ingin menghapus keunggulan program ini?" />
); diff --git a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/_lib/layoutTabs.tsx index d5c09000..488a0d93 100644 --- a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/_lib/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/_lib/layoutTabs.tsx @@ -1,68 +1,118 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; +import { IconSchool, IconCalendar, IconBuildingCommunity } from '@tabler/icons-react'; function LayoutTabs({ children }: { children: React.ReactNode }) { - const router = useRouter() - const pathname = usePathname() + const router = useRouter(); + const pathname = usePathname(); + const tabs = [ { label: "Tujuan Program", value: "tujuan-program", - href: "/admin/pendidikan/bimbingan-belajar-desa/tujuan-program" + href: "/admin/pendidikan/bimbingan-belajar-desa/tujuan-program", + icon: , + tooltip: "Lihat tujuan utama program bimbingan belajar", }, { label: "Lokasi dan Jadwal", value: "lokasi-dan-jadwal", - href: "/admin/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal" + href: "/admin/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal", + icon: , + tooltip: "Atur lokasi pelaksanaan dan jadwal bimbingan", }, { - label: "Fasilitas yang disediakan", + label: "Fasilitas yang Disediakan", value: "fasilitas-yang-disediakan", - href: "/admin/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan" + href: "/admin/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan", + icon: , + tooltip: "Kelola fasilitas yang tersedia untuk peserta", }, - ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find(tab => tab.href === pathname); + const [activeTab, setActiveTab] = useState(currentTab?.value || tabs[0].value); const handleTabChange = (value: string | null) => { - const tab = tabs.find(t => t.value === value) + const tab = tabs.find(t => t.value === value); if (tab) { - router.push(tab.href) + router.push(tab.href); } - setActiveTab(value) - } + setActiveTab(value); + }; useEffect(() => { - const match = tabs.find(tab => tab.href === pathname) + const match = tabs.find(tab => tab.href === pathname); if (match) { - setActiveTab(match.value) + setActiveTab(match.value); } - }, [pathname]) + }, [pathname]); return ( - - Bimbingan Belajar Desa - - - {tabs.map((e, i) => ( - {e.label} + + + Bimbingan Belajar Desa + + + + {tabs.map((tab, i) => ( + + + {tab.label} + + ))} - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + {tabs.map((tab, i) => ( + + {children} ))} - {children} ); } -export default LayoutTabs; \ No newline at end of file +export default LayoutTabs; diff --git a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/edit/page.tsx b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/edit/page.tsx index 6a8e2f23..29d4e159 100644 --- a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/edit/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/edit/page.tsx @@ -1,86 +1,167 @@ 'use client' import stateBimbinganBelajarDesa from '@/app/admin/(dashboard)/_state/pendidikan/bimbingan-belajar-desa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowBack } from '@tabler/icons-react'; import dynamic from 'next/dynamic'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; +import { toast } from 'react-toastify'; -const BimbinganBelajarDesaTextEditor = dynamic(() => import('../../_lib/bimbinganBelajarDesaTextEditor').then(mod => mod.BimbinganBelajarDesaTextEditor), { - ssr: false, -}); +const BimbinganBelajarDesaTextEditor = dynamic( + () => + import('../../_lib/bimbinganBelajarDesaTextEditor').then( + (mod) => mod.BimbinganBelajarDesaTextEditor + ), + { ssr: false } +); + +function EditFasilitasYangDisediakan() { + const router = useRouter(); + const editState = useProxy(stateBimbinganBelajarDesa.fasilitasYangDisediakanState); -function EditTujuanProgram() { - const router = useRouter() - const editState = useProxy(stateBimbinganBelajarDesa.fasilitasYangDisediakanState) const [judul, setJudul] = useState(''); const [content, setContent] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + // Load data pertama kali useShallowEffect(() => { if (!editState.findById.data) { - editState.findById.initialize(); // biar masuk ke `findFirst` route kamu + editState.findById.initialize(); } }, []); + // Sinkronkan state dengan data yang sudah di-load useEffect(() => { if (editState.findById.data) { - setJudul(editState.findById.data.judul ?? '') - setContent(editState.findById.data.deskripsi ?? '') + setJudul(editState.findById.data.judul ?? ''); + setContent(editState.findById.data.deskripsi ?? ''); } - }, [editState.findById.data]) + }, [editState.findById.data]); - const submit = () => { - if (editState.findById.data) { - editState.findById.data.judul = judul; - editState.findById.data.deskripsi = content; - editState.update.save(editState.findById.data) + const handleSubmit = async () => { + if (!judul.trim()) { + toast.error('Judul wajib diisi'); + return; } - router.push('/admin/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan') + + setIsSubmitting(true); + try { + if (editState.findById.data) { + editState.findById.data.judul = judul; + editState.findById.data.deskripsi = content; + await editState.update.save(editState.findById.data); + + toast.success('Berhasil menyimpan perubahan'); + router.push('/admin/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan'); + } + } catch (err) { + console.error('Error saving:', err); + toast.error('Gagal menyimpan data'); + } finally { + setIsSubmitting(false); + } + }; + + const handleBack = () => router.back(); + + // Loading state + if (editState.findById.loading) { + return ( + +
+ Memuat data fasilitas yang disediakan... +
+
+ ); } + return ( - - - - - - - - Edit Fasilitas Yang Disediakan - Judul} - value={judul} - onChange={(e) => setJudul(e.target.value)} - /> - Deskripsi + + + + + + + Edit Fasilitas Yang Disediakan + + + + + + Formulir Edit + + {/* Judul */} + Judul} + placeholder="Masukkan judul fasilitas" + value={judul} + onChange={(e) => setJudul(e.currentTarget.value)} + error={!judul && 'Judul wajib diisi'} + /> + + {/* Deskripsi */} + + Deskripsi - - - - - - + + + {/* Tombol Aksi */} + + + + + +
+
); } -export default EditTujuanProgram; +export default EditFasilitasYangDisediakan; diff --git a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/page.tsx b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/page.tsx index 9d41cd0b..6e129fa0 100644 --- a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/fasilitas-yang-disediakan/page.tsx @@ -1,6 +1,6 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; +import { Box, Button, Divider, Grid, GridCol, Paper, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconEdit } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -8,47 +8,86 @@ import { useProxy } from 'valtio/utils'; import stateBimbinganBelajarDesa from '../../../_state/pendidikan/bimbingan-belajar-desa'; function Page() { - const router = useRouter() - const listPreview = useProxy(stateBimbinganBelajarDesa.fasilitasYangDisediakanState) + const router = useRouter(); + const listPreview = useProxy(stateBimbinganBelajarDesa.fasilitasYangDisediakanState); + useShallowEffect(() => { - listPreview.findById.load('edit') - }, []) + listPreview.findById.load('edit'); + }, []); if (!listPreview.findById.data) { return ( - - + + - ) + ); } + + const data = listPreview.findById.data; + return ( - - - + + + {/* Header */} + - Preview Fasilitas Yang Disediakan + + Pratinjau Fasilitas Yang Disediakan + - + + + - - - - - - - - - - - - + + {/* Konten Preview */} + + + + {/* Judul */} + + + + {/* Deskripsi */} + + + + - ) + ); } export default Page; diff --git a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/edit/page.tsx b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/edit/page.tsx index e4f28a3e..b1ca2730 100644 --- a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/edit/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/edit/page.tsx @@ -1,86 +1,169 @@ 'use client' import stateBimbinganBelajarDesa from '@/app/admin/(dashboard)/_state/pendidikan/bimbingan-belajar-desa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowBack } from '@tabler/icons-react'; import dynamic from 'next/dynamic'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; +import { toast } from 'react-toastify'; -const BimbinganBelajarDesaTextEditor = dynamic(() => import('../../_lib/bimbinganBelajarDesaTextEditor').then(mod => mod.BimbinganBelajarDesaTextEditor), { - ssr: false, -}); +const BimbinganBelajarDesaTextEditor = dynamic( + () => + import('../../_lib/bimbinganBelajarDesaTextEditor').then( + (mod) => mod.BimbinganBelajarDesaTextEditor + ), + { ssr: false } +); + +function EditLokasiDanJadwal() { + const router = useRouter(); + const editState = useProxy(stateBimbinganBelajarDesa.lokasiDanJadwalState); -function EditTujuanProgram() { - const router = useRouter() - const editState = useProxy(stateBimbinganBelajarDesa.lokasiDanJadwalState) const [judul, setJudul] = useState(''); const [content, setContent] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + // load data sekali useShallowEffect(() => { if (!editState.findById.data) { - editState.findById.initialize(); // biar masuk ke `findFirst` route kamu + editState.findById.initialize(); } }, []); + // isi state ketika data loaded useEffect(() => { if (editState.findById.data) { - setJudul(editState.findById.data.judul ?? '') - setContent(editState.findById.data.deskripsi ?? '') + setJudul(editState.findById.data.judul ?? ''); + setContent(editState.findById.data.deskripsi ?? ''); } - }, [editState.findById.data]) + }, [editState.findById.data]); - const submit = () => { - if (editState.findById.data) { - editState.findById.data.judul = judul; - editState.findById.data.deskripsi = content; - editState.update.save(editState.findById.data) + const handleSubmit = async () => { + if (!judul.trim()) { + toast.error('Judul wajib diisi'); + return; } - router.push('/admin/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal') + + setIsSubmitting(true); + try { + if (editState.findById.data) { + editState.findById.data.judul = judul; + editState.findById.data.deskripsi = content; + await editState.update.save(editState.findById.data); + + toast.success('Berhasil menyimpan perubahan'); + router.push('/admin/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal'); + } + } catch (err) { + console.error('Error saving:', err); + toast.error('Gagal menyimpan data'); + } finally { + setIsSubmitting(false); + } + }; + + const handleBack = () => router.back(); + + // loading state + if (editState.findById.loading) { + return ( + +
+ Memuat data lokasi & jadwal... +
+
+ ); } + return ( - - - - - - - - Edit Lokasi Dan Jadwal - Judul} - value={judul} - onChange={(e) => setJudul(e.target.value)} - /> - Deskripsi + + {/* Header dengan tombol kembali */} + + + + + + Edit Lokasi & Jadwal + + + + {/* Form Card */} + + + Form Lokasi & Jadwal + + {/* Judul Field */} + Judul} + placeholder="Masukkan judul lokasi/jadwal" + value={judul} + onChange={(e) => setJudul(e.currentTarget.value)} + error={!judul && 'Judul wajib diisi'} + /> + + {/* Deskripsi Field */} + + Deskripsi - - - - - - + + + {/* Action Button */} + + + + + +
+
); } -export default EditTujuanProgram; +export default EditLokasiDanJadwal; diff --git a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/page.tsx b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/page.tsx index 6145a65a..360465dd 100644 --- a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/lokasi-dan-jadwal/page.tsx @@ -1,6 +1,6 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; +import { Box, Button, Divider, Grid, GridCol, Paper, Skeleton, Stack, Text, Title, Tooltip } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconEdit } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -8,47 +8,86 @@ import { useProxy } from 'valtio/utils'; import stateBimbinganBelajarDesa from '../../../_state/pendidikan/bimbingan-belajar-desa'; function Page() { - const router = useRouter() - const listPreview = useProxy(stateBimbinganBelajarDesa.lokasiDanJadwalState) + const router = useRouter(); + const listPreview = useProxy(stateBimbinganBelajarDesa.lokasiDanJadwalState); + useShallowEffect(() => { - listPreview.findById.load('edit') - }, []) + listPreview.findById.load('edit'); + }, []); if (!listPreview.findById.data) { return ( - - + + - ) + ); } + + const data = listPreview.findById.data; + return ( - - - + + + {/* Header */} + - Preview Lokasi Dan Jadwal + + Pratinjau Lokasi & Jadwal + - + + + - - - - - - - - - - - - + + {/* Konten Preview */} + + + + {/* Judul */} + + + + {/* Deskripsi */} + + + + - ) + ); } export default Page; diff --git a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/tujuan-program/edit/page.tsx b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/tujuan-program/edit/page.tsx index a54f42c3..2b318d89 100644 --- a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/tujuan-program/edit/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/tujuan-program/edit/page.tsx @@ -1,83 +1,158 @@ 'use client' import stateBimbinganBelajarDesa from '@/app/admin/(dashboard)/_state/pendidikan/bimbingan-belajar-desa'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Center, + Group, + Paper, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowBack } from '@tabler/icons-react'; import dynamic from 'next/dynamic'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; import { useProxy } from 'valtio/utils'; +import { toast } from 'react-toastify'; -const BimbinganBelajarDesaTextEditor = dynamic(() => import('../../_lib/bimbinganBelajarDesaTextEditor').then(mod => mod.BimbinganBelajarDesaTextEditor), { - ssr: false, -}); +const BimbinganBelajarDesaTextEditor = dynamic( + () => + import('../../_lib/bimbinganBelajarDesaTextEditor').then( + (mod) => mod.BimbinganBelajarDesaTextEditor + ), + { ssr: false } +); function EditTujuanProgram() { - const router = useRouter() - const editState = useProxy(stateBimbinganBelajarDesa.stateTujuanProgram) + const router = useRouter(); + const editState = useProxy(stateBimbinganBelajarDesa.stateTujuanProgram); + const [judul, setJudul] = useState(''); const [content, setContent] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + // load data once useShallowEffect(() => { if (!editState.findById.data) { - editState.findById.initialize(); // biar masuk ke `findFirst` route kamu + editState.findById.initialize(); } }, []); useEffect(() => { if (editState.findById.data) { - setJudul(editState.findById.data.judul ?? '') - setContent(editState.findById.data.deskripsi ?? '') + setJudul(editState.findById.data.judul ?? ''); + setContent(editState.findById.data.deskripsi ?? ''); } - }, [editState.findById.data]) + }, [editState.findById.data]); - const submit = () => { - if (editState.findById.data) { - editState.findById.data.judul = judul; - editState.findById.data.deskripsi = content; - editState.update.save(editState.findById.data) + const handleSubmit = async () => { + if (!judul.trim()) { + toast.error('Judul wajib diisi'); + return; } - router.push('/admin/pendidikan/bimbingan-belajar-desa/tujuan-program') + + setIsSubmitting(true); + try { + if (editState.findById.data) { + editState.findById.data.judul = judul; + editState.findById.data.deskripsi = content; + await editState.update.save(editState.findById.data); + + toast.success('Berhasil menyimpan perubahan'); + router.push('/admin/pendidikan/bimbingan-belajar-desa/tujuan-program'); + } + } catch (err) { + console.error('Error saving:', err); + toast.error('Gagal menyimpan data'); + } finally { + setIsSubmitting(false); + } + }; + + const handleBack = () => router.back(); + + // loading state + if (editState.findById.loading) { + return ( + +
+ Memuat data tujuan program... +
+
+ ); } + return ( - - - - - - - - Edit Tujuan Program - Judul} - value={judul} - onChange={(e) => setJudul(e.target.value)} - /> - Deskripsi + + + + + + + Edit Tujuan Program + + + + + + Form Edit Tujuan Program + + {/* Judul Field */} + Judul} + placeholder="Masukkan judul program" + value={judul} + onChange={(e) => setJudul(e.currentTarget.value)} + error={!judul && 'Judul wajib diisi'} + /> + + {/* Deskripsi Field */} + + Deskripsi - - - - - - + + + {/* Submit & Cancel */} + + + + + +
+
); diff --git a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/tujuan-program/page.tsx b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/tujuan-program/page.tsx index f8b1519d..30544096 100644 --- a/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/tujuan-program/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/bimbingan-belajar-desa/tujuan-program/page.tsx @@ -1,6 +1,18 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Grid, GridCol, Paper, Skeleton, Stack, Text, Title } from '@mantine/core'; +import { + Box, + Button, + Divider, + Grid, + GridCol, + Paper, + Skeleton, + Stack, + Text, + Title, + Tooltip, +} from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconEdit } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -8,47 +20,86 @@ import { useProxy } from 'valtio/utils'; import stateBimbinganBelajarDesa from '../../../_state/pendidikan/bimbingan-belajar-desa'; function Page() { - const router = useRouter() - const listPreview = useProxy(stateBimbinganBelajarDesa.stateTujuanProgram) + const router = useRouter(); + const listPreview = useProxy(stateBimbinganBelajarDesa.stateTujuanProgram); + useShallowEffect(() => { - listPreview.findById.load('edit') - }, []) + listPreview.findById.load('edit'); + }, []); if (!listPreview.findById.data) { return ( - - + + - ) + ); } + + const data = listPreview.findById.data; + return ( - - - + + + {/* Header */} + - Preview Tujuan Program + + Pratinjau Tujuan Program + - + + + - - - - - - - - - - - - + + {/* Konten Preview */} + + + + {/* Judul */} + + + + {/* Deskripsi */} + + + + - ) + ); } export default Page; diff --git a/src/app/admin/(dashboard)/pendidikan/data-pendidikan/[id]/page.tsx b/src/app/admin/(dashboard)/pendidikan/data-pendidikan/[id]/page.tsx index c1479a82..edaad7da 100644 --- a/src/app/admin/(dashboard)/pendidikan/data-pendidikan/[id]/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/data-pendidikan/[id]/page.tsx @@ -2,79 +2,94 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Stack, TextInput, Title } from '@mantine/core'; +import { Box, Button, Group, Paper, Stack, TextInput, Title, Tooltip } from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect } from 'react'; import { useProxy } from 'valtio/utils'; import dataPendidikan from '../../../_state/pendidikan/data-pendidikan'; -function EditDataPendidikan() { - const router = useRouter() - const params = useParams() as { id: string } - const stateDPM = useProxy(dataPendidikan) +export default function EditDataPendidikan() { + const router = useRouter(); + const params = useParams() as { id: string }; + const stateDPM = useProxy(dataPendidikan); + const id = params.id; - const id = params.id - - // Load data saat komponen mount + // Load data saat mount useEffect(() => { if (id) { stateDPM.findUnique.load(id).then(() => { - const data = stateDPM.findUnique.data + const data = stateDPM.findUnique.data; if (data) { stateDPM.update.form = { - name: data.name || "", - jumlah: data.jumlah || "", - } + name: data.name || '', + jumlah: data.jumlah || '', + }; } - }) + }); } - }, [id]) + }, [id]); const handleSubmit = async () => { - // Set the ID before submitting stateDPM.update.id = id; await stateDPM.update.submit(); - router.push('/admin/pendidikan/data-pendidikan') - } -return ( - - - - - - - Edit Data Pendidikan - { - stateDPM.update.form.name = val.currentTarget.value; - }} - /> - { - stateDPM.update.form.jumlah = val.currentTarget.value; - }} - /> - + + Edit Data Pendidikan + + + + + (stateDPM.update.form.name = e.currentTarget.value)} + radius="md" + required + /> + (stateDPM.update.form.jumlah = e.currentTarget.value)} + radius="md" + required + /> + + + - ) + ); } - -export default EditDataPendidikan; diff --git a/src/app/admin/(dashboard)/pendidikan/data-pendidikan/create/page.tsx b/src/app/admin/(dashboard)/pendidikan/data-pendidikan/create/page.tsx index c97094e2..602849f4 100644 --- a/src/app/admin/(dashboard)/pendidikan/data-pendidikan/create/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/data-pendidikan/create/page.tsx @@ -1,81 +1,93 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ 'use client' import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, TextInput, Title } from '@mantine/core'; +import { Box, Button, Group, Paper, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; import dataPendidikan from '../../../_state/pendidikan/data-pendidikan'; -function CreateDataPendidikan() { +export default function CreateDataPendidikan() { const stateDPM = useProxy(dataPendidikan); const [chartData, setChartData] = useState([]); - const router = useRouter() + const router = useRouter(); const resetForm = () => { - stateDPM.create.form = { - name: "", - jumlah: "", - } - } + stateDPM.create.form = { name: '', jumlah: '' }; + }; const handleSubmit = async () => { const id = await stateDPM.create.create(); if (id) { const idStr = String(id); await stateDPM.findUnique.load(idStr); - if (stateDPM.findUnique.data) { - setChartData([stateDPM.findUnique.data]); - } + if (stateDPM.findUnique.data) setChartData([stateDPM.findUnique.data]); } resetForm(); - router.push("/admin/pendidikan/data-pendidikan"); - } + router.push('/admin/pendidikan/data-pendidikan'); + }; + return ( - - - - - - - Tambah Data Pendidikan - - { - stateDPM.create.form.name = val.currentTarget.value; + + + + + + Tambah Data Pendidikan + + + + + (stateDPM.create.form.name = e.currentTarget.value)} + radius="md" + required + /> + (stateDPM.create.form.jumlah = e.currentTarget.value)} + radius="md" + type="number" + required + /> + + - - - - + radius="md" + size="md" + > + Simpan + + + + + + {chartData.length === 0 && ( + + Data belum tersedia. Silakan tambahkan data pendidikan baru. + + )} ); } - -export default CreateDataPendidikan; diff --git a/src/app/admin/(dashboard)/pendidikan/data-pendidikan/page.tsx b/src/app/admin/(dashboard)/pendidikan/data-pendidikan/page.tsx index 5ac0e91e..0f3e9285 100644 --- a/src/app/admin/(dashboard)/pendidikan/data-pendidikan/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/data-pendidikan/page.tsx @@ -1,24 +1,23 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title } from '@mantine/core'; -import { useMediaQuery, useShallowEffect } from '@mantine/hooks'; -import { IconEdit, IconSearch, IconTrash } from '@tabler/icons-react'; +import { Box, Button, Group, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core'; +import { useShallowEffect } from '@mantine/hooks'; +import { IconDatabase, IconEdit, IconPlus, IconSearch, IconTrash } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; -import { Bar, BarChart, Legend, Tooltip, XAxis, YAxis } from 'recharts'; +import { Bar, BarChart, Legend, Tooltip as RechartTooltip, ResponsiveContainer, XAxis, YAxis } from 'recharts'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../_com/header'; -import JudulList from '../../_com/judulList'; import { ModalKonfirmasiHapus } from '../../_com/modalKonfirmasiHapus'; import dataPendidikan from '../../_state/pendidikan/data-pendidikan'; function DataPendidikan() { - const [search, setSearch] = useState(""); + const [search, setSearch] = useState(''); return ( - + } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -29,138 +28,153 @@ function DataPendidikan() { } function ListDataPendidikan({ search }: { search: string }) { - type DPMrafik = { - id: string; - name: string; - jumlah: number; - } + type DPMrafik = { id: string; name: string; jumlah: number }; const stateDPM = useProxy(dataPendidikan); const [chartData, setChartData] = useState([]); - const [mounted, setMounted] = useState(false); // untuk memastikan DOM sudah ready - const isTablet = useMediaQuery('(max-width: 1024px)') - const isMobile = useMediaQuery('(max-width: 768px)') - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) + const [mounted, setMounted] = useState(false); + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); const router = useRouter(); const handleDelete = () => { if (selectedId) { - stateDPM.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - - stateDPM.findMany.load() + stateDPM.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + stateDPM.findMany.load(); } - } + }; useShallowEffect(() => { - setMounted(true) - stateDPM.findMany.load() - }, []) + setMounted(true); + stateDPM.findMany.load(); + }, []); useEffect(() => { setMounted(true); if (stateDPM.findMany.data) { - setChartData(stateDPM.findMany.data.map((item) => ({ - id: item.id, - name: item.name, - jumlah: Number(item.jumlah) - }))); + setChartData( + stateDPM.findMany.data.map((item) => ({ + id: item.id, + name: item.name, + jumlah: Number(item.jumlah), + })) + ); } }, [stateDPM.findMany.data]); - const filteredData = (stateDPM.findMany.data || []).filter(item => { + const filteredData = (stateDPM.findMany.data || []).filter((item) => { const keyword = search.toLowerCase(); - return ( - item.name.toLowerCase().includes(keyword) || - item.jumlah.toLowerCase().includes(keyword) - ); + return item.name.toLowerCase().includes(keyword) || item.jumlah.toString().includes(keyword); }); if (!stateDPM.findMany.data) { return ( - - + + - ) + ); } return ( - - - - - - - - Nama - Jumlah - Edit - Delete - - - - {filteredData.map((item) => ( - - {item.name} - {item.jumlah} - - - - - - + + + + + Daftar Data Pendidikan + + + + + {filteredData.length === 0 ? ( + + + + Tidak ada data pendidikan yang sesuai pencarian + + + ) : ( +
+ + + Nama + Jumlah + Edit + Hapus - ))} - -
+ + + {filteredData.map((item) => ( + + {item.name} + {item.jumlah} + + + + + + + + + + + + ))} + + + )}
- {/* Chart */} - {!mounted && !chartData ? ( - - - Grafik Data Pendidikan - Belum ada data untuk ditampilkan dalam grafik - - - ) : ( - - - Grafik Data Pendidikan - {mounted && chartData.length > 0 && ( - - - - - - - - )} - - - )} + + + Grafik Data Pendidikan + + {mounted && chartData.length > 0 ? ( + + + + + + + + + + ) : ( + + + + Belum ada data untuk ditampilkan + + + )} +
- {/* Modal Konfirmasi Hapus */} setModalHapus(false)} onConfirm={handleDelete} - text='Apakah anda yakin ingin menghapus grafik data pendidikan ini?' + text="Apakah Anda yakin ingin menghapus data pendidikan ini?" />
); diff --git a/src/app/admin/(dashboard)/pendidikan/info-sekolah/_lib/layoutTabs.tsx b/src/app/admin/(dashboard)/pendidikan/info-sekolah/_lib/layoutTabs.tsx index 77ae73ba..e0493f56 100644 --- a/src/app/admin/(dashboard)/pendidikan/info-sekolah/_lib/layoutTabs.tsx +++ b/src/app/admin/(dashboard)/pendidikan/info-sekolah/_lib/layoutTabs.tsx @@ -1,72 +1,124 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title } from '@mantine/core'; +import { Stack, Tabs, TabsList, TabsPanel, TabsTab, Title, Tooltip } from '@mantine/core'; +import { IconBuilding, IconChalkboard, IconMicroscope, IconSchool } from '@tabler/icons-react'; import { usePathname, useRouter } from 'next/navigation'; import React, { useEffect, useState } from 'react'; function LayoutTabs({ children }: { children: React.ReactNode }) { - const router = useRouter() - const pathname = usePathname() + const router = useRouter(); + const pathname = usePathname(); + const tabs = [ { label: "Jenjang Pendidikan", value: "jenjangPendidikan", - href: "/admin/pendidikan/info-sekolah/jenjang-pendidikan" + href: "/admin/pendidikan/info-sekolah/jenjang-pendidikan", + icon: , + tooltip: "Lihat dan kelola jenjang pendidikan", }, { label: "Lembaga", value: "lembaga", - href: "/admin/pendidikan/info-sekolah/lembaga" + href: "/admin/pendidikan/info-sekolah/lembaga", + icon: , + tooltip: "Lihat dan kelola lembaga", }, { label: "Siswa", value: "siswa", - href: "/admin/pendidikan/info-sekolah/siswa" + href: "/admin/pendidikan/info-sekolah/siswa", + icon: , + tooltip: "Lihat dan kelola siswa", }, { label: "Pengajar", value: "pengajar", - href: "/admin/pendidikan/info-sekolah/pengajar" - }, + href: "/admin/pendidikan/info-sekolah/pengajar", + icon: , + tooltip: "Lihat dan kelola pengajar", + } ]; - const curentTab = tabs.find(tab => tab.href === pathname) - const [activeTab, setActiveTab] = useState(curentTab?.value || tabs[0].value); + + const currentTab = tabs.find(tab => tab.href === pathname); + const [activeTab, setActiveTab] = useState(currentTab?.value || tabs[0].value); const handleTabChange = (value: string | null) => { - const tab = tabs.find(t => t.value === value) + const tab = tabs.find(t => t.value === value); if (tab) { - router.push(tab.href) + router.push(tab.href); } - setActiveTab(value) - } + setActiveTab(value); + }; useEffect(() => { - const match = tabs.find(tab => tab.href === pathname) + const match = tabs.find(tab => tab.href === pathname); if (match) { - setActiveTab(match.value) + setActiveTab(match.value); } - }, [pathname]) + }, [pathname]); return ( - - Info Sekolah & PAUD - - - {tabs.map((e, i) => ( - {e.label} + + + Info Sekolah + + + + {tabs.map((tab, i) => ( + + + {tab.label} + + ))} - {tabs.map((e, i) => ( - - {/* Konten dummy, bisa diganti tergantung routing */} - <> + + {tabs.map((tab, i) => ( + + {children} ))} - {children} ); } -export default LayoutTabs; \ No newline at end of file +export default LayoutTabs; + + + + + \ No newline at end of file diff --git a/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/[id]/page.tsx b/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/[id]/page.tsx index ea2a04bc..ff1ce12c 100644 --- a/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/[id]/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/[id]/page.tsx @@ -2,7 +2,16 @@ 'use client' import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useEffect, useState } from 'react'; @@ -27,7 +36,6 @@ function EditJenjangPendidikan() { const data = await stateJenjang.edit.load(id); if (data) { - // pastikan id-nya masuk ke state edit stateJenjang.edit.id = id; setFormData({ nama: data.nama || '', @@ -53,41 +61,67 @@ function EditJenjangPendidikan() { nama: formData.nama.trim(), }; - // Safety check tambahan: pastikan ID tidak kosong if (!stateJenjang.edit.id) { - stateJenjang.edit.id = id; // fallback + stateJenjang.edit.id = id; } const success = await stateJenjang.edit.update(); if (success) { + toast.success("Jenjang pendidikan berhasil diperbarui!"); router.push("/admin/pendidikan/info-sekolah/jenjang-pendidikan"); } } catch (error) { console.error("Error updating jenjang pendidikan:", error); - // toast akan ditampilkan dari fungsi update + toast.error("Terjadi kesalahan saat memperbarui jenjang pendidikan"); } }; return ( - - - - + + {/* Header Back + Title */} + + + + + + Edit Jenjang Pendidikan + + - - - Edit Jenjang Pendidikan + {/* Form Container */} + + setFormData({ ...formData, nama: e.target.value })} - label={Nama Jenjang Pendidikan} - placeholder='Masukkan nama jenjang pendidikan' + required /> - - + + + diff --git a/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/create/page.tsx b/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/create/page.tsx index 4005e157..62b33467 100644 --- a/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/create/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/create/page.tsx @@ -1,16 +1,26 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client' import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Stack, + TextInput, + Title, + Tooltip +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; +import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; import infoSekolahPaud from '../../../../_state/pendidikan/info-sekolah-paud'; function CreateJenjangPendidikan() { const router = useRouter(); - const stateJenjang = useProxy(infoSekolahPaud.jenjangPendidikan) + const stateJenjang = useProxy(infoSekolahPaud.jenjangPendidikan); useEffect(() => { stateJenjang.findMany.load(); @@ -18,42 +28,73 @@ function CreateJenjangPendidikan() { const resetForm = () => { stateJenjang.create.form = { - nama: "", + nama: '', }; - } + }; const handleSubmit = async () => { + if (!stateJenjang.create.form.nama) { + return toast.warn('Nama jenjang pendidikan tidak boleh kosong'); + } + await stateJenjang.create.create(); resetForm(); - router.push("/admin/pendidikan/info-sekolah/jenjang-pendidikan") - } + router.push('/admin/pendidikan/info-sekolah/jenjang-pendidikan'); + }; return ( - - - - - + + + Tambah Jenjang Pendidikan + + - - - Create Jenjang Pendidikan - { - stateJenjang.create.form.nama = val.target.value; + {/* Form */} + + + (stateJenjang.create.form.nama = e.target.value)} + required + /> + + + - - - - + > + Simpan + + + + ); } diff --git a/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/page.tsx b/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/page.tsx index f6955f75..ce0b3453 100644 --- a/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/info-sekolah/jenjang-pendidikan/page.tsx @@ -1,13 +1,12 @@ 'use client' import colors from '@/con/colors'; -import { Box, Button, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr } from '@mantine/core'; +import { Box, Button, Center, Group, Paper, Pagination, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconEdit, IconSearch, IconX } from '@tabler/icons-react'; +import { IconEdit, IconPlus, IconSearch, IconX } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; import { useProxy } from 'valtio/utils'; import HeaderSearch from '../../../_com/header'; -import JudulList from '../../../_com/judulList'; import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus'; import infoSekolahPaud from '../../../_state/pendidikan/info-sekolah-paud'; @@ -18,7 +17,7 @@ function JenjangPendidikan() { } value={search} onChange={(e) => setSearch(e.currentTarget.value)} @@ -34,6 +33,14 @@ function ListJenjangPendidikan({ search }: { search: string }) { const [selectedId, setSelectedId] = useState(null) const router = useRouter() + const { + data, + page, + totalPages, + loading, + load, + } = stateList.findMany + const handleHapus = () => { if (selectedId) { stateList.delete.byId(selectedId) @@ -43,61 +50,104 @@ function ListJenjangPendidikan({ search }: { search: string }) { } useShallowEffect(() => { - stateList.findMany.load() - }, []) + load(page, 10, search) + }, [page, search]) - const filteredData = (stateList.findMany.data || []).filter(item => { - const keyword = search.toLowerCase(); - return ( - item.nama.toLowerCase().includes(keyword) - ); - }); + const filteredData = data || [] - if (!stateList.findMany.data) { + if (loading || !data) { return ( - + ) } return ( - - - - - - Nama Jenjang Pendidikan - Edit - Delete - - - - {filteredData.map((item) => ( - - {item.nama} - - - - - - + + + Daftar Jenjang Pendidikan + + + + + + +
+ + + Nama Jenjang Pendidikan + Edit + Delete - ))} - -
+ + + {filteredData.length > 0 ? ( + filteredData.map((item) => ( + + + {item.nama} + + + + + + + + + )) + ) : ( + + +
+ Tidak ada data jenjang pendidikan +
+
+
+ )} +
+ +
+ +
+ { + load(newPage, 10); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }} + total={totalPages} + mt="md" + mb="md" + color="blue" + radius="md" + /> +
+ {/* Modal Konfirmasi Hapus */} (); @@ -26,7 +35,7 @@ export default function EditLembaga() { infoSekolahPaud.jenjangPendidikan.findMany.load(); if (id) { - state.edit.load(id).then(data => { + state.edit.load(id).then((data) => { if (data) { setForm({ nama: data.nama, @@ -39,7 +48,7 @@ export default function EditLembaga() { const handleSubmit = async () => { if (!form.nama || !form.jenjangId) { - toast.warn("Nama dan jenjang pendidikan harus diisi"); + toast.warn('Nama dan jenjang pendidikan harus diisi'); return; } @@ -49,39 +58,77 @@ export default function EditLembaga() { const result = await state.edit.update(); if (result) { - toast.success("Data berhasil diperbarui"); + toast.success('Data berhasil diperbarui'); router.push('/admin/pendidikan/info-sekolah/lembaga'); } }; return ( - - - - - - - Edit Lembaga + + {/* Header dengan back button */} + + + + + + Edit Lembaga Pendidikan + + + + {/* Card Form */} + + setForm({ ...form, nama: e.currentTarget.value })} + required /> + ({ + {/* Form */} + + + { + stateLembaga.create.form.nama = val.target.value; + }} + label={Nama Lembaga} + placeholder="Masukkan nama lembaga" + required + /> + + ({ value: k.id, label: k.nama })) ?? []} value={formData.lembagaId} onChange={(val) => setFormData({ ...formData, lembagaId: val ?? '' })} + required /> - + + + + diff --git a/src/app/admin/(dashboard)/pendidikan/info-sekolah/pengajar/[id]/page.tsx b/src/app/admin/(dashboard)/pendidikan/info-sekolah/pengajar/[id]/page.tsx index d223393e..770bf166 100644 --- a/src/app/admin/(dashboard)/pendidikan/info-sekolah/pengajar/[id]/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/info-sekolah/pengajar/[id]/page.tsx @@ -1,107 +1,124 @@ 'use client' -import { useProxy } from 'valtio/utils'; - -import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; -import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; +import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; - -import colors from '@/con/colors'; +import { useProxy } from 'valtio/utils'; import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud'; +import colors from '@/con/colors'; function DetailPengajar() { - const detailState = useProxy(infoSekolahPaud.pengajar) - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) - const params = useParams() - const router = useRouter() + const detailState = useProxy(infoSekolahPaud.pengajar); + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); + const params = useParams(); + const router = useRouter(); useShallowEffect(() => { - detailState.findUnique.load(params?.id as string) - }, []) - + detailState.findUnique.load(params?.id as string); + }, []); const handleHapus = () => { if (selectedId) { - detailState.delete.byId(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/pendidikan/info-sekolah/pengajar") + detailState.delete.byId(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push("/admin/pendidikan/info-sekolah/pengajar"); } - } + }; if (!detailState.findUnique.data) { return ( - + - ) + ); } + const data = detailState.findUnique.data; + return ( - - - - - - - Detail Pengajar - {detailState.findUnique.data ? ( - - - - Nama - {detailState.findUnique.data?.nama} - - - Lembaga - {detailState.findUnique.data?.lembaga?.nama} - - + + + + + + + Detail Pengajar + + + + + + Nama + {data.nama || '-'} + + + + Lembaga + {data.lembaga?.nama || '-'} + + + + + + + - - - - ) : null} + + + + - {/* Modal Konfirmasi Hapus */} setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus pengajar ini?' + text="Apakah Anda yakin ingin menghapus pengajar ini?" /> ); } -export default DetailPengajar; \ No newline at end of file +export default DetailPengajar; diff --git a/src/app/admin/(dashboard)/pendidikan/info-sekolah/pengajar/create/page.tsx b/src/app/admin/(dashboard)/pendidikan/info-sekolah/pengajar/create/page.tsx index d7504f39..61abd8ef 100644 --- a/src/app/admin/(dashboard)/pendidikan/info-sekolah/pengajar/create/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/info-sekolah/pengajar/create/page.tsx @@ -1,17 +1,28 @@ /* eslint-disable react-hooks/exhaustive-deps */ -'use client' +'use client'; import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Select, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; import { useProxy } from 'valtio/utils'; - +import { toast } from 'react-toastify'; function CreatePengajar() { const router = useRouter(); - const stateCreate = useProxy(infoSekolahPaud.pengajar) + const stateCreate = useProxy(infoSekolahPaud.pengajar); useEffect(() => { stateCreate.findMany.load(); @@ -20,51 +31,81 @@ function CreatePengajar() { const resetForm = () => { stateCreate.create.form = { - nama: "", - lembagaId: "", + nama: '', + lembagaId: '', }; }; + const handleSubmit = async () => { + if (!stateCreate.create.form.nama || !stateCreate.create.form.lembagaId) { + return toast.warn('Nama dan Lembaga wajib diisi!'); + } + await stateCreate.create.create(); resetForm(); - router.push("/admin/pendidikan/info-sekolah/pengajar") - } - return ( - - - - + router.push('/admin/pendidikan/info-sekolah/pengajar'); + }; - - - Create Pengajar + return ( + + {/* Header Back + Title */} + + + + + + Tambah Pengajar + + + + {/* Card Form */} + + Nama} + placeholder="Masukkan nama pengajar" value={stateCreate.create.form.nama} - onChange={(val) => { - stateCreate.create.form.nama = val.target.value; - }} - label={Nama} - placeholder='Masukkan nama' + onChange={(e) => (stateCreate.create.form.nama = e.target.value)} + required /> + ({ - value: k.id, - label: k.nama - })) ?? []} + label={Lembaga Pendidikan} + placeholder="Pilih lembaga pendidikan" + data={ + infoSekolahPaud.lembagaPendidikan.findMany.data?.map((k) => ({ + value: k.id, + label: k.nama, + })) ?? [] + } value={formData.lembagaId} onChange={(val) => setFormData({ ...formData, lembagaId: val ?? '' })} + required /> - + + + + diff --git a/src/app/admin/(dashboard)/pendidikan/info-sekolah/siswa/[id]/page.tsx b/src/app/admin/(dashboard)/pendidikan/info-sekolah/siswa/[id]/page.tsx index 605b82e5..56502739 100644 --- a/src/app/admin/(dashboard)/pendidikan/info-sekolah/siswa/[id]/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/info-sekolah/siswa/[id]/page.tsx @@ -1,16 +1,14 @@ 'use client' -import { useProxy } from 'valtio/utils'; +import { Box, Button, Group, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core' +import { useShallowEffect } from '@mantine/hooks' +import { IconArrowBack, IconEdit, IconTrash } from '@tabler/icons-react' +import { useParams, useRouter } from 'next/navigation' +import { useState } from 'react' +import { useProxy } from 'valtio/utils' -import { Box, Button, Flex, Paper, Skeleton, Stack, Text } from '@mantine/core'; -import { useShallowEffect } from '@mantine/hooks'; -import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; -import { useParams, useRouter } from 'next/navigation'; -import { useState } from 'react'; - -import colors from '@/con/colors'; - -import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; -import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud'; +import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus' +import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud' +import colors from '@/con/colors' function DetailSiswa() { const detailState = useProxy(infoSekolahPaud.siswa) @@ -23,7 +21,6 @@ function DetailSiswa() { detailState.findUnique.load(params?.id as string) }, []) - const handleHapus = () => { if (selectedId) { detailState.delete.byId(selectedId) @@ -36,60 +33,83 @@ function DetailSiswa() { if (!detailState.findUnique.data) { return ( - + ) } + const data = detailState.findUnique.data + return ( - - - - - - - Detail Siswa - {detailState.findUnique.data ? ( - - - - Nama - {detailState.findUnique.data?.nama} - - - Lembaga - {detailState.findUnique.data?.lembaga?.nama} - - + + {/* Tombol Kembali */} + + + {/* Card Utama */} + + + + Detail Siswa + + + + + + Nama + {data?.nama || '-'} + + + + Lembaga + {data?.lembaga?.nama || '-'} + + + + + + + - - - - ) : null} + + + + @@ -98,10 +118,10 @@ function DetailSiswa() { opened={modalHapus} onClose={() => setModalHapus(false)} onConfirm={handleHapus} - text='Apakah anda yakin ingin menghapus siswa ini?' + text="Apakah Anda yakin ingin menghapus siswa ini?" /> - ); + ) } -export default DetailSiswa; \ No newline at end of file +export default DetailSiswa diff --git a/src/app/admin/(dashboard)/pendidikan/info-sekolah/siswa/create/page.tsx b/src/app/admin/(dashboard)/pendidikan/info-sekolah/siswa/create/page.tsx index 927fb9ed..7dd841a5 100644 --- a/src/app/admin/(dashboard)/pendidikan/info-sekolah/siswa/create/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/info-sekolah/siswa/create/page.tsx @@ -2,16 +2,26 @@ 'use client' import infoSekolahPaud from '@/app/admin/(dashboard)/_state/pendidikan/info-sekolah-paud'; import colors from '@/con/colors'; -import { Box, Button, Group, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core'; +import { + Box, + Button, + Group, + Paper, + Select, + Stack, + Text, + TextInput, + Title, + Tooltip, +} from '@mantine/core'; import { IconArrowBack } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; import { useProxy } from 'valtio/utils'; - function CreateSiswa() { const router = useRouter(); - const stateCreate = useProxy(infoSekolahPaud.siswa) + const stateCreate = useProxy(infoSekolahPaud.siswa); useEffect(() => { stateCreate.findMany.load(); @@ -20,41 +30,58 @@ function CreateSiswa() { const resetForm = () => { stateCreate.create.form = { - nama: "", - lembagaId: "", + nama: '', + lembagaId: '', }; }; + const handleSubmit = async () => { await stateCreate.create.create(); resetForm(); - router.push("/admin/pendidikan/info-sekolah/siswa") - } - return ( - - - - + router.push('/admin/pendidikan/info-sekolah/siswa'); + }; - - - Create Siswa + return ( + + {/* Header + Back Button */} + + + + + + Tambah Siswa + + + + {/* Form Card */} + + { stateCreate.create.form.nama = val.target.value; }} - label={Nama} - placeholder='Masukkan nama' + label={Nama} + placeholder="Masukkan nama siswa" + required /> + setFormData({ ...formData, kategoriId: val || "" })} - label={Kategori} + label="Kategori" placeholder='Pilih kategori' - data={ - perpustakaanDigitalState.kategoriBuku.findMany.data?.map((v) => ({ - value: v.id, - label: v.name - })) || [] - } + data={perpustakaanDigitalState.kategoriBuku.findMany.data?.map(v => ({ value: v.id, label: v.name })) || []} clearable searchable required error={!formData.kategoriId ? "Pilih kategori" : undefined} /> - - + {/* Submit Button */} + + @@ -195,4 +184,4 @@ function EditPasarDesa() { ); } -export default EditPasarDesa; +export default EditPerpustakaanDigital; diff --git a/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/data-perpustakaan/[id]/page.tsx b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/data-perpustakaan/[id]/page.tsx index bda78de7..8bd13b9d 100644 --- a/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/data-perpustakaan/[id]/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/data-perpustakaan/[id]/page.tsx @@ -2,7 +2,7 @@ import { ModalKonfirmasiHapus } from '@/app/admin/(dashboard)/_com/modalKonfirmasiHapus'; import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital'; import colors from '@/con/colors'; -import { Box, Button, Flex, Image, Paper, Skeleton, Stack, Text } from '@mantine/core'; +import { Box, Button, Group, Image, Paper, Skeleton, Stack, Text, Tooltip } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowBack, IconEdit, IconX } from '@tabler/icons-react'; import { useParams, useRouter } from 'next/navigation'; @@ -10,97 +10,130 @@ import { useState } from 'react'; import { useProxy } from 'valtio/utils'; function DetailDataPerpustakaan() { - const stateDetail = useProxy(perpustakaanDigitalState.dataPerpustakaan) - const [modalHapus, setModalHapus] = useState(false) - const [selectedId, setSelectedId] = useState(null) - const params = useParams() + const stateDetail = useProxy(perpustakaanDigitalState.dataPerpustakaan); + const [modalHapus, setModalHapus] = useState(false); + const [selectedId, setSelectedId] = useState(null); + const params = useParams(); const router = useRouter(); useShallowEffect(() => { - stateDetail.findUnique.load(params?.id as string) - }, []) + stateDetail.findUnique.load(params?.id as string); + }, []); const handleHapus = () => { if (selectedId) { - stateDetail.delete.delete(selectedId) - setModalHapus(false) - setSelectedId(null) - router.push("/admin/pendidikan/perpustakaan-digital/data-perpustakaan") + stateDetail.delete.delete(selectedId); + setModalHapus(false); + setSelectedId(null); + router.push("/admin/pendidikan/perpustakaan-digital/data-perpustakaan"); } - } + }; if (!stateDetail.findUnique.data) { return ( - + - ) + ); } + const data = stateDetail.findUnique.data; + return ( - - - - - - - Detail Data Perpustakaan - - + + + + + + + Detail Data Perpustakaan + + + + - Judul - {stateDetail.findUnique.data?.judul} + Judul + {data.judul || '-'} + - Deskripsi - + Deskripsi + + - Kategori - {stateDetail.findUnique.data?.kategori.name} + Kategori + {data.kategori?.name || '-'} + - Gambar - gambar + Gambar + {data.image?.link ? ( + {data.judul + ) : ( + Tidak ada gambar + )} - - + + + + + + - - + + - {/* Modal Hapus */} setModalHapus(false)} onConfirm={handleHapus} - text="Apakah anda yakin ingin menghapus data ini?" + text="Apakah Anda yakin ingin menghapus data ini?" /> ); diff --git a/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/data-perpustakaan/create/page.tsx b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/data-perpustakaan/create/page.tsx index ede1518b..8f51ec81 100644 --- a/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/data-perpustakaan/create/page.tsx +++ b/src/app/admin/(dashboard)/pendidikan/perpustakaan-digital/data-perpustakaan/create/page.tsx @@ -3,7 +3,7 @@ import CreateEditor from '@/app/admin/(dashboard)/_com/createEditor'; import perpustakaanDigitalState from '@/app/admin/(dashboard)/_state/pendidikan/perpustakaan-digital'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; -import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title } from '@mantine/core'; +import { Box, Button, Group, Image, Paper, Select, Stack, Text, TextInput, Title, Tooltip } from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; import { useRouter } from 'next/navigation'; @@ -11,7 +11,6 @@ import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; - function CreateDataPerpustakaan() { const createState = useProxy(perpustakaanDigitalState.dataPerpustakaan) const router = useRouter(); @@ -29,7 +28,6 @@ function CreateDataPerpustakaan() { imageId: "", kategoriId: "", }; - setPreviewImage(null) setFile(null) }; @@ -56,103 +54,119 @@ function CreateDataPerpustakaan() { }; return ( - - - - + + {/* Tombol Kembali */} + + + + + + Tambah Data Perpustakaan + + - - - Create Data Perpustakaan + + + {/* Judul */} Judul} + label={Judul} placeholder='Masukkan judul' value={createState.create.form.judul} onChange={(val) => { createState.create.form.judul = val.target.value; }} + required /> + + {/* Deskripsi */} - Deskripsi + Deskripsi { - createState.create.form.deskripsi = val; - }} + onChange={(val) => { createState.create.form.deskripsi = val; }} /> + + {/* Kategori */}