diff --git a/package.json b/package.json index 7b8112a5..9be8cd09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "desa-darmasaba", - "version": "0.1.40", + "version": "0.1.41", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts b/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts index 2f8f23a1..0f785224 100644 --- a/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts +++ b/src/app/admin/(dashboard)/_state/ekonomi/umkm/umkm.ts @@ -493,23 +493,31 @@ export const umkmState = proxy({ summary: { data: null as any, loading: false }, topProduk: { data: [] as any[], loading: false }, detail: { data: [] as any[], loading: false }, - async loadAll(periode = "") { + mode: "month" as "week" | "month", + async loadAll(periode = "", mode?: "week" | "month", kategoriId = "", umkmId = "") { const p = periode || `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`; + const m = mode ?? this.mode; + const modeParam = m === "week" ? "&mode=week" : ""; + const detailFilter = [ + kategoriId ? `&kategoriId=${kategoriId}` : "", + umkmId ? `&umkmId=${umkmId}` : "", + ].join(""); this.kpi.loading = true; this.summary.loading = true; this.topProduk.loading = true; this.detail.loading = true; try { const [kpi, sum, top, det] = await Promise.all([ - fetch(`/api/ekonomi/umkm/dashboard/kpi?periode=${p}`).then(r => r.json()), - fetch(`/api/ekonomi/umkm/dashboard/ringkasan-penjualan?periode=${p}`).then(r => r.json()), - fetch(`/api/ekonomi/umkm/dashboard/top-produk?periode=${p}`).then(r => r.json()), - fetch(`/api/ekonomi/umkm/dashboard/detail-penjualan?periode=${p}`).then(r => r.json()) + fetch(`/api/ekonomi/umkm/dashboard/kpi?periode=${p}${modeParam}`).then(r => r.json()), + fetch(`/api/ekonomi/umkm/dashboard/ringkasan-penjualan?periode=${p}${modeParam}`).then(r => r.json()), + fetch(`/api/ekonomi/umkm/dashboard/top-produk?periode=${p}${modeParam}`).then(r => r.json()), + fetch(`/api/ekonomi/umkm/dashboard/detail-penjualan?periode=${p}${modeParam}${detailFilter}`).then(r => r.json()), ]); if (kpi.success) this.kpi.data = kpi.data; if (sum.success) this.summary.data = sum.data; if (top.success) this.topProduk.data = top.data; if (det.success) this.detail.data = det.data; + this.mode = m; } catch (e) { console.error(e); } finally { this.kpi.loading = false; this.summary.loading = false; diff --git a/src/app/admin/(dashboard)/ekonomi/umkm/dashboard/page.tsx b/src/app/admin/(dashboard)/ekonomi/umkm/dashboard/page.tsx index 702c6c7e..240dc3d0 100644 --- a/src/app/admin/(dashboard)/ekonomi/umkm/dashboard/page.tsx +++ b/src/app/admin/(dashboard)/ekonomi/umkm/dashboard/page.tsx @@ -1,10 +1,12 @@ 'use client' import colors from '@/con/colors'; import { + Badge, Box, Card, Grid, Group, + Select, SimpleGrid, Skeleton, Stack, @@ -16,10 +18,11 @@ import { TableTr, Text, Title, - Badge + SegmentedControl, } from '@mantine/core'; import { useShallowEffect } from '@mantine/hooks'; import { IconArrowUpRight, IconArrowDownRight, IconMinus } from '@tabler/icons-react'; +import { useState, useEffect } from 'react'; import { useProxy } from 'valtio/utils'; import { Bar, BarChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; import umkmState from '../../../_state/ekonomi/umkm/umkm'; @@ -27,10 +30,25 @@ import umkmState from '../../../_state/ekonomi/umkm/umkm'; function UmkmDashboard() { const state = useProxy(umkmState.dashboard); + const [kategoriId, setKategoriId] = useState(""); + const [umkmId, setUmkmId] = useState(""); + const [kategoriList, setKategoriList] = useState<{ value: string; label: string }[]>([]); + const [umkmList, setUmkmList] = useState<{ value: string; label: string }[]>([]); + useShallowEffect(() => { state.loadAll(); + fetch('/api/ekonomi/kategoriproduk/find-many-all').then(r => r.json()).then(res => { + if (res.success) setKategoriList(res.data.map((k: any) => ({ value: k.id, label: k.nama }))); + }); + fetch('/api/ekonomi/umkm/find-many-all').then(r => r.json()).then(res => { + if (res.success) setUmkmList(res.data.map((u: any) => ({ value: u.id, label: u.nama }))); + }); }, []); + useEffect(() => { + if (state.kpi.data) state.loadAll("", state.mode, kategoriId, umkmId); + }, [kategoriId, umkmId]); + if (state.kpi.loading || !state.kpi.data) { return ; } @@ -42,15 +60,31 @@ function UmkmDashboard() { return ( + + Update Penjualan Produk + state.loadAll("", v as "week" | "month", kategoriId, umkmId)} + data={[ + { label: 'Minggu ini', value: 'week' }, + { label: 'Bulan ini', value: 'month' }, + ]} + /> + + - + + - - @@ -59,7 +93,7 @@ function UmkmDashboard() { Grafik Penjualan per Produk - ({ + ({ name: item.namaProduk, penjualan: item.penjualanBulanIni }))}> @@ -76,16 +110,21 @@ function UmkmDashboard() { - - Top 3 Produk + + Top 3 Produk Terlaris - {topProduk.map((item, i) => ( - + {topProduk.map((item: any, i: number) => ( + - {item.namaProduk} + {item.namaProduk} {item.namaUmkm} + + Rp {item.totalPenjualan.toLocaleString()} ยท {item.jumlahTerjual} terjual + - Rp {item.totalPenjualan.toLocaleString()} + = 0 ? 'teal' : 'red'} variant="light" size="sm"> + {item.growth >= 0 ? '+' : ''}{item.growth}% + ))} @@ -94,24 +133,62 @@ function UmkmDashboard() { - Detail Penjualan & Stok + + Detail Penjualan Produk + + setUmkmId(v || "")} + clearable + size="xs" + style={{ minWidth: 140 }} + /> + + Produk - Penjualan + Penjualan Bulan Ini + Bulan Lalu Trend + Volume Stok Status - {detail.map((item, i) => ( + {detail.map((item: any, i: number) => ( {item.namaProduk} Rp {item.penjualanBulanIni.toLocaleString()} - {renderTrend(item.trend)} + Rp {item.penjualanBulanLalu.toLocaleString()} + + + {renderTrend(item.trend)} + {item.trendPersen !== 0 && ( + + {item.trendPersen > 0 ? '+' : ''}{item.trendPersen}% + + )} + + + {item.volume} {item.stok}