Notifikasi

# feat:
- Notifikasi di bagian admin

## No issue
This commit is contained in:
2024-06-10 16:22:05 +08:00
parent 0e16d6501f
commit 76f0396005
35 changed files with 618 additions and 200 deletions

View File

@@ -5,6 +5,14 @@ import { atomWithStorage } from "jotai/utils";
* @type number
* @
*/
export const gs_admin_hotMenu = atomWithStorage("gs_admin_hotMenu", 1)
export const gs_admin_hotMenu = atomWithStorage("gs_admin_hotMenu", 1);
export const gs_admin_subMenu = atomWithStorage<number | null>("gs_admin_subMenu",null)
export const gs_admin_subMenu = atomWithStorage<number | null>(
"gs_admin_subMenu",
null
);
export const gs_layout_admin_isNavbarOpen = atomWithStorage(
"gs_layout_admin_isNavbarOpen",
false
);

View File

@@ -1,6 +1,6 @@
import AdminJob_Main from "./main";
import AdminJob_TablePublish from "./child/table_publish";
import AdminJob_TableReview from "./child/table_review";
import AdminJob_TableReject from "./child/table_reject";
import AdminJob_TablePublish from "./child/publish";
import AdminJob_TableReview from "./child/review";
import AdminJob_TableReject from "./child/reject";
export { AdminJob_Main, AdminJob_TablePublish, AdminJob_TableReview, AdminJob_TableReject };

View File

@@ -3,69 +3,90 @@
import {
ActionIcon,
AppShell,
Badge,
Box,
Burger,
Button,
Card,
Center,
Divider,
Drawer,
Group,
Header,
Indicator,
MediaQuery,
NavLink,
Navbar,
Paper,
ScrollArea,
Stack,
Text,
Title,
useMantineTheme
useMantineTheme,
} from "@mantine/core";
import {
IconBell,
IconCheck,
IconChecks,
IconCircleDot,
IconCircleDotFilled,
IconDashboard
IconDashboard,
IconUserSquareRounded,
} from "@tabler/icons-react";
import { useAtom } from "jotai";
import _ from "lodash";
import { useRouter } from "next/navigation";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { auth_Logout } from "../auth/fun/fun_logout";
import { gs_kodeId } from "../auth/state/state";
import { ComponentGlobal_NotifikasiBerhasil } from "../component_global/notif_global/notifikasi_berhasil";
import { ComponentGlobal_NotifikasiPeringatan } from "../component_global/notif_global/notifikasi_peringatan";
import Admin_Logout from "./component_global/logout";
import { gs_admin_hotMenu, gs_admin_subMenu } from "./global_state";
import {
gs_admin_hotMenu,
gs_admin_subMenu,
gs_layout_admin_isNavbarOpen,
} from "./global_state";
import { listAdminPage } from "./list_page";
import { MODEL_NOTIFIKASI } from "../notifikasi/model/interface";
import { MODEL_USER } from "../home/model/interface";
import { useHover, useShallowEffect, useToggle } from "@mantine/hooks";
import moment from "moment";
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
import { RouterAdminJob } from "@/app/lib/router_admin/router_admin_job";
import adminNotifikasi_funUpdateIsReadById from "./notifikasi/fun/update/fun_update_is_read_by_id";
import adminNotifikasi_getByUserId from "./notifikasi/fun/get/get_notifikasi_by_user_id";
import adminNotifikasi_countNotifikasi from "./notifikasi/fun/count/count_is_read";
import mqtt_client from "@/util/mqtt_client";
export default function AdminLayout({
userRole,
children,
listNotif,
dataUser,
countNotifikasi,
}: {
userRole: string;
children: React.ReactNode;
listNotif: MODEL_NOTIFIKASI[];
dataUser: MODEL_USER;
countNotifikasi: number;
}) {
const theme = useMantineTheme();
const [opened, setOpened] = useState(false);
const router = useRouter();
const [active, setActive] = useAtom(gs_admin_hotMenu);
const [activeId, setActiveId] = useAtom(gs_admin_hotMenu);
const [activeChild, setActiveChild] = useAtom(gs_admin_subMenu);
const [loading, setLoading] = useState(false);
const [kodeId, setKodeId] = useAtom(gs_kodeId);
async function onClickLogout() {
// await auth_Logout(kodeId).then((res) => {
// ComponentGlobal_NotifikasiBerhasil("Berhasil Logout");
// });
await auth_Logout(kodeId).then((res) => {
if (res.status === 200) {
ComponentGlobal_NotifikasiBerhasil(res.message);
setKodeId("");
} else {
ComponentGlobal_NotifikasiPeringatan(res.message);
}
});
}
const [user, setUser] = useState(dataUser);
const userRoleId = user.masterUserRoleId;
const navbarItems = listAdminPage.map((e, i) => (
const [isNotif, setIsNotif] = useState(false);
const [dataNotif, setDataNotif] = useState(listNotif);
const [countNotif, setCountNotif] = useState(countNotifikasi);
const [isNavbarOpen, setIsNavbarOpen] = useAtom(gs_layout_admin_isNavbarOpen);
const developerNavbar = listAdminPage.map((e, i) => (
<Box key={e.id}>
<NavLink
sx={{
@@ -73,7 +94,7 @@ export default function AdminLayout({
backgroundColor: "transparent",
},
}}
fw={active === e.id ? "bold" : "normal"}
fw={activeId === e.id ? "bold" : "normal"}
icon={
// active === e.id ? loading ? <Loader size={10} /> : e.icon : e.icon
e.icon
@@ -81,7 +102,7 @@ export default function AdminLayout({
label={<Text size={"sm"}>{e.name}</Text>}
onClick={() => {
setLoading(true);
setActive(e.id);
setActiveId(e.id);
setActiveChild(null);
e.path === "" ? router.push(e.child[0].path) : router.push(e.path);
e.path === "" ? setActiveChild(e.child[0].id) : "";
@@ -109,7 +130,7 @@ export default function AdminLayout({
)
}
onClick={() => {
setActive(e.id);
setActiveId(e.id);
setActiveChild(v.id);
router.push(v.path);
}}
@@ -123,26 +144,26 @@ export default function AdminLayout({
));
const bukanDeveloper = listAdminPage.slice(0, -1);
const notAdminDev = bukanDeveloper.map((e) => (
const adminNavbar = bukanDeveloper.map((e) => (
<Box key={e.id}>
<NavLink
opened={e?.id === activeId && isNavbarOpen ? true : false}
sx={{
":hover": {
backgroundColor: "transparent",
},
}}
fw={active === e.id ? "bold" : "normal"}
icon={
// active === e.id ? loading ? <Loader size={10} /> : e.icon : e.icon
e.icon
}
fw={activeId === e.id ? "bold" : "normal"}
icon={e.icon}
label={<Text size={"sm"}>{e.name}</Text>}
onClick={() => {
setLoading(true);
setActive(e.id);
setActiveId(e.id);
setActiveChild(null);
e.path === "" ? router.push(e.child[0].path) : router.push(e.path);
e.path === "" ? setActiveChild(e.child[0].id) : "";
setIsNavbarOpen(true);
}}
>
{_.isEmpty(e.child) ? (
@@ -167,7 +188,7 @@ export default function AdminLayout({
)
}
onClick={() => {
setActive(e.id);
setActiveId(e.id);
setActiveChild(v.id);
router.push(v.path);
}}
@@ -180,20 +201,20 @@ export default function AdminLayout({
</Box>
));
const navbarAdmin = (
<Box>
<NavLink
c="orange"
icon={<IconDashboard />}
label="Developer"
sx={{
":hover": {
backgroundColor: "transparent",
},
}}
/>
</Box>
);
async function onLoadNotifikasi() {
const loadNotif = await adminNotifikasi_getByUserId();
setDataNotif(loadNotif as any);
}
useEffect(() => {
mqtt_client.subscribe("ADMIN");
mqtt_client.on("message", (topic: any, message: any) => {
const data = JSON.parse(message.toString());
// console.log(data);
setCountNotif(countNotif + data.count);
});
}, [countNotif]);
return (
<>
@@ -201,59 +222,34 @@ export default function AdminLayout({
padding="md"
navbarOffsetBreakpoint="md"
asideOffsetBreakpoint="sm"
navbar={
<MediaQuery smallerThan={"md"} styles={{ display: "none" }}>
<Navbar
h={"95%"}
width={{ lg: 250, md: 200, sm: 200, base: 250 }}
hiddenBreakpoint="md"
hidden={!opened}
p="xs"
bg={"gray.2"}
>
{/* <Navbar.Section>
<Center h={50}>
<Title order={4} ff={"sans-serif"}>
Dashboard Admin
</Title>
</Center>
<Divider />
</Navbar.Section> */}
<Navbar.Section grow component={ScrollArea}>
<Stack>{userRole === "3" ? navbarItems : notAdminDev}</Stack>
</Navbar.Section>
<Navbar.Section>
<Stack>
<Divider />
<Group position="apart">
<Text fs={"italic"} c={"gray"} fz={"xs"}>
V 1.0.0
</Text>
<Admin_Logout />
</Group>
</Stack>
</Navbar.Section>
</Navbar>
</MediaQuery>
}
header={
<Header height={"5vh"} bg={"gray.2"}>
<Header height={"6vh"} bg={"gray.2"}>
{/* Web View */}
<MediaQuery smallerThan={"md"} styles={{ display: "none" }}>
<Group position="apart" align="center" h={"100%"} px={"md"}>
<Text fw={"lighter"}>Dashboard Admin</Text>
<Title order={4}> HIPMI</Title>
{/* <Group>
{listAdminPage.map((e) => (
<Text key={e.id} onClick={() => router.push(e.route)}>
{e.name}
</Text>
))}
</Group> */}
{/* <Admin_Logout /> */}
<ActionIcon radius={"xl"}>
<IconBell />
</ActionIcon>
<Title order={3}>Dashboard Admin</Title>
<Group>
<ActionIcon
radius={"xl"}
onClick={() => {
setIsNotif(true);
onLoadNotifikasi();
}}
>
<Indicator
processing
label={<Text fz={10}>{countNotif}</Text>}
>
<IconBell />
</Indicator>
</ActionIcon>
<Divider orientation="vertical" color="dark" />
<Group>
<Text>{user?.username}</Text>
<IconUserSquareRounded />
</Group>
</Group>
</Group>
</MediaQuery>
@@ -276,10 +272,39 @@ export default function AdminLayout({
</MediaQuery>
</Header>
}
navbar={
<MediaQuery smallerThan={"md"} styles={{ display: "none" }}>
<Navbar
h={"94vh"}
width={{ lg: 250, md: 200, sm: 200, base: 250 }}
hiddenBreakpoint="md"
hidden={!opened}
p="xs"
bg={"gray.2"}
>
<Navbar.Section grow component={ScrollArea}>
<Stack>
{userRoleId === "3" ? developerNavbar : adminNavbar}
</Stack>
</Navbar.Section>
<Navbar.Section>
<Stack>
<Divider />
<Group position="apart">
<Text fs={"italic"} c={"gray"} fz={"xs"}>
V 1.0.0
</Text>
<Admin_Logout />
</Group>
</Stack>
</Navbar.Section>
</Navbar>
</MediaQuery>
}
>
{/* {JSON.stringify(active)} */}
{children}
</AppShell>
{/* Drawer Mobile View */}
<Drawer opened={opened} onClose={() => setOpened(false)} size={"50%"}>
<Stack spacing={"xl"}>
{listAdminPage.map((e) => (
@@ -289,6 +314,183 @@ export default function AdminLayout({
))}
</Stack>
</Drawer>
{/* Drawer Notifikasi */}
<Drawer
title={
<Group position="apart">
<Text fw={"bold"} fz={"lg"}>
Notifikasi
</Text>
{/* <Button compact radius={"xl"} fz={10}>Tandai terliha</Button> */}
</Group>
}
opened={isNotif}
onClose={() => setIsNotif(false)}
position="right"
size={"xs"}
>
<DrawerNotifikasi
data={dataNotif}
onLoadReadNotif={(val: any) => {
setDataNotif(val);
}}
onChangeNavbar={(val: any) => {
setActiveId(val.id);
setActiveChild(val.childId);
}}
onToggleNavbar={setIsNavbarOpen}
onLoadCountNotif={(val: any) => {
setCountNotif(val);
}}
/>
</Drawer>
</>
);
}
function DrawerNotifikasi({
data,
onLoadReadNotif,
onChangeNavbar,
onToggleNavbar,
onLoadCountNotif,
}: {
data: MODEL_NOTIFIKASI[];
onLoadReadNotif: (val: any) => void;
onChangeNavbar: (val: any) => void;
onToggleNavbar: (val: any) => void;
onLoadCountNotif: (val: any) => void;
}) {
const router = useRouter();
if (_.isEmpty(data)){
return (
<>
<Center>
<Text c={"gray"} fz={"xs"}>
Tidak ada notifikasi
</Text>
</Center>
</>
);
}
return (
<>
<Paper h={"100%"}>
<Stack>
{data.map((e, i) => (
<Card
key={e?.id}
// withBorder
bg={e?.isRead ? "gray.1" : "gray.4"}
sx={{
borderColor: "gray",
borderStyle: "solid",
borderWidth: "0.5px",
":hover": {
borderColor: "gray",
borderStyle: "solid",
borderWidth: "1.5px",
},
}}
onClick={async () => {
e?.kategoriApp === "JOB" &&
findRouterJob({
data: e,
router: router,
onChangeNavbar2: (val: any) => {
onChangeNavbar(val);
},
onToggleNavbar2: onToggleNavbar,
});
const updateIsRead = await adminNotifikasi_funUpdateIsReadById({
notifId: e?.id,
});
if (updateIsRead) {
const loadCountNotif =
await adminNotifikasi_countNotifikasi();
onLoadCountNotif(loadCountNotif);
const loadDataNotif = await adminNotifikasi_getByUserId();
onLoadReadNotif(loadDataNotif);
} else {
return null;
}
// callBackIsNotifikasi(false);
}}
>
<Card.Section p={"sm"}>
<Group position="apart">
<Text fw={"bold"} fz={"xs"}>
# {e?.kategoriApp}
</Text>
{e?.status ? <Badge w={70}>{e?.status}</Badge> : ""}
</Group>
</Card.Section>
<Card.Section p={"sm"}>
<Text lineClamp={2}>{e?.pesan}</Text>
</Card.Section>
<Card.Section p={"sm"}>
<Group position="apart">
<Text fz={10} color="gray">
{new Intl.DateTimeFormat("id-ID", {
dateStyle: "long",
}).format(e?.createdAt)}
<Text span inherit fz={10} color="gray">
{", "}
{new Intl.DateTimeFormat("id-ID", {
timeStyle: "short",
}).format(e?.createdAt)}
</Text>
</Text>
{e?.isRead ? (
<Group spacing={5}>
<IconChecks color="gray" size={10} />
<Text fz={10} color="gray">
Sudah dilihat
</Text>
</Group>
) : (
<Group spacing={5}>
<IconCheck color="gray" size={10} />
<Text fz={10} color="gray">
Belum dilihat
</Text>
</Group>
)}
</Group>
</Card.Section>
</Card>
))}
</Stack>
</Paper>
</>
);
}
async function findRouterJob({
data,
router,
onChangeNavbar2,
onToggleNavbar2,
}: {
data: MODEL_NOTIFIKASI;
router: AppRouterInstance;
onChangeNavbar2: (val: any) => void;
onToggleNavbar2: (val: any) => void;
}) {
const routeName = "/dev/admin/job/child/";
router.push(routeName + _.lowerCase(data.status));
onChangeNavbar2({
id: 6,
childId: 63,
});
onToggleNavbar2(true);
}

View File

@@ -194,17 +194,17 @@ export const listAdminPage = [
{
id: 62,
name: "Table Publish",
path: RouterAdminJob.table_publish,
path: RouterAdminJob.publish,
},
{
id: 63,
name: "Table Review",
path: RouterAdminJob.table_review,
path: RouterAdminJob.review,
},
{
id: 64,
name: "Table Reject",
path: RouterAdminJob.table_reject,
path: RouterAdminJob.reject,
},
{
id: 65,

View File

@@ -0,0 +1,17 @@
"use server";
import prisma from "@/app/lib/prisma";
import { user_getOneUserId } from "@/app_modules/fun_global/get_user_token";
export default async function adminNotifikasi_countNotifikasi() {
const userId = await user_getOneUserId();
const data = await prisma.notifikasi.findMany({
where: {
adminId: userId,
isRead: false,
},
});
return data.length;
}

View File

@@ -0,0 +1,19 @@
"use server";
import prisma from "@/app/lib/prisma";
import { user_getOneUserId } from "@/app_modules/fun_global/get_user_token";
export default async function adminNotifikasi_getByUserId() {
const adminId = await user_getOneUserId();
const data = await prisma.notifikasi.findMany({
orderBy:{
createdAt: "desc"
},
where: {
adminId: adminId,
userRoleId: "2",
},
});
return data;
}

View File

@@ -0,0 +1,21 @@
"use server";
import prisma from "@/app/lib/prisma";
export default async function adminNotifikasi_funUpdateIsReadById({
notifId,
}: {
notifId: string;
}) {
const updt = await prisma.notifikasi.update({
where: {
id: notifId,
},
data: {
isRead: true,
},
});
if (!updt) return { status: 400 };
return { status: 200 };
}

View File

@@ -0,0 +1,5 @@
// test notif
import Notifikasi_MainView from "./main";
export { Notifikasi_MainView };

View File

@@ -0,0 +1,25 @@
"use client";
import AppComponentGlobal_LayoutTamplate from "@/app_modules/component_global/component_layout_tamplate";
import ComponentGlobal_HeaderTamplate from "@/app_modules/component_global/header_tamplate";
import { Text } from "@mantine/core";
export default function Notifikasi_MainView() {
return (
<>
<AppComponentGlobal_LayoutTamplate
header={<ComponentGlobal_HeaderTamplate title="Notifikasi" />}
>
<MainView />
</AppComponentGlobal_LayoutTamplate>
</>
);
}
function MainView() {
return (
<>
<Text>notif</Text>
</>
);
}