Merge pull request #306 from bipproduction/bagas/12-feb-25

fix API portofolio
This commit is contained in:
Bagasbanuna02
2025-02-12 17:41:25 +08:00
committed by GitHub
18 changed files with 462 additions and 567 deletions

View File

@@ -1,138 +0,0 @@
/* 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;
}
}

View File

@@ -1,106 +0,0 @@
// 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<LogEntry[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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 <div className={styles.loading}>Loading logs...</div>;
if (error) return <div className={styles.error}>Error: {error}</div>;
return (
<div className={styles.container}>
<h1 className={styles.title}>System Logs</h1>
<div className={styles.filterContainer}>
<select
value={filter}
onChange={(e) => setFilter(e.target.value as any)}
className={styles.select}
>
<option value="all">All Logs</option>
<option value="error">Errors</option>
<option value="warn">Warnings</option>
<option value="info">Info</option>
</select>
</div>
<div className={styles.logsContainer}>
{filteredLogs.map((log, index) => (
<div
key={index}
className={`${styles.logItem} ${
log.level === "error"
? styles.errorLog
: log.level === "warn"
? styles.warnLog
: styles.infoLog
}`}
>
<div className={styles.logHeader}>
<span className={styles.timestamp}>
{format(new Date(log.timestamp), "yyyy-MM-dd HH:mm:ss")}
</span>
<span
className={`${styles.level} ${
log.level === "error"
? styles.errorLevel
: log.level === "warn"
? styles.warnLevel
: styles.infoLevel
}`}
>
{log.level.toUpperCase()}
</span>
</div>
<div className={styles.message}>{log.message}</div>
{log.metadata && (
<pre className={styles.metadata}>
{JSON.stringify(log.metadata, null, 2)}
</pre>
)}
</div>
))}
</div>
</div>
);
}

View File

@@ -1,71 +0,0 @@
// 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<LogEntry[]> {
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 }
);
}
}

View File

@@ -1,7 +1,131 @@
import { prisma } from "@/lib";
import backendLogger from "@/util/backendLogger";
import { NextResponse } from "next/server";
export { POST };
export { GET, POST, PUT };
async function GET(request: Request, { params }: { params: { id: string } }) {
try {
const { id } = params;
const data = await prisma.portofolio.findUnique({
where: {
id: id,
},
include: {
MasterBidangBisnis: {
select: {
id: true,
name: true,
active: true,
},
},
Portofolio_MediaSosial: true,
Profile: {
select: {
userId: true,
User: {
select: {
id: true,
},
},
},
},
BusinessMaps: {
include: {
Author: true,
},
},
},
});
if (!data)
return NextResponse.json(
{
success: false,
message: "Data tidak ditemukan",
},
{ status: 404 }
);
return NextResponse.json(
{
success: true,
message: "Berhasil mendapatkan data",
data: data,
},
{ status: 200 }
);
} catch (error) {
backendLogger.error("API Error Get Data Portofolio", error);
return NextResponse.json(
{
success: false,
message: "API Error Get Data Potofolio",
reason: (error as Error).message,
},
{ status: 500 }
);
}
}
async function PUT(request: Request, { params }: { params: { id: string } }) {
if (request.method !== "PUT") {
return NextResponse.json(
{
success: false,
message: "Method not allowed",
},
{ status: 405 }
);
}
try {
const { id } = params;
const { data } = await request.json();
const udpateData = await prisma.portofolio.update({
where: {
id: id,
},
data: {
namaBisnis: data.namaBisnis,
alamatKantor: data.alamatKantor,
tlpn: data.tlpn,
deskripsi: data.deskripsi,
masterBidangBisnisId: data.masterBidangBisnisId,
},
});
if (!udpateData)
return NextResponse.json(
{
success: false,
message: "Gagal update data",
},
{ status: 400 }
);
return NextResponse.json(
{
success: true,
message: "Berhasil mendapatkan data",
data: udpateData,
},
{ status: 200 }
);
} catch (error) {
return NextResponse.json(
{
success: false,
message: "Error update data",
reason: (error as Error).message,
},
{ status: 500 }
);
}
}
async function POST(request: Request, { params }: { params: { id: string } }) {
if (request.method !== "POST") {

View File

@@ -1,24 +1,9 @@
import { Portofolio_EditDataBisnis } from "@/app_modules/katalog/portofolio";
import { portofolio_getOneById } from "@/app_modules/katalog/portofolio/fun/get/get_one_portofolio";
import { Portofolio_getMasterBidangBisnis } from "@/app_modules/katalog/portofolio/fun/master/get_bidang_bisnis";
import _ from "lodash";
export default async function Page({ params }: { params: { id: string } }) {
let portoId = params.id;
const data = await portofolio_getOneById(portoId);
const dataPorto = _.omit(data, [
"Logo",
"Portofolio_MediaSosial",
"Portofolio_MediaSosial",
"logoId",
"profileId",
]);
const listBidang = await Portofolio_getMasterBidangBisnis()
export default async function Page() {
return (
<>
<Portofolio_EditDataBisnis dataPorto={dataPorto as any} listBidang={listBidang as any} />
<Portofolio_EditDataBisnis />
</>
);
}

View File

@@ -2,26 +2,11 @@ import { Portofolio_EditLogoBisnis } from "@/app_modules/katalog/portofolio";
import { portofolio_getOneById } from "@/app_modules/katalog/portofolio/fun/get/get_one_portofolio";
import _ from "lodash";
export default async function Page({ params }: { params: { id: string } }) {
let portoId = params.id;
const dataPorto = await portofolio_getOneById(portoId).then((res) =>
_.omit(res, [
"Logo",
"MasterBidangBisnis",
"Portofolio_MediaSosial",
"active",
"alamatKantor",
"deskripsi",
"masterBidangBisnisId",
"profileId",
"tlpn",
"namaBisnis"
])
);
export default async function Page() {
return (
<>
<Portofolio_EditLogoBisnis dataPorto={dataPorto as any} />
<Portofolio_EditLogoBisnis />
</>
);
}

View File

@@ -5,6 +5,7 @@ import {
UIGlobal_LayoutHeaderTamplate,
UIGlobal_LayoutTamplate,
} from "@/app_modules/_global/ui";
import CustomSkeleton from "@/app_modules/components/CustomSkeleton";
import { Button, Grid, Skeleton, Stack } from "@mantine/core";
import Link from "next/link";
@@ -14,100 +15,12 @@ export default function Voting_ComponentSkeletonViewPuh() {
<UIGlobal_LayoutTamplate
header={<UIGlobal_LayoutHeaderTamplate title="Skeleton Maker" />}
>
<Button
>
<Link
color="white"
style={{
color: "white",
textDecoration: "none",
}}
target="_blank"
href={
"https://wa.me/+6281339158911?text=Hallo , Apa boleh saya minta informasi tentang DariBaliMice?"
}
>
{" "}
Kirim
</Link>
</Button>
<Stack>
<ComponentGlobal_CardStyles marginBottom={"0"}>
<Stack>
<Skeleton h={20} w={100} />
{Array.from(new Array(2)).map((e, i) => (
<Grid align="center" gutter={"md"} key={i}>
<Grid.Col span={"content"}>
<Skeleton circle height={40} />
</Grid.Col>
<Grid.Col span={3}>
<Skeleton height={20} w={150} />
</Grid.Col>
</Grid>
))}
</Stack>
</ComponentGlobal_CardStyles>
{/* <ComponentGlobal_CardStyles marginBottom={"0"}>
<Stack spacing={"xl"}>
<Grid align="center" gutter={"md"}>
<Grid.Col span={"content"}>
<Skeleton circle height={40} />
</Grid.Col>
<Grid.Col span={3}>
<Skeleton height={20} w={150} />
</Grid.Col>
</Grid>
<Center>
<Skeleton height={15} w={200} />
</Center>
<Grid align="center" gutter={"md"}>
<Grid.Col span={"content"}>
<Skeleton h={15} w={70} />
</Grid.Col>
<Grid.Col span={3}>
<Skeleton height={15} w={200} />
</Grid.Col>
</Grid>
<Grid align="center" gutter={"md"}>
<Grid.Col span={"content"}>
<Skeleton h={15} w={70} />
</Grid.Col>
<Grid.Col span={3}>
<Skeleton height={15} w={200} />
</Grid.Col>
</Grid>
<Skeleton height={15} w={100} />
<Skeleton height={15} w={"100%"} />
<Skeleton height={15} w={100} />
<Skeleton height={15} w={"100%"} />
</Stack>
</ComponentGlobal_CardStyles> */}
{/* <ComponentGlobal_CardStyles>
<Stack>
<Center>
<Skeleton h={20} w={"30%"} />
</Center>
<Group position="center" spacing={50}>
<Stack align="center">
<Skeleton circle height={70} />
<Skeleton height={20} w={50} />
</Stack>
<Stack align="center">
<Skeleton circle height={70} />
<Skeleton height={20} w={50} />
</Stack>
</Group>
</Stack>
</ComponentGlobal_CardStyles> */}
<Stack spacing={"xl"} p={"sm"}>
{Array.from({ length: 4 }).map((_, i) => (
<CustomSkeleton key={i} height={50} width={"100%"} />
))}
<CustomSkeleton height={100} width={"100%"} />
<CustomSkeleton radius="xl" height={50} width={"100%"} />
</Stack>
</UIGlobal_LayoutTamplate>
</>