feature(deals by category pie chart):

- add a pie chart that shows deals by category
- change UI to make it more clear
This commit is contained in:
kusowl 2026-02-02 17:22:01 +05:30
parent 1edfd7b9d4
commit d7c06c38a6
7 changed files with 115 additions and 47 deletions

View File

@ -4,6 +4,8 @@
use App\Enums\UserTypes; use App\Enums\UserTypes;
use App\Http\Resources\ActiveUsersStatsCollection; use App\Http\Resources\ActiveUsersStatsCollection;
use App\Http\Resources\DealsCountByCategoryCollection;
use App\Models\DealCategory;
use App\Queries\PageVisitStatsQuery; use App\Queries\PageVisitStatsQuery;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -22,4 +24,13 @@ public function getActiveUsers(Request $request, PageVisitStatsQuery $baseQuery)
'activeBrokers' => new ActiveUsersStatsCollection($activeBrokers), 'activeBrokers' => new ActiveUsersStatsCollection($activeBrokers),
]); ]);
} }
public function getDealsByCategory()
{
return new DealsCountByCategoryCollection(
DealCategory::select(['id', 'name'])
->withCount('deals')
->get()
);
}
} }

View File

@ -0,0 +1,16 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class DealsCountByCategoryCollection extends ResourceCollection
{
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
];
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class DealsCountByCategoryResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'name' => $this->name,
'dealsCount' => $this->deals_count,
];
}
}

View File

@ -1,5 +1,7 @@
<div id="active-users-chart-parent" class="col-span-2"> <div id="active-users-chart-parent" class="col-span-2">
<x-dashboard.card data-is-loading="false" class="h-75"> <x-dashboard.card class="h-75">
<div class="flex justify-between items-baseline">
<h3 class="text-md font-bold mb-4">User Activity</h3>
<x-ui.toggle-button-group class="mb-4"> <x-ui.toggle-button-group class="mb-4">
<x-ui.toggle-button :active="request('sortBy') == null"> <x-ui.toggle-button :active="request('sortBy') == null">
<button onclick="switchGraph(this, 30)" class="graphBtn flex items-center px-2 space-x-2"> <button onclick="switchGraph(this, 30)" class="graphBtn flex items-center px-2 space-x-2">
@ -14,22 +16,41 @@
</x-ui.toggle-button> </x-ui.toggle-button>
<x-ui.toggle-button> <x-ui.toggle-button>
<button class="flex items-center pt-0.5 px-4 space-x-2"> <button id="dateRange" class="flex items-center pt-0.5 px-4 space-x-2">
<x-heroicon-o-calendar-date-range class="w-4"/> <x-heroicon-o-calendar-date-range class="w-4"/>
</button> </button>
</x-ui.toggle-button> </x-ui.toggle-button>
</x-ui.toggle-button-group> </x-ui.toggle-button-group>
</div>
<div class="h-50"> <div data-is-loading="false" class="h-50">
<canvas id="active-users-chart"></canvas> <canvas id="active-users-chart"></canvas>
</div> </div>
</x-dashboard.card> </x-dashboard.card>
</div> </div>
@push('scripts') @push('scripts')
<script> <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
window.addEventListener('DOMContentLoaded', () => { window.addEventListener('DOMContentLoaded', () => {
// BY default load 30 days data
flatpickr("#dateRange", {
mode: "range",
dateFormat: "Y-m-d",
onClose: function (selectedDates) {
if (selectedDates.length === 2) {
const start = selectedDates[0].toISOString().split('T')[0];
const end = selectedDates[1].toISOString().split('T')[0];
activeBtn(document.getElementById('dateRange'));
generateActiveUsersChart(start, end);
}
}
});
// By default load 30 days data
const end = new Date(); const end = new Date();
const start = new Date(); const start = new Date();
start.setDate(end.getDate() - 30); start.setDate(end.getDate() - 30);

View File

@ -1,37 +1,32 @@
<x-dashboard.card class="col-span-1 h-75"> <div id="deals-pie-chart-parent" class="col-span-1">
<x-dashboard.card class="col-span-1 h-75">
<h3 class="text-md font-bold mb-4">Deals by category</h3>
<div data-is-loading="false" class="h-55">
<canvas id="category-wise-deals-pie-chart"></canvas> <canvas id="category-wise-deals-pie-chart"></canvas>
</x-dashboard.card> </div>
</x-dashboard.card>
</div>
@push('scripts') @push('scripts')
<script async> <script async>
window.addEventListener('DOMContentLoaded', async () => { window.addEventListener('DOMContentLoaded', async () => {
const dealsPieChart = document.getElementById('category-wise-deals-pie-chart'); const dealsPieChart = document.getElementById('category-wise-deals-pie-chart');
const dealsPieChartParent = document.getElementById('deals-pie-chart-parent');
if (!dealsPieChart || !dealsPieChartParent) {
console.error('canvas not defined');
return;
}
toggleShimmer(false, dealsPieChartParent);
try { try {
// const response = await axios.get('/api/stats/customer/active/30'); const {data: apiData} = await axios.get('/api/stats/deals-by-category');
// const apiData = response.data.data;
const categories = apiData?.data.map((item) => item?.name);
// Fill the data from api response const dealsCounts = apiData?.data.map((item) => item?.dealsCount);
// const chartData = labels.map((date) => {
// let found = apiData.find(item => item.date === date);
// return found ? found.userCount : 0;
// })
const data = { const data = {
labels: [ labels: categories,
'Real Estate',
'Food',
'Sell & Deal',
'Palaces'
],
datasets: [{ datasets: [{
label: 'My First Dataset', data: dealsCounts,
data: [30, 20, 15, 5],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(99,102,255)',
],
hoverOffset: 4 hoverOffset: 4
}] }]
}; };
@ -42,10 +37,15 @@
options: { options: {
maintainAspectRatio: false, maintainAspectRatio: false,
responsive: true, responsive: true,
plugins: {
legend: {
position: 'bottom',
}
}
} }
}; };
const chart = new Chart(dealsPieChart, config); const chart = new Chart(dealsPieChart, config);
// toggleShimmer(false, activeChartParent); toggleShimmer(true, dealsPieChartParent);
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }

View File

@ -19,6 +19,8 @@
<link rel="preconnect" href="https://fonts.bunny.net"> <link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet"/> <link href="https://fonts.bunny.net/css?family=instrument-sans:400,500,600" rel="stylesheet"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<!-- Styles / Scripts --> <!-- Styles / Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js']) @vite(['resources/css/app.css', 'resources/js/app.js'])

View File

@ -4,4 +4,5 @@
Route::prefix('/stats')->group(function () { Route::prefix('/stats')->group(function () {
Route::get('/active-users', [StatsController::class, 'getActiveUsers']); Route::get('/active-users', [StatsController::class, 'getActiveUsers']);
Route::get('/deals-by-category', [StatsController::class, 'getDealsByCategory'])->name('stats.deals-by-category');
}); });