404 lines
16 KiB
PHP
404 lines
16 KiB
PHP
<flux:main>
|
|
<flux:heading size="xl" level="1">Haloo, {{ auth()->user()?->employee?->full_name }}</flux:heading>
|
|
<flux:text class="mb-6 mt-2 text-base">Selamat datang di Studio Yadi Parfum.</flux:text>
|
|
|
|
<div class="mt-6" x-data="overviewCharts">
|
|
@if (count($outlets) > 1)
|
|
<div class="mb-6 grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
<flux:checkbox.group wire:model.live="selectedOutletIds" variant="buttons">
|
|
@foreach ($outlets as $key => $value)
|
|
<flux:checkbox value="{{ $key }}" label="{{ $value }}" />
|
|
@endforeach
|
|
</flux:checkbox.group>
|
|
</div>
|
|
<flux:separator class="mb-6" />
|
|
@endif
|
|
|
|
<flux:subheading>Data yang ditampilkan di bawah ini adalah data hari ini dan hari kemarin.</flux:subheading>
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4 mb-6 mt-2">
|
|
@foreach ($stats as $stat)
|
|
<div
|
|
class="@if ($stat['title'] === 'Top Parfum') md:col-span-2 xl:col-span-2 @endif rounded-lg px-6 py-4 bg-zinc-200 dark:bg-zinc-700">
|
|
<flux:subheading>{{ $stat['title'] }}</flux:subheading>
|
|
<div class="flex items-center justify-between">
|
|
<flux:heading size="xl">{{ $stat['value'] }}</flux:heading>
|
|
<flux:subheading size="xs">{{ $stat['previous'] }}</flux:subheading>
|
|
</div>
|
|
<div
|
|
class="flex items-center gap-1 font-medium text-sm @if ($stat['trendUp']) text-green-600 dark:text-green-400 @else text-red-500 dark:text-red-400 @endif">
|
|
<flux:icon :icon="$stat['trendUp'] ? 'arrow-trending-up' : 'arrow-trending-down'"
|
|
variant="micro" />
|
|
{{ $stat['trend'] }}
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6 mt-8">
|
|
<!-- 1. Today vs Yesterday Hourly Comparison -->
|
|
<flux:card class="md:col-span-2 xl:col-span-2" wire:ignore>
|
|
<flux:heading class="mb-4">Perbandingan Order per Jam (Hari Ini vs Kemarin)</flux:heading>
|
|
<div class="h-80">
|
|
<canvas id="hourlyComparisonChart"></canvas>
|
|
</div>
|
|
</flux:card>
|
|
|
|
<!-- 2. Today Category Distribution -->
|
|
<flux:card wire:ignore>
|
|
<flux:heading class="mb-4">Distribusi Penjualan</flux:heading>
|
|
<div class="h-80 flex items-center justify-center">
|
|
<canvas id="todayCategoryChart"></canvas>
|
|
</div>
|
|
</flux:card>
|
|
|
|
<!-- 3. Today Payment Methods -->
|
|
<flux:card wire:ignore>
|
|
<flux:heading class="mb-4">Metode Pembayaran</flux:heading>
|
|
<div class="h-80 flex items-center justify-center">
|
|
<canvas id="todayPaymentChart"></canvas>
|
|
</div>
|
|
</flux:card>
|
|
|
|
<!-- 4. Today Customer Type -->
|
|
<flux:card wire:ignore>
|
|
<flux:heading class="mb-4">Member vs Guest</flux:heading>
|
|
<div class="h-80 flex items-center justify-center">
|
|
<canvas id="todayCustomerTypeChart"></canvas>
|
|
</div>
|
|
</flux:card>
|
|
|
|
<!-- 5. Today Top Selling Items -->
|
|
<flux:card wire:ignore>
|
|
<flux:heading class="mb-4">5 Item Terlaris</flux:heading>
|
|
<div class="h-80 flex items-center justify-center">
|
|
<canvas id="todayTopSellingChart"></canvas>
|
|
</div>
|
|
</flux:card>
|
|
|
|
<!-- 6. Today Top Customers -->
|
|
<flux:card wire:ignore>
|
|
<flux:heading class="mb-4">Top 5 Pelanggan</flux:heading>
|
|
<div class="h-80 flex items-center justify-center">
|
|
<canvas id="todayTopCustomersChart"></canvas>
|
|
</div>
|
|
</flux:card>
|
|
|
|
<!-- 7. Today Discount Distribution -->
|
|
<flux:card wire:ignore>
|
|
<flux:heading class="mb-4">Normal vs Diskon</flux:heading>
|
|
<div class="h-80 flex items-center justify-center">
|
|
<canvas id="todayDiscountChart"></canvas>
|
|
</div>
|
|
</flux:card>
|
|
</div>
|
|
</div>
|
|
</flux:main>
|
|
|
|
@assets
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
@endassets
|
|
|
|
@script
|
|
<script>
|
|
Alpine.data('overviewCharts', () => ({
|
|
charts: {},
|
|
isInitialized: false,
|
|
|
|
init() {
|
|
const start = () => {
|
|
if (typeof Chart !== 'undefined') {
|
|
this.initAll();
|
|
} else {
|
|
setTimeout(start, 50);
|
|
}
|
|
};
|
|
|
|
this.$nextTick(() => start());
|
|
|
|
this.$watch('$wire.hourlyComparisonChart', () => this.updateHourlyComparison());
|
|
this.$watch('$wire.todayCategoryChart', () => this.updateCategoryDist());
|
|
this.$watch('$wire.todayPaymentChart', () => this.updatePaymentMethod());
|
|
this.$watch('$wire.todayCustomerTypeChart', () => this.updateCustomerType());
|
|
this.$watch('$wire.todayTopSellingChart', () => this.updateTopSelling());
|
|
this.$watch('$wire.todayTopCustomersChart', () => this.updateTopCustomers());
|
|
this.$watch('$wire.todayDiscountChart', () => this.updateDiscountDist());
|
|
},
|
|
|
|
initAll() {
|
|
this.initHourlyComparison();
|
|
this.initCategoryDist();
|
|
this.initPaymentMethod();
|
|
this.initCustomerType();
|
|
this.initTopSelling();
|
|
this.initTopCustomers();
|
|
this.initDiscountDist();
|
|
this.isInitialized = true;
|
|
},
|
|
|
|
initHourlyComparison() {
|
|
const ctx = document.getElementById('hourlyComparisonChart');
|
|
if (!ctx) return;
|
|
const data = $wire.hourlyComparisonChart;
|
|
if (!data || !data.labels) return;
|
|
|
|
this.charts.hourly = new Chart(ctx, {
|
|
type: 'line',
|
|
data: this.getHourlyData(),
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
position: 'top'
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: {
|
|
stepSize: 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
getHourlyData() {
|
|
const data = $wire.hourlyComparisonChart;
|
|
return {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
label: 'Hari Ini',
|
|
data: data.today || [],
|
|
borderColor: '#10b981',
|
|
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
fill: true,
|
|
tension: 0.4
|
|
},
|
|
{
|
|
label: 'Kemarin',
|
|
data: data.yesterday || [],
|
|
borderColor: '#94a3b8',
|
|
backgroundColor: 'rgba(148, 163, 184, 0.05)',
|
|
borderDash: [5, 5],
|
|
fill: false,
|
|
tension: 0.4
|
|
}
|
|
]
|
|
};
|
|
},
|
|
updateHourlyComparison() {
|
|
if (this.charts.hourly) {
|
|
this.charts.hourly.data = this.getHourlyData();
|
|
this.charts.hourly.update();
|
|
} else {
|
|
this.initHourlyComparison();
|
|
}
|
|
},
|
|
|
|
initCategoryDist() {
|
|
const ctx = document.getElementById('todayCategoryChart');
|
|
if (!ctx) return;
|
|
const data = $wire.todayCategoryChart;
|
|
if (!data || !data.labels) return;
|
|
|
|
this.charts.category = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: this.getCategoryData(),
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
},
|
|
getCategoryData() {
|
|
const data = $wire.todayCategoryChart;
|
|
return {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
data: data.data || [],
|
|
backgroundColor: ['#10b981', '#3b82f6', '#f59e0b']
|
|
}]
|
|
};
|
|
},
|
|
updateCategoryDist() {
|
|
if (this.charts.category) {
|
|
this.charts.category.data = this.getCategoryData();
|
|
this.charts.category.update();
|
|
} else {
|
|
this.initCategoryDist();
|
|
}
|
|
},
|
|
|
|
initPaymentMethod() {
|
|
const ctx = document.getElementById('todayPaymentChart');
|
|
if (!ctx) return;
|
|
const data = $wire.todayPaymentChart;
|
|
if (!data || !data.labels) return;
|
|
|
|
this.charts.payment = new Chart(ctx, {
|
|
type: 'pie',
|
|
data: this.getPaymentData(),
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
},
|
|
getPaymentData() {
|
|
const data = $wire.todayPaymentChart;
|
|
return {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
data: data.data || [],
|
|
backgroundColor: ['#10b981', '#3b82f6', '#8b5cf6', '#6b7280']
|
|
}]
|
|
};
|
|
},
|
|
updatePaymentMethod() {
|
|
if (this.charts.payment) {
|
|
this.charts.payment.data = this.getPaymentData();
|
|
this.charts.payment.update();
|
|
} else {
|
|
this.initPaymentMethod();
|
|
}
|
|
},
|
|
|
|
initCustomerType() {
|
|
const ctx = document.getElementById('todayCustomerTypeChart');
|
|
if (!ctx) return;
|
|
const data = $wire.todayCustomerTypeChart;
|
|
if (!data || !data.labels) return;
|
|
|
|
this.charts.customerType = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: this.getCustomerTypeData(),
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
},
|
|
getCustomerTypeData() {
|
|
const data = $wire.todayCustomerTypeChart;
|
|
return {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
data: data.data || [],
|
|
backgroundColor: ['#8b5cf6', '#94a3b8']
|
|
}]
|
|
};
|
|
},
|
|
updateCustomerType() {
|
|
if (this.charts.customerType) {
|
|
this.charts.customerType.data = this.getCustomerTypeData();
|
|
this.charts.customerType.update();
|
|
} else {
|
|
this.initCustomerType();
|
|
}
|
|
},
|
|
|
|
initTopSelling() {
|
|
const ctx = document.getElementById('todayTopSellingChart');
|
|
if (!ctx) return;
|
|
const data = $wire.todayTopSellingChart;
|
|
if (!data || !data.labels) return;
|
|
|
|
this.charts.topSelling = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: this.getTopSellingData(),
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
},
|
|
getTopSellingData() {
|
|
const data = $wire.todayTopSellingChart;
|
|
return {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
data: data.data || [],
|
|
backgroundColor: ['#f43f5e', '#fbbf24', '#10b981', '#3b82f6', '#8b5cf6']
|
|
}]
|
|
};
|
|
},
|
|
updateTopSelling() {
|
|
if (this.charts.topSelling) {
|
|
this.charts.topSelling.data = this.getTopSellingData();
|
|
this.charts.topSelling.update();
|
|
} else {
|
|
this.initTopSelling();
|
|
}
|
|
},
|
|
|
|
initTopCustomers() {
|
|
const ctx = document.getElementById('todayTopCustomersChart');
|
|
if (!ctx) return;
|
|
const data = $wire.todayTopCustomersChart;
|
|
if (!data || !data.labels) return;
|
|
|
|
this.charts.topCustomers = new Chart(ctx, {
|
|
type: 'pie',
|
|
data: this.getTopCustomersData(),
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
},
|
|
getTopCustomersData() {
|
|
const data = $wire.todayTopCustomersChart;
|
|
return {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
data: data.data || [],
|
|
backgroundColor: ['#6366f1', '#ec4899', '#f59e0b', '#14b8a6', '#94a3b8']
|
|
}]
|
|
};
|
|
},
|
|
updateTopCustomers() {
|
|
if (this.charts.topCustomers) {
|
|
this.charts.topCustomers.data = this.getTopCustomersData();
|
|
this.charts.topCustomers.update();
|
|
} else {
|
|
this.initTopCustomers();
|
|
}
|
|
},
|
|
|
|
initDiscountDist() {
|
|
const ctx = document.getElementById('todayDiscountChart');
|
|
if (!ctx) return;
|
|
const data = $wire.todayDiscountChart;
|
|
if (!data || !data.labels) return;
|
|
|
|
this.charts.discountDist = new Chart(ctx, {
|
|
type: 'doughnut',
|
|
data: this.getDiscountData(),
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false
|
|
}
|
|
});
|
|
},
|
|
getDiscountData() {
|
|
const data = $wire.todayDiscountChart;
|
|
return {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
data: data.data || [],
|
|
backgroundColor: ['#f87171', '#4ade80']
|
|
}]
|
|
};
|
|
},
|
|
updateDiscountDist() {
|
|
if (this.charts.discountDist) {
|
|
this.charts.discountDist.data = this.getDiscountData();
|
|
this.charts.discountDist.update();
|
|
} else {
|
|
this.initDiscountDist();
|
|
}
|
|
}
|
|
}));
|
|
</script>
|
|
@endscript
|