Nico 20 Nov 25
Dibagian layout admin sudah disesuaikan dengan rolenya : supadmin, admin desa, admin kesehatan, admin pendidikan Fix API User & Role Admin
This commit is contained in:
@@ -1,23 +1,26 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"id": "role-1",
|
"id": "0",
|
||||||
"name": "ADMIN DESA",
|
"name": "SUPER ADMIN",
|
||||||
"description": "Administrator Desa",
|
"description": "Administrator",
|
||||||
"permissions": ["manage_users", "manage_content", "view_reports"],
|
"isActive": true
|
||||||
"isActive": true
|
},
|
||||||
},
|
{
|
||||||
{
|
"id": "1",
|
||||||
"id": "role-2",
|
"name": "ADMIN DESA",
|
||||||
"name": "ADMIN KESEHATAN",
|
"description": "Administrator Desa",
|
||||||
"description": "Administrator Bidang Kesehatan",
|
"isActive": true
|
||||||
"permissions": ["manage_health_data", "view_reports"],
|
},
|
||||||
"isActive": true
|
{
|
||||||
},
|
"id": "2",
|
||||||
{
|
"name": "ADMIN KESEHATAN",
|
||||||
"id": "role-3",
|
"description": "Administrator Bidang Kesehatan",
|
||||||
"name": "ADMIN SEKOLAH",
|
"isActive": true
|
||||||
"description": "Administrator Sekolah",
|
},
|
||||||
"permissions": ["manage_school_data", "view_reports"],
|
{
|
||||||
"isActive": true
|
"id": "3",
|
||||||
}
|
"name": "ADMIN PENDIDIKAN",
|
||||||
]
|
"description": "Administrator Bidang Pendidikan",
|
||||||
|
"isActive": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"id": "user-1",
|
|
||||||
"nama": "Admin Desa",
|
|
||||||
"nomor": "089647037426",
|
|
||||||
"roleId": "role-1",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "user-2",
|
|
||||||
"nama": "Admin Kesehatan",
|
|
||||||
"nomor": "082339004198",
|
|
||||||
"roleId": "role-2",
|
|
||||||
"isActive": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "user-3",
|
|
||||||
"nama": "Admin Sekolah",
|
|
||||||
"nomor": "085237157222",
|
|
||||||
"roleId": "role-3",
|
|
||||||
"isActive": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -2181,7 +2181,6 @@ model Role {
|
|||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique // ADMIN_DESA, ADMIN_KESEHATAN, ADMIN_SEKOLAH
|
name String @unique // ADMIN_DESA, ADMIN_KESEHATAN, ADMIN_SEKOLAH
|
||||||
description String?
|
description String?
|
||||||
permissions Json // Menyimpan permission dalam format JSON
|
|
||||||
isActive Boolean @default(true)
|
isActive Boolean @default(true)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
@@ -2200,17 +2199,6 @@ model KodeOtp {
|
|||||||
otp Int
|
otp Int
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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")
|
|
||||||
}
|
|
||||||
|
|
||||||
model UserSession {
|
model UserSession {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
token String
|
token String
|
||||||
|
|||||||
@@ -54,14 +54,13 @@ import tujuanProgram2 from "./data/pendidikan/pendidikan-non-formal/tujuan-progr
|
|||||||
import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json";
|
import programUnggulan from "./data/pendidikan/program-pendidikan-anak/program-unggulan.json";
|
||||||
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
import tujuanProgram from "./data/pendidikan/program-pendidikan-anak/tujuan-program.json";
|
||||||
import roles from "./data/user/roles.json";
|
import roles from "./data/user/roles.json";
|
||||||
import users from "./data/user/users.json";
|
|
||||||
import fileStorage from "./data/file-storage.json";
|
import fileStorage from "./data/file-storage.json";
|
||||||
import jenjangPendidikan from "./data/pendidikan/info-sekolah/jenjang-pendidikan.json";
|
import jenjangPendidikan from "./data/pendidikan/info-sekolah/jenjang-pendidikan.json";
|
||||||
import seedAssets from "./seed_assets";
|
import seedAssets from "./seed_assets";
|
||||||
import { safeSeedUnique } from "./safeseedUnique";
|
import { safeSeedUnique } from "./safeseedUnique";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// =========== USER & ROLE ===========
|
// =========== ROLE ===========
|
||||||
// In your seed.ts
|
// In your seed.ts
|
||||||
// =========== ROLES ===========
|
// =========== ROLES ===========
|
||||||
console.log("🔄 Seeding roles...");
|
console.log("🔄 Seeding roles...");
|
||||||
@@ -69,35 +68,12 @@ import { safeSeedUnique } from "./safeseedUnique";
|
|||||||
await safeSeedUnique("role", { id: r.id }, {
|
await safeSeedUnique("role", { id: r.id }, {
|
||||||
name: r.name,
|
name: r.name,
|
||||||
description: r.description,
|
description: r.description,
|
||||||
permissions: r.permissions,
|
|
||||||
isActive: r.isActive,
|
isActive: r.isActive,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("✅ Roles seeded");
|
console.log("✅ Roles seeded");
|
||||||
|
|
||||||
// =========== USERS ===========
|
|
||||||
console.log("🔄 Seeding users...");
|
|
||||||
for (const u of users) {
|
|
||||||
// First verify the role exists
|
|
||||||
const roleExists = await prisma.role.findUnique({
|
|
||||||
where: { id: u.roleId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!roleExists) {
|
|
||||||
console.error(`❌ Role with id ${u.roleId} not found for user ${u.nama}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
await safeSeedUnique("user", { id: u.id }, {
|
|
||||||
username: u.nama,
|
|
||||||
nomor: u.nomor,
|
|
||||||
roleId: u.roleId,
|
|
||||||
isActive: u.isActive,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log("✅ Users seeded");
|
|
||||||
|
|
||||||
// =========== FILE STORAGE ===========
|
// =========== FILE STORAGE ===========
|
||||||
console.log("🔄 Seeding file storage...");
|
console.log("🔄 Seeding file storage...");
|
||||||
for (const f of fileStorage) {
|
for (const f of fileStorage) {
|
||||||
|
|||||||
@@ -90,27 +90,35 @@ const userState = proxy({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
updateActive: {
|
update: {
|
||||||
loading: false,
|
loading: false,
|
||||||
async submit(id: string, isActive: boolean) {
|
|
||||||
|
async submit(payload: { id: string; isActive?: boolean; roleId?: string }) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/user/updt`, {
|
const res = await fetch(`/api/user/updt`, {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ id, isActive }),
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (res.status === 200 && data.success) {
|
if (res.status === 200 && data.success) {
|
||||||
toast.success(data.message);
|
toast.success(data.message);
|
||||||
userState.findMany.load(userState.findMany.page, 10, userState.findMany.search);
|
|
||||||
|
// refresh list
|
||||||
|
userState.findMany.load(
|
||||||
|
userState.findMany.page,
|
||||||
|
10,
|
||||||
|
userState.findMany.search
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
toast.error(data.message || "Gagal update status user");
|
toast.error(data.message || "Gagal update user");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
toast.error("Gagal update status user");
|
toast.error("Gagal update user");
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@@ -120,12 +128,10 @@ const userState = proxy({
|
|||||||
|
|
||||||
const templateRole = z.object({
|
const templateRole = z.object({
|
||||||
name: z.string().min(1, "Nama harus diisi"),
|
name: z.string().min(1, "Nama harus diisi"),
|
||||||
permissions: z.array(z.string()).min(1, "Permission harus diisi"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultRole = {
|
const defaultRole = {
|
||||||
name: "",
|
name: "",
|
||||||
permissions: [] as string[],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const roleState = proxy({
|
const roleState = proxy({
|
||||||
@@ -237,7 +243,7 @@ const roleState = proxy({
|
|||||||
toast.warn("ID tidak valid");
|
toast.warn("ID tidak valid");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/role/${id}`, {
|
const response = await fetch(`/api/role/${id}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -245,31 +251,25 @@ const roleState = proxy({
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (result?.success) {
|
if (result?.success) {
|
||||||
const data = result.data;
|
const data = result.data;
|
||||||
this.id = data.id;
|
|
||||||
this.form = {
|
// langsung set melalui root state, bukan this
|
||||||
|
roleState.update.id = data.id;
|
||||||
|
roleState.update.form = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
permissions: data.permissions,
|
|
||||||
};
|
};
|
||||||
return data; // Return the loaded data
|
|
||||||
} else {
|
return data;
|
||||||
throw new Error(result?.message || "Gagal memuat data");
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading role:", error);
|
console.error("Error loading role:", error);
|
||||||
toast.error(
|
toast.error("Gagal memuat data");
|
||||||
error instanceof Error ? error.message : "Gagal memuat data"
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async update() {
|
async update() {
|
||||||
const cek = templateRole.safeParse(roleState.update.form);
|
const cek = templateRole.safeParse(roleState.update.form);
|
||||||
if (!cek.success) {
|
if (!cek.success) {
|
||||||
@@ -290,7 +290,6 @@ const roleState = proxy({
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name: this.form.name,
|
name: this.form.name,
|
||||||
permissions: this.form.permissions,
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Box, Button, Loader, Paper, PinInput, Stack, Text, Title } from '@manti
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import { authStore } from '@/store/authStore';
|
||||||
|
|
||||||
export default function Validasi() {
|
export default function Validasi() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -53,10 +54,17 @@ export default function Validasi() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
const verifyResult = await apiFetchVerifyOtp({ nomor, otp, kodeId });
|
const verifyResult = await apiFetchVerifyOtp({ nomor, otp, kodeId });
|
||||||
|
|
||||||
if (verifyResult.success) {
|
if (verifyResult.success && verifyResult.user) {
|
||||||
|
// ✅ SET USER KE STORE
|
||||||
|
authStore.setUser({
|
||||||
|
id: verifyResult.user.id,
|
||||||
|
name: verifyResult.user.name,
|
||||||
|
roleId: Number(verifyResult.user.roleId),
|
||||||
|
});
|
||||||
|
|
||||||
cleanupStorage();
|
cleanupStorage();
|
||||||
router.push('/admin/landing-page/profil/program-inovasi');
|
router.push('/admin/landing-page/profil/program-inovasi');
|
||||||
return; // ✅ HENTIKAN eksekusi di sini
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hanya coba registrasi jika akun tidak ditemukan
|
// Hanya coba registrasi jika akun tidak ditemukan
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Loader, Group, MultiSelect, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
import { Box, Button, Group, Loader, Paper, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
@@ -9,6 +10,7 @@ import { toast } from 'react-toastify';
|
|||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import user from '../../../_state/user/user-state';
|
import user from '../../../_state/user/user-state';
|
||||||
|
|
||||||
|
|
||||||
function EditRole() {
|
function EditRole() {
|
||||||
const stateRole = useProxy(user.roleState);
|
const stateRole = useProxy(user.roleState);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -17,46 +19,37 @@ function EditRole() {
|
|||||||
// Controlled local state
|
// Controlled local state
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
permissions: [] as string[],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [originalData, setOriginalData] = useState({
|
const [originalData, setOriginalData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
permissions: [] as string[],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
// Load role data
|
// Load role data
|
||||||
const loadRole = useCallback(async (id: string) => {
|
const loadRole = useCallback(async (id: string) => {
|
||||||
try {
|
const data = await stateRole.update.load(id);
|
||||||
const data = await stateRole.update.load(id);
|
|
||||||
if (data) {
|
if (data) {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: data.name || '',
|
name: data.name ?? '',
|
||||||
permissions: data.permissions || [],
|
});
|
||||||
});
|
|
||||||
setOriginalData({
|
setOriginalData({
|
||||||
name: data.name || '',
|
name: data.name ?? '',
|
||||||
permissions: data.permissions || [],
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading role:', error);
|
|
||||||
toast.error(error instanceof Error ? error.message : 'Gagal mengambil data role');
|
|
||||||
}
|
}
|
||||||
}, [stateRole.update]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
stateRole.findMany.load(); // Load permissions/options
|
stateRole.findMany.load(); // load permission
|
||||||
const id = params?.id as string;
|
if (params?.id) loadRole(params.id as string);
|
||||||
if (id) loadRole(id);
|
}, [params?.id]);
|
||||||
}, [params?.id, loadRole, stateRole.findMany]);
|
|
||||||
|
|
||||||
const handleResetForm = () => {
|
const handleResetForm = () => {
|
||||||
setFormData({
|
setFormData({
|
||||||
name: originalData.name,
|
name: originalData.name,
|
||||||
permissions: originalData.permissions,
|
|
||||||
});
|
});
|
||||||
toast.info("Form dikembalikan ke data awal");
|
toast.info("Form dikembalikan ke data awal");
|
||||||
};
|
};
|
||||||
@@ -66,10 +59,6 @@ function EditRole() {
|
|||||||
toast.error('Nama role tidak boleh kosong');
|
toast.error('Nama role tidak boleh kosong');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!formData.permissions.length) {
|
|
||||||
toast.error('Pilih minimal satu permission');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
@@ -77,7 +66,6 @@ function EditRole() {
|
|||||||
stateRole.update.form = {
|
stateRole.update.form = {
|
||||||
...stateRole.update.form,
|
...stateRole.update.form,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
permissions: formData.permissions,
|
|
||||||
};
|
};
|
||||||
await stateRole.update.update();
|
await stateRole.update.update();
|
||||||
toast.success('Role berhasil diperbarui!');
|
toast.success('Role berhasil diperbarui!');
|
||||||
@@ -116,24 +104,7 @@ function EditRole() {
|
|||||||
label={<Text fw="bold" fz="sm">Nama Role</Text>}
|
label={<Text fw="bold" fz="sm">Nama Role</Text>}
|
||||||
placeholder="Masukkan nama role"
|
placeholder="Masukkan nama role"
|
||||||
/>
|
/>
|
||||||
<MultiSelect
|
<Group justify="right">
|
||||||
value={formData.permissions}
|
|
||||||
onChange={(val) => setFormData({ ...formData, permissions: val })}
|
|
||||||
label={<Text fw="bold" fz="sm">Permission</Text>}
|
|
||||||
placeholder="Pilih permission"
|
|
||||||
data={
|
|
||||||
stateRole.findMany.data?.map((v) => ({
|
|
||||||
value: v.id,
|
|
||||||
label: v.name,
|
|
||||||
})) || []
|
|
||||||
}
|
|
||||||
clearable
|
|
||||||
searchable
|
|
||||||
required
|
|
||||||
error={!formData.permissions.length ? 'Pilih minimal satu permission' : undefined}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Group justify="right">
|
|
||||||
{/* Tombol Batal */}
|
{/* Tombol Batal */}
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -5,9 +5,8 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Group,
|
Group,
|
||||||
MultiSelect,
|
|
||||||
Paper,
|
|
||||||
Loader,
|
Loader,
|
||||||
|
Paper,
|
||||||
Stack,
|
Stack,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title
|
Title
|
||||||
@@ -15,9 +14,9 @@ import {
|
|||||||
import { IconArrowBack } from '@tabler/icons-react';
|
import { IconArrowBack } from '@tabler/icons-react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import user from '../../../_state/user/user-state';
|
import user from '../../../_state/user/user-state';
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
|
|
||||||
|
|
||||||
export default function CreateRole() {
|
export default function CreateRole() {
|
||||||
@@ -31,8 +30,7 @@ export default function CreateRole() {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
stateRole.create.form = {
|
stateRole.create.form = {
|
||||||
name: '',
|
name: ''
|
||||||
permissions: [],
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,28 +78,6 @@ export default function CreateRole() {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
|
||||||
<MultiSelect
|
|
||||||
label="Permission"
|
|
||||||
placeholder="Pilih permission"
|
|
||||||
data={
|
|
||||||
Array.from(
|
|
||||||
new Set(
|
|
||||||
stateRole.findMany.data
|
|
||||||
?.map((item) => item.permissions)
|
|
||||||
.flat()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.filter((p): p is string => typeof p === 'string')
|
|
||||||
.map((p) => ({ label: p, value: p }))
|
|
||||||
}
|
|
||||||
value={stateRole.create.form.permissions}
|
|
||||||
onChange={(value) => (stateRole.create.form.permissions = value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Group justify="right">
|
<Group justify="right">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import colors from '@/con/colors';
|
import colors from '@/con/colors';
|
||||||
import { Box, Button, Center, Group, Pagination, Paper, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
import { Box, Button, Center, Group, Pagination, Paper, Select, Skeleton, Stack, Table, TableTbody, TableTd, TableTh, TableThead, TableTr, Text, Title, Tooltip } from '@mantine/core';
|
||||||
import { useShallowEffect } from '@mantine/hooks';
|
import { useShallowEffect } from '@mantine/hooks';
|
||||||
import { IconCheck, IconSearch, IconX } from '@tabler/icons-react';
|
import { IconCheck, IconSearch, IconX } from '@tabler/icons-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -28,6 +28,7 @@ function User() {
|
|||||||
|
|
||||||
function ListUser({ search }: { search: string }) {
|
function ListUser({ search }: { search: string }) {
|
||||||
const stateUser = useProxy(user.userState)
|
const stateUser = useProxy(user.userState)
|
||||||
|
const stateRole = useProxy(user.roleState)
|
||||||
const [modalHapus, setModalHapus] = useState(false)
|
const [modalHapus, setModalHapus] = useState(false)
|
||||||
const [selectedId, setSelectedId] = useState<string | null>(null)
|
const [selectedId, setSelectedId] = useState<string | null>(null)
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ function ListUser({ search }: { search: string }) {
|
|||||||
|
|
||||||
|
|
||||||
useShallowEffect(() => {
|
useShallowEffect(() => {
|
||||||
|
stateRole.findMany.load()
|
||||||
load(page, 10, search)
|
load(page, 10, search)
|
||||||
}, [page, search])
|
}, [page, search])
|
||||||
|
|
||||||
@@ -92,11 +94,31 @@ function ListUser({ search }: { search: string }) {
|
|||||||
{item.nomor}
|
{item.nomor}
|
||||||
</Text>
|
</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd style={{ width: '20%', }}>
|
<TableTd style={{ width: '20%' }}>
|
||||||
<Text truncate fz="sm" c="dimmed">
|
<Select
|
||||||
{item.role.name}
|
placeholder="Pilih role"
|
||||||
</Text>
|
data={stateRole.findMany.data.map((r) => ({
|
||||||
|
label: r.name,
|
||||||
|
value: r.id,
|
||||||
|
}))}
|
||||||
|
value={item.roleId} // ⬅ role milik user ini
|
||||||
|
onChange={async (val) => {
|
||||||
|
if (!val) return;
|
||||||
|
|
||||||
|
await stateUser.update.submit({
|
||||||
|
id: item.id,
|
||||||
|
roleId: val, // ⬅ kirim roleId
|
||||||
|
});
|
||||||
|
|
||||||
|
// reload data supaya UI up-to-date
|
||||||
|
stateUser.findMany.load(page, 10, search);
|
||||||
|
}}
|
||||||
|
searchable
|
||||||
|
clearable={false} // role harus ada
|
||||||
|
nothingFoundMessage="Role tidak ditemukan"
|
||||||
|
/>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
|
|
||||||
<TableTd style={{ width: '15%' }}>
|
<TableTd style={{ width: '15%' }}>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={item.isActive ? "Nonaktifkan user" : "Aktifkan user"}
|
label={item.isActive ? "Nonaktifkan user" : "Aktifkan user"}
|
||||||
@@ -106,8 +128,12 @@ function ListUser({ search }: { search: string }) {
|
|||||||
variant="light"
|
variant="light"
|
||||||
color={item.isActive ? "green" : "red"}
|
color={item.isActive ? "green" : "red"}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await stateUser.updateActive.submit(item.id, !item.isActive)
|
await stateUser.update.submit({
|
||||||
stateUser.findMany.load(page, 10, search)
|
id: item.id,
|
||||||
|
isActive: !item.isActive, // toggle
|
||||||
|
});
|
||||||
|
|
||||||
|
stateUser.findMany.load(page, 10, search);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.isActive ? <IconCheck size={20} /> : <IconX size={20} />}
|
{item.isActive ? <IconCheck size={20} /> : <IconX size={20} />}
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ export const navBar = [
|
|||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "Desa",
|
id: "Desa",
|
||||||
name: "Desa",
|
name: "Desa",
|
||||||
@@ -387,12 +386,377 @@ export const navBar = [
|
|||||||
{
|
{
|
||||||
id: "User",
|
id: "User",
|
||||||
name: "User",
|
name: "User",
|
||||||
path: "/admin/user/user"
|
path: "/admin/user&role/user"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Role",
|
id: "Role",
|
||||||
name: "Role",
|
name: "Role",
|
||||||
path: "/admin/role/role"
|
path: "/admin/user&role/role"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const role1 = [
|
||||||
|
{
|
||||||
|
id: "Landing Page",
|
||||||
|
name: "Landing Page",
|
||||||
|
path: "",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "Landing_Page_1",
|
||||||
|
name: "Profil",
|
||||||
|
path: "/admin/landing-page/profil/program-inovasi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Landing_Page_2",
|
||||||
|
name: "Desa Anti Korupsi",
|
||||||
|
path: "/admin/landing-page/desa-anti-korupsi/list-desa-anti-korupsi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Landing_Page_3",
|
||||||
|
name: "Indeks Kepuasan Masyarakat",
|
||||||
|
path: "/admin/landing-page/indeks-kepuasan-masyarakat/grafik-kepuasan-masyarakat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Landing_Page_4",
|
||||||
|
name: "SDGs",
|
||||||
|
path: "/admin/landing-page/SDGs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Landing_Page_5",
|
||||||
|
name: "APBDes",
|
||||||
|
path: "/admin/landing-page/apbdes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Landing_Page_6",
|
||||||
|
name: "Prestasi Desa",
|
||||||
|
path: "/admin/landing-page/prestasi-desa/list-prestasi-desa"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "PPID",
|
||||||
|
name: "PPID",
|
||||||
|
path: "",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "PPID_1",
|
||||||
|
name: "Profil PPID",
|
||||||
|
path: "/admin/ppid/profil-ppid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "PPID_2",
|
||||||
|
name: "Struktur PPID",
|
||||||
|
path: "/admin/ppid/struktur-ppid/pegawai"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "PPID_3",
|
||||||
|
name: "Visi Misi PPID",
|
||||||
|
path: "/admin/ppid/visi-misi-ppid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "PPID_4",
|
||||||
|
name: "Dasar Hukum",
|
||||||
|
path: "/admin/ppid/dasar-hukum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "PPID_5",
|
||||||
|
name: "Permohonan Informasi Publik",
|
||||||
|
path: "/admin/ppid/permohonan-informasi-publik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "PPID_6",
|
||||||
|
name: "Permohonan Keberatan Informasi Publik",
|
||||||
|
path: "/admin/ppid/permohonan-keberatan-informasi-publik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "PPID_7",
|
||||||
|
name: "Daftar Informasi Publik",
|
||||||
|
path: "/admin/ppid/daftar-informasi-publik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "PPID_8",
|
||||||
|
name: "Indeks Kepuasan Masyarakat",
|
||||||
|
path: "/admin/ppid/indeks-kepuasan-masyarakat/grafik-kepuasan-masyarakat"
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa",
|
||||||
|
name: "Desa",
|
||||||
|
path: "",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "Desa_1",
|
||||||
|
name: "Profile",
|
||||||
|
path: "/admin/desa/profile/profile-desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_2",
|
||||||
|
name: "Potensi",
|
||||||
|
path: "/admin/desa/potensi/list-potensi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_3",
|
||||||
|
name: "Berita",
|
||||||
|
path: "/admin/desa/berita/list-berita"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_4",
|
||||||
|
name: "Pengumuman",
|
||||||
|
path: "/admin/desa/pengumuman/list-pengumuman"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_5",
|
||||||
|
name: "Gallery",
|
||||||
|
path: "/admin/desa/gallery/foto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_6",
|
||||||
|
name: "Layanan",
|
||||||
|
path: "/admin/desa/layanan/pelayanan_surat_keterangan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Desa_7",
|
||||||
|
name: "Penghargaan",
|
||||||
|
path: "/admin/desa/penghargaan"
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Keamanan",
|
||||||
|
name: "Keamanan",
|
||||||
|
path: "",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "Keamanan_1",
|
||||||
|
name: "Keamanan Lingkungan (Pecalang/Patwal)",
|
||||||
|
path: "/admin/keamanan/keamanan-lingkungan-pecalang-patwal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Keamanan_2",
|
||||||
|
name: "Polsek Terdekat",
|
||||||
|
path: "/admin/keamanan/polsek-terdekat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Keamanan_3",
|
||||||
|
name: "Kontak Darurat",
|
||||||
|
path: "/admin/keamanan/kontak-darurat/kontak-darurat-keamanan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Keamanan_4",
|
||||||
|
name: "Pencegahan Kriminalitas",
|
||||||
|
path: "/admin/keamanan/pencegahan-kriminalitas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Keamanan_5",
|
||||||
|
name: "Laporan Publik",
|
||||||
|
path: "/admin/keamanan/laporan-publik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Keamanan_6",
|
||||||
|
name: "Tips Keamanan",
|
||||||
|
path: "/admin/keamanan/tips-keamanan"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi",
|
||||||
|
name: "Ekonomi",
|
||||||
|
path: "",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "Ekonomi_1",
|
||||||
|
name: "Pasar Desa",
|
||||||
|
path: "/admin/ekonomi/pasar-desa/produk-pasar-desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi_2",
|
||||||
|
name: "Lowongan Kerja Lokal",
|
||||||
|
path: "/admin/ekonomi/lowongan-kerja-lokal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi_3",
|
||||||
|
name: "Struktur Organisasi Dan Sk Pengurus Bumdesa",
|
||||||
|
path: "/admin/ekonomi/struktur-organisasi-dan-sk-pengurus-bumdesa/pegawai"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi_4",
|
||||||
|
name: "PADesa (Pendapatan Asli Desa)",
|
||||||
|
path: "/admin/ekonomi/PADesa-pendapatan-asli-desa/apbdesa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi_5",
|
||||||
|
name: "Jumlah Pengangguran",
|
||||||
|
path: "/admin/ekonomi/jumlah-pengangguran"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi_6",
|
||||||
|
name: "Jumlah penduduk usia kerja yang menganggur",
|
||||||
|
path: "/admin/ekonomi/jumlah-penduduk-usia-kerja-yang-menganggur/pengangguran_berdasarkan_usia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi_7",
|
||||||
|
name: "Jumlah Penduduk Miskin",
|
||||||
|
path: "/admin/ekonomi/jumlah-penduduk-miskin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi_8",
|
||||||
|
name: "Program Kemiskinan",
|
||||||
|
path: "/admin/ekonomi/program-kemiskinan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi_9",
|
||||||
|
name: "Sektor Unggulan Desa",
|
||||||
|
path: "/admin/ekonomi/sektor-unggulan-desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Ekonomi_10",
|
||||||
|
name: "Demografi Pekerjaan",
|
||||||
|
path: "/admin/ekonomi/demografi-pekerjaan"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Inovasi",
|
||||||
|
name: "Inovasi",
|
||||||
|
path: "",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "Inovasi_1",
|
||||||
|
name: "Desa Digital/Smart Village",
|
||||||
|
path: "/admin/inovasi/desa-digital-smart-village"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Inovasi_2",
|
||||||
|
name: "Layanan Online Desa",
|
||||||
|
path: "/admin/inovasi/layanan-online-desa/administrasi-online"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Inovasi_3",
|
||||||
|
name: "Program Kreatif Desa",
|
||||||
|
path: "/admin/inovasi/program-kreatif-desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Inovasi_4",
|
||||||
|
name: "Kolaborasi Inovasi",
|
||||||
|
path: "/admin/inovasi/kolaborasi-inovasi/list-kolaborasi-inovasi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Inovasi_5",
|
||||||
|
name: "Info Teknologi Tepat Guna",
|
||||||
|
path: "/admin/inovasi/info-teknologi-tepat-guna"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Inovasi_6",
|
||||||
|
name: "Ajukan Ide Inovatif",
|
||||||
|
path: "/admin/inovasi/ajukan-ide-inovatif"
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Lingkungan",
|
||||||
|
name: "Lingkungan",
|
||||||
|
path: "",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "Lingkungan_1",
|
||||||
|
name: "Pengelolaan Sampah (Bank Sampah)",
|
||||||
|
path: "/admin/lingkungan/pengelolaan-sampah-bank-sampah/list-pengelolaan-sampah-bank-sampah"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Lingkungan_2",
|
||||||
|
name: "Program Penghijauan",
|
||||||
|
path: "/admin/lingkungan/program-penghijauan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Lingkungan_3",
|
||||||
|
name: "Data Lingkungan Desa",
|
||||||
|
path: "/admin/lingkungan/data-lingkungan-desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Lingkungan_4",
|
||||||
|
name: "Gotong Royong",
|
||||||
|
path: "/admin/lingkungan/gotong-royong/kegiatan-desa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Lingkungan_5",
|
||||||
|
name: "Edukasi Lingkungan",
|
||||||
|
path: "/admin/lingkungan/edukasi-lingkungan/tujuan-edukasi-lingkungan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Lingkungan_6",
|
||||||
|
name: "Konservasi Adat Bali",
|
||||||
|
path: "/admin/lingkungan/konservasi-adat-bali/filosofi-tri-hita-karana"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const role2 = [
|
||||||
|
{
|
||||||
|
id: "Kesehatan",
|
||||||
|
name: "Kesehatan",
|
||||||
|
path: "",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "Kesehatan_1",
|
||||||
|
name: "Posyandu",
|
||||||
|
path: "/admin/kesehatan/posyandu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Kesehatan_2",
|
||||||
|
name: "Data Kesehatan Warga",
|
||||||
|
path: "/admin/kesehatan/data-kesehatan-warga/persentase_data_kelahiran_kematian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Kesehatan_3",
|
||||||
|
name: "Puskesmas",
|
||||||
|
path: "/admin/kesehatan/puskesmas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Kesehatan_4",
|
||||||
|
name: "Program Kesehatan",
|
||||||
|
path: "/admin/kesehatan/program-kesehatan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Kesehatan_5",
|
||||||
|
name: "Penanganan Darurat",
|
||||||
|
path: "/admin/kesehatan/penanganan-darurat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Kesehatan_6",
|
||||||
|
name: "Kontak Darurat",
|
||||||
|
path: "/admin/kesehatan/kontak-darurat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Kesehatan_7",
|
||||||
|
name: "Info Wabah/Penyakit",
|
||||||
|
path: "/admin/kesehatan/info-wabah-penyakit"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const role3 = [
|
||||||
|
{
|
||||||
|
id: "Pendidikan",
|
||||||
|
name: "Pendidikan",
|
||||||
|
path: "",
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: "Pendidikan_1",
|
||||||
|
name: "Info Sekolah",
|
||||||
|
path: "/admin/pendidikan/info-sekolah/jenjang-pendidikan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "Pendidikan_2",
|
||||||
|
name: "Beasiswa Desa",
|
||||||
|
path: "/admin/pendidikan/beasiswa-desa/beasiswa-pendaftar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "Pendidikan_3",
|
id: "Pendidikan_3",
|
||||||
@@ -422,5 +786,3 @@ export const navBar = [
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
9
src/app/admin/_com/navigationByRole.ts
Normal file
9
src/app/admin/_com/navigationByRole.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// src/app/(admin)/_com/navigationByRole.ts
|
||||||
|
import { navBar, role1, role2, role3 } from './list_PageAdmin';
|
||||||
|
|
||||||
|
export const navigationByRole = {
|
||||||
|
0: navBar, // SUPERADMIN
|
||||||
|
1: role1, // ADMIN DESA
|
||||||
|
2: role2, // ADMIN KESEHATAN
|
||||||
|
3: role3, // ADMIN PENDIDIKAN
|
||||||
|
} as const;
|
||||||
@@ -1,6 +1,269 @@
|
|||||||
|
// 'use client'
|
||||||
|
|
||||||
|
// import colors from "@/con/colors";
|
||||||
|
// import {
|
||||||
|
// ActionIcon,
|
||||||
|
// AppShell,
|
||||||
|
// AppShellHeader,
|
||||||
|
// AppShellMain,
|
||||||
|
// AppShellNavbar,
|
||||||
|
// Burger,
|
||||||
|
// Flex,
|
||||||
|
// Group,
|
||||||
|
// Image,
|
||||||
|
// NavLink,
|
||||||
|
// ScrollArea,
|
||||||
|
// Text,
|
||||||
|
// Tooltip,
|
||||||
|
// rem
|
||||||
|
// } from "@mantine/core";
|
||||||
|
// import { useDisclosure } from "@mantine/hooks";
|
||||||
|
// import {
|
||||||
|
// IconChevronLeft,
|
||||||
|
// IconChevronRight,
|
||||||
|
// IconLogout2
|
||||||
|
// } from "@tabler/icons-react";
|
||||||
|
// import _ from "lodash";
|
||||||
|
// import Link from "next/link";
|
||||||
|
// import { useRouter, useSelectedLayoutSegments } from "next/navigation";
|
||||||
|
// import { navBar } from "./_com/list_PageAdmin";
|
||||||
|
|
||||||
|
// export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
// const [opened, { toggle }] = useDisclosure();
|
||||||
|
// const [desktopOpened, { toggle: toggleDesktop }] = useDisclosure(true);
|
||||||
|
// const router = useRouter();
|
||||||
|
// const segments = useSelectedLayoutSegments().map((s) => _.lowerCase(s));
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <AppShell
|
||||||
|
// suppressHydrationWarning
|
||||||
|
// header={{ height: 64 }}
|
||||||
|
// navbar={{
|
||||||
|
// width: { base: 260, sm: 280, lg: 300 },
|
||||||
|
// breakpoint: 'sm',
|
||||||
|
// collapsed: {
|
||||||
|
// mobile: !opened,
|
||||||
|
// desktop: !desktopOpened,
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// padding="md"
|
||||||
|
// >
|
||||||
|
// <AppShellHeader
|
||||||
|
// style={{
|
||||||
|
// background: "linear-gradient(90deg, #ffffff, #f9fbff)",
|
||||||
|
// borderBottom: `1px solid ${colors["blue-button"]}20`,
|
||||||
|
// padding: '0 16px',
|
||||||
|
// }}
|
||||||
|
// px={{ base: 'sm', sm: 'md' }}
|
||||||
|
// py={{ base: 'xs', sm: 'sm' }}
|
||||||
|
// >
|
||||||
|
// <Group w="100%" h="100%" justify="space-between" wrap="nowrap">
|
||||||
|
// <Flex align="center" gap="sm">
|
||||||
|
// <Image
|
||||||
|
// src="/assets/images/darmasaba-icon.png"
|
||||||
|
// alt="Logo Darmasaba"
|
||||||
|
// w={{ base: 32, sm: 40 }}
|
||||||
|
// h={{ base: 32, sm: 40 }}
|
||||||
|
// radius="md"
|
||||||
|
// loading="lazy"
|
||||||
|
// style={{
|
||||||
|
// minWidth: '32px',
|
||||||
|
// height: 'auto',
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// <Text
|
||||||
|
// fw={700}
|
||||||
|
// c={colors["blue-button"]}
|
||||||
|
// fz={{ base: 'md', sm: 'xl' }}
|
||||||
|
// >
|
||||||
|
// Admin Darmasaba
|
||||||
|
// </Text>
|
||||||
|
// </Flex>
|
||||||
|
|
||||||
|
// <Group gap="xs">
|
||||||
|
// {!desktopOpened && (
|
||||||
|
// <Tooltip label="Buka Navigasi" position="bottom" withArrow>
|
||||||
|
// <ActionIcon
|
||||||
|
// variant="light"
|
||||||
|
// radius="xl"
|
||||||
|
// size="lg"
|
||||||
|
// onClick={toggleDesktop}
|
||||||
|
// color={colors["blue-button"]}
|
||||||
|
// >
|
||||||
|
// <IconChevronRight />
|
||||||
|
// </ActionIcon>
|
||||||
|
// </Tooltip>
|
||||||
|
// )}
|
||||||
|
|
||||||
|
// <Burger
|
||||||
|
// opened={opened}
|
||||||
|
// onClick={toggle}
|
||||||
|
// hiddenFrom="sm"
|
||||||
|
// size="md"
|
||||||
|
// color={colors["blue-button"]}
|
||||||
|
// mr="xs"
|
||||||
|
// />
|
||||||
|
|
||||||
|
// <Tooltip label="Kembali ke Website Desa" position="bottom" withArrow>
|
||||||
|
// <ActionIcon
|
||||||
|
// onClick={() => {
|
||||||
|
// router.push("/darmasaba");
|
||||||
|
// }}
|
||||||
|
// color={colors["blue-button"]}
|
||||||
|
// radius="xl"
|
||||||
|
// size="lg"
|
||||||
|
// variant="gradient"
|
||||||
|
// gradient={{ from: colors["blue-button"], to: "#228be6" }}
|
||||||
|
// >
|
||||||
|
// <Image
|
||||||
|
// src="/assets/images/darmasaba-icon.png"
|
||||||
|
// alt="Logo Darmasaba"
|
||||||
|
// w={20}
|
||||||
|
// h={20}
|
||||||
|
// radius="md"
|
||||||
|
// loading="lazy"
|
||||||
|
// style={{
|
||||||
|
// minWidth: '20px',
|
||||||
|
// height: 'auto',
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </ActionIcon>
|
||||||
|
// </Tooltip>
|
||||||
|
// <Tooltip label="Keluar" position="bottom" withArrow>
|
||||||
|
// <ActionIcon
|
||||||
|
// onClick={() => {
|
||||||
|
// router.push("/darmasaba");
|
||||||
|
// }}
|
||||||
|
// color={colors["blue-button"]}
|
||||||
|
// radius="xl"
|
||||||
|
// size="lg"
|
||||||
|
// variant="gradient"
|
||||||
|
// gradient={{ from: colors["blue-button"], to: "#228be6" }}
|
||||||
|
// >
|
||||||
|
// <IconLogout2 size={22} />
|
||||||
|
// </ActionIcon>
|
||||||
|
// </Tooltip>
|
||||||
|
// </Group>
|
||||||
|
// </Group>
|
||||||
|
// </AppShellHeader>
|
||||||
|
|
||||||
|
// <AppShellNavbar
|
||||||
|
// component={ScrollArea}
|
||||||
|
// style={{
|
||||||
|
// background: "#ffffff",
|
||||||
|
// borderRight: `1px solid ${colors["blue-button"]}20`,
|
||||||
|
// }}
|
||||||
|
// p={{ base: 'xs', sm: 'sm' }}
|
||||||
|
// >
|
||||||
|
// <AppShell.Section p="sm">
|
||||||
|
// {navBar.map((v, k) => {
|
||||||
|
// const isParentActive = segments.includes(_.lowerCase(v.name));
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <NavLink
|
||||||
|
// key={k}
|
||||||
|
// defaultOpened={isParentActive}
|
||||||
|
// c={isParentActive ? colors["blue-button"] : "gray"}
|
||||||
|
// label={
|
||||||
|
// <Text fw={isParentActive ? 600 : 400} fz="sm">
|
||||||
|
// {v.name}
|
||||||
|
// </Text>
|
||||||
|
// }
|
||||||
|
// style={{
|
||||||
|
// borderRadius: rem(10),
|
||||||
|
// marginBottom: rem(4),
|
||||||
|
// transition: "background 150ms ease",
|
||||||
|
// }}
|
||||||
|
// styles={{
|
||||||
|
// root: {
|
||||||
|
// '&:hover': {
|
||||||
|
// backgroundColor: 'rgba(25, 113, 194, 0.05)',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// variant="light"
|
||||||
|
// active={isParentActive}
|
||||||
|
// >
|
||||||
|
// {v.children.map((child, key) => {
|
||||||
|
// const isChildActive = segments.includes(
|
||||||
|
// _.lowerCase(child.name)
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <NavLink
|
||||||
|
// key={key}
|
||||||
|
// href={child.path}
|
||||||
|
// c={isChildActive ? colors["blue-button"] : "gray"}
|
||||||
|
// label={
|
||||||
|
// <Text fw={isChildActive ? 600 : 400} fz="sm">
|
||||||
|
// {child.name}
|
||||||
|
// </Text>
|
||||||
|
// }
|
||||||
|
// styles={{
|
||||||
|
// root: {
|
||||||
|
// borderRadius: rem(8),
|
||||||
|
// marginBottom: rem(2),
|
||||||
|
// transition: 'background 150ms ease',
|
||||||
|
// padding: '6px 12px',
|
||||||
|
// '&:hover': {
|
||||||
|
// backgroundColor: isChildActive ? 'rgba(25, 113, 194, 0.15)' : 'rgba(25, 113, 194, 0.05)',
|
||||||
|
// },
|
||||||
|
// ...(isChildActive && {
|
||||||
|
// backgroundColor: 'rgba(25, 113, 194, 0.1)',
|
||||||
|
// }),
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// active={isChildActive}
|
||||||
|
// component={Link}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// })}
|
||||||
|
// </NavLink>
|
||||||
|
// );
|
||||||
|
// })}
|
||||||
|
// </AppShell.Section>
|
||||||
|
|
||||||
|
// <AppShell.Section py="md">
|
||||||
|
// <Group justify="end" pr="sm">
|
||||||
|
// <Tooltip
|
||||||
|
// label={desktopOpened ? "Tutup Navigasi" : "Buka Navigasi"}
|
||||||
|
// position="top"
|
||||||
|
// withArrow
|
||||||
|
// >
|
||||||
|
// <ActionIcon
|
||||||
|
// variant="light"
|
||||||
|
// radius="xl"
|
||||||
|
// size="lg"
|
||||||
|
// onClick={toggleDesktop}
|
||||||
|
// color={colors["blue-button"]}
|
||||||
|
// >
|
||||||
|
// <IconChevronLeft />
|
||||||
|
// </ActionIcon>
|
||||||
|
// </Tooltip>
|
||||||
|
// </Group>
|
||||||
|
// </AppShell.Section>
|
||||||
|
// </AppShellNavbar>
|
||||||
|
|
||||||
|
// <AppShellMain
|
||||||
|
// style={{
|
||||||
|
// background: "linear-gradient(180deg, #fdfdfd, #f6f9fc)",
|
||||||
|
// minHeight: "100vh",
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// {children}
|
||||||
|
// </AppShellMain>
|
||||||
|
// </AppShell>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import colors from "@/con/colors";
|
import colors from "@/con/colors";
|
||||||
|
import { authStore } from "@/store/authStore";
|
||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
AppShell,
|
AppShell,
|
||||||
@@ -26,7 +289,8 @@ import {
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter, useSelectedLayoutSegments } from "next/navigation";
|
import { useRouter, useSelectedLayoutSegments } from "next/navigation";
|
||||||
import { navBar } from "./_com/list_PageAdmin";
|
import { useSnapshot } from "valtio";
|
||||||
|
import { navigationByRole } from "./_com/navigationByRole";
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
const [opened, { toggle }] = useDisclosure();
|
const [opened, { toggle }] = useDisclosure();
|
||||||
@@ -34,6 +298,15 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const segments = useSelectedLayoutSegments().map((s) => _.lowerCase(s));
|
const segments = useSelectedLayoutSegments().map((s) => _.lowerCase(s));
|
||||||
|
|
||||||
|
//ambil user dari authStore
|
||||||
|
const {user} = useSnapshot(authStore)
|
||||||
|
console.log("Current User:", user); // 👈 Tambahkan ini
|
||||||
|
|
||||||
|
//ambil navigation berdasarkan role
|
||||||
|
const currentNav = user?.roleId !== undefined
|
||||||
|
? navigationByRole[user.roleId as keyof typeof navigationByRole] || []
|
||||||
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
suppressHydrationWarning
|
suppressHydrationWarning
|
||||||
@@ -156,7 +429,7 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
p={{ base: 'xs', sm: 'sm' }}
|
p={{ base: 'xs', sm: 'sm' }}
|
||||||
>
|
>
|
||||||
<AppShell.Section p="sm">
|
<AppShell.Section p="sm">
|
||||||
{navBar.map((v, k) => {
|
{currentNav.map((v, k) => {
|
||||||
const isParentActive = segments.includes(_.lowerCase(v.name));
|
const isParentActive = segments.includes(_.lowerCase(v.name));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -255,3 +528,4 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ const User = new Elysia({ prefix: "/api/user" })
|
|||||||
id: t.String(),
|
id: t.String(),
|
||||||
}),
|
}),
|
||||||
}) // pakai PUT untuk soft delete
|
}) // pakai PUT untuk soft delete
|
||||||
.put("/updt", userUpdate);
|
.put(
|
||||||
|
"/updt",
|
||||||
|
userUpdate,
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
id: t.String(),
|
||||||
|
isActive: t.Optional(t.Boolean()),
|
||||||
|
roleId: t.Optional(t.String()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
export default User;
|
export default User;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Context } from "elysia";
|
|||||||
|
|
||||||
type FormCreate = {
|
type FormCreate = {
|
||||||
name: string;
|
name: string;
|
||||||
permissions: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function roleCreate(context: Context) {
|
export default async function roleCreate(context: Context) {
|
||||||
@@ -13,7 +12,6 @@ export default async function roleCreate(context: Context) {
|
|||||||
const result = await prisma.role.create({
|
const result = await prisma.role.create({
|
||||||
data: {
|
data: {
|
||||||
name: body.name,
|
name: body.name,
|
||||||
permissions: body.permissions,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ const Role = new Elysia({
|
|||||||
.post("/create", roleCreate, {
|
.post("/create", roleCreate, {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
name: t.String(),
|
name: t.String(),
|
||||||
permissions: t.Array(t.String()),
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -27,7 +26,6 @@ const Role = new Elysia({
|
|||||||
.put("/:id", roleUpdate, {
|
.put("/:id", roleUpdate, {
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
name: t.String(),
|
name: t.String(),
|
||||||
permissions: t.Array(t.String()),
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.delete("/del/:id", roleDelete);
|
.delete("/del/:id", roleDelete);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Context } from "elysia";
|
|||||||
|
|
||||||
type FormUpdate = {
|
type FormUpdate = {
|
||||||
name: string;
|
name: string;
|
||||||
permissions: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function roleUpdate(context: Context) {
|
export default async function roleUpdate(context: Context) {
|
||||||
@@ -15,7 +14,6 @@ export default async function roleUpdate(context: Context) {
|
|||||||
where: { id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
name: body.name,
|
name: body.name,
|
||||||
permissions: body.permissions,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import { Context } from "elysia";
|
|||||||
|
|
||||||
export default async function userUpdate(context: Context) {
|
export default async function userUpdate(context: Context) {
|
||||||
try {
|
try {
|
||||||
const { id, isActive } = await context.body as { id: string, isActive: boolean };
|
const { id, isActive, roleId } = await context.body as {
|
||||||
|
id: string,
|
||||||
|
isActive?: boolean,
|
||||||
|
roleId?: string
|
||||||
|
};
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return {
|
return {
|
||||||
@@ -13,28 +17,53 @@ export default async function userUpdate(context: Context) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Optional: cek apakah roleId valid
|
||||||
|
if (roleId) {
|
||||||
|
const cekRole = await prisma.role.findUnique({
|
||||||
|
where: { id: roleId }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!cekRole) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Role tidak ditemukan",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatedUser = await prisma.user.update({
|
const updatedUser = await prisma.user.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { isActive },
|
data: {
|
||||||
|
...(isActive !== undefined && { isActive }),
|
||||||
|
...(roleId && { roleId }),
|
||||||
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
nomor: true,
|
nomor: true,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
roleId: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
|
role: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: `User berhasil ${isActive ? "diaktifkan" : "dinonaktifkan"}`,
|
message: `User berhasil diupdate`,
|
||||||
data: updatedUser,
|
data: updatedUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error("Error update user:", e);
|
console.error("Error update user:", e);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: "Gagal mengupdate status user",
|
message: "Gagal mengupdate user",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,11 +108,18 @@ export async function POST(req: Request) {
|
|||||||
data: { isActive: false },
|
data: { isActive: false },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const userData = {
|
||||||
|
id: user.id,
|
||||||
|
name: user.username, // atau user.nama jika ada kolom nama
|
||||||
|
roleId: user.roleId,
|
||||||
|
};
|
||||||
|
|
||||||
// Set cookie & respons
|
// Set cookie & respons
|
||||||
const response = NextResponse.json(
|
const response = NextResponse.json(
|
||||||
{
|
{
|
||||||
success: true,
|
success: true,
|
||||||
message: "Berhasil login",
|
message: "Berhasil login",
|
||||||
|
user: userData,
|
||||||
roleId: user.roleId,
|
roleId: user.roleId,
|
||||||
},
|
},
|
||||||
{ status: 200 }
|
{ status: 200 }
|
||||||
|
|||||||
17
src/store/authStore.ts
Normal file
17
src/store/authStore.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { proxy } from 'valtio';
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
roleId: number; // 0, 1, 2, 3
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authStore = proxy<{
|
||||||
|
user: User | null;
|
||||||
|
setUser: (user: User | null) => void;
|
||||||
|
}>({
|
||||||
|
user: null,
|
||||||
|
setUser(user) {
|
||||||
|
authStore.user = user;
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user