API Dashboard Investasi & Event

This commit is contained in:
2025-01-30 17:44:18 +08:00
36 changed files with 1070 additions and 288 deletions

View File

@@ -2,6 +2,8 @@
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.42](https://github.com/bipproduction/hipmi/compare/v1.2.41...v1.2.42) (2025-01-30)
## [1.2.41](https://github.com/bipproduction/hipmi/compare/v1.2.40...v1.2.41) (2025-01-21)
## [1.2.40](https://github.com/bipproduction/hipmi/compare/v1.2.39...v1.2.40) (2025-01-16)

View File

@@ -1,6 +1,6 @@
{
"name": "hipmi",
"version": "1.2.41",
"version": "1.2.42",
"private": true,
"prisma": {
"seed": "bun prisma/seed.ts"

View File

@@ -11,7 +11,6 @@ datasource db {
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
username String @unique
@@ -182,6 +181,15 @@ model MasterStatus {
Job Job[]
}
model MasterStatusTransaksi {
id String @id @default(cuid())
name String
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
EventTransaksi EventTransaksi[]
}
// -------------------- INVESTASI --------------------- //
// Table investasi / saham
model Investasi {
@@ -983,16 +991,17 @@ model EventSponsor {
}
model EventTransaksi {
id String @id @default(cuid())
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id String @id @default(cuid())
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
nominal Int
MasterBank MasterBank? @relation(fields: [masterBankId], references: [id])
masterBankId String?
status String
transferImageId String?
MasterBank MasterBank? @relation(fields: [masterBankId], references: [id])
masterBankId String?
AuthorId User? @relation(fields: [authorId], references: [id])
authorId String?
@@ -1001,4 +1010,7 @@ model EventTransaksi {
EventSponsor EventSponsor? @relation(fields: [eventSponsorId], references: [id])
eventSponsorId String? @unique
MasterStatusTransaksi MasterStatusTransaksi? @relation(fields: [masterStatusTransaksiId], references: [id])
masterStatusTransaksiId String?
}

View File

@@ -0,0 +1,50 @@
import { prisma } from "@/app/lib";
import backendLogger from "@/util/backendLogger";
import { data } from "autoprefixer";
import _ from "lodash";
import { NextResponse } from "next/server";
export async function GET(request: Request, { params }: {
params: { name: string }
}) {
const method = request.method;
if (method !== "GET") {
return NextResponse.json({
success: false,
message: "Method not allowed",
},
{ status: 405 }
);
}
const { name } = params;
try {
let fixData;
const fixStatus = _.startCase(name);
fixData = await prisma.donasi.count({
where: {
DonasiMaster_Status: {
name: fixStatus
}
}
});
return NextResponse.json({
success: true,
message: "Success get data donasi dashboard",
data: fixData
},
{ status: 200 }
)
} catch (error) {
backendLogger.error("Error get data donasi dashboard >>", error);
return NextResponse.json({
success: false,
message: "Failed to get data donasi dashboard",
reason: (error as Error).message
},
{ status: 500 }
)
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,53 @@
import { prisma } from "@/app/lib";
import backendLogger from "@/util/backendLogger";
import _ from "lodash";
import { NextResponse } from "next/server";
export async function GET(
request: Request,
{ params }: { params: { name: string } }
) {
const method = request.method;
if (method !== "GET") {
return NextResponse.json(
{ success: false, message: "Method not allowed" },
{ status: 405 }
);
}
const { name } = params;
try {
let fixData;
const fixStatus = _.startCase(name);
fixData = await prisma.event.count({
where: {
EventMaster_Status: {
name: fixStatus,
},
isArsip: false,
},
});
return NextResponse.json(
{
success: true,
message: "Success get data event dashboard",
data: fixData,
},
{ status: 200 }
);
} catch (error) {
backendLogger.error("Error get data event dashboard >>", error);
return NextResponse.json(
{
success: false,
message: "Failed to get data",
reason: (error as Error).message,
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,47 @@
import { prisma } from "@/app/lib";
import backendLogger from "@/util/backendLogger";
import _ from "lodash";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const method = request.method;
if (method !== "GET") {
return NextResponse.json(
{ success: false, message: "Method not allowed" },
{ status: 405 }
);
}
try {
let fixData;
fixData = await prisma.event.count({
where: {
EventMaster_Status: {
name: "Publish",
},
isArsip: true,
},
});
return NextResponse.json(
{
success: true,
message: "Success get data riwayat event dashboard",
data: fixData,
},
{ status: 200 }
);
} catch (error) {
backendLogger.error("Error get data riwayat event dashboard >>", error);
return NextResponse.json(
{
success: false,
message: "Failed to get data",
reason: (error as Error).message,
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,44 @@
import { prisma } from "@/app/lib";
import backendLogger from "@/util/backendLogger";
import _ from "lodash";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const method = request.method;
if (method !== "GET") {
return NextResponse.json(
{ success: false, message: "Method not allowed" },
{ status: 405 }
);
}
try {
let fixData;
fixData = await prisma.eventMaster_TipeAcara.count({
where: {
active: true,
},
});
return NextResponse.json(
{
success: true,
message: "Success get data riwayat event dashboard",
data: fixData,
},
{ status: 200 }
);
} catch (error) {
backendLogger.error("Error get data riwayat event dashboard >>", error);
return NextResponse.json(
{
success: false,
message: "Failed to get data",
reason: (error as Error).message,
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -1,24 +1,41 @@
import { prisma } from "@/app/lib";
import backendLogger from "@/util/backendLogger";
import _ from "lodash";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
export async function GET(request: Request, { params }: { params: { name: string } }) {
const method = request.method;
if (method !== "GET") {
return NextResponse.json({
success: false,
message: "Method not allowed",
},
{ status: 405 }
);
}
const { name } = params;
try {
const data = await prisma.investasi.count({
let fixData;
const fixStatus = _.startCase(name);
fixData = await prisma.investasi.count({
where: {
active: true
},
MasterStatusInvestasi: {
name: fixStatus
},
}
})
return NextResponse.json({
message: "Data Investasi",
data: data,
success: true,
message: "Success get data investasi dashboard",
data: fixData,
},
{ status: 200 }
)
} catch (error) {
backendLogger.error("Error Get Count Investasi Main Dashboard")
backendLogger.error("Error get data investasi dashboard >>", error);
return NextResponse.json({
message: "Error Get Count Investasi Main Dashboard",
success: false,
message: "Error get data investasi dashboard",
reason: (error as Error).message
},
{ status: 500 }

View File

@@ -32,19 +32,17 @@ export async function GET(
},
});
await prisma.$disconnect();
return NextResponse.json({
success: true,
message: "Berhasil mendapatkan data",
data: fixData,
});
} catch (error) {
await prisma.$disconnect();
return NextResponse.json(
{ success: false, message: "Gagal mendapatkan data" },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -0,0 +1,41 @@
import { prisma } from "@/app/lib";
import backendLogger from "@/util/backendLogger";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const method = request.method;
if (method !== "GET") {
return NextResponse.json(
{ success: false, message: "Method not allowed" },
{ status: 405 }
);
}
try {
const res = await prisma.masterBank.findMany({
orderBy: {
updatedAt: "asc",
},
where: {
isActive: true,
},
});
await prisma.$disconnect();
return NextResponse.json(
{ success: true, message: "Berhasil mendapatkan data", data: res },
{ status: 200 }
);
} catch (error) {
await prisma.$disconnect();
backendLogger.error("Error Get Master Bank >>", error);
return NextResponse.json(
{
success: false,
message: "API Error Get Data",
reason: (error as Error).message,
},
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,41 @@
import { prisma } from "@/app/lib";
import backendLogger from "@/util/backendLogger";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const method = request.method;
if (method !== "GET") {
return NextResponse.json(
{ success: false, message: "Method not allowed" },
{ status: 405 }
);
}
try {
const res = await prisma.masterStatusTransaksi.findMany({
orderBy: {
updatedAt: "asc",
},
where: {
isActive: true,
},
});
await prisma.$disconnect();
return NextResponse.json(
{ success: true, message: "Berhasil mendapatkan data", data: res },
{ status: 200 }
);
} catch (error) {
await prisma.$disconnect();
backendLogger.error("Error Get Master Status Transaksi >>", error);
return NextResponse.json(
{
success: false,
message: "API Error Get Data",
reason: (error as Error).message,
},
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,60 @@
export {
apiGetEventStatusCountDashboard,
apiGetEventTipeAcara,
apiGetEventRiwayatCount,
};
const apiGetEventStatusCountDashboard = async ({
name,
}: {
name: "Publish" | "Review" | "Reject";
}) => {
const { token } = await fetch("/api/get-cookie").then((res) => res.json());
if (!token) return await token.json().catch(() => null);
const response = await fetch(`/api/admin/event/dashboard/${name}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
return await response.json().catch(() => null);
};
const apiGetEventRiwayatCount = async () => {
const { token } = await fetch("/api/get-cookie").then((res) => res.json());
if (!token) return await token.json().catch(() => null);
const response = await fetch(`/api/admin/event/dashboard/riwayat`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
return await response.json().catch(() => null);
}
const apiGetEventTipeAcara = async () => {
const { token } = await fetch("/api/get-cookie").then((res) => res.json());
if (!token) return await token.json().catch(() => null);
const response = await fetch(`/api/admin/event/dashboard/tipe-acara`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
return await response.json().catch(() => null);
};

View File

@@ -1,26 +1,9 @@
import { AdminEvent_Main } from "@/app_modules/admin/event";
import AdminEvent_funCountByStatusId from "@/app_modules/admin/event/fun/count/fun_count_event_by_status_id";
import { AdminEvent_funCountRiwayat } from "@/app_modules/admin/event/fun/count/fun_count_riwayat";
import { AdminEvent_funCountTipeAcara } from "@/app_modules/admin/event/fun/count/fun_count_tipe_acara";
export default async function Page() {
const countPublish = await AdminEvent_funCountByStatusId("1");
const countReview = await AdminEvent_funCountByStatusId("2");
const countDraft = await AdminEvent_funCountByStatusId("3");
const countReject = await AdminEvent_funCountByStatusId("4");
const countTipeAcara = await AdminEvent_funCountTipeAcara();
const countRiwayat = await AdminEvent_funCountRiwayat();
return (
<>
<AdminEvent_Main
countPublish={countPublish as number}
countReview={countReview as number}
countDraft={countDraft as number}
countReject={countReject as number}
countTipeAcara={countTipeAcara as number}
countRiwayat={countRiwayat}
/>
<AdminEvent_Main />
</>
);
}

View File

@@ -6,10 +6,10 @@ import Admin_getTotalInvestasiByUser from "@/app_modules/admin/investasi/fun/get
export default async function Page() {
const listInvestasi = await Admin_funGetAllInvestasi();
const countDraft = await Admin_CountStatusInvestasi(1);
const countReview = await Admin_CountStatusInvestasi(2);
const countPublish = await Admin_CountStatusInvestasi(3);
const countReject = await Admin_CountStatusInvestasi(4);
// const countDraft = await Admin_CountStatusInvestasi(1);
// const countReview = await Admin_CountStatusInvestasi(2);
// const countPublish = await Admin_CountStatusInvestasi(3);
// const countReject = await Admin_CountStatusInvestasi(4);
const totalInvestasiByUser = await Admin_getTotalInvestasiByUser()
const publishProgres = await Admin_getPublishProgresInvestasi()
// console.log(targetTerbesar)
@@ -18,10 +18,10 @@ export default async function Page() {
<>
<Admin_Investasi
listInvestasi={listInvestasi as any}
countDraft={countDraft}
countReview={countReview}
countPublish={countPublish}
countReject={countReject}
// countDraft={countDraft}
// countReview={countReview}
// countPublish={countPublish}
// countReject={countReject}
totalInvestasiByUser={totalInvestasiByUser}
publishProgres={publishProgres}

View File

@@ -1,7 +1,5 @@
import Event_MetodePembayaran from '@/app_modules/event/detail/sponsor/metode_pembayaran';
import { MODEL_MASTER_BANK } from '@/app_modules/investasi/_lib/interface';
import React from 'react';
function Page() {
return (

View File

@@ -0,0 +1,14 @@
import { funGetUserIdByToken } from '@/app_modules/_global/fun/get';
import Event_Invoice from '@/app_modules/event/detail/invoice';
import React from 'react';
async function Page() {
const userLoginId = await funGetUserIdByToken();
return (
<>
<Event_Invoice userLoginId={userLoginId} />
</>
);
}
export default Page;

View File

@@ -1,12 +0,0 @@
import Event_Invoice from '@/app_modules/event/detail/invoice';
import React from 'react';
function Page() {
return (
<>
<Event_Invoice/>
</>
);
}
export default Page;

5
src/app/lib/limit.ts Normal file
View File

@@ -0,0 +1,5 @@
import pLimit from "p-limit";
const global_limit = pLimit(1);
export default global_limit;

View File

@@ -1,28 +0,0 @@
"use server";
import _ from "lodash";
import { cookies } from "next/headers";
import { decrypt } from "../auth/_lib/decrypt";
import backendLogger from "@/util/backendLogger";
export async function newFunGetUserId() {
try {
const key = process.env.NEXT_PUBLIC_BASE_SESSION_KEY;
const c = cookies().get("hipmi-key");
if (!c || !c?.value || _.isEmpty(c?.value) || _.isUndefined(c?.value)) {
return null;
}
const token = c.value;
const dataUser = await decrypt({
token: token,
encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!,
});
return dataUser?.id;
} catch (error) {
backendLogger.log("Gagal mendapatkan user id", error);
return null;
}
}

View File

@@ -43,7 +43,11 @@ export const RouterEvent = {
`/dev/event/detail/sponsor/tambah_sponsor/${id}`,
detail_sponsor: ({ id }: { id: string }) =>
`/dev/event/detail/detail_sponsor/${id}`,
nominal_sponsor: ({ id }: { id: string }) =>
`/dev/event/detail/sponsor/nominal_sponsor/${id}`,
metode_pembayaran: ({ id }: { id: string }) =>
`/dev/event/detail/sponsor/metode_pembayaran/${id}`,
invoice: ({ id }: { id: string }) =>
`/dev/event/invoice/${id}`,
};

View File

@@ -25,6 +25,7 @@ import voting_status from "../../../bin/seeder/voting/master_status.json";
import { master_kategori_app } from "@/bin/seeder/master";
import { new_status_transaksi_investasi } from "@/bin/seeder/investasi";
import { master_nama_bank } from "@/bin/seeder/master";
import { master_status_transaksi } from "@/bin/seeder/master";
import pLimit from "p-limit";
async function masterUserRole() {
@@ -543,6 +544,26 @@ async function masterInvestasiNewTransaksiStatus() {
console.log("masterInvestasiNewTransaksiStatus success");
}
async function masterStatusTransaksi() {
for (let a of master_status_transaksi) {
await prisma.masterStatusTransaksi.upsert({
where: {
id: a.id,
},
create: {
id: a.id,
name: a.name,
},
update: {
id: a.id,
name: a.name,
},
});
}
console.log("masterStatusTransaksi success");
}
const listSeederQueue = [
masterUserRole,
seederUser,
@@ -570,6 +591,7 @@ const listSeederQueue = [
seederNomorAdmin,
masterKategoriApp,
masterInvestasiNewTransaksiStatus,
masterStatusTransaksi,
];
const limit = pLimit(1);

View File

@@ -0,0 +1,19 @@
export { apiGetMasterBank };
const apiGetMasterBank = async () => {
const { token } = await fetch("/api/get-cookie").then((res) => res.json());
if (!token) return await token.json().catch(() => null);
const respone = await fetch(`/api/master/bank`, {
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
});
return await respone.json().catch(() => null);
};

View File

@@ -0,0 +1,16 @@
const apiGetDonasiStatusCountDashboard = async ({ name }:
{ name: "Publish" | "Review" | "Reject" }) => {
const { token } = await fetch("/api/get-cookie").then((res) => res.json());
if (!token) return await token.json().catch(() => null);
const response = await fetch(`api/admin/donasi/dashboard/${name}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
}
});
return await response.json().catch(() => null);
};

View File

@@ -2,7 +2,16 @@
import { RouterAdminEvent } from "@/app/lib/router_admin/router_admin_event";
import {
apiGetEventRiwayatCount,
apiGetEventStatusCountDashboard,
apiGetEventTipeAcara,
} from "@/app/dev/admin/event/_lib/api_fecth_admin_event";
import global_limit from "@/app/lib/limit";
import { AccentColor, MainColor } from "@/app_modules/_global/color";
import { AdminColor } from "@/app_modules/_global/color/color_pallet";
import CustomSkeleton from "@/app_modules/components/CustomSkeleton";
import { clientLogger } from "@/util/clientLogger";
import {
Flex,
Paper,
@@ -10,81 +19,192 @@ import {
Stack,
Text,
ThemeIcon,
Title
Title,
} from "@mantine/core";
import { IconAlertTriangle, IconBookmark, IconBriefcase, IconHistory, IconUpload } from "@tabler/icons-react";
import { useShallowEffect } from "@mantine/hooks";
import {
IconAlertTriangle,
IconBookmark,
IconBriefcase,
IconHistory,
IconUpload,
} from "@tabler/icons-react";
import { useRouter } from "next/navigation";
import { useState } from "react";
import ComponentAdminGlobal_HeaderTamplate from "../../_admin_global/header_tamplate";
import { AdminColor } from "@/app_modules/_global/color/color_pallet";
export default function AdminEvent_Main({
countPublish,
countReview,
countDraft,
countReject,
countTipeAcara,
countRiwayat,
}: {
countPublish: number;
countReview: number;
countDraft: number;
countReject: number;
countTipeAcara: number;
countRiwayat: number
}) {
export default function AdminEvent_Main() {
const router = useRouter();
const [countPublish, setCountPublish] = useState<number | null>(null);
const [countReview, setCountReview] = useState<number | null>(null);
const [countReject, setCountReject] = useState<number | null>(null);
const [countTipeAcara, setCountTipeAcara] = useState<number | null>(null);
const [countRiwayat, setCountRiwayat] = useState<number | null>(null);
useShallowEffect(() => {
handlerLoadData();
}, []);
async function handlerLoadData() {
try {
const listLoadData = [
global_limit(() => onLoadCountPublish()),
global_limit(() => onLoadCountReview()),
global_limit(() => onLoadCountReject()),
global_limit(() => onLoadCountRiwayat()),
global_limit(() => onLoadCountTipeAcara()),
];
const result = await Promise.all(listLoadData);
} catch (error) {
clientLogger.error("Error handler load data", error);
}
}
async function onLoadCountPublish() {
try {
const respone = await apiGetEventStatusCountDashboard({
name: "Publish",
});
if (respone) {
setCountPublish(respone.data);
}
} catch (error) {
clientLogger.error("Error get count publish", error);
}
}
async function onLoadCountReview() {
try {
const respone = await apiGetEventStatusCountDashboard({
name: "Review",
});
if (respone) {
setCountReview(respone.data);
}
} catch (error) {
clientLogger.error("Error get count review", error);
}
}
async function onLoadCountReject() {
try {
const respone = await apiGetEventStatusCountDashboard({
name: "Reject",
});
if (respone) {
setCountReject(respone.data);
}
} catch (error) {
clientLogger.error("Error get count reject", error);
}
}
async function onLoadCountRiwayat() {
try {
const respone = await apiGetEventRiwayatCount();
if (respone) {
setCountRiwayat(respone.data);
}
} catch (error) {
clientLogger.error("Error get count riwayat", error);
}
}
async function onLoadCountTipeAcara() {
try {
const respone = await apiGetEventTipeAcara();
if (respone) {
setCountTipeAcara(respone.data);
}
} catch (error) {
clientLogger.error("Error get count tipe acara", error);
}
}
const listStatus = [
{
id: 1,
name: "Publish",
jumlah: countPublish,
jumlah:
countPublish == null ? (
<CustomSkeleton height={40} width={40} />
) : countPublish ? (
countPublish
) : (
"-"
),
path: RouterAdminEvent.table_publish,
color: MainColor.green,
icon: <IconUpload size={18} color="#4CAF4F"/>,
icon: <IconUpload size={18} color="#4CAF4F" />,
},
{
id: 2,
name: "Review",
jumlah: countReview,
jumlah:
countReview == null ? (
<CustomSkeleton height={40} width={40} />
) : countReview ? (
countReview
) : (
"-"
),
path: RouterAdminEvent.table_review,
color: MainColor.orange,
icon: <IconBookmark size={18} color="#FF7043"/>
icon: <IconBookmark size={18} color="#FF7043" />,
},
// {
// id: 3,
// name: "Draft",
// jumlah: countDraft,
// path: "",
// color: "yellow",
// },
{
id: 3,
name: "Reject",
jumlah: countReject,
jumlah:
countReject == null ? (
<CustomSkeleton height={40} width={40} />
) : countReject ? (
countReject
) : (
"-"
),
path: RouterAdminEvent.table_reject,
color: MainColor.red,
icon: <IconAlertTriangle size={18} color="#FF4B4C" />
icon: <IconAlertTriangle size={18} color="#FF4B4C" />,
},
{
id: 4,
name: "Riwayat Event",
jumlah: countRiwayat,
jumlah:
countRiwayat == null ? (
<CustomSkeleton height={40} width={40} />
) : countRiwayat ? (
countRiwayat
) : (
"-"
),
path: RouterAdminEvent.table_publish,
color: AccentColor.softblue,
icon: <IconHistory size={18} color="#007CBA"/>
icon: <IconHistory size={18} color="#007CBA" />,
},
];
const listBox2 = [
{
id: 1,
name: "Tipe Acara",
jumlah: countTipeAcara,
jumlah:
countTipeAcara == null ? (
<CustomSkeleton height={40} width={40} />
) : countTipeAcara ? (
countTipeAcara
) : (
"-"
),
path: RouterAdminEvent.table_publish,
color: "#A888E2",
icon: <IconBriefcase size={18} color="#A888E2" />
icon: <IconBriefcase size={18} color="#A888E2" />,
},
];
@@ -109,19 +229,23 @@ export default function AdminEvent_Main({
shadow="md"
radius="md"
p="md"
// sx={{ borderColor: e.color, borderStyle: "solid" }}
// sx={{ borderColor: e.color, borderStyle: "solid" }}
>
<Stack spacing={0}>
<Text fw={"bold"} color={AccentColor.white}>{e.name}</Text>
<Text fw={"bold"} color={AccentColor.white}>
{e.name}
</Text>
<Flex align={"center"} justify={"space-between"}>
<Title c={AccentColor.white}>{e.jumlah}</Title>
<ThemeIcon radius={"xl"} size={"md"} color={AccentColor.white}>
<ThemeIcon
radius={"xl"}
size={"md"}
color={AccentColor.white}
>
{e.icon}
</ThemeIcon>
</Flex>
</Stack>
</Paper>
))}
</SimpleGrid>
@@ -141,17 +265,19 @@ export default function AdminEvent_Main({
shadow="md"
radius="md"
p="md"
// sx={{ borderColor: e.color, borderStyle: "solid" }}
// sx={{ borderColor: e.color, borderStyle: "solid" }}
>
<Stack spacing={0}>
<Text fw={"bold"} color={AccentColor.white}>{e.name}</Text>
<Flex align={"center"} justify={"space-between"}>
<Title c={AccentColor.white}>{e.jumlah}</Title>
<ThemeIcon radius={"xl"} size={"md"} bg={AccentColor.white}>
{e.icon}
</ThemeIcon>
</Flex>
</Stack>
<Stack spacing={0}>
<Text fw={"bold"} color={AccentColor.white}>
{e.name}
</Text>
<Flex align={"center"} justify={"space-between"}>
<Title c={AccentColor.white}>{e.jumlah}</Title>
<ThemeIcon radius={"xl"} size={"md"} bg={AccentColor.white}>
{e.icon}
</ThemeIcon>
</Flex>
</Stack>
</Paper>
))}
</SimpleGrid>

View File

@@ -0,0 +1,22 @@
const apiGetInvestasiCountDashboard = async ({ name }:
{ name: "Publish" | "Review" | "Reject" }) => {
const { token } = await fetch("/api/get-cookie").then((res) => res.json());
if (!token) return await token.json().catch(() => null);
const response = await fetch(`/api/admin/investasi/dashboard/${name}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${token}`,
},
})
return await response.json().catch(() => null);
};
export default apiGetInvestasiCountDashboard;

View File

@@ -46,49 +46,129 @@ import TablePublikasiProgresInvestasi from "./table_publikasi_progres";
import ComponentAdminGlobal_HeaderTamplate from "../../_admin_global/header_tamplate";
import { AccentColor, MainColor } from "@/app_modules/_global/color";
import { AdminColor } from "@/app_modules/_global/color/color_pallet";
import { clientLogger } from "@/util/clientLogger";
import CustomSkeleton from "@/app_modules/components/CustomSkeleton";
import global_limit from "@/app/lib/limit";
import { useShallowEffect } from "@mantine/hooks";
import apiGetInvestasiCountDashboard from "../_lib/api_fetch_count_status";
export default function Admin_Investasi({
listInvestasi,
countDraft,
countReview,
countPublish,
countReject,
totalInvestasiByUser,
publishProgres,
}: {
listInvestasi: MODEL_INVESTASI[];
countDraft: number | any;
countReview: number | any;
countPublish: number | any;
countReject: number | any;
totalInvestasiByUser: any[];
publishProgres: any[];
}) {
const [investasi, setInvestasi] = useState(listInvestasi);
const router = useRouter();
const [countPublish, setCountPublish] = useState<number | null>(null);
const [countReview, setCountReview] = useState<number | null>(null);
const [countReject, setCountReject] = useState<number | null>(null);
useShallowEffect(() => {
handlerLoadData();
}, [])
async function handlerLoadData() {
try {
const listLoadData = [
global_limit(() => onLoadCountPublish()),
global_limit(() => onLoadCountReview()),
global_limit(() => onLoadCountReject()),
];
const result = await Promise.all(listLoadData);
} catch (error) {
clientLogger.error("Error handler load data", error);
}
}
async function onLoadCountPublish() {
try {
const response = await apiGetInvestasiCountDashboard({
name: "Publish",
});
console.log("Response Publish", response);
if (response) {
setCountPublish(response.data);
}
} catch (error) {
clientLogger.error("Error get count publish", error);
}
}
async function onLoadCountReview() {
try {
const response = await apiGetInvestasiCountDashboard({
name: "Review",
});
if (response) {
setCountReview(response.data);
}
} catch (error) {
clientLogger.error("Error get count review", error);
}
}
async function onLoadCountReject() {
try {
const response = await apiGetInvestasiCountDashboard({
name: "Reject",
});
if (response) {
setCountReject(response.data);
}
} catch (error) {
clientLogger.error("Error get count reject", error);
}
}
const listBox = [
{
id: 1,
name: "Publish",
jumlah: countPublish,
link: RouterAdminInvestasi_OLD.table_status_publish,
jumlah:
countPublish == null ? (
<CustomSkeleton height={40} width={40} />
) : countPublish ? (
countPublish
) : (
"-"
),
path: RouterAdminInvestasi_OLD.table_status_publish,
color: MainColor.green,
icon: <IconUpload size={18} color="#4CAF4F" />,
},
{
id: 2,
name: "Review",
jumlah: countReview,
link: RouterAdminInvestasi_OLD.table_status_review,
jumlah: countReview == null ? (
<CustomSkeleton height={40} width={40} />
) : countReview ? (
countReview
) : (
"-"
),
path: RouterAdminInvestasi_OLD.table_status_review,
color: MainColor.orange,
icon: <IconBookmark size={18} color="#FF7043" />
},
{
id: 3,
name: "Reject",
jumlah: countReject,
link: RouterAdminInvestasi_OLD.table_status_reject,
jumlah: countReject == null ? (
<CustomSkeleton height={40} width={40} />
) : countReject ? (
countReject
) : (
"-"
),
path: RouterAdminInvestasi_OLD.table_status_reject,
color: MainColor.red,
icon: <IconAlertTriangle size={18} color="#FF4B4C" />

View File

@@ -10,6 +10,7 @@ import { apiGetCountPortofolio, apiGetCountUserActive } from "../lib/api_fetch_m
import { NextResponse } from "next/server";
import { clientLogger } from "@/util/clientLogger";
import MainDashboardSkeleton from "../../_admin_global/_component/skeleton/main_dashboard_skeleton";
import CustomSkeleton from "@/app_modules/components/CustomSkeleton";
export default function AdminMain() {
const [countUser, setCountUser] = useState<number | null>(null);
@@ -34,7 +35,7 @@ export default function AdminMain() {
clientLogger.error("Error get count user data", error);
}
}
async function onLoadDataPortofolio() {
try {
const response = await apiGetCountPortofolio();
@@ -52,14 +53,26 @@ export default function AdminMain() {
{
id: 1,
name: "User",
jumlah: countUser,
jumlah:
countUser == null ? (<CustomSkeleton height={40} width={40} />
) : countUser ? (
countUser
) : (
"-"
),
link: "",
icon: <IconUsers size={18} color="#0066CCFF" />
},
{
id: 2,
name: "Portofolio",
jumlah: countPortofolio,
jumlah:
countPortofolio == null ? (<CustomSkeleton height={40} width={40} />
) : countPortofolio ? (
countPortofolio
) : (
"-"
),
link: "",
icon: <IconFileText size={18} color={"#B6A22EFF"} />
},
@@ -68,35 +81,31 @@ export default function AdminMain() {
return (
<>
{!countUser && !countPortofolio ? (
<MainDashboardSkeleton />
) : (
<Stack spacing={"sm"}>
<ComponentAdminGlobal_HeaderTamplate name="Main Dashboard" />
{/* <Title c={AdminColor.white}>Main Dashboard</Title>
<Stack spacing={"sm"}>
<ComponentAdminGlobal_HeaderTamplate name="Main Dashboard" />
{/* <Title c={AdminColor.white}>Main Dashboard</Title>
<Divider c={AdminColor.dividerWhite} mb={"md"} size={"xs"} /> */}
<Grid>
{listBox.map((e) => (
<Grid.Col md={4} lg={4} key={e.id}>
<Paper style={{ borderColor: "transparent" }} bg={AdminColor.softBlue} withBorder shadow="md" radius="md" p="md">
<Stack spacing={0}>
<Text fw={"bold"} c={MainColor.white}>{e.name}</Text>
<Flex align={"center"} justify={"space-between"}>
<Title c={MainColor.white}>{e.jumlah}</Title>
<ThemeIcon radius={"xl"} size={"md"} color={AccentColor.white}>{e.icon}</ThemeIcon>
</Flex>
</Stack>
</Paper>
</Grid.Col>
))}
<Grid.Col md={4} lg={4}>
{/* <PieChart /> */}
<Grid>
{listBox.map((e) => (
<Grid.Col md={4} lg={4} key={e.id}>
<Paper style={{ borderColor: "transparent" }} bg={AdminColor.softBlue} withBorder shadow="md" radius="md" p="md">
<Stack spacing={0}>
<Text fw={"bold"} c={MainColor.white}>{e.name}</Text>
<Flex align={"center"} justify={"space-between"}>
<Title c={MainColor.white}>{e.jumlah}</Title>
<ThemeIcon radius={"xl"} size={"md"} color={AccentColor.white}>{e.icon}</ThemeIcon>
</Flex>
</Stack>
</Paper>
</Grid.Col>
</Grid>
</Stack>
)}
))}
<Grid.Col md={4} lg={4}>
{/* <PieChart /> */}
</Grid.Col>
</Grid>
</Stack>
</>
);
}

View File

@@ -1,10 +1,23 @@
'use client';
import { AccentColor, MainColor } from '@/app_modules/_global/color';
import { ActionIcon, Button, Grid, Group, Paper, Stack, Text, Title } from '@mantine/core';
import { IconCamera } from '@tabler/icons-react';
import React from 'react';
"use client";
import { AccentColor, MainColor } from "@/app_modules/_global/color";
import { Button, Grid, Group, Paper, Stack, Text, Title } from "@mantine/core";
import { IconCamera } from "@tabler/icons-react";
import { useAtom } from "jotai";
import { useParams, useRouter } from "next/navigation";
import { gs_nominal_sponsor, gs_event_bank_id } from "../../global_state";
function Event_Invoice({ userLoginId }: { userLoginId: string }) {
const router = useRouter();
const params = useParams<{ id: string }>();
const sponsorId = params.id;
const [nominal, setNominal] = useAtom(gs_nominal_sponsor);
const [bankId, setBankId] = useAtom(gs_event_bank_id);
console.log("nominal >>", nominal);
console.log("bankId >>", bankId);
function Event_Invoice() {
return (
<>
<Stack spacing={"lg"} py={"md"}>
@@ -54,17 +67,19 @@ function Event_Invoice() {
>
<Grid>
<Grid.Col span={8}>
<Group position='left' align='center' h={"100%"}>
<Group position="left" align="center" h={"100%"}>
<Title order={4} color={MainColor.yellow}>
9065456754325643
</Title>
</Group>
</Grid.Col>
<Grid.Col span={4}>
<Group position='right'>
<Button radius={"xl"}
<Group position="right">
<Button
radius={"xl"}
style={{ backgroundColor: MainColor.yellow }}
c={MainColor.darkblue}>
c={MainColor.darkblue}
>
Salin
</Button>
</Group>
@@ -101,15 +116,19 @@ function Event_Invoice() {
>
<Grid>
<Grid.Col span={8}>
<Group position='left' align='center' h={"100%"}>
<Group position="left" align="center" h={"100%"}>
<Title order={4} color={MainColor.yellow}>
Rp. 100.000
</Title>
</Group>
</Grid.Col>
<Grid.Col span={4}>
<Group position='right'>
<Button radius={"xl"} style={{ backgroundColor: MainColor.yellow }} c={MainColor.darkblue}>
<Group position="right">
<Button
radius={"xl"}
style={{ backgroundColor: MainColor.yellow }}
c={MainColor.darkblue}
>
Salin
</Button>
</Group>
@@ -131,15 +150,22 @@ function Event_Invoice() {
}}
>
<Stack spacing={"sm"}>
<Group position='center'>
<Button leftIcon={<IconCamera />} radius={"xl"} style={{ backgroundColor: MainColor.yellow }} c={MainColor.darkblue}>
<Group position="center">
<Button
leftIcon={<IconCamera />}
radius={"xl"}
style={{ backgroundColor: MainColor.yellow }}
c={MainColor.darkblue}
>
Upload
</Button>
</Group>
<Text ta={"center"} fz={"xs"} fs={"italic"}>Upload bukti transfer anda</Text>
<Text ta={"center"} fz={"xs"} fs={"italic"}>
Upload bukti transfer anda
</Text>
</Stack>
</Paper>
<Button radius={"xl"} bg={MainColor.yellow} color='yellow' c="black">
<Button radius={"xl"} bg={MainColor.yellow} color="yellow" c="black">
Saya Sudah Transfer
</Button>
</Stack>

View File

@@ -1,84 +1,130 @@
'use client';
"use client";
import { AccentColor, MainColor } from '@/app_modules/_global/color';
import { MODEL_MASTER_BANK } from '@/app_modules/investasi/_lib/interface';
import { Button, Paper, Radio, Stack, Title } from '@mantine/core';
import { useRouter } from 'next/navigation';
import React, { useState } from 'react';
const bank = [
{
id: 1,
namaBank: "BRI",
},
{
id: 2,
namaBank: "BCA",
},
{
id: 3,
namaBank: "BNI",
},
{
id: 4,
namaBank: "BSI",
}
]
import { RouterEvent } from "@/app/lib/router_hipmi/router_event";
import { AccentColor, MainColor } from "@/app_modules/_global/color";
import ComponentGlobal_IsEmptyData from "@/app_modules/_global/component/is_empty_data";
import { apiGetMasterBank } from "@/app_modules/_global/lib/api_master";
import CustomSkeleton from "@/app_modules/components/CustomSkeleton";
import {
gs_event_bank_id,
gs_nominal_sponsor,
} from "@/app_modules/event/global_state";
import { MODEL_MASTER_BANK } from "@/app_modules/investasi/_lib/interface";
import { clientLogger } from "@/util/clientLogger";
import { Button, Paper, Radio, Stack, Title } from "@mantine/core";
import { useShallowEffect } from "@mantine/hooks";
import { useAtom } from "jotai";
import _ from "lodash";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
function Event_MetodePembayaran() {
const params = useParams<{ id: string }>();
const [pilihBank, setPilihBank] = useState("");
const router = useRouter();
const [nominal, setNominal] = useAtom(gs_nominal_sponsor);
const [bankId, setBankId] = useAtom(gs_event_bank_id);
const [isLoading, setIsLoading] = useState(false);
console.log("nominal >>", nominal);
const [data, setData] = useState<MODEL_MASTER_BANK[] | null>(null);
useShallowEffect(() => {
onLoadBank();
}, []);
async function onLoadBank() {
try {
const respone = await apiGetMasterBank();
if (respone) {
setData(respone.data);
}
} catch (error) {
clientLogger.error("Error get data bank", error);
}
}
if (!data) {
return (
<>
<Stack>
{Array.from({ length: 2 }).map((e, i) => (
<CustomSkeleton key={i} height={50} width={"100%"} />
))}
</Stack>
</>
);
}
return (
<>
<Stack>
<Radio.Group
value={pilihBank}
onChange={setPilihBank}
withAsterisk
color='yellow'
>
{bank.map((e) => (
<Paper
key={e.id}
style={{
backgroundColor: AccentColor.blue,
border: `2px solid ${AccentColor.darkblue}`,
padding: "15px",
cursor: "pointer",
borderRadius: "10px",
color: "white",
marginBottom: "15px",
{_.isEmpty(data) ? (
<ComponentGlobal_IsEmptyData />
) : (
<Stack>
<Radio.Group
value={pilihBank}
onChange={setPilihBank}
withAsterisk
color="yellow"
>
{data.map((e) => (
<Paper
key={e.id}
style={{
backgroundColor: AccentColor.blue,
border: `2px solid ${AccentColor.darkblue}`,
padding: "15px",
cursor: "pointer",
borderRadius: "10px",
color: "white",
marginBottom: "15px",
}}
>
<Radio
styles={{
radio: {
color: "yellow",
},
}}
value={e.id}
label={
<Title order={6} color="white">
{e.namaBank}
</Title>
}
/>
</Paper>
))}
</Radio.Group>
<Button
loading={isLoading}
loaderPosition="center"
disabled={pilihBank === ""}
style={{ transition: "0.5s" }}
radius={"xl"}
bg={MainColor.yellow}
color="yellow"
c={"black"}
onClick={() => {
try {
setIsLoading(true);
setBankId(pilihBank);
router.push(RouterEvent.invoice({ id: params.id }), {
scroll: false,
});
} catch (error) {
console.log(error);
}
}}
>
<Radio
styles={{
radio: {
color: "yellow"
},
}}
value={e.id}
label={
<Title order={6} color='white'>
{e.namaBank}
</Title>
}
/>
</Paper>
))}
</Radio.Group>
<Button
style={{ transition: "0.5s" }}
radius={"xl"}
bg={MainColor.yellow}
color='yellow'
c={"black"}
onClick={() => router.push("/dev/event/invoice")}
>
Pilih
</Button>
</Stack>
Pilih
</Button>
</Stack>
)}
</>
);
}
export default Event_MetodePembayaran;
export default Event_MetodePembayaran;

View File

@@ -1,5 +1,7 @@
"use client";
import { RouterEvent } from "@/app/lib/router_hipmi/router_event";
import { AccentColor, MainColor } from "@/app_modules/_global/color";
import { gs_nominal_sponsor } from "@/app_modules/event/global_state";
import {
Box,
Button,
@@ -9,6 +11,7 @@ import {
Text,
TextInput,
Title,
Loader,
} from "@mantine/core";
import {
IconChevronRight,
@@ -17,35 +20,42 @@ import {
IconMoodSmileDizzy,
IconMoodXd,
} from "@tabler/icons-react";
import { useAtom } from "jotai";
import { useParams, useRouter } from "next/navigation";
import React from "react";
import { useState } from "react";
const listNominal = [
{
id: 1,
jumlah: " 25.000",
value: 25000,
icon: <IconMoodSmile />,
},
{
id: 2,
jumlah: " 50.000",
value: 50000,
icon: <IconMoodSmileBeam />,
},
{
id: 3,
jumlah: " 75.000",
value: 75000,
icon: <IconMoodSmileDizzy />,
},
{
id: 4,
jumlah: " 100.000",
value: 100000,
icon: <IconMoodXd />,
},
];
function Event_PilihNominalSponsor() {
const params = useParams<{ id: string }>();
const router = useRouter();
const [inputNominal, setInputNominal] = useState("");
const [valueNominal, setValueNominal] = useState(0);
const [fixNominal, setFixNominal] = useAtom(gs_nominal_sponsor);
const [isLoading, setLoading] = useState(false);
const [isLoadingPaper, setLoadingPaper] = useState(false);
const [chooseId, setChooseId] = useState(0);
return (
<>
<Stack>
@@ -54,6 +64,7 @@ function Event_PilihNominalSponsor() {
<Paper
key={e.id}
style={{
transition: "all 0.3s ease-in-out",
backgroundColor: AccentColor.blue,
border: `2px solid ${AccentColor.darkblue}`,
padding: "15px",
@@ -62,13 +73,32 @@ function Event_PilihNominalSponsor() {
color: "white",
marginBottom: "15px",
}}
onClick={() => {
try {
setChooseId(e.id);
setLoadingPaper(true);
setFixNominal(e.value);
router.push(RouterEvent.metode_pembayaran({ id: params.id }));
} catch (error) {
console.error(error);
}
}}
>
<Group position="apart">
<Group>
{e.icon}
<Title order={4}>Rp.{e.jumlah}</Title>
<Title order={4}>
Rp.
{new Intl.NumberFormat("id-ID", { currency: "IDR" }).format(
e.value
)}
</Title>
</Group>
<IconChevronRight />
{isLoadingPaper && e.id === chooseId ? (
<Loader size={20} color="yellow" />
) : (
<IconChevronRight />
)}
</Group>
</Paper>
))}
@@ -89,21 +119,41 @@ function Event_PilihNominalSponsor() {
icon={<Text fw={"bold"}>Rp.</Text>}
placeholder="0"
min={0}
value={inputNominal}
onChange={(val) => {
const match = val.currentTarget.value
.replace(/\./g, "")
.match(/^[0-9]+$/);
if (val.currentTarget.value === "")
return setInputNominal(0 + "");
if (!match?.[0]) return null;
const nilai = val.currentTarget.value.replace(/\./g, "");
const target = Intl.NumberFormat("id-ID").format(+nilai);
setValueNominal(+nilai);
setInputNominal(target);
}}
/>
<Text c={"gray"} fz={"xs"}>
Minimal Donasi Rp. 10.000
</Text>
</Stack>
</Paper>
<Button
style={{ transition: "0.5s" }}
disabled={valueNominal <= 0}
loaderPosition="center"
loading={isLoading}
style={{ transition: " all 0.3s ease-in-out" }}
radius={"xl"}
bg={MainColor.yellow}
color="yellow"
c={"black"}
onClick={() =>
router.push("/dev/event/detail/sponsor/metode_pembayaran")
}
onClick={() => {
setLoading(true);
setFixNominal(valueNominal);
router.push(RouterEvent.metode_pembayaran({ id: params.id }));
}}
>
Lanjutan Pembayaran
</Button>

View File

@@ -1,13 +1,24 @@
import { atom } from "jotai";
import { atomWithStorage } from "jotai/utils";
/**
* @param index | 0 - 3 | 0: Beranda, 1: Status, 2: Kontibusi, 3: Riwayat
* @type number
*/
export const gs_event_hotMenu = atomWithStorage("gs_event_hotMenu", 0)
export const gs_event_hotMenu = atomWithStorage("gs_event_hotMenu", 0);
/**
* @param status | "Publish", "Review", "Draft", "Reject"
* @type string
*/
export const gs_event_status = atomWithStorage<string | any>("gs_status_event", "Publish")
export const gs_event_status = atomWithStorage<string | any>(
"gs_status_event",
"Publish"
);
export const gs_nominal_sponsor = atomWithStorage<number>(
"gs_nominal_sponsor",
0
);
export const gs_event_bank_id = atomWithStorage<string>("gs_event_bank_id", "");

View File

@@ -1,5 +1,7 @@
import master_kategori_app from "./master_kategori_app.json";
import master_nama_bank from "./master_nama_bank.json";
import master_status_transaksi from "./master_status_transaksi.json";
export { master_kategori_app };
export { master_nama_bank };
export { master_status_transaksi };

View File

@@ -0,0 +1,18 @@
[
{
"id": "1",
"name": "Berhasil"
},
{
"id": "2",
"name": "Proses"
},
{
"id": "3",
"name": "Menunggu"
},
{
"id": "4",
"name": "Gagal"
}
]

View File

@@ -32,9 +32,15 @@ const middlewareConfig: MiddlewareConfig = {
"/api/auth/*",
"/api/origin-url",
"/api/event/*",
"/api/master/*",
// "/api/image/*",
// "/api/user/*",
// "/api/new/*",
// ADMIN API
// "/api/admin/event/*",
// "/api/admin/investasi/*",
// Akses awal
"/api/get-cookie",
"/api/user/activation",