diff --git a/.gitignore b/.gitignore index 924563ab..72c1174a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ yarn-error.log* .vercel # logs -logs +/logs # typescript *.tsbuildinfo diff --git a/CHANGELOG.md b/CHANGELOG.md index dc295cf0..c4d9e45d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. +## [1.2.32](https://github.com/bipproduction/hipmi/compare/v1.2.31...v1.2.32) (2024-12-19) + + +### Bug Fixes + +* donasi ([72b6d23](https://github.com/bipproduction/hipmi/commit/72b6d239fee3e90e812ab24192154e5e8910fccb)) + ## [1.2.31](https://github.com/bipproduction/hipmi/compare/v1.2.30...v1.2.31) (2024-12-17) ## [1.2.30](https://github.com/bipproduction/hipmi/compare/v1.2.29...v1.2.30) (2024-12-16) diff --git a/package.json b/package.json index 5d5a2564..b7686848 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hipmi", - "version": "1.2.31", + "version": "1.2.32", "private": true, "prisma": { "seed": "npx tsx prisma/seed.ts --yes" diff --git a/src/app/(admin)/logs/logs.module.css b/src/app/(admin)/logs/logs.module.css new file mode 100644 index 00000000..42dadbaa --- /dev/null +++ b/src/app/(admin)/logs/logs.module.css @@ -0,0 +1,138 @@ +/* app/admin/logs/logs.module.css */ +.container { + padding: 24px; + max-width: 1200px; + margin: 0 auto; + } + + .title { + font-size: 24px; + font-weight: bold; + margin-bottom: 16px; + } + + .filterContainer { + margin-bottom: 16px; + } + + .select { + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + min-width: 200px; + } + + .logsContainer { + display: flex; + flex-direction: column; + gap: 8px; + } + + .logItem { + padding: 16px; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .errorLog { + background-color: #fef2f2; + } + + .warnLog { + background-color: #fefce8; + } + + .infoLog { + background-color: #ffffff; + } + + .logHeader { + display: flex; + align-items: center; + gap: 8px; + } + + .timestamp { + font-size: 14px; + color: #666; + } + + .level { + padding: 4px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 500; + } + + .errorLevel { + background-color: #fee2e2; + color: #991b1b; + } + + .warnLevel { + background-color: #fef3c7; + color: #92400e; + } + + .infoLevel { + background-color: #dbeafe; + color: #1e40af; + } + + .message { + margin-top: 8px; + } + + .metadata { + margin-top: 8px; + padding: 8px; + background-color: #f9fafb; + border-radius: 4px; + font-size: 14px; + overflow-x: auto; + white-space: pre-wrap; + } + + .loading { + text-align: center; + padding: 24px; + color: #666; + } + + .error { + color: #dc2626; + text-align: center; + padding: 24px; + } + + /* Hover effects */ + .logItem:hover { + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + } + + .select:hover { + border-color: #999; + } + + /* Focus states */ + .select:focus { + outline: none; + border-color: #2563eb; + box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.2); + } + + /* Responsive adjustments */ + @media (max-width: 768px) { + .container { + padding: 16px; + } + + .logHeader { + flex-direction: column; + align-items: flex-start; + } + + .metadata { + font-size: 12px; + } + } \ No newline at end of file diff --git a/src/app/(admin)/logs/page.tsx b/src/app/(admin)/logs/page.tsx new file mode 100644 index 00000000..2ac03d42 --- /dev/null +++ b/src/app/(admin)/logs/page.tsx @@ -0,0 +1,106 @@ +// app/admin/logs/page.tsx +"use client"; + +import { useEffect, useState } from "react"; +import { format } from "date-fns"; +import styles from "./logs.module.css"; + +interface LogEntry { + timestamp: string; + level: string; + message: string; + metadata?: any; +} + +export default function LogsPage() { + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [filter, setFilter] = useState<"all" | "error" | "info" | "warn">( + "all" + ); + + useEffect(() => { + fetchLogs(); + }, []); + + async function fetchLogs() { + try { + const response = await fetch("/api/logs/view"); + if (!response.ok) throw new Error("Failed to fetch logs"); + + const data = await response.json(); + setLogs(data.logs); + } catch (err) { + setError(err instanceof Error ? err.message : "Error fetching logs"); + } finally { + setLoading(false); + } + } + + const filteredLogs = logs.filter((log) => + filter === "all" ? true : log.level === filter + ); + + if (loading) return
Loading logs...
; + if (error) return
Error: {error}
; + + return ( +
+

System Logs

+ +
+ +
+ +
+ {filteredLogs.map((log, index) => ( +
+
+ + {format(new Date(log.timestamp), "yyyy-MM-dd HH:mm:ss")} + + + {log.level.toUpperCase()} + +
+ +
{log.message}
+ + {log.metadata && ( +
+                {JSON.stringify(log.metadata, null, 2)}
+              
+ )} +
+ ))} +
+
+ ); +} diff --git a/src/app/api/logs/view/route.ts b/src/app/api/logs/view/route.ts new file mode 100644 index 00000000..b660e323 --- /dev/null +++ b/src/app/api/logs/view/route.ts @@ -0,0 +1,71 @@ +// app/api/logs/view/route.ts +import { NextRequest, NextResponse } from "next/server"; +import fs from "fs/promises"; +import path from "path"; + +interface LogEntry { + timestamp: string; + level: string; + message: string; + metadata?: any; +} + +async function readLogFiles(directory: string): Promise { + try { + const logPath = path.join(process.cwd(), directory); + const files = await fs.readdir(logPath); + const logFiles = files.filter((file) => file.endsWith(".log")); + + const allLogs: LogEntry[] = []; + + for (const file of logFiles) { + const filePath = path.join(logPath, file); + const content = await fs.readFile(filePath, "utf-8"); + + // Parse setiap baris log + const logs = content + .split("\n") + .filter(Boolean) + .map((line) => { + try { + return JSON.parse(line); + } catch (e) { + return null; + } + }) + .filter(Boolean); + + allLogs.push(...logs); + } + + // Sort berdasarkan timestamp, terbaru di atas + return allLogs.sort( + (a, b) => + new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() + ); + } catch (error) { + console.error("Error reading log files:", error); + return []; + } +} + +export async function GET(request: NextRequest) { + try { + // Baca logs dari frontend dan backend + const frontendLogs = await readLogFiles("logs/frontend"); + const backendLogs = await readLogFiles("logs/backend"); + + // Gabungkan dan sort semua logs + const allLogs = [...frontendLogs, ...backendLogs].sort( + (a, b) => + new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() + ); + + return NextResponse.json({ logs: allLogs }); + } catch (error) { + return NextResponse.json( + { error: "Failed to fetch logs" }, + { status: 500 } + ); + } +} diff --git a/src/app/dev/(user)/home/page.tsx b/src/app/dev/(user)/home/page.tsx index c0fdc4c2..675c907a 100644 --- a/src/app/dev/(user)/home/page.tsx +++ b/src/app/dev/(user)/home/page.tsx @@ -1,21 +1,9 @@ import { HomeViewNew } from "@/app_modules/home"; -import notifikasi_countUserNotifikasi from "@/app_modules/notifikasi/fun/count/fun_count_by_id"; export default async function PageHome() { - // const userLoginId = await funGetUserIdByToken(); - // const dataUser = await user_getOneByUserId(userLoginId as string); - // const dataJob = await job_getTwoForHomeView(); - const countNotifikasi = await notifikasi_countUserNotifikasi(); - - return ( <> - {/* */} - + ); } diff --git a/src/app/dev/layout.tsx b/src/app/dev/layout.tsx index 49f4a4c7..bfb694c1 100644 --- a/src/app/dev/layout.tsx +++ b/src/app/dev/layout.tsx @@ -7,9 +7,13 @@ export default async function Layout({ }: { children: React.ReactNode; }) { + + const userId = await newFunGetUserId(); + return ( <> { process.exit(0); }); -console.log("==> Test prisma"); +// console.log("==> Test prisma"); export default prisma; diff --git a/src/app/lib/realtime_provider.tsx b/src/app/lib/realtime_provider.tsx index 07919a7f..3e25986b 100644 --- a/src/app/lib/realtime_provider.tsx +++ b/src/app/lib/realtime_provider.tsx @@ -17,8 +17,6 @@ import { gs_votingTiggerBeranda, IRealtimeData, } from "./global_state"; -import { newFunGetUserId } from "./new_fun_user_id"; -import { useState } from "react"; // const WIBU_REALTIME_TOKEN: string | undefined = // process.env.NEXT_PUBLIC_WIBU_REALTIME_TOKEN; @@ -28,15 +26,16 @@ export type TypeNotification = { type: "message" | "notification" | "trigger"; pushNotificationTo: "ADMIN" | "USER"; dataMessage?: IRealtimeData; - userLoginId?: string; + userId?: string; }; export default function RealtimeProvider({ + userId, WIBU_REALTIME_TOKEN, }: { + userId: string; WIBU_REALTIME_TOKEN: string; }) { - const [userLoginId, setUserLoginId] = useState(""); const [dataRealtime, setDataRealtime] = useAtom(gs_realtimeData); const [newAdminNtf, setNewAdminNtf] = useAtom(gs_admin_ntf); const [newUserNtf, setNewUserNtf] = useAtom(gs_user_ntf); @@ -72,15 +71,7 @@ export default function RealtimeProvider({ gs_donasiTriggerBeranda ); - async function loadUserId() { - const userId = await newFunGetUserId(); - - setUserLoginId(userId as string); - } - useShallowEffect(() => { - loadUserId(); - try { WibuRealtime.init({ project: "hipmi", @@ -97,7 +88,7 @@ export default function RealtimeProvider({ if ( data.type == "notification" && data.pushNotificationTo == "USER" && - data.dataMessage?.userId == userLoginId + data.dataMessage?.userId == userId ) { setNewUserNtf((e) => e + 1); setDataRealtime(data.dataMessage as any); @@ -144,7 +135,7 @@ export default function RealtimeProvider({ data.type == "notification" && data.pushNotificationTo == "USER" && data.dataMessage?.status == "Peserta Event" && - userLoginId !== data.dataMessage?.userId + userId !== data.dataMessage?.userId ) { setNewUserNtf((e) => e + 1); } @@ -172,7 +163,7 @@ export default function RealtimeProvider({ data.type == "notification" && data.pushNotificationTo == "USER" && data.dataMessage?.status == "Voting Masuk" && - userLoginId !== data.dataMessage?.userId + userId !== data.dataMessage?.userId ) { setNewUserNtf((e) => e + 1); } @@ -200,9 +191,9 @@ export default function RealtimeProvider({ // data.type == "notification" && // data.pushNotificationTo == "ADMIN" && // data.dataMessage?.status == "Menunggu" && - // userLoginId !== data.dataMessage?.userId + // userId !== data.dataMessage?.userId // ) { - // console.log("yes"); + // } // ---------------------- DONASI ------------------------- // diff --git a/src/app_modules/admin/job/child/reject/index.tsx b/src/app_modules/admin/job/child/reject/index.tsx index 8ba05154..ec3364a9 100644 --- a/src/app_modules/admin/job/child/reject/index.tsx +++ b/src/app_modules/admin/job/child/reject/index.tsx @@ -1,5 +1,6 @@ "use client"; +import { IRealtimeData } from "@/app/lib/global_state"; import { RouterAdminJob } from "@/app/lib/router_admin/router_admin_job"; import ComponentGlobal_InputCountDown from "@/app_modules/_global/component/input_countdown"; import { ComponentGlobal_NotifikasiBerhasil } from "@/app_modules/_global/notif_global/notifikasi_berhasil"; @@ -8,7 +9,6 @@ import { ComponentAdminGlobal_TitlePage } from "@/app_modules/admin/_admin_globa import ComponentAdminGlobal_HeaderTamplate from "@/app_modules/admin/_admin_global/header_tamplate"; import adminNotifikasi_funCreateToUser from "@/app_modules/admin/notifikasi/fun/create/fun_create_notif_user"; import { MODEL_JOB } from "@/app_modules/job/model/interface"; -import mqtt_client from "@/util/mqtt_client"; import { Button, Center, @@ -22,11 +22,12 @@ import { Table, Text, TextInput, - Textarea + Textarea, } from "@mantine/core"; import { IconBan, IconPhotoCheck, IconSearch } from "@tabler/icons-react"; import { useRouter } from "next/navigation"; import { useState } from "react"; +import { WibuRealtime } from "wibu-pkg"; import { AdminJob_funEditCatatanById } from "../../fun/edit/fun_edit_catatan_by_id"; import adminJob_getListReject from "../../fun/get/get_list_reject"; @@ -311,7 +312,7 @@ async function onReject({ const loadData = await adminJob_getListReject({ page: 1 }); onSetData(loadData); - const dataNotif = { + const dataNotifikasi: IRealtimeData = { appId: reject.data?.id as any, status: reject.data?.MasterStatus?.name as any, userId: reject.data?.authorId as any, @@ -321,14 +322,15 @@ async function onReject({ }; const notif = await adminNotifikasi_funCreateToUser({ - data: dataNotif as any, + data: dataNotifikasi as any, }); if (notif.status === 201) { - mqtt_client.publish( - "USER", - JSON.stringify({ userId: reject?.data?.authorId, count: 1 }) - ); + WibuRealtime.setData({ + type: "notification", + pushNotificationTo: "USER", + dataMessage: dataNotifikasi, + }); } ComponentGlobal_NotifikasiBerhasil(reject.message); diff --git a/src/app_modules/home/view_home_new.tsx b/src/app_modules/home/view_home_new.tsx index e383952e..b6f4dd51 100644 --- a/src/app_modules/home/view_home_new.tsx +++ b/src/app_modules/home/view_home_new.tsx @@ -1,5 +1,7 @@ "use client"; +import { API_RouteNotifikasi } from "@/app/lib/api_user_router/route_api_notifikasi"; import { gs_count_ntf, gs_user_ntf } from "@/app/lib/global_state"; +import { RouterProfile } from "@/app/lib/router_hipmi/router_katalog"; import { RouterNotifikasi } from "@/app/lib/router_hipmi/router_notifikasi"; import { RouterUserSearch } from "@/app/lib/router_hipmi/router_user_search"; import { ActionIcon, Indicator, Text } from "@mantine/core"; @@ -9,46 +11,35 @@ import { useAtom } from "jotai"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { MainColor } from "../_global/color"; -import { ComponentGlobal_NotifikasiPeringatan } from "../_global/notif_global"; import UIGlobal_LayoutHeaderTamplate from "../_global/ui/ui_header_tamplate"; import UIGlobal_LayoutTamplate from "../_global/ui/ui_layout_tamplate"; -import notifikasi_countUserNotifikasi from "../notifikasi/fun/count/fun_count_by_id"; +import { gs_notifikasi_kategori_app } from "../notifikasi/lib"; import BodyHome from "./component/body_home"; import FooterHome from "./component/footer_home"; import { apiGetDataHome } from "./fun/get/api_home"; -import { RouterProfile } from "@/app/lib/router_hipmi/router_katalog"; -import { gs_notifikasi_kategori_app } from "../notifikasi/lib"; -export default function HomeViewNew({ - countNotifikasi, -}: { - countNotifikasi: number; -}) { - const [countNtf, setCountNtf] = useState(countNotifikasi); +export default function HomeViewNew() { + const [countNtf, setCountNtf] = useAtom(gs_count_ntf); const [newUserNtf, setNewUserNtf] = useAtom(gs_user_ntf); - const [countLoadNtf, setCountLoadNtf] = useAtom(gs_count_ntf); const [dataUser, setDataUser] = useState({}); const [categoryPage, setCategoryPage] = useAtom(gs_notifikasi_kategori_app); const router = useRouter(); useShallowEffect(() => { - onLoadNotifikasi({ - onLoad(val) { - setCountNtf(val); - }, - }); - - setCountNtf(countLoadNtf as any); - }, [countLoadNtf, setCountNtf]); + onLoadNotifikasi(); + }, []); useShallowEffect(() => { - setCountNtf(countNtf + newUserNtf); - setNewUserNtf(0); - }, [newUserNtf, setCountNtf]); + if (countNtf != null) { + setCountNtf(countNtf + newUserNtf); + setNewUserNtf(0); + } + }, [newUserNtf, countNtf]); - async function onLoadNotifikasi({ onLoad }: { onLoad: (val: any) => void }) { - const loadNotif = await notifikasi_countUserNotifikasi(); - onLoad(loadNotif); + async function onLoadNotifikasi() { + const loadNotif = await fetch(API_RouteNotifikasi.get_count_by_id()); + const data = await loadNotif.json().then((res) => res.data); + setCountNtf(data); } useShallowEffect(() => { @@ -75,6 +66,7 @@ export default function HomeViewNew({ customButtonLeft={ { if ( @@ -93,6 +85,7 @@ export default function HomeViewNew({ customButtonRight={ { if ( dataUser.profile === undefined || @@ -100,24 +93,23 @@ export default function HomeViewNew({ ) { router.push(RouterProfile.create, { scroll: false }); } else { - setCategoryPage("Semua") + setCategoryPage("Semua"); router.push( RouterNotifikasi.categoryApp({ name: "semua" }), { scroll: false, } ); - } }} > - {countNotifikasi > 0 ? ( + {countNtf != null && countNtf > 0 ? ( - {countNotifikasi > 99 ? "99+" : countNotifikasi} + {countNtf > 99 ? "99+" : countNtf} } > @@ -137,137 +129,3 @@ export default function HomeViewNew({ ); } - -// "use client"; -// import { API_RouteNotifikasi } from "@/app/lib/api_user_router/route_api_notifikasi"; -// import { gs_count_ntf, gs_user_ntf } from "@/app/lib/global_state"; -// import { RouterProfile } from "@/app/lib/router_hipmi/router_katalog"; -// import { RouterNotifikasi } from "@/app/lib/router_hipmi/router_notifikasi"; -// import { RouterUserSearch } from "@/app/lib/router_hipmi/router_user_search"; -// import { ActionIcon, Indicator, Text } from "@mantine/core"; -// import { useShallowEffect } from "@mantine/hooks"; -// import { IconBell, IconUserSearch } from "@tabler/icons-react"; -// import { useAtom } from "jotai"; -// import { useRouter } from "next/navigation"; -// import { useState } from "react"; -// import { MainColor } from "../_global/color"; -// import UIGlobal_LayoutHeaderTamplate from "../_global/ui/ui_header_tamplate"; -// import UIGlobal_LayoutTamplate from "../_global/ui/ui_layout_tamplate"; -// import { gs_notifikasi_kategori_app } from "../notifikasi/lib"; -// import BodyHome from "./component/body_home"; -// import FooterHome from "./component/footer_home"; -// import { apiGetDataHome } from "./fun/get/api_home"; - -// export default function HomeViewNew() { -// const [countNtf, setCountNtf] = useAtom(gs_count_ntf); -// const [newUserNtf, setNewUserNtf] = useAtom(gs_user_ntf); -// const [dataUser, setDataUser] = useState({}); -// const [categoryPage, setCategoryPage] = useAtom(gs_notifikasi_kategori_app); -// const router = useRouter(); - -// useShallowEffect(() => { -// onLoadNotifikasi(); -// }, []); - -// useShallowEffect(() => { -// if (countNtf != null) { -// setCountNtf(countNtf + newUserNtf); -// setNewUserNtf(0); -// } -// console.log("notif baru", newUserNtf); -// console.log("notif baru", countNtf); -// }, [newUserNtf, countNtf]); - -// async function onLoadNotifikasi() { -// const loadNotif = await fetch(API_RouteNotifikasi.get_count_by_id()); -// const data = await loadNotif.json().then((res) => res.data); -// setCountNtf(data); -// } - -// useShallowEffect(() => { -// cekUserLogin(); -// }, []); - -// async function cekUserLogin() { -// try { -// const response = await apiGetDataHome("?cat=cek_profile"); -// if (response.success) { -// setDataUser(response.data); -// } -// } catch (error) { -// console.error(error); -// } -// } - -// return ( -// <> -// { -// if ( -// dataUser.profile === undefined || -// dataUser?.profile === null -// ) { -// router.push(RouterProfile.create, { scroll: false }); -// } else { -// router.push(RouterUserSearch.main, { scroll: false }); -// } -// }} -// > -// -// -// } -// customButtonRight={ -// { -// if ( -// dataUser.profile === undefined || -// dataUser?.profile === null -// ) { -// router.push(RouterProfile.create, { scroll: false }); -// } else { -// setCategoryPage("Semua"); -// router.push( -// RouterNotifikasi.categoryApp({ name: "semua" }), -// { -// scroll: false, -// } -// ); -// } -// }} -// > -// {countNtf != null && countNtf > 0 ? ( -// -// {countNtf > 99 ? "99+" : countNtf} -// -// } -// > -// -// -// ) : ( -// -// )} -// -// } -// /> -// } -// footer={} -// > -// -// -// -// ); -// }