feat(analitik) : membuat fitur analitik untuk barber

This commit is contained in:
Yoga Pangestu 2024-12-21 14:59:11 +07:00
parent 9dd4e63f4b
commit 48f3a53343
7 changed files with 350 additions and 137 deletions

View File

@ -24,14 +24,28 @@ public function index(): View
$totalPayroll = Payroll::query()->barbershop()->sum('total');
$totalShaving = Transaction::query()->barbershop()->count();
$topService = Service::query()->barbershop()->withCount('transactions')->get();
$paymentMethod = Transaction::select('payment_method', DB::raw('SUM(total) as total_amount'))
$topService = Service::query()
->barbershop()
->groupBy('payment_method')
->withCount(['transactions' => function ($query) {
$query->when(auth()->user()->role_id === 5, function ($query) {
$query->where('user_id', auth()->id());
});
}])
->when(auth()->user()->role_id === 5, function ($query) {
$query->whereHas('transactions', function ($query) {
$query->where('user_id', auth()->id());
});
})
->get();
$incomeByBarber = User::with('biography')->barbershop()->barber()->withSum('transactions', 'total')->get();
$paymentMethod = Transaction::query()
->select('payment_method', DB::raw('SUM(total) as total_amount'))
->barbershop()
->when(auth()->user()->role_id === 5, function ($query) {
$query->where('user_id', auth()->id());
})
->groupBy('payment_method')
->get();
$transactionByBarber = User::with('biography')->barbershop()->barber()->withCount('transactions')->get();
@ -47,6 +61,43 @@ public function index(): View
$totalUser = User::query()->barbershop()->count();
$totalBarber = User::query()->barbershop()->barber()->count();
$topCustomersByTotal = Transaction::groupBy('customer_id')
->select(
'customer_id',
DB::raw('SUM(total) as total_transaction'),
DB::raw('COUNT(*) as transaction_count')
)
->orderBy('total_transaction', 'desc')
->when(auth()->user()->role_id === 5, function ($query) {
return $query->where('user_id', auth()->id());
})
->limit(5)
->get();
$incomeByBarber = User::with('biography')
->barbershop()
->barber()
->withSum(['transactions' => function ($query) {
$query->when(auth()->user()->role_id === 5, function ($query) {
return $query->where('user_id', auth()->id());
});
}], 'total')
->orderBy('transactions_sum_total', 'desc')
->withCount(['transactions' => function ($query) {
$query->when(auth()->user()->role_id === 5, function ($query) {
return $query->where('user_id', auth()->id());
});
}]);
if (in_array(auth()->user()->role_id, [1, 2, 3])) {
$incomeByBarber = $incomeByBarber->get();
} elseif (auth()->user()->role_id === 5) {
$incomeByBarber = $incomeByBarber->find(auth()->id());
$incomeByBarber = (object) [
'transactions_sum_total' => formatToRupiah($incomeByBarber->transactions_sum_total),
'transactions_count' => $incomeByBarber->transactions_count,
];
}
return view('pages.admin.dashboard.analytic', [
'pageTitle' => 'Analitik',
'totalIncome' => $totalIncome,
@ -60,6 +111,7 @@ public function index(): View
'topBarber' => $topBarber,
'totalUser' => $totalUser,
'totalBarber' => $totalBarber,
'topCustomersByTotal' => $topCustomersByTotal,
]);
}
@ -106,8 +158,20 @@ public function filter(Request $request): JsonResponse
$query->whereBetween('created_at', [
Carbon::parse($startDate)->startOfDay(),
Carbon::parse($endDate)->endOfDay(),
]);
])
->when(auth()->user()->role_id === 5, function ($query) {
$query->where('user_id', auth()->id());
});
}])
->when(auth()->user()->role_id === 5, function ($query) {
$query->whereHas('transactions', function ($query) {
$query->where('user_id', auth()->id());
});
})
->whereBetween('created_at', [
Carbon::parse($startDate)->startOfDay(),
Carbon::parse($endDate)->endOfDay(),
])
->get();
$paymentMethod = Transaction::select('payment_method', DB::raw('SUM(total) as total_amount'))
@ -116,6 +180,9 @@ public function filter(Request $request): JsonResponse
Carbon::parse($startDate)->startOfDay(),
Carbon::parse($endDate)->endOfDay(),
])
->when(auth()->user()->role_id === 5, function ($query) {
return $query->where('user_id', auth()->id());
})
->groupBy('payment_method')
->get();
@ -177,6 +244,55 @@ public function filter(Request $request): JsonResponse
->barber()
->count();
$topCustomersByTotal = Transaction::groupBy('customer_id')
->select(
'customer_id',
DB::raw('SUM(total) as total_transaction'),
DB::raw('COUNT(*) as transaction_count')
)
->whereBetween('created_at', [
Carbon::parse($startDate)->startOfDay(),
Carbon::parse($endDate)->endOfDay(),
])
->orderBy('total_transaction', 'desc')
->when(auth()->user()->role_id === 5, function ($query) {
return $query->where('user_id', auth()->id());
})
->limit(5)
->get();
$renderCustomerByTotal = view('partials.admin._customer-by-total', compact('topCustomersByTotal'))->render();
$incomeByBarber = User::with('biography')
->barbershop()
->barber()
->withSum(['transactions' => function ($query) use ($startDate, $endDate) {
$query->whereBetween('created_at', [
Carbon::parse($startDate)->startOfDay(),
Carbon::parse($endDate)->endOfDay(),
])->when(auth()->user()->role_id === 5, function ($query) {
$query->where('user_id', auth()->id());
});
}], 'total')
->orderBy('transactions_sum_total', 'desc')
->withCount(['transactions' => function ($query) use ($startDate, $endDate) {
$query->whereBetween('created_at', [
Carbon::parse($startDate)->startOfDay(),
Carbon::parse($endDate)->endOfDay(),
])->when(auth()->user()->role_id === 5, function ($query) {
$query->where('user_id', auth()->id());
});
}]);
if (in_array(auth()->user()->role_id, [1, 2, 3])) {
$incomeByBarber = $incomeByBarber->get();
} elseif (auth()->user()->role_id === 5) {
$incomeByBarber = $incomeByBarber->find(auth()->id());
$incomeByBarber = [
'transactions_sum_total' => formatToRupiah($incomeByBarber->transactions_sum_total ?? 0),
'transactions_count' => $incomeByBarber->transactions_count,
];
}
return response()->json([
'status' => true,
'message' => 'Data Successfully Retrieved',
@ -194,6 +310,7 @@ public function filter(Request $request): JsonResponse
'topBarber' => $topBarber,
'totalUser' => $totalUser,
'totalBarber' => $totalBarber,
'renderCustomerByTotal' => $renderCustomerByTotal,
],
], 200);
}

View File

@ -3,6 +3,7 @@ let incomeByBarberCtx = document.getElementById('incomeByBarberChart').getContex
let incomeByBarberLabels = incomeByBarber.map(incomeByBarber => incomeByBarber.biography.full_name);
let incomeByBarberData = incomeByBarber.map(incomeByBarber => incomeByBarber.transactions_sum_total);
console.log(incomeByBarberLabels, incomeByBarberData)
let incomeByBarberBackgroundColors = incomeByBarberData.map(() => getRandomColor());
let incomeByBarberBorderColors = incomeByBarberBackgroundColors.map(color => color.replace('0.2', '1'));

View File

@ -30,15 +30,25 @@ $(document).ready(function () {
$("#top-barber-name").text(response.data.topBarber.biography.full_name || "-deleted-");
$("#top-barber-total").text(formatToRupiah(response.data.topBarber.transactions_sum_total));
if (userRole === 5) {
$("#top-customers-by-total").empty();
$("#top-customers-by-total").html(response.data.renderCustomerByTotal);
$("#total-transaction").text(response.data.incomeByBarber.transactions_count);
$("#total-income").text(response.data.incomeByBarber.transactions_sum_total);
}
// income expense
let totalIncome = response.data.totalIncome;
let totalExpense = response.data.totalExpense;
incomeExpenseChart.data.datasets[0].data = [
totalIncome,
totalExpense,
];
incomeExpenseChart.update();
if (userRole !== 5) {
incomeExpenseChart.data.datasets[0].data = [
totalIncome,
totalExpense,
];
incomeExpenseChart.update();
}
// top service
let topService = response.data.topService;
@ -70,22 +80,26 @@ $(document).ready(function () {
paymentMethodChart.update();
// income by barber
let updatedIncomeByBarber = response.data.incomeByBarber;
let updatedLabels = updatedIncomeByBarber.map(item => item.biography.full_name);
let updatedData = updatedIncomeByBarber.map(item => item.transactions_sum_total);
if (userRole !== 5) {
let updatedIncomeByBarber = response.data.incomeByBarber;
let updatedLabels = updatedIncomeByBarber.map(item => item.biography.full_name);
let updatedData = updatedIncomeByBarber.map(item => item.transactions_sum_total);
incomeByBarberChart.data.labels = updatedLabels;
incomeByBarberChart.data.datasets[0].data = updatedData;
incomeByBarberChart.update();
incomeByBarberChart.data.labels = updatedLabels;
incomeByBarberChart.data.datasets[0].data = updatedData;
incomeByBarberChart.update();
}
// transaction by barber
let transactionByBarber = response.data.transactionByBarber;
let transactionByBarberLabels = transactionByBarber.map(item => item.biography.full_name);
let transactionByBarberData = transactionByBarber.map(item => item.transactions_count);
if (userRole !== 5) {
let transactionByBarber = response.data.transactionByBarber;
let transactionByBarberLabels = transactionByBarber.map(item => item.biography.full_name);
let transactionByBarberData = transactionByBarber.map(item => item.transactions_count);
transactionByBarberChart.data.labels = transactionByBarberLabels;
transactionByBarberChart.data.datasets[0].data = transactionByBarberData;
transactionByBarberChart.update();
transactionByBarberChart.data.labels = transactionByBarberLabels;
transactionByBarberChart.data.datasets[0].data = transactionByBarberData;
transactionByBarberChart.update();
}
},
error: function (xhr) { },
});

View File

@ -19,101 +19,104 @@
</div>
</div>
<div class="row widget-grid">
<div class="col-12 col-md-6 col-lg-4">
<div class="card small-widget">
<div class="card-body primary">
<span class="f-light">Total Aset</span>
<div class="d-flex align-items-end gap-1">
<h4 id="total-asset">{{ formatToRupiah($totalAsset) }}</h4>
</div>
<div class="bg-gradient">
</div>
</div>
</div>
<div class="card">
<div class="card-body">
<ul class="user-list">
<li>
<div class="user-icon primary">
<div class="user-box">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="-3 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="feather feather-user-plus font-primary">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="8.5" cy="7" r="4"></circle>
</svg>
</div>
</div>
<div>
<h5 class="mb-1" id="total-barber">{{ $totalBarber }}</h5>
<span class="font-primary d-flex align-items-center">
<i class="icon-arrow-up icon-rotate me-1"> </i>
<span class="f-w-500">Barber</span>
</span>
</div>
</li>
<li>
<div class="user-icon success">
<div class="user-box">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="-3 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="feather feather-user-minus font-success">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="8.5" cy="7" r="4"></circle>
</svg>
</div>
</div>
<div>
<h5 class="mb-1" id="total-user">{{ $totalUser }}</h5>
<span class="font-danger d-flex align-items-center">
<i class="icon-arrow-down icon-rotate me-1"></i>
<span class="f-w-500">Semua</span>
</span>
</div>
</li>
</ul>
</div>
</div>
@if ($topBarber && $topBarber->transactions_sum_total)
<div class="card 0-hidden">
<div class="card-body balance-widget">
<span class="f-w-500 f-light">Top Barber</span>
<h4 class="mb-3 mt-1 f-w-500 mb-0 f-22">
<span class="counter"
id="top-barber-name">{{ $topBarber->biography->full_name ?? '-deleted-' }}</span>
</h4>
<span class=" btn-hover-effect f-w-500"
id="top-barber-total">{{ formatToRupiah($topBarber->transactions_sum_total ?? 0) }}</span>
<div class="mobile-right-img">
<img class="mobile-img" src="{{ asset('assets/admin/images/crown.png') }}"
alt="mobile with coin">
@if (auth()->user()->role_id !== 5)
<div class="col-12 col-md-6 col-lg-4">
<div class="card small-widget">
<div class="card-body primary">
<span class="f-light">Total Aset</span>
<div class="d-flex align-items-end gap-1">
<h4 id="total-asset">{{ formatToRupiah($totalAsset) }}</h4>
</div>
<div class="bg-gradient">
</div>
</div>
</div>
@endif
</div>
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-header card-no-border">
<h5>Grafik Keuangan</h5>
<div class="card">
<div class="card-body">
<ul class="user-list">
<li>
<div class="user-icon primary">
<div class="user-box">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="-3 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="feather feather-user-plus font-primary">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="8.5" cy="7" r="4"></circle>
</svg>
</div>
</div>
<div>
<h5 class="mb-1" id="total-barber">{{ $totalBarber }}</h5>
<span class="font-primary d-flex align-items-center">
<i class="icon-arrow-up icon-rotate me-1"> </i>
<span class="f-w-500">Barber</span>
</span>
</div>
</li>
<li>
<div class="user-icon success">
<div class="user-box">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
viewBox="-3 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"
class="feather feather-user-minus font-success">
<path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="8.5" cy="7" r="4"></circle>
</svg>
</div>
</div>
<div>
<h5 class="mb-1" id="total-user">{{ $totalUser }}</h5>
<span class="font-danger d-flex align-items-center">
<i class="icon-arrow-down icon-rotate me-1"></i>
<span class="f-w-500">Semua</span>
</span>
</div>
</li>
</ul>
</div>
</div>
<div class="card-body pt-0">
<div class="row m-0 overall-card">
<div class="chart-right">
<div class="card-body p-0">
<p>Keuntungan : <span
id="net-income">{{ formatToRupiah($totalIncome - $totalExpense) }}</span></p>
<div class="current-sale-container">
<canvas id="incomeExpenseChart" width="400" height="400"></canvas>
@if ($topBarber && $topBarber->transactions_sum_total)
<div class="card 0-hidden">
<div class="card-body balance-widget">
<span class="f-w-500 f-light">Top Barber</span>
<h4 class="mb-3 mt-1 f-w-500 mb-0 f-22">
<span class="counter"
id="top-barber-name">{{ $topBarber->biography->full_name ?? '-deleted-' }}</span>
</h4>
<span class=" btn-hover-effect f-w-500"
id="top-barber-total">{{ formatToRupiah($topBarber->transactions_sum_total ?? 0) }}</span>
<div class="mobile-right-img">
<img class="mobile-img" src="{{ asset('assets/admin/images/crown.png') }}"
alt="mobile with coin">
</div>
</div>
</div>
@endif
</div>
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-header card-no-border">
<h5>Grafik Keuangan</h5>
</div>
<div class="card-body pt-0">
<div class="row m-0 overall-card">
<div class="chart-right">
<div class="card-body p-0">
<p>Keuntungan : <span
id="net-income">{{ formatToRupiah($totalIncome - $totalExpense) }}</span>
</p>
<div class="current-sale-container">
<canvas id="incomeExpenseChart" width="400" height="400"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endif
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-header card-no-border">
@ -150,42 +153,107 @@ class="feather feather-user-minus font-success">
</div>
</div>
</div>
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-header card-no-border">
<h5>Pendapatan By Barber</h5>
</div>
<div class="card-body pt-0">
<div class="row m-0 overall-card">
<div class="chart-right">
<div class="card-body p-0">
<div class="current-sale-container">
<canvas id="incomeByBarberChart" width="400" height="400"></canvas>
@if (auth()->user()->role_id !== 5)
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-header card-no-border">
<h5>Pendapatan By Barber</h5>
</div>
<div class="card-body pt-0">
<div class="row m-0 overall-card">
<div class="chart-right">
<div class="card-body p-0">
<div class="current-sale-container">
<canvas id="incomeByBarberChart" width="400" height="400"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-header card-no-border">
<h5>Transaksi By Barber</h5>
</div>
<div class="card-body pt-0">
<div class="row m-0 overall-card">
<div class="chart-right">
<div class="card-body p-0">
<div class="current-sale-container">
<canvas id="transactionByBarberChart" width="400" height="400"></canvas>
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-header card-no-border">
<h5>Transaksi By Barber</h5>
</div>
<div class="card-body pt-0">
<div class="row m-0 overall-card">
<div class="chart-right">
<div class="card-body p-0">
<div class="current-sale-container">
<canvas id="transactionByBarberChart" width="400" height="400"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@else
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-header card-no-border">
<div class="header-top">
<h5 class="m-0">Top Pelanggan</h5>
</div>
</div>
<div class="card-body pt-0">
<div class="table-responsive custom-scrollbar">
<table class="table table-dashed">
<thead>
<tr>
<th>Nama</th>
<th>Kontak</th>
<th>Jumlah Transaksi</th>
<th>Total Transakski</th>
</tr>
</thead>
<tbody id="top-customers-by-total">
@foreach ($topCustomersByTotal as $item)
<tr>
<td>{{ $item->customer->name ?? '--deleted--' }}</td>
<td>{{ $item->customer->contact_number ?? '-' }}</td>
<td>{{ $item->transaction_count }}</td>
<td>{{ formatToRupiah($item->total_transaction) }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-6 col-lg-4">
<div class="card">
<div class="card-header card-no-border">
<div class="header-top">
<h5 class="m-0">Pendapatan Barber</h5>
</div>
</div>
<div class="card-body pt-0">
<div class="table-responsive custom-scrollbar">
<table class="table table-dashed">
<thead>
<tr>
<th>Jumlah Transaksi</th>
<th>Total Transakski</th>
</tr>
</thead>
<tbody>
<tr>
<td id="total-transaction">{{ $incomeByBarber->transactions_count }}</td>
<td id="total-income">
{{ $incomeByBarber->transactions_sum_total }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
@endif
</div>
</div>
@endsection
@ -196,12 +264,16 @@ class="feather feather-user-minus font-success">
@section('afterScripts')
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.6/dist/chart.umd.min.js"></script>
<script>
let userRole = {!! auth()->user()->role_id !!}
const totalIncome = @json($totalIncome);
const totalExpense = @json($totalExpense);
const topService = @json($topService);
const paymentMethod = @json($paymentMethod);
const incomeByBarber = @json($incomeByBarber);
const transactionByBarber = @json($transactionByBarber);
const topCustomersByTotal = @json($topCustomersByTotal);
const topBarber = @json($topBarber);
</script>
<script src="{{ asset('assets/admin/js/random-color.js?ver=1.0.0') }}"></script>
<script src="{{ asset('assets/admin/js/chart/finance.js?ver=1.0.0') }}"></script>

View File

@ -0,0 +1,8 @@
@foreach ($topCustomersByTotal as $item)
<tr>
<td>{{ $item->customer->name ?? '--deleted--' }}</td>
<td>{{ $item->customer->contact_number ?? '-' }}</td>
<td>{{ $item->transaction_count }}</td>
<td>{{ formatToRupiah($item->total_transaction) }}</td>
</tr>
@endforeach

View File

@ -49,7 +49,7 @@
<span>Ringkasan</span>
</a>
</li>
@if (in_array(auth()->user()->role_id, [1, 2, 3]))
@if (auth()->user()->role_id !== 4)
<li class="sidebar-list">
<i class="fa fa-thumb-tack"></i>
<a class="sidebar-link sidebar-title link-nav" href="{{ route('dashboard.analytic') }}">
@ -57,6 +57,8 @@
<span>Analitik</span>
</a>
</li>
@endif
@if (in_array(auth()->user()->role_id, [1, 2, 3]))
<li class="sidebar-main-title">
<div>
<h6>Master</h6>

View File

@ -19,7 +19,6 @@
use App\Http\Controllers\Client\HomepageController;
use App\Models\Transaction;
use Illuminate\Support\Facades\Route;
use Psy\Readline\Transient;
Route::prefix('auth')->group(function () {
Route::get('login', [LoginController::class, 'index'])->name('login');
@ -32,8 +31,8 @@
'prefix' => 'dashboard',
], function () {
Route::get('overview', [OverviewController::class, 'index'])->name('dashboard.overview');
Route::get('analytic', [AnalyticController::class, 'index'])->name('dashboard.analytic')->middleware('check_role:1,2,3');
Route::get('analytic/filter', [AnalyticController::class, 'filter'])->name('dashboard.analytic.filter')->middleware('check_role:1,2,3');
Route::get('analytic', [AnalyticController::class, 'index'])->name('dashboard.analytic')->middleware('check_role:1,2,3,5');
Route::get('analytic/filter', [AnalyticController::class, 'filter'])->name('dashboard.analytic.filter')->middleware('check_role:1,2,3,5');
Route::get('change-barbershop/{barbershop}', [OverviewController::class, 'changeBarbershop'])->name('dashboard.changeBarbershop');
@ -181,6 +180,6 @@
Route::get('/print-page', function () {
return view('print-page', [
'transaction' => Transaction::first()
'transaction' => Transaction::first(),
]); // Ganti dengan nama view halaman cetak Anda
})->name('print.page');