chore: update reka-ui dependency to version 2.10.0 and refactor Dashboard.vue for improved chart configurations and color handling
This commit is contained in:
parent
1dc274cce1
commit
e90ff1abe0
8
package-lock.json
generated
8
package-lock.json
generated
@ -27,7 +27,7 @@
|
||||
"concurrently": "^9.0.1",
|
||||
"dropzone-vue": "^0.1.11",
|
||||
"laravel-vite-plugin": "^3.1",
|
||||
"reka-ui": "^2.9.10",
|
||||
"reka-ui": "^2.10.0",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"tailwindcss": "^4.1.1",
|
||||
"typescript": "^5.2.2",
|
||||
@ -10503,9 +10503,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/reka-ui": {
|
||||
"version": "2.9.10",
|
||||
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.9.10.tgz",
|
||||
"integrity": "sha512-yuvZVTp4fWH2G3qk+ze/x6YYlyc2Xl1d+eMUlIYrKqzTowBKteoDoN17fitURmqSUck3mc7JbcYgp49DnGu2EQ==",
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.10.0.tgz",
|
||||
"integrity": "sha512-HIUVfSBM/AyGkcUI7aiOxxMc4N+0UD2ZEun8dcrT0H4fveotEoeDdvzyZu97eeEvEa1H9oGHoOpApkfxlgnC7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
"concurrently": "^9.0.1",
|
||||
"dropzone-vue": "^0.1.11",
|
||||
"laravel-vite-plugin": "^3.1",
|
||||
"reka-ui": "^2.9.10",
|
||||
"reka-ui": "^2.10.0",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"tailwindcss": "^4.1.1",
|
||||
"typescript": "^5.2.2",
|
||||
|
||||
@ -10,30 +10,22 @@ import {
|
||||
TrendingUp,
|
||||
UserCheck,
|
||||
} from '@lucide/vue';
|
||||
import { VisAxis, VisDonut, VisGroupedBar, VisXYContainer } from '@unovis/vue';
|
||||
import { Donut } from '@unovis/ts';
|
||||
import { VisAxis, VisDonut, VisGroupedBar, VisSingleContainer, VisXYContainer } from '@unovis/vue';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import StatCard from '@/components/card/StatCard.vue';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription, CardHeader,
|
||||
CardTitle
|
||||
} from '@/components/ui/card';
|
||||
import type { ChartConfig } from '@/components/ui/chart';
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartCrosshair,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
componentToString,
|
||||
} from '@/components/ui/chart';
|
||||
import { ChartContainer } from '@/components/ui/chart';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
import { formatRupiah } from '@/lib/rupiah';
|
||||
|
||||
interface DashboardProps {
|
||||
// Row 1: Stat Cards
|
||||
attendance: {
|
||||
total_employees: number;
|
||||
percentage: number;
|
||||
@ -63,7 +55,6 @@ interface DashboardProps {
|
||||
advance_total: number;
|
||||
};
|
||||
|
||||
// Row 2: Top 5 Charts
|
||||
topSuppliers: Array<{
|
||||
name: string;
|
||||
total_amount: number;
|
||||
@ -80,7 +71,6 @@ interface DashboardProps {
|
||||
total_revenue: number;
|
||||
}>;
|
||||
|
||||
// Row 3: Order Stats
|
||||
orderStats: {
|
||||
by_channel: Array<{
|
||||
channel: string;
|
||||
@ -180,7 +170,7 @@ const productChartConfig = {
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
const channelColors = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)'];
|
||||
const channelColors = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)'];
|
||||
const channelChartConfig = computed(() => {
|
||||
const config: ChartConfig = {};
|
||||
props.orderStats.by_channel.forEach((item, index) => {
|
||||
@ -189,16 +179,10 @@ const channelChartConfig = computed(() => {
|
||||
color: channelColors[index % channelColors.length],
|
||||
};
|
||||
});
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
const paymentColors = [
|
||||
'var(--chart-1)',
|
||||
'var(--chart-2)',
|
||||
'var(--chart-3)',
|
||||
'var(--chart-4)',
|
||||
];
|
||||
const paymentColors = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)'];
|
||||
const paymentChartConfig = computed(() => {
|
||||
const config: ChartConfig = {};
|
||||
props.orderStats.by_payment_type.forEach((item, index) => {
|
||||
@ -207,16 +191,10 @@ const paymentChartConfig = computed(() => {
|
||||
color: paymentColors[index % paymentColors.length],
|
||||
};
|
||||
});
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
const statusColors = [
|
||||
'#facc15',
|
||||
'var(--chart-1)',
|
||||
'var(--chart-2)',
|
||||
'var(--chart-4)',
|
||||
];
|
||||
const statusColors = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)'];
|
||||
const statusChartConfig = computed(() => {
|
||||
const config: ChartConfig = {};
|
||||
props.orderStats.by_status.forEach((item, index) => {
|
||||
@ -225,17 +203,10 @@ const statusChartConfig = computed(() => {
|
||||
color: statusColors[index % statusColors.length],
|
||||
};
|
||||
});
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
const marketingColors = [
|
||||
'var(--chart-1)',
|
||||
'var(--chart-2)',
|
||||
'var(--chart-3)',
|
||||
'var(--chart-4)',
|
||||
'var(--chart-5)',
|
||||
];
|
||||
const marketingColors = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)'];
|
||||
const marketingChartConfig = computed(() => {
|
||||
const config: ChartConfig = {};
|
||||
props.orderStats.by_marketing.forEach((item, index) => {
|
||||
@ -244,7 +215,6 @@ const marketingChartConfig = computed(() => {
|
||||
color: marketingColors[index % marketingColors.length],
|
||||
};
|
||||
});
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
@ -387,9 +357,16 @@ const productBarData = computed<ProductData[]>(() => {
|
||||
<CardContent>
|
||||
<ChartContainer v-if="supplierBarData.length > 0" :config="supplierChartConfig"
|
||||
class="min-h-[250px] w-full">
|
||||
<VisXYContainer :data="supplierBarData">
|
||||
<VisGroupedBar :x="(d: SupplierData) => d.name" :y="(d: SupplierData) => d.amount"
|
||||
:color="supplierChartConfig.amount.color" :rounded-corners="4" bar-padding="0.1" />
|
||||
<VisXYContainer :data="supplierBarData" :y-domain="[0, undefined]">
|
||||
<VisGroupedBar :x="(_d: SupplierData, i: number) => i"
|
||||
:y="(d: SupplierData) => d.amount" :color="supplierChartConfig.amount.color"
|
||||
:rounded-corners="4" bar-padding="0.1" />
|
||||
<VisAxis type="x" :x="(_d: SupplierData, i: number) => i" :tick-line="false"
|
||||
:domain-line="false" :grid-line="false"
|
||||
:tick-format="(d: number) => supplierBarData[d]?.name ?? ''"
|
||||
:tick-values="supplierBarData.map((_, i) => i)" />
|
||||
<VisAxis type="y" :num-ticks="3" :tick-line="false" :domain-line="false"
|
||||
:tick-format="(d: number) => 'Rp' + formatRupiah(d)" />
|
||||
</VisXYContainer>
|
||||
</ChartContainer>
|
||||
<div v-else class="flex h-[250px] items-center justify-center text-muted-foreground">
|
||||
@ -406,9 +383,16 @@ const productBarData = computed<ProductData[]>(() => {
|
||||
<CardContent>
|
||||
<ChartContainer v-if="customerBarData.length > 0" :config="customerChartConfig"
|
||||
class="min-h-[250px] w-full">
|
||||
<VisXYContainer :data="customerBarData">
|
||||
<VisGroupedBar :x="(d: CustomerData) => d.name" :y="(d: CustomerData) => d.amount"
|
||||
:color="customerChartConfig.amount.color" :rounded-corners="4" bar-padding="0.1" />
|
||||
<VisXYContainer :data="customerBarData" :y-domain="[0, undefined]">
|
||||
<VisGroupedBar :x="(_d: CustomerData, i: number) => i"
|
||||
:y="(d: CustomerData) => d.amount" :color="customerChartConfig.amount.color"
|
||||
:rounded-corners="4" bar-padding="0.1" />
|
||||
<VisAxis type="x" :x="(_d: CustomerData, i: number) => i" :tick-line="false"
|
||||
:domain-line="false" :grid-line="false"
|
||||
:tick-format="(d: number) => customerBarData[d]?.name ?? ''"
|
||||
:tick-values="customerBarData.map((_, i) => i)" />
|
||||
<VisAxis type="y" :num-ticks="3" :tick-line="false" :domain-line="false"
|
||||
:tick-format="(d: number) => 'Rp' + formatRupiah(d)" />
|
||||
</VisXYContainer>
|
||||
</ChartContainer>
|
||||
<div v-else class="flex h-[250px] items-center justify-center text-muted-foreground">
|
||||
@ -425,33 +409,20 @@ const productBarData = computed<ProductData[]>(() => {
|
||||
<CardContent>
|
||||
<ChartContainer v-if="productBarData.length > 0" :config="productChartConfig"
|
||||
class="min-h-[250px] w-full">
|
||||
<VisXYContainer :data="productBarData" :margin="{ left: -24 }" :y-domain="[0, undefined]">
|
||||
<VisGroupedBar :x="(d: ProductData) => d.name" :y="(d: ProductData) => d.qty"
|
||||
:color="productChartConfig.qty.color" :rounded-corners="10" bar-padding="0.1" />
|
||||
<VisAxis type="x" :x="(d: ProductData) => d.name" :tick-line="false"
|
||||
:domain-line="false" :grid-line="false" :num-ticks="productBarData.length"
|
||||
:tick-values="productBarData.map((d) => d.name)
|
||||
" />
|
||||
<VisXYContainer :data="productBarData" :y-domain="[0, undefined]">
|
||||
<VisGroupedBar :x="(_d: ProductData, i: number) => i" :y="(d: ProductData) => d.qty"
|
||||
:color="productChartConfig.qty.color" :rounded-corners="4" bar-padding="0.1" />
|
||||
<VisAxis type="x" :x="(_d: ProductData, i: number) => i" :tick-line="false"
|
||||
:domain-line="false" :grid-line="false"
|
||||
:tick-format="(d: number) => productBarData[d]?.name ?? ''"
|
||||
:tick-values="productBarData.map((_, i) => i)" />
|
||||
<VisAxis type="y" :num-ticks="3" :tick-line="false" :domain-line="false" />
|
||||
<ChartTooltip />
|
||||
<ChartCrosshair :template="componentToString(
|
||||
productChartConfig,
|
||||
ChartTooltipContent,
|
||||
{ hideLabel: true },
|
||||
)
|
||||
" color="#0000" />
|
||||
</VisXYContainer>
|
||||
</ChartContainer>
|
||||
<div v-else class="flex h-[250px] items-center justify-center text-muted-foreground">
|
||||
Belum ada data produk
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter class="flex-col items-start gap-2 text-sm">
|
||||
<div class="flex gap-2 leading-none font-medium text-muted-foreground">
|
||||
Menampilkan 5 produk dengan penjualan tertinggi
|
||||
<TrendingUp class="h-4 w-4 text-green-500" />
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@ -462,34 +433,20 @@ const productBarData = computed<ProductData[]>(() => {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div v-if="orderStats.by_channel.length > 0">
|
||||
<ChartContainer :config="channelChartConfig" class="min-h-[200px] w-full">
|
||||
<VisXYContainer :data="orderStats.by_channel">
|
||||
<VisDonut :value="(
|
||||
d: (typeof orderStats.by_channel)[number],
|
||||
) => d.count
|
||||
" :color="orderStats.by_channel.map(
|
||||
(_, i) =>
|
||||
channelColors[
|
||||
i % channelColors.length
|
||||
],
|
||||
)
|
||||
" />
|
||||
</VisXYContainer>
|
||||
<ChartContainer :config="channelChartConfig" class="mx-auto aspect-square max-h-[200px]">
|
||||
<VisSingleContainer :data="orderStats.by_channel" :margin="{ top: 10, bottom: 10 }">
|
||||
<VisDonut :value="(d: (typeof orderStats.by_channel)[number]) => d.count"
|
||||
:color="(d: (typeof orderStats.by_channel)[number]) => channelChartConfig[d.channel]?.color ?? 'var(--chart-1)'"
|
||||
:arc-width="30" />
|
||||
</VisSingleContainer>
|
||||
</ChartContainer>
|
||||
<div class="mt-3 flex flex-wrap justify-center gap-3">
|
||||
<div v-for="(
|
||||
item, index
|
||||
) in orderStats.by_channel" :key="item.channel" class="flex items-center gap-1.5">
|
||||
<span class="size-2.5 rounded-full" :style="{
|
||||
backgroundColor:
|
||||
channelColors[
|
||||
index % channelColors.length
|
||||
],
|
||||
}" />
|
||||
<div v-for="(item, index) in orderStats.by_channel" :key="item.channel"
|
||||
class="flex items-center gap-1.5">
|
||||
<span class="size-2.5 rounded-full"
|
||||
:style="{ backgroundColor: channelColors[index % channelColors.length] }" />
|
||||
<span class="text-xs text-muted-foreground">{{ item.label }}</span>
|
||||
<span class="text-xs font-medium">{{
|
||||
item.count
|
||||
}}</span>
|
||||
<span class="text-xs font-medium">{{ item.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -505,35 +462,21 @@ item, index
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div v-if="orderStats.by_payment_type.length > 0">
|
||||
<ChartContainer :config="paymentChartConfig" class="min-h-[200px] w-full">
|
||||
<VisXYContainer :data="orderStats.by_payment_type">
|
||||
<VisDonut :value="(
|
||||
d: (typeof orderStats.by_payment_type)[number],
|
||||
) => d.count
|
||||
" :color="orderStats.by_payment_type.map(
|
||||
(_, i) =>
|
||||
paymentColors[
|
||||
i % paymentColors.length
|
||||
],
|
||||
)
|
||||
" />
|
||||
</VisXYContainer>
|
||||
<ChartContainer :config="paymentChartConfig" class="mx-auto aspect-square max-h-[200px]">
|
||||
<VisSingleContainer :data="orderStats.by_payment_type"
|
||||
:margin="{ top: 10, bottom: 10 }">
|
||||
<VisDonut :value="(d: (typeof orderStats.by_payment_type)[number]) => d.count"
|
||||
:color="(d: (typeof orderStats.by_payment_type)[number]) => paymentChartConfig[d.payment_type]?.color ?? 'var(--chart-1)'"
|
||||
:arc-width="30" />
|
||||
</VisSingleContainer>
|
||||
</ChartContainer>
|
||||
<div class="mt-3 flex flex-wrap justify-center gap-3">
|
||||
<div v-for="(
|
||||
item, index
|
||||
) in orderStats.by_payment_type" :key="item.payment_type"
|
||||
<div v-for="(item, index) in orderStats.by_payment_type" :key="item.payment_type"
|
||||
class="flex items-center gap-1.5">
|
||||
<span class="size-2.5 rounded-full" :style="{
|
||||
backgroundColor:
|
||||
paymentColors[
|
||||
index % paymentColors.length
|
||||
],
|
||||
}" />
|
||||
<span class="size-2.5 rounded-full"
|
||||
:style="{ backgroundColor: paymentColors[index % paymentColors.length] }" />
|
||||
<span class="text-xs text-muted-foreground">{{ item.label }}</span>
|
||||
<span class="text-xs font-medium">{{
|
||||
item.count
|
||||
}}</span>
|
||||
<span class="text-xs font-medium">{{ item.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -549,36 +492,21 @@ item, index
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div v-if="orderStats.by_marketing.length > 0">
|
||||
<ChartContainer :config="marketingChartConfig" class="min-h-[200px] w-full">
|
||||
<VisXYContainer :data="orderStats.by_marketing">
|
||||
<VisDonut :value="(
|
||||
d: (typeof orderStats.by_marketing)[number],
|
||||
) => d.count
|
||||
" :color="orderStats.by_marketing.map(
|
||||
(_, i) =>
|
||||
marketingColors[
|
||||
i %
|
||||
marketingColors.length
|
||||
],
|
||||
)
|
||||
" />
|
||||
</VisXYContainer>
|
||||
<ChartContainer :config="marketingChartConfig" class="mx-auto aspect-square max-h-[200px]">
|
||||
<VisSingleContainer :data="orderStats.by_marketing"
|
||||
:margin="{ top: 10, bottom: 10 }">
|
||||
<VisDonut :value="(d: (typeof orderStats.by_marketing)[number]) => d.count"
|
||||
:color="(_d: (typeof orderStats.by_marketing)[number], i: number) => marketingColors[i % marketingColors.length]"
|
||||
:arc-width="30" />
|
||||
</VisSingleContainer>
|
||||
</ChartContainer>
|
||||
<div class="mt-3 flex flex-wrap justify-center gap-3">
|
||||
<div v-for="(
|
||||
item, index
|
||||
) in orderStats.by_marketing" :key="item.name" class="flex items-center gap-1.5">
|
||||
<span class="size-2.5 rounded-full" :style="{
|
||||
backgroundColor:
|
||||
marketingColors[
|
||||
index %
|
||||
marketingColors.length
|
||||
],
|
||||
}" />
|
||||
<div v-for="(item, index) in orderStats.by_marketing" :key="item.name"
|
||||
class="flex items-center gap-1.5">
|
||||
<span class="size-2.5 rounded-full"
|
||||
:style="{ backgroundColor: marketingColors[index % marketingColors.length] }" />
|
||||
<span class="text-xs text-muted-foreground">{{ item.name }}</span>
|
||||
<span class="text-xs font-medium">{{
|
||||
item.count
|
||||
}}</span>
|
||||
<span class="text-xs font-medium">{{ item.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -594,34 +522,20 @@ item, index
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div v-if="orderStats.by_status.length > 0">
|
||||
<ChartContainer :config="statusChartConfig" class="min-h-[200px] w-full">
|
||||
<VisXYContainer :data="orderStats.by_status">
|
||||
<VisDonut :value="(
|
||||
d: (typeof orderStats.by_status)[number],
|
||||
) => d.count
|
||||
" :color="orderStats.by_status.map(
|
||||
(_, i) =>
|
||||
statusColors[
|
||||
i % statusColors.length
|
||||
],
|
||||
)
|
||||
" />
|
||||
</VisXYContainer>
|
||||
<ChartContainer :config="statusChartConfig" class="mx-auto aspect-square max-h-[200px]">
|
||||
<VisSingleContainer :data="orderStats.by_status" :margin="{ top: 10, bottom: 10 }">
|
||||
<VisDonut :value="(d: (typeof orderStats.by_status)[number]) => d.count"
|
||||
:color="(d: (typeof orderStats.by_status)[number]) => statusChartConfig[d.status]?.color ?? 'var(--chart-1)'"
|
||||
:arc-width="30" />
|
||||
</VisSingleContainer>
|
||||
</ChartContainer>
|
||||
<div class="mt-3 flex flex-wrap justify-center gap-3">
|
||||
<div v-for="(
|
||||
item, index
|
||||
) in orderStats.by_status" :key="item.status" class="flex items-center gap-1.5">
|
||||
<span class="size-2.5 rounded-full" :style="{
|
||||
backgroundColor:
|
||||
statusColors[
|
||||
index % statusColors.length
|
||||
],
|
||||
}" />
|
||||
<div v-for="(item, index) in orderStats.by_status" :key="item.status"
|
||||
class="flex items-center gap-1.5">
|
||||
<span class="size-2.5 rounded-full"
|
||||
:style="{ backgroundColor: statusColors[index % statusColors.length] }" />
|
||||
<span class="text-xs text-muted-foreground">{{ item.label }}</span>
|
||||
<span class="text-xs font-medium">{{
|
||||
item.count
|
||||
}}</span>
|
||||
<span class="text-xs font-medium">{{ item.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user