refactor: improve Alpine chart instance management to prevent maximum call stack size errors and streamline updates.
This commit is contained in:
parent
ab01645ccd
commit
754b65259e
@ -263,584 +263,361 @@
|
||||
|
||||
@script
|
||||
<script>
|
||||
Alpine.data('analysisCharts', () => ({
|
||||
charts: {},
|
||||
Alpine.data('analysisCharts', () => {
|
||||
// Local storage for chart instances to avoid Alpine reactivity proxying
|
||||
// This prevents "Maximum call stack size exceeded" errors
|
||||
const charts = {};
|
||||
|
||||
init() {
|
||||
// Memastikan DOM siap dan library tersedia sebelum inisialisasi awal
|
||||
const start = () => {
|
||||
if (typeof Chart !== 'undefined') {
|
||||
this.initAll();
|
||||
return {
|
||||
unwrap(data) {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(data));
|
||||
} catch (e) {
|
||||
// Fallback for safety
|
||||
return { labels: [], datasets: [] };
|
||||
}
|
||||
},
|
||||
|
||||
init() {
|
||||
const start = () => {
|
||||
if (typeof Chart !== 'undefined') {
|
||||
this.initAll();
|
||||
} else {
|
||||
setTimeout(start, 50);
|
||||
}
|
||||
};
|
||||
|
||||
this.$nextTick(() => start());
|
||||
|
||||
// Watchers
|
||||
this.$watch('$wire.revenueTrendChart', () => this.updateChart('revenue', this.getRevenueData));
|
||||
this.$watch('$wire.memberGrowthChart', () => this.updateChart('memberGrowth', this.getMemberGrowthData));
|
||||
this.$watch('$wire.hourlySalesChart', () => this.updateChart('hourlySales', this.getHourlySalesData));
|
||||
this.$watch('$wire.outletPerformanceChart', () => this.updateChart('outletPerformance', this.getOutletPerformanceData));
|
||||
this.$watch('$wire.categoryDistributionChart', () => this.updateChart('categoryDist', this.getCategoryDistributionData));
|
||||
this.$watch('$wire.paymentMethodChart', () => this.updateChart('paymentMethod', this.getPaymentMethodData));
|
||||
this.$watch('$wire.dayOfWeekSalesChart', () => this.updateChart('dayOfWeekSales', this.getDayOfWeekData));
|
||||
this.$watch('$wire.customerTypeChart', () => this.updateChart('customerType', this.getCustomerTypeData));
|
||||
this.$watch('$wire.transactionTrendChart', () => this.updateChart('transactionTrend', this.getTransactionTrendData));
|
||||
this.$watch('$wire.aovTrendChart', () => this.updateChart('aovTrend', this.getAovTrendData));
|
||||
this.$watch('$wire.expenseBreakdownChart', () => this.updateChart('expenseBreakdown', this.getExpenseBreakdownData));
|
||||
this.$watch('$wire.revenueVsCostChart', () => this.updateChart('revenueVsCost', this.getRevenueVsCostData));
|
||||
this.$watch('$wire.customerRetentionChart', () => this.updateChart('customerRetention', this.getCustomerRetentionData));
|
||||
this.$watch('$wire.voucherUsageTrendChart', () => this.updateChart('voucherUsage', this.getVoucherUsageData));
|
||||
},
|
||||
|
||||
updateChart(key, getDataFn) {
|
||||
if (charts[key]) {
|
||||
try {
|
||||
charts[key].data = getDataFn.call(this);
|
||||
charts[key].update();
|
||||
} catch(e) {
|
||||
console.error(`Error updating chart ${key}`, e);
|
||||
}
|
||||
} else {
|
||||
setTimeout(start, 50);
|
||||
// init map for manual mapping if needed, or rely on naming convention
|
||||
const initMap = {
|
||||
'revenue': 'initRevenueTrend',
|
||||
'memberGrowth': 'initMemberGrowth',
|
||||
'hourlySales': 'initHourlySales',
|
||||
'outletPerformance': 'initOutletPerformance',
|
||||
'categoryDist': 'initCategoryDistribution',
|
||||
'paymentMethod': 'initPaymentMethod',
|
||||
'dayOfWeekSales': 'initDayOfWeekSales',
|
||||
'customerType': 'initCustomerType',
|
||||
'transactionTrend': 'initTransactionTrend',
|
||||
'aovTrend': 'initAovTrend',
|
||||
'expenseBreakdown': 'initExpenseBreakdown',
|
||||
'revenueVsCost': 'initRevenueVsCost',
|
||||
'customerRetention': 'initCustomerRetention',
|
||||
'voucherUsage': 'initVoucherUsage',
|
||||
};
|
||||
const fnName = initMap[key];
|
||||
if (fnName && typeof this[fnName] === 'function') {
|
||||
this[fnName]();
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
this.$nextTick(() => start());
|
||||
|
||||
// Watchers untuk pembaruan data reaktif
|
||||
this.$watch('$wire.revenueTrendChart', () => this.updateRevenueTrend());
|
||||
this.$watch('$wire.memberGrowthChart', () => this.updateMemberGrowth());
|
||||
this.$watch('$wire.hourlySalesChart', () => this.updateHourlySales());
|
||||
this.$watch('$wire.outletPerformanceChart', () => this.updateOutletPerformance());
|
||||
this.$watch('$wire.categoryDistributionChart', () => this.updateCategoryDistribution());
|
||||
this.$watch('$wire.paymentMethodChart', () => this.updatePaymentMethod());
|
||||
this.$watch('$wire.dayOfWeekSalesChart', () => this.updateDayOfWeekSales());
|
||||
this.$watch('$wire.customerTypeChart', () => this.updateCustomerType());
|
||||
this.$watch('$wire.transactionTrendChart', () => this.updateTransactionTrend());
|
||||
this.$watch('$wire.aovTrendChart', () => this.updateAovTrend());
|
||||
this.$watch('$wire.expenseBreakdownChart', () => this.updateExpenseBreakdown());
|
||||
this.$watch('$wire.revenueVsCostChart', () => this.updateRevenueVsCost());
|
||||
this.$watch('$wire.customerRetentionChart', () => this.updateCustomerRetention());
|
||||
this.$watch('$wire.voucherUsageTrendChart', () => this.updateVoucherUsage());
|
||||
},
|
||||
|
||||
initAll() {
|
||||
console.log('Rendering Analysis Charts...');
|
||||
this.initRevenueTrend();
|
||||
this.initMemberGrowth();
|
||||
this.initHourlySales();
|
||||
this.initOutletPerformance();
|
||||
this.initCategoryDistribution();
|
||||
this.initPaymentMethod();
|
||||
this.initDayOfWeekSales();
|
||||
this.initCustomerType();
|
||||
this.initTransactionTrend();
|
||||
this.initAovTrend();
|
||||
this.initExpenseBreakdown();
|
||||
this.initRevenueVsCost();
|
||||
this.initCustomerRetention();
|
||||
this.initVoucherUsage();
|
||||
|
||||
this.isInitialized = true;
|
||||
},
|
||||
|
||||
initRevenueTrend() {
|
||||
const ctx = document.getElementById('revenueTrendChart');
|
||||
if (!ctx) return;
|
||||
|
||||
const data = $wire.revenueTrendChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.revenue = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: this.getRevenueData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top'
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
getRevenueData() {
|
||||
const data = $wire.revenueTrendChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Pendapatan',
|
||||
data: data.revenue || [],
|
||||
borderColor: '#10b981',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
},
|
||||
{
|
||||
label: 'Laba Bersih',
|
||||
data: data.profit || [],
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
updateRevenueTrend() {
|
||||
if (this.charts.revenue) {
|
||||
this.charts.revenue.data = this.getRevenueData();
|
||||
this.charts.revenue.update();
|
||||
} else {
|
||||
initAll() {
|
||||
console.log('Rendering Analysis Charts...');
|
||||
this.initRevenueTrend();
|
||||
}
|
||||
},
|
||||
|
||||
initMemberGrowth() {
|
||||
const ctx = document.getElementById('memberGrowthChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.memberGrowthChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.memberGrowth = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: this.getMemberGrowthData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
getMemberGrowthData() {
|
||||
const data = $wire.memberGrowthChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Total Member',
|
||||
data: data.data || [],
|
||||
borderColor: '#8b5cf6',
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.2
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateMemberGrowth() {
|
||||
if (this.charts.memberGrowth) {
|
||||
this.charts.memberGrowth.data = this.getMemberGrowthData();
|
||||
this.charts.memberGrowth.update();
|
||||
} else {
|
||||
this.initMemberGrowth();
|
||||
}
|
||||
},
|
||||
|
||||
initHourlySales() {
|
||||
const ctx = document.getElementById('hourlySalesChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.hourlySalesChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.hourlySales = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: this.getHourlySalesData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getHourlySalesData() {
|
||||
const data = $wire.hourlySalesChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Jumlah Order',
|
||||
data: data.data || [],
|
||||
backgroundColor: '#f59e0b'
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateHourlySales() {
|
||||
if (this.charts.hourlySales) {
|
||||
this.charts.hourlySales.data = this.getHourlySalesData();
|
||||
this.charts.hourlySales.update();
|
||||
} else {
|
||||
this.initHourlySales();
|
||||
}
|
||||
},
|
||||
|
||||
initOutletPerformance() {
|
||||
const ctx = document.getElementById('outletPerformanceChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.outletPerformanceChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.outletPerformance = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: this.getOutletPerformanceData(),
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getOutletPerformanceData() {
|
||||
const data = $wire.outletPerformanceChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Pendapatan',
|
||||
data: data.data || [],
|
||||
backgroundColor: '#ef4444'
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateOutletPerformance() {
|
||||
if (this.charts.outletPerformance) {
|
||||
this.charts.outletPerformance.data = this.getOutletPerformanceData();
|
||||
this.charts.outletPerformance.update();
|
||||
} else {
|
||||
this.initOutletPerformance();
|
||||
}
|
||||
},
|
||||
|
||||
initCategoryDistribution() {
|
||||
const ctx = document.getElementById('categoryDistributionChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.categoryDistributionChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.categoryDist = new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: this.getCategoryDistributionData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getCategoryDistributionData() {
|
||||
const data = $wire.categoryDistributionChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#10b981', '#3b82f6', '#f59e0b']
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateCategoryDistribution() {
|
||||
if (this.charts.categoryDist) {
|
||||
this.charts.categoryDist.data = this.getCategoryDistributionData();
|
||||
this.charts.categoryDist.update();
|
||||
} else {
|
||||
this.initCategoryDistribution();
|
||||
}
|
||||
},
|
||||
|
||||
initPaymentMethod() {
|
||||
const ctx = document.getElementById('paymentMethodChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.paymentMethodChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.paymentMethod = new Chart(ctx, {
|
||||
type: 'pie',
|
||||
data: this.getPaymentMethodData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getPaymentMethodData() {
|
||||
const data = $wire.paymentMethodChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#10b981', '#3b82f6', '#8b5cf6', '#6b7280']
|
||||
}]
|
||||
};
|
||||
},
|
||||
updatePaymentMethod() {
|
||||
if (this.charts.paymentMethod) {
|
||||
this.charts.paymentMethod.data = this.getPaymentMethodData();
|
||||
this.charts.paymentMethod.update();
|
||||
} else {
|
||||
this.initPaymentMethod();
|
||||
}
|
||||
},
|
||||
|
||||
initDayOfWeekSales() {
|
||||
const ctx = document.getElementById('dayOfWeekSalesChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.dayOfWeekSalesChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.dayOfWeek = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: this.getDayOfWeekData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getDayOfWeekData() {
|
||||
const data = $wire.dayOfWeekSalesChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Pendapatan',
|
||||
data: data.data || [],
|
||||
backgroundColor: '#6366f1'
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateDayOfWeekSales() {
|
||||
if (this.charts.dayOfWeek) {
|
||||
this.charts.dayOfWeek.data = this.getDayOfWeekData();
|
||||
this.charts.dayOfWeek.update();
|
||||
} else {
|
||||
this.initDayOfWeekSales();
|
||||
}
|
||||
},
|
||||
|
||||
initCustomerType() {
|
||||
const ctx = document.getElementById('customerTypeChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.customerTypeChart;
|
||||
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.customerTypeChart;
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
initTransactionTrend() {
|
||||
const ctx = document.getElementById('transactionTrendChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.transactionTrendChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.transactionTrend = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: this.getTransactionTrendData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getTransactionTrendData() {
|
||||
const data = $wire.transactionTrendChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Jumlah Transaksi',
|
||||
data: data.data || [],
|
||||
borderColor: '#f43f5e',
|
||||
backgroundColor: 'rgba(244, 63, 94, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.3
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateTransactionTrend() {
|
||||
if (this.charts.transactionTrend) {
|
||||
this.charts.transactionTrend.data = this.getTransactionTrendData();
|
||||
this.charts.transactionTrend.update();
|
||||
} else {
|
||||
this.initTransactionTrend();
|
||||
}
|
||||
},
|
||||
|
||||
initAovTrend() {
|
||||
const ctx = document.getElementById('aovTrendChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.aovTrendChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.aovTrend = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: this.getAovTrendData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getAovTrendData() {
|
||||
const data = $wire.aovTrendChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Rata-rata Keranjang (AOV)',
|
||||
data: data.data || [],
|
||||
borderColor: '#0ea5e9',
|
||||
backgroundColor: 'rgba(14, 165, 233, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.3
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateAovTrend() {
|
||||
if (this.charts.aovTrend) {
|
||||
this.charts.aovTrend.data = this.getAovTrendData();
|
||||
this.charts.aovTrend.update();
|
||||
} else {
|
||||
this.initAovTrend();
|
||||
}
|
||||
},
|
||||
|
||||
initExpenseBreakdown() {
|
||||
const ctx = document.getElementById('expenseBreakdownChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.expenseBreakdownChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.expenseBreakdown = new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: this.getExpenseBreakdownData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getExpenseBreakdownData() {
|
||||
const data = $wire.expenseBreakdownChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#fb923c', '#4ade80']
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateExpenseBreakdown() {
|
||||
if (this.charts.expenseBreakdown) {
|
||||
this.charts.expenseBreakdown.data = this.getExpenseBreakdownData();
|
||||
this.charts.expenseBreakdown.update();
|
||||
} else {
|
||||
this.initExpenseBreakdown();
|
||||
}
|
||||
},
|
||||
|
||||
initRevenueVsCost() {
|
||||
const ctx = document.getElementById('revenueVsCostChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.revenueVsCostChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.revenueVsCost = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: this.getRevenueVsCostData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getRevenueVsCostData() {
|
||||
const data = $wire.revenueVsCostChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Total Nilai',
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#10b981', '#ef4444']
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateRevenueVsCost() {
|
||||
if (this.charts.revenueVsCost) {
|
||||
this.charts.revenueVsCost.data = this.getRevenueVsCostData();
|
||||
this.charts.revenueVsCost.update();
|
||||
} else {
|
||||
this.initRevenueVsCost();
|
||||
}
|
||||
},
|
||||
|
||||
initCustomerRetention() {
|
||||
const ctx = document.getElementById('customerRetentionChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.customerRetentionChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.customerRetention = new Chart(ctx, {
|
||||
type: 'bar',
|
||||
data: this.getCustomerRetentionData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getCustomerRetentionData() {
|
||||
const data = $wire.customerRetentionChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Jumlah Pelanggan',
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#8b5cf6', '#ec4899']
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateCustomerRetention() {
|
||||
if (this.charts.customerRetention) {
|
||||
this.charts.customerRetention.data = this.getCustomerRetentionData();
|
||||
this.charts.customerRetention.update();
|
||||
} else {
|
||||
this.initCustomerRetention();
|
||||
}
|
||||
},
|
||||
|
||||
initVoucherUsage() {
|
||||
const ctx = document.getElementById('voucherUsageTrendChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.voucherUsageTrendChart;
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
this.charts.voucherUsage = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: this.getVoucherUsageData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
}
|
||||
});
|
||||
},
|
||||
getVoucherUsageData() {
|
||||
const data = $wire.voucherUsageTrendChart;
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Voucher Terpakai',
|
||||
data: data.data || [],
|
||||
borderColor: '#f59e0b',
|
||||
backgroundColor: 'rgba(245, 158, 11, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.3
|
||||
}]
|
||||
};
|
||||
},
|
||||
updateVoucherUsage() {
|
||||
if (this.charts.voucherUsage) {
|
||||
this.charts.voucherUsage.data = this.getVoucherUsageData();
|
||||
this.charts.voucherUsage.update();
|
||||
} else {
|
||||
this.initVoucherUsage();
|
||||
this.isInitialized = true;
|
||||
},
|
||||
|
||||
initRevenueTrend() {
|
||||
this.createChart('revenueTrendChart', 'revenue', 'line', this.getRevenueData(), {
|
||||
plugins: { legend: { position: 'top' } },
|
||||
scales: { y: { beginAtZero: true } }
|
||||
});
|
||||
},
|
||||
getRevenueData() {
|
||||
const data = this.unwrap($wire.revenueTrendChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Pendapatan',
|
||||
data: data.revenue || [],
|
||||
borderColor: '#10b981',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
},
|
||||
{
|
||||
label: 'Laba Bersih',
|
||||
data: data.profit || [],
|
||||
borderColor: '#3b82f6',
|
||||
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
initMemberGrowth() {
|
||||
this.createChart('memberGrowthChart', 'memberGrowth', 'line', this.getMemberGrowthData(), {
|
||||
scales: { y: { beginAtZero: true } }
|
||||
});
|
||||
},
|
||||
getMemberGrowthData() {
|
||||
const data = this.unwrap($wire.memberGrowthChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Total Member',
|
||||
data: data.data || [],
|
||||
borderColor: '#8b5cf6',
|
||||
backgroundColor: 'rgba(139, 92, 246, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.2
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initHourlySales() {
|
||||
this.createChart('hourlySalesChart', 'hourlySales', 'bar', this.getHourlySalesData());
|
||||
},
|
||||
getHourlySalesData() {
|
||||
const data = this.unwrap($wire.hourlySalesChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Jumlah Order',
|
||||
data: data.data || [],
|
||||
backgroundColor: '#f59e0b'
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initOutletPerformance() {
|
||||
this.createChart('outletPerformanceChart', 'outletPerformance', 'bar', this.getOutletPerformanceData(), {
|
||||
indexAxis: 'y'
|
||||
});
|
||||
},
|
||||
getOutletPerformanceData() {
|
||||
const data = this.unwrap($wire.outletPerformanceChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Pendapatan',
|
||||
data: data.data || [],
|
||||
backgroundColor: '#ef4444'
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initCategoryDistribution() {
|
||||
this.createChart('categoryDistributionChart', 'categoryDist', 'doughnut', this.getCategoryDistributionData());
|
||||
},
|
||||
getCategoryDistributionData() {
|
||||
const data = this.unwrap($wire.categoryDistributionChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#10b981', '#3b82f6', '#f59e0b']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initPaymentMethod() {
|
||||
this.createChart('paymentMethodChart', 'paymentMethod', 'pie', this.getPaymentMethodData());
|
||||
},
|
||||
getPaymentMethodData() {
|
||||
const data = this.unwrap($wire.paymentMethodChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#10b981', '#3b82f6', '#8b5cf6', '#6b7280']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initDayOfWeekSales() {
|
||||
this.createChart('dayOfWeekSalesChart', 'dayOfWeekSales', 'bar', this.getDayOfWeekData());
|
||||
},
|
||||
getDayOfWeekData() {
|
||||
const data = this.unwrap($wire.dayOfWeekSalesChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Pendapatan',
|
||||
data: data.data || [],
|
||||
backgroundColor: '#6366f1'
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initCustomerType() {
|
||||
this.createChart('customerTypeChart', 'customerType', 'doughnut', this.getCustomerTypeData());
|
||||
},
|
||||
getCustomerTypeData() {
|
||||
const data = this.unwrap($wire.customerTypeChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#8b5cf6', '#94a3b8']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initTransactionTrend() {
|
||||
this.createChart('transactionTrendChart', 'transactionTrend', 'line', this.getTransactionTrendData());
|
||||
},
|
||||
getTransactionTrendData() {
|
||||
const data = this.unwrap($wire.transactionTrendChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Jumlah Transaksi',
|
||||
data: data.data || [],
|
||||
borderColor: '#f43f5e',
|
||||
backgroundColor: 'rgba(244, 63, 94, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.3
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initAovTrend() {
|
||||
this.createChart('aovTrendChart', 'aovTrend', 'line', this.getAovTrendData());
|
||||
},
|
||||
getAovTrendData() {
|
||||
const data = this.unwrap($wire.aovTrendChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Rata-rata Keranjang (AOV)',
|
||||
data: data.data || [],
|
||||
borderColor: '#0ea5e9',
|
||||
backgroundColor: 'rgba(14, 165, 233, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.3
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initExpenseBreakdown() {
|
||||
this.createChart('expenseBreakdownChart', 'expenseBreakdown', 'doughnut', this.getExpenseBreakdownData());
|
||||
},
|
||||
getExpenseBreakdownData() {
|
||||
const data = this.unwrap($wire.expenseBreakdownChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#fb923c', '#4ade80']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initRevenueVsCost() {
|
||||
this.createChart('revenueVsCostChart', 'revenueVsCost', 'bar', this.getRevenueVsCostData());
|
||||
},
|
||||
getRevenueVsCostData() {
|
||||
const data = this.unwrap($wire.revenueVsCostChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Total Nilai',
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#10b981', '#ef4444']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initCustomerRetention() {
|
||||
this.createChart('customerRetentionChart', 'customerRetention', 'bar', this.getCustomerRetentionData());
|
||||
},
|
||||
getCustomerRetentionData() {
|
||||
const data = this.unwrap($wire.customerRetentionChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Jumlah Pelanggan',
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#8b5cf6', '#ec4899']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initVoucherUsage() {
|
||||
this.createChart('voucherUsageTrendChart', 'voucherUsage', 'line', this.getVoucherUsageData());
|
||||
},
|
||||
getVoucherUsageData() {
|
||||
const data = this.unwrap($wire.voucherUsageTrendChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
label: 'Voucher Terpakai',
|
||||
data: data.data || [],
|
||||
borderColor: '#f59e0b',
|
||||
backgroundColor: 'rgba(245, 158, 11, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.3
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
// Helper to create chart efficiently
|
||||
createChart(elementId, chartKey, type, data, extraOptions = {}) {
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
const ctx = document.getElementById(elementId);
|
||||
if (!ctx) return;
|
||||
|
||||
if (charts[chartKey]) {
|
||||
charts[chartKey].destroy();
|
||||
}
|
||||
|
||||
charts[chartKey] = new Chart(ctx, {
|
||||
type: type,
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
...extraOptions
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@endscript
|
||||
|
||||
@ -99,53 +99,112 @@ class="flex items-center gap-1 font-medium text-sm @if ($stat['trendUp']) text-g
|
||||
|
||||
@script
|
||||
<script>
|
||||
Alpine.data('overviewCharts', () => ({
|
||||
charts: {},
|
||||
isInitialized: false,
|
||||
Alpine.data('overviewCharts', () => {
|
||||
// Local storage for chart instances to avoid Alpine reactivity proxying
|
||||
// This prevents "Maximum call stack size exceeded" errors
|
||||
const charts = {};
|
||||
|
||||
init() {
|
||||
const start = () => {
|
||||
if (typeof Chart !== 'undefined') {
|
||||
this.initAll();
|
||||
} else {
|
||||
setTimeout(start, 50);
|
||||
return {
|
||||
isInitialized: false,
|
||||
|
||||
unwrap(data) {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(data));
|
||||
} catch (e) {
|
||||
return {
|
||||
labels: [],
|
||||
datasets: []
|
||||
};
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
this.$nextTick(() => start());
|
||||
init() {
|
||||
const start = () => {
|
||||
if (typeof Chart !== 'undefined') {
|
||||
this.initAll();
|
||||
} else {
|
||||
setTimeout(start, 50);
|
||||
}
|
||||
};
|
||||
|
||||
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());
|
||||
},
|
||||
this.$nextTick(() => start());
|
||||
|
||||
initAll() {
|
||||
this.initHourlyComparison();
|
||||
this.initCategoryDist();
|
||||
this.initPaymentMethod();
|
||||
this.initCustomerType();
|
||||
this.initTopSelling();
|
||||
this.initTopCustomers();
|
||||
this.initDiscountDist();
|
||||
this.isInitialized = true;
|
||||
},
|
||||
this.$watch('$wire.hourlyComparisonChart', () => this.updateChart('hourly', this
|
||||
.getHourlyData));
|
||||
this.$watch('$wire.todayCategoryChart', () => this.updateChart('category', this
|
||||
.getCategoryData));
|
||||
this.$watch('$wire.todayPaymentChart', () => this.updateChart('payment', this.getPaymentData));
|
||||
this.$watch('$wire.todayCustomerTypeChart', () => this.updateChart('customerType', this
|
||||
.getCustomerTypeData));
|
||||
this.$watch('$wire.todayTopSellingChart', () => this.updateChart('topSelling', this
|
||||
.getTopSellingData));
|
||||
this.$watch('$wire.todayTopCustomersChart', () => this.updateChart('topCustomers', this
|
||||
.getTopCustomersData));
|
||||
this.$watch('$wire.todayDiscountChart', () => this.updateChart('discountDist', this
|
||||
.getDiscountData));
|
||||
},
|
||||
|
||||
initHourlyComparison() {
|
||||
const ctx = document.getElementById('hourlyComparisonChart');
|
||||
if (!ctx) return;
|
||||
const data = $wire.hourlyComparisonChart;
|
||||
if (!data || !data.labels) return;
|
||||
updateChart(key, getDataFn) {
|
||||
if (charts[key]) {
|
||||
try {
|
||||
charts[key].data = getDataFn.call(this);
|
||||
charts[key].update();
|
||||
} catch (e) {
|
||||
console.error(`Error updating chart ${key}`, e);
|
||||
}
|
||||
} else {
|
||||
// Map key to init function if chart doesn't exist
|
||||
const initMap = {
|
||||
'hourly': 'initHourlyComparison',
|
||||
'category': 'initCategoryDist',
|
||||
'payment': 'initPaymentMethod',
|
||||
'customerType': 'initCustomerType',
|
||||
'topSelling': 'initTopSelling',
|
||||
'topCustomers': 'initTopCustomers',
|
||||
'discountDist': 'initDiscountDist',
|
||||
};
|
||||
const fnName = initMap[key];
|
||||
if (fnName && typeof this[fnName] === 'function') {
|
||||
this[fnName]();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
this.charts.hourly = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: this.getHourlyData(),
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
initAll() {
|
||||
this.initHourlyComparison();
|
||||
this.initCategoryDist();
|
||||
this.initPaymentMethod();
|
||||
this.initCustomerType();
|
||||
this.initTopSelling();
|
||||
this.initTopCustomers();
|
||||
this.initDiscountDist();
|
||||
this.isInitialized = true;
|
||||
},
|
||||
|
||||
// Helper to create charts consistently
|
||||
createChart(elementId, key, type, data, options = {}) {
|
||||
const ctx = document.getElementById(elementId);
|
||||
if (!ctx) return;
|
||||
|
||||
if (!data || !data.labels) return;
|
||||
|
||||
if (charts[key]) {
|
||||
charts[key].destroy();
|
||||
}
|
||||
|
||||
charts[key] = new Chart(ctx, {
|
||||
type: type,
|
||||
data: data,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
...options
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
initHourlyComparison() {
|
||||
this.createChart('hourlyComparisonChart', 'hourly', 'line', this.getHourlyData(), {
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top'
|
||||
@ -159,245 +218,118 @@ class="flex items-center gap-1 font-medium text-sm @if ($stat['trendUp']) text-g
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
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();
|
||||
});
|
||||
},
|
||||
getHourlyData() {
|
||||
const data = this.unwrap($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
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
initCategoryDist() {
|
||||
this.createChart('todayCategoryChart', 'category', 'doughnut', this.getCategoryData());
|
||||
},
|
||||
getCategoryData() {
|
||||
const data = this.unwrap($wire.todayCategoryChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#10b981', '#3b82f6', '#f59e0b']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initPaymentMethod() {
|
||||
this.createChart('todayPaymentChart', 'payment', 'pie', this.getPaymentData());
|
||||
},
|
||||
getPaymentData() {
|
||||
const data = this.unwrap($wire.todayPaymentChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#10b981', '#3b82f6', '#8b5cf6', '#6b7280']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initCustomerType() {
|
||||
this.createChart('todayCustomerTypeChart', 'customerType', 'doughnut', this
|
||||
.getCustomerTypeData());
|
||||
},
|
||||
getCustomerTypeData() {
|
||||
const data = this.unwrap($wire.todayCustomerTypeChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#8b5cf6', '#94a3b8']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initTopSelling() {
|
||||
this.createChart('todayTopSellingChart', 'topSelling', 'doughnut', this.getTopSellingData());
|
||||
},
|
||||
getTopSellingData() {
|
||||
const data = this.unwrap($wire.todayTopSellingChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#f43f5e', '#fbbf24', '#10b981', '#3b82f6', '#8b5cf6']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initTopCustomers() {
|
||||
this.createChart('todayTopCustomersChart', 'topCustomers', 'pie', this.getTopCustomersData());
|
||||
},
|
||||
getTopCustomersData() {
|
||||
const data = this.unwrap($wire.todayTopCustomersChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#6366f1', '#ec4899', '#f59e0b', '#14b8a6', '#94a3b8']
|
||||
}]
|
||||
};
|
||||
},
|
||||
|
||||
initDiscountDist() {
|
||||
this.createChart('todayDiscountChart', 'discountDist', 'doughnut', this.getDiscountData());
|
||||
},
|
||||
getDiscountData() {
|
||||
const data = this.unwrap($wire.todayDiscountChart);
|
||||
return {
|
||||
labels: data.labels || [],
|
||||
datasets: [{
|
||||
data: data.data || [],
|
||||
backgroundColor: ['#f87171', '#4ade80']
|
||||
}]
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user