From 502f2fdf779cfa9c98dedb7d1674be0bc2b2f94b Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Fri, 14 Aug 2026 04:00:22 +0700 Subject: [PATCH] feat: implement dynamic color generation for charts and improve color usage in analysis and dashboard components --- resources/css/app.css | 24 ++-- resources/js/lib/utils.ts | 11 ++ resources/js/pages/admin/analysis/index.tsx | 131 ++++++++------------ resources/js/pages/dashboard.tsx | 19 ++- 4 files changed, 84 insertions(+), 101 deletions(-) diff --git a/resources/css/app.css b/resources/css/app.css index 9f1b594..54b8370 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -85,12 +85,12 @@ :root { --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); --ring: oklch(0.708 0 0); - --chart-1: oklch(0.879 0.169 91.605); - --chart-2: oklch(0.769 0.188 70.08); - --chart-3: oklch(0.666 0.179 58.318); - --chart-4: oklch(0.555 0.163 48.998); - --chart-5: oklch(0.473 0.137 46.201); - --chart-6: oklch(0.696 0.17 162.48); + --chart-1: oklch(0.65 0.15 250); + --chart-2: oklch(0.72 0.17 145); + --chart-3: oklch(0.75 0.18 70); + --chart-4: oklch(0.60 0.18 300); + --chart-5: oklch(0.65 0.20 25); + --chart-6: oklch(0.75 0.12 195); --radius: 0.625rem; --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.145 0 0); @@ -122,12 +122,12 @@ .dark { --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.556 0 0); - --chart-1: oklch(0.879 0.169 91.605); - --chart-2: oklch(0.769 0.188 70.08); - --chart-3: oklch(0.666 0.179 58.318); - --chart-4: oklch(0.555 0.163 48.998); - --chart-5: oklch(0.473 0.137 46.201); - --chart-6: oklch(0.696 0.17 162.48); + --chart-1: oklch(0.70 0.15 250); + --chart-2: oklch(0.77 0.17 145); + --chart-3: oklch(0.80 0.18 70); + --chart-4: oklch(0.65 0.18 300); + --chart-5: oklch(0.70 0.20 25); + --chart-6: oklch(0.80 0.12 195); --sidebar: oklch(0.205 0 0); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.769 0.188 70.08); diff --git a/resources/js/lib/utils.ts b/resources/js/lib/utils.ts index fb29ee0..963af72 100644 --- a/resources/js/lib/utils.ts +++ b/resources/js/lib/utils.ts @@ -18,3 +18,14 @@ export function formatCurrency(amount: number): string { minimumFractionDigits: 0, }).format(amount); } + +export function generateRandomColors(count: number): string[] { + const colors: string[] = []; + for (let i = 0; i < count; i++) { + const hue = Math.floor(Math.random() * 360); + const saturation = 65 + Math.floor(Math.random() * 20); + const lightness = 45 + Math.floor(Math.random() * 20); + colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`); + } + return colors; +} diff --git a/resources/js/pages/admin/analysis/index.tsx b/resources/js/pages/admin/analysis/index.tsx index 96e556e..9aecbf2 100644 --- a/resources/js/pages/admin/analysis/index.tsx +++ b/resources/js/pages/admin/analysis/index.tsx @@ -12,6 +12,7 @@ import { SelectValue, } from '@/components/ui/select'; import { useCan } from '@/hooks/use-can'; +import { generateRandomColors } from '@/lib/utils'; import { formatRupiah } from '@/lib/rupiah'; import { Head, router } from '@inertiajs/react'; import { @@ -198,85 +199,59 @@ type AnalysisProps = { }>; }; -const revenueChartConfig = { - total: { - label: 'Total', - color: 'var(--chart-1)', - }, - gross: { - label: 'Keuntungan Kotor', - color: 'var(--chart-2)', - }, - net: { - label: 'Keuntungan Bersih', - color: 'var(--chart-6)', - }, - deduction: { - label: 'Potongan Nego', - color: 'var(--chart-3)', - }, - discount: { - label: 'Diskon', - color: 'var(--chart-4)', - }, - cogs: { - label: 'HPP', - color: 'var(--chart-5)', - }, -} satisfies ChartConfig; +const revenueChartConfig = (() => { + const colors = generateRandomColors(6); + return { + total: { label: 'Total', color: colors[0] }, + gross: { label: 'Keuntungan Kotor', color: colors[1] }, + net: { label: 'Keuntungan Bersih', color: colors[2] }, + deduction: { label: 'Potongan Nego', color: colors[3] }, + discount: { label: 'Diskon', color: colors[4] }, + cogs: { label: 'HPP', color: colors[5] }, + } satisfies ChartConfig; +})(); const revenueKeys = ['total', 'deduction', 'discount', 'cogs', 'gross', 'net'] as const; -const expenseChartConfig = { - total: { - label: 'Total', - color: 'var(--chart-1)', - }, - purchase: { - label: 'Belanja', - color: 'var(--chart-2)', - }, - expense: { - label: 'Pengeluaran Toko', - color: 'var(--chart-3)', - }, - advance: { - label: 'Kasbon', - color: 'var(--chart-4)', - }, -} satisfies ChartConfig; +const expenseChartConfig = (() => { + const colors = generateRandomColors(4); + return { + total: { label: 'Total', color: colors[0] }, + purchase: { label: 'Belanja', color: colors[1] }, + expense: { label: 'Pengeluaran Toko', color: colors[2] }, + advance: { label: 'Kasbon', color: colors[3] }, + } satisfies ChartConfig; +})(); const expenseKeys = ['total', 'purchase', 'expense', 'advance'] as const; -const revenueTrendChartConfig = { - qty: { - label: 'Qty', - color: 'var(--chart-1)', - }, -} satisfies ChartConfig; +const revenueTrendChartConfig = (() => { + const colors = generateRandomColors(1); + return { + qty: { label: 'Qty', color: colors[0] }, + } satisfies ChartConfig; +})(); const revenueTrendKeys = ['qty'] as const; -const CHANNEL_COLORS: Record = { - store: '#22c55e', - shopee: '#ee4d2d', - tiktok: '#000000', -}; +const CHANNEL_COLORS: Record = (() => { + const colors = generateRandomColors(3); + return { + store: colors[0], + shopee: colors[1], + tiktok: colors[2], + }; +})(); -const PAYMENT_COLORS: Record = { - cash: '#22c55e', - transfer: '#60a5fa', - qris: '#a855f7', - marketplace: '#f97316', -}; - -const PIE_COLORS = [ - 'var(--chart-1)', - 'var(--chart-2)', - 'var(--chart-3)', - 'var(--chart-4)', - 'var(--chart-5)', -]; +const PAYMENT_COLORS: Record = (() => { + const colors = generateRandomColors(4); + return { + cash: colors[0], + transfer: colors[1], + qris: colors[2], + marketplace: colors[3], + }; +})(); type PieChartItem = { name: string; @@ -293,23 +268,25 @@ type DashboardPieChartProps = { function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartProps) { const hasData = data.length > 0 && data.some((d) => d.count > 0); + const pieColors = useMemo(() => generateRandomColors(5), []); + const chartConfig = useMemo(() => { const config: ChartConfig = {}; data.forEach((item, index) => { config[item.name] = { label: item.name, - color: PIE_COLORS[index % PIE_COLORS.length], + color: pieColors[index % pieColors.length], }; }); return config; - }, [data]); + }, [data, pieColors]); const chartData = useMemo(() => { return data.map((item) => ({ ...item, - fill: PIE_COLORS[data.indexOf(item) % PIE_COLORS.length], + fill: pieColors[data.indexOf(item) % pieColors.length], })); - }, [data]); + }, [data, pieColors]); return ( @@ -1042,7 +1019,7 @@ export default function Analysis({ {topSuppliers.length > 0 ? ( - + ({ name: s.name.length > 15 ? s.name.substring(0, 15) + '...' : s.name, amount: s.total_amount }))} margin={{ left: 12, right: 12 }}> @@ -1084,7 +1061,7 @@ export default function Analysis({ {topProducts.length > 0 ? ( - + ({ name: p.name.length > 20 ? p.name.substring(0, 20) + '...' : p.name, qty: p.total_qty }))} margin={{ left: 12, right: 12 }}> @@ -1126,7 +1103,7 @@ export default function Analysis({ {topCustomers.length > 0 ? ( - + ({ name: c.name.length > 15 ? c.name.substring(0, 15) + '...' : c.name, amount: c.total_amount }))} margin={{ left: 12, right: 12 }}> @@ -1180,7 +1157,7 @@ export default function Analysis({ {busyHours.length > 0 ? ( - + diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx index 3d05a1f..ec31503 100644 --- a/resources/js/pages/dashboard.tsx +++ b/resources/js/pages/dashboard.tsx @@ -8,6 +8,7 @@ import { DialogContent, } from '@/components/ui/dialog'; import { useCan } from '@/hooks/use-can'; +import { generateRandomColors } from '@/lib/utils'; import { formatRupiah } from '@/lib/rupiah'; import { store, update } from '@/routes/admin/hr/attendances'; import { Head, router, usePage } from '@inertiajs/react'; @@ -467,34 +468,28 @@ type DashboardPieChartProps = { nameKey: string; }; -const PIE_COLORS = [ - 'var(--chart-1)', - 'var(--chart-2)', - 'var(--chart-3)', - 'var(--chart-4)', - 'var(--chart-5)', -]; - function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartProps) { const hasData = data.length > 0 && data.some((d) => d.count > 0); + const pieColors = useMemo(() => generateRandomColors(5), []); + const chartConfig = useMemo(() => { const config: ChartConfig = {}; data.forEach((item, index) => { config[item.name] = { label: item.name, - color: PIE_COLORS[index % PIE_COLORS.length], + color: pieColors[index % pieColors.length], }; }); return config; - }, [data]); + }, [data, pieColors]); const chartData = useMemo(() => { return data.map((item) => ({ ...item, - fill: PIE_COLORS[data.indexOf(item) % PIE_COLORS.length], + fill: pieColors[data.indexOf(item) % pieColors.length], })); - }, [data]); + }, [data, pieColors]); return (