Files
ilycats/resources/views/pages/dashboard/index.blade.php
T

549 lines
35 KiB
PHP
Raw Normal View History

2026-02-08 15:16:35 +07:00
<x-app-layout>
2026-09-23 12:45:02 +07:00
<div class="mb-6 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div>
<h1 class="text-2xl font-bold text-slate-900">Beranda</h1>
<p class="mt-1 text-sm text-slate-500">Ringkasan operasional cattery dan pemantauan kondisi kucing.</p>
</div>
<div class="flex flex-wrap gap-2">
<a href="{{ route('cats.create') }}" class="bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-indigo-700">
<i class="fas fa-plus mr-2"></i>Tambah kucing
</a>
<a href="{{ route('matting.create') }}" class="border border-slate-300 bg-white px-4 py-2.5 text-sm font-semibold text-slate-700 hover:bg-slate-50">
Catat perkawinan
</a>
</div>
</div>
<div class="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
@foreach([
['Total kucing',$stats['total_cats'] ?? 0,'fa-cat','cats.index'],
['Dalam perawatan',$stats['sick_cats'] ?? 0,'fa-heart-pulse','cats.index'],
['Pembiakan aktif',$stats['total_mating'] ?? 0,'fa-venus-mars','matting.index'],
['Anakan tercatat',$stats['total_kittens'] ?? 0,'fa-paw','matting.index']
] as $i=>$card)
<a href="{{ $i===1 ? route($card[3],['filter_health'=>'treatment']) : route($card[3]) }}" class="border border-slate-200 bg-white p-5 hover:border-slate-300">
<div class="flex items-start justify-between">
<div>
<p class="text-sm text-slate-500">{{ $card[0] }}</p>
<p class="mt-2 text-3xl font-bold text-slate-900">{{ $card[1] }}</p>
</div>
<i class="fas {{ $card[2] }} mt-1 text-slate-300"></i>
</div>
</a>
@endforeach
</div>
{{-- PEMANTAUAN BERAT BADAN - REVISION 3 --}}
<section id="pemantauan-berat" class="mt-6 scroll-mt-6 rounded-2xl border border-slate-200 bg-white shadow-sm">
<div class="flex flex-col gap-5 border-b border-slate-100 px-5 py-5 lg:flex-row lg:items-end lg:justify-between">
<div class="flex items-start gap-3">
<div class="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-blue-50 text-blue-600">
<i class="fas fa-weight-scale text-lg"></i>
<span class="absolute -bottom-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full border-2 border-white bg-white text-[8px] text-blue-500 shadow-sm">
<i class="fas fa-paw"></i>
</span>
</div>
<div>
<h2 class="text-xl font-bold text-slate-900">Pemantauan Berat Badan</h2>
<p class="mt-1 text-sm text-slate-500">Pantau perkembangan berat badan kucing secara ringkas dan mudah dibaca.</p>
2026-02-08 15:16:35 +07:00
</div>
</div>
2026-09-23 12:45:02 +07:00
<form method="GET" action="{{ route('dashboard') }}#pemantauan-berat" class="grid gap-3 sm:grid-cols-2 lg:min-w-[470px]">
<input type="hidden" name="weight_focus" value="1">
<input type="hidden" name="trend_category" value="{{ $weightTrendCategory ?? 'Kitten' }}">
<div>
<label for="weight_months" class="mb-1.5 block text-xs font-semibold text-slate-500">Periode</label>
<div class="relative">
<i class="fas fa-calendar-days pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-xs text-slate-400"></i>
<select id="weight_months" name="weight_months" class="w-full rounded-xl border-slate-200 bg-white py-2.5 pl-9 pr-8 text-sm text-slate-700 shadow-sm focus:border-blue-400 focus:ring-blue-400" onchange="this.form.submit()">
@foreach([3=>'3 bulan',6=>'6 bulan',12=>'12 bulan',24=>'24 bulan'] as $months=>$label)
<option value="{{ $months }}" @selected(($weightTrendMonths ?? 6) == $months)>{{ $label }}</option>
@endforeach
</select>
2026-02-08 15:16:35 +07:00
</div>
</div>
2026-09-23 12:45:02 +07:00
<div>
<label for="weight_category" class="mb-1.5 block text-xs font-semibold text-slate-500">Kategori</label>
<div class="relative">
<i class="fas fa-cat pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-xs text-slate-400"></i>
<select id="weight_category" name="weight_category" class="w-full rounded-xl border-slate-200 bg-white py-2.5 pl-9 pr-8 text-sm text-slate-700 shadow-sm focus:border-blue-400 focus:ring-blue-400" onchange="this.form.submit()">
@foreach(['Semua'=>'Semua Kategori','Kitten'=>'Kitten','Junior'=>'Junior','Dewasa'=>'Dewasa'] as $value=>$label)
<option value="{{ $value }}" @selected(($weightTableCategory ?? 'Semua') === $value)>{{ $label }}</option>
@endforeach
</select>
</div>
</div>
</form>
2026-02-08 15:16:35 +07:00
</div>
2026-09-23 12:45:02 +07:00
@php
$weightCards = [
[
'label' => 'Total Ditimbang Bulan Ini',
'value' => (int) data_get($weightKpis ?? [], 'weighed.value', 0),
'suffix' => 'kucing',
'change' => (float) data_get($weightKpis ?? [], 'weighed.change', 0),
'hasComparison' => true,
'icon' => 'fa-paw',
'box' => 'bg-blue-50/70 border-blue-100',
'iconBox' => 'bg-blue-100 text-blue-600',
],
[
'label' => 'Rata-rata Kitten',
'value' => (float) data_get($weightKpis ?? [], 'categories.Kitten.average', 0),
'suffix' => 'kg',
'change' => (float) data_get($weightKpis ?? [], 'categories.Kitten.change', 0),
'hasComparison' => (bool) data_get($weightKpis ?? [], 'categories.Kitten.has_comparison', false),
'icon' => 'fa-cat',
'box' => 'bg-indigo-50/70 border-indigo-100',
'iconBox' => 'bg-indigo-100 text-indigo-600',
],
[
'label' => 'Rata-rata Junior',
'value' => (float) data_get($weightKpis ?? [], 'categories.Junior.average', 0),
'suffix' => 'kg',
'change' => (float) data_get($weightKpis ?? [], 'categories.Junior.change', 0),
'hasComparison' => (bool) data_get($weightKpis ?? [], 'categories.Junior.has_comparison', false),
'icon' => 'fa-cat',
'box' => 'bg-rose-50/70 border-rose-100',
'iconBox' => 'bg-rose-100 text-rose-600',
],
[
'label' => 'Rata-rata Dewasa',
'value' => (float) data_get($weightKpis ?? [], 'categories.Dewasa.average', 0),
'suffix' => 'kg',
'change' => (float) data_get($weightKpis ?? [], 'categories.Dewasa.change', 0),
'hasComparison' => (bool) data_get($weightKpis ?? [], 'categories.Dewasa.has_comparison', false),
'icon' => 'fa-cat',
'box' => 'bg-emerald-50/70 border-emerald-100',
'iconBox' => 'bg-emerald-100 text-emerald-600',
],
];
@endphp
<div class="grid gap-3 p-5 sm:grid-cols-2 xl:grid-cols-4">
@foreach($weightCards as $card)
@php
$isCount = $card['suffix'] === 'kucing';
$change = $card['change'];
$positive = $change >= 0;
@endphp
<div class="rounded-2xl border p-4 {{ $card['box'] }}">
<div class="flex items-start gap-3">
<div class="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl {{ $card['iconBox'] }}">
<i class="fas {{ $card['icon'] }}"></i>
<span class="absolute -bottom-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full border-2 border-white bg-white text-[6px] opacity-90"><i class="fas fa-paw"></i></span>
</div>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-slate-700">{{ $card['label'] }}</p>
<div class="mt-1 flex items-end gap-1.5">
<p class="text-3xl font-bold tracking-tight text-slate-900">
{{ $isCount ? number_format($card['value'], 0) : number_format($card['value'], 1, ',', '.') }}
</p>
<span class="mb-1 text-sm font-medium text-slate-500">{{ $card['suffix'] }}</span>
</div>
@if($card['hasComparison'])
<p class="mt-1 text-xs {{ $positive ? 'text-emerald-600' : 'text-rose-600' }}">
<i class="fas {{ $positive ? 'fa-arrow-up' : 'fa-arrow-down' }} mr-1"></i>{{ $positive ? '+' : '' }}{{ number_format($change, 1, ',', '.') }}%
<span class="text-slate-400">dari bulan lalu</span>
</p>
@else
<p class="mt-1 text-xs text-slate-400">Belum ada pembanding bulan lalu</p>
@endif
</div>
2026-02-08 15:16:35 +07:00
</div>
</div>
2026-09-23 12:45:02 +07:00
@endforeach
</div>
2026-02-08 15:16:35 +07:00
2026-09-23 12:45:02 +07:00
<div class="grid gap-4 px-5 pb-5 xl:grid-cols-2">
<div class="rounded-2xl border border-slate-200 bg-white p-4">
<div class="mb-4">
<h3 class="text-base font-bold text-slate-900">Rata-rata per kategori</h3>
<p class="mt-0.5 text-xs text-slate-500">Rata-rata berat terakhir setiap kucing pada masing-masing bulan.</p>
</div>
<div class="h-72 sm:h-80">
<canvas id="weightCategoryAverageChart"></canvas>
</div>
</div>
<div class="rounded-2xl border border-slate-200 bg-white p-4">
<div class="mb-4 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div>
<h3 class="text-base font-bold text-slate-900">Tren kategori terpilih</h3>
<p class="mt-0.5 text-xs text-slate-500">Maksimal 6 kucing dengan data terlengkap agar grafik tetap mudah dibaca.</p>
2026-02-08 15:16:35 +07:00
</div>
2026-09-23 12:45:02 +07:00
<div class="inline-flex rounded-xl bg-slate-100 p-1">
@foreach(['Kitten','Junior','Dewasa'] as $category)
@php
$tabQuery = array_merge(request()->except('page', 'trend_category'), [
'weight_focus' => 1,
'trend_category' => $category,
]);
@endphp
<a href="{{ route('dashboard', $tabQuery) }}#pemantauan-berat"
class="rounded-lg px-3 py-1.5 text-xs font-semibold transition {{ ($weightTrendCategory ?? 'Kitten') === $category ? 'bg-blue-600 text-white shadow-sm' : 'text-slate-600 hover:bg-white hover:text-slate-900' }}">
{{ $category }}
</a>
@endforeach
2026-02-08 15:16:35 +07:00
</div>
</div>
2026-09-23 12:45:02 +07:00
<div class="h-72 sm:h-80">
@if(count($weightTrendSeries ?? []) > 0)
<canvas id="weightSelectedCategoryChart"></canvas>
@else
<div class="flex h-full flex-col items-center justify-center rounded-xl bg-slate-50 text-center">
<div class="mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-white text-slate-300 shadow-sm"><i class="fas fa-cat text-xl"></i></div>
<p class="text-sm font-semibold text-slate-600">Belum ada tren {{ $weightTrendCategory ?? 'kategori ini' }}</p>
<p class="mt-1 text-xs text-slate-400">Tambahkan minimal satu pencatatan berat pada periode yang dipilih.</p>
</div>
@endif
</div>
2026-02-08 15:16:35 +07:00
</div>
2026-09-23 12:45:02 +07:00
</div>
2026-02-08 15:16:35 +07:00
2026-09-23 12:45:02 +07:00
<div class="border-t border-slate-100 px-5 py-5">
<div class="mb-4 flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
<div>
<h3 class="text-base font-bold text-slate-900">Berat Terakhir</h3>
<p class="mt-0.5 text-xs text-slate-500">Daftar berat terbaru, perubahan dari pencatatan sebelumnya, dan tanggal timbang.</p>
</div>
<div class="flex flex-col gap-2 sm:flex-row sm:items-center">
<label class="relative block">
<span class="sr-only">Cari nama kucing</span>
<i class="fas fa-magnifying-glass pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-xs text-slate-400"></i>
<input id="weightTableSearch" type="search" placeholder="Cari nama kucing..." class="w-full rounded-xl border-slate-200 py-2.5 pl-9 pr-3 text-sm text-slate-700 sm:w-64 focus:border-blue-400 focus:ring-blue-400">
</label>
<div class="flex items-center gap-2 text-xs text-slate-500">
<span>Tampilkan</span>
<select id="weightRowsPerPage" class="rounded-xl border-slate-200 py-2 text-sm text-slate-700 focus:border-blue-400 focus:ring-blue-400">
<option value="10" selected>10</option>
<option value="20">20</option>
<option value="50">50</option>
</select>
<span>data</span>
2026-02-08 15:16:35 +07:00
</div>
2026-09-23 12:45:02 +07:00
@can('check-access', ['cats','update'])
<a href="{{ route('cats.bulk-weight') }}" class="inline-flex items-center justify-center rounded-xl bg-blue-600 px-3.5 py-2.5 text-sm font-semibold text-white hover:bg-blue-700">
<i class="fas fa-weight-scale mr-2"></i>Perbarui berat
</a>
@endcan
2026-02-08 15:16:35 +07:00
</div>
2026-09-23 12:45:02 +07:00
</div>
<div class="overflow-hidden rounded-2xl border border-slate-200">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200 text-sm">
<thead class="bg-slate-50/80 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">
<tr>
<th class="px-4 py-3">Nama Kucing</th>
<th class="px-4 py-3">Kategori</th>
<th class="px-4 py-3 text-right">Berat Terakhir</th>
<th class="px-4 py-3 text-right">Perubahan</th>
<th class="px-4 py-3">Tanggal</th>
</tr>
</thead>
<tbody id="weightTableBody" class="divide-y divide-slate-100 bg-white">
@forelse(($weightLatestTable ?? collect()) as $item)
@php
$photoUrl = $item['photo'] ?
\Illuminate\Support\Facades\Storage::url($item['photo']) : asset('images/default-cat.png');
$change = $item['change'];
@endphp
<tr data-weight-row data-name="{{ strtolower($item['name']) }}" class="hover:bg-slate-50/70">
<td class="px-4 py-3">
<div class="flex items-center gap-3">
<img src="{{ $photoUrl }}" alt="Foto {{ $item['name'] }}" class="h-9 w-9 rounded-full border border-slate-200 object-cover" onerror="this.src='{{ asset('images/default-cat.png') }}'">
<div class="min-w-0">
<p class="truncate font-semibold text-slate-800">{{ $item['name'] }}</p>
<p class="text-xs text-slate-400">{{ $item['gender'] ?: '-' }}</p>
</div>
</div>
</td>
<td class="px-4 py-3">
@php
$badge = match($item['category']) {
'Kitten' => 'bg-blue-50 text-blue-700 ring-blue-100',
'Junior' => 'bg-rose-50 text-rose-700 ring-rose-100',
default => 'bg-emerald-50 text-emerald-700 ring-emerald-100',
};
@endphp
<span class="inline-flex rounded-full px-2.5 py-1 text-xs font-semibold ring-1 ring-inset {{ $badge }}">{{ $item['category'] }}</span>
</td>
<td class="whitespace-nowrap px-4 py-3 text-right font-bold text-slate-800">{{ number_format($item['latest'], 2, ',', '.') }} kg</td>
<td class="whitespace-nowrap px-4 py-3 text-right">
@if($change === null)
<span class="text-xs text-slate-400">Belum ada pembanding</span>
@elseif($change > 0)
<span class="font-semibold text-emerald-600"><i class="fas fa-arrow-up mr-1 text-xs"></i>+{{ number_format($change, 2, ',', '.') }} kg</span>
@elseif($change < 0)
<span class="font-semibold text-rose-600"><i class="fas fa-arrow-down mr-1 text-xs"></i>{{ number_format($change, 2, ',', '.') }} kg</span>
@else
<span class="font-semibold text-slate-500"><i class="fas fa-minus mr-1 text-xs"></i>0,00 kg</span>
@endif
</td>
<td class="whitespace-nowrap px-4 py-3 text-slate-500">{{ \Carbon\Carbon::parse($item['date'])->translatedFormat('d M Y') }}</td>
</tr>
@empty
<tr data-empty-row><td colspan="5" class="px-4 py-10 text-center text-sm text-slate-500">Belum ada data berat pada periode yang dipilih.</td></tr>
@endforelse
</tbody>
</table>
</div>
<div class="flex flex-col gap-3 border-t border-slate-100 bg-slate-50/40 px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
<p id="weightTableInfo" class="text-xs text-slate-500"></p>
<div class="flex items-center gap-2">
<button id="weightPrevPage" type="button" class="rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-xs font-semibold text-slate-600 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-40">Sebelumnya</button>
<span id="weightPageLabel" class="min-w-20 text-center text-xs font-semibold text-slate-600"></span>
<button id="weightNextPage" type="button" class="rounded-lg border border-slate-200 bg-white px-3 py-1.5 text-xs font-semibold text-slate-600 hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-40">Berikutnya</button>
</div>
2026-02-08 15:16:35 +07:00
</div>
</div>
</div>
2026-09-23 12:45:02 +07:00
</section>
<div class="mt-6 grid gap-6 xl:grid-cols-3">
<section class="border border-slate-200 bg-white xl:col-span-2">
<div class="flex items-center justify-between border-b border-slate-200 px-5 py-4">
<div>
<h2 class="font-semibold text-slate-900">Perkiraan kelahiran</h2>
<p class="text-xs text-slate-500">Perkawinan aktif dengan perkiraan lahir sekitar 63 hari setelah kawin.</p>
</div>
<a href="{{ route('matting.index') }}" class="text-sm font-medium text-indigo-600">Lihat semua</a>
</div>
<div class="divide-y divide-slate-100">
@forelse($upcomingBirths as $mating)
<a href="{{ route('matting.edit',$mating->id) }}" class="grid gap-3 px-5 py-4 hover:bg-slate-50 sm:grid-cols-[1fr_auto] sm:items-center">
<div>
<p class="font-semibold text-slate-800">{{ $mating->dam->nama_kucing ?? 'Betina' }} <span class="font-normal text-slate-400">×</span> {{ $mating->sire->nama_kucing ?? 'Jantan' }}</p>
<p class="mt-1 text-sm text-slate-500">Perkiraan lahir {{ $mating->edd?->translatedFormat('d M Y') }}</p>
</div>
<span class="text-sm font-semibold {{ $mating->days_to_go <= 2 ? 'text-rose-600' : 'text-slate-600' }}">{{ $mating->days_to_go < 0 ? 'Lewat '.abs($mating->days_to_go).' hari' : $mating->days_to_go.' hari lagi' }}</span>
</a>
@empty
<div class="px-5 py-10 text-center text-sm text-slate-500">Belum ada perkiraan kelahiran dalam waktu dekat.</div>
@endforelse
</div>
</section>
<section class="border border-slate-200 bg-white">
<div class="border-b border-slate-200 px-5 py-4">
<h2 class="font-semibold text-slate-900">Jadwal yang terlambat</h2>
<p class="text-xs text-slate-500">Perlu ditindaklanjuti dari data kucing.</p>
</div>
<div class="grid grid-cols-2 gap-px bg-slate-200">
@php $labels=['vaccine'=>'Vaksin','flea'=>'Obat kutu','deworming'=>'Obat cacing','grooming'=>'Grooming']; @endphp
@foreach($overdueAlerts as $key=>$data)
<a href="{{ $data['url'] }}" class="bg-white p-4 hover:bg-slate-50">
<p class="text-xs text-slate-500">{{ $labels[$key] ?? $key }}</p>
<p class="mt-1 text-2xl font-bold {{ $data['count']>0?'text-rose-600':'text-slate-800' }}">{{ $data['count'] }}</p>
</a>
@endforeach
</div>
</section>
</div>
<div class="mt-6 grid gap-6 xl:grid-cols-2">
<section class="border border-slate-200 bg-white">
<div class="border-b border-slate-200 px-5 py-4"><h2 class="font-semibold text-slate-900">Stok menipis</h2></div>
<div class="divide-y divide-slate-100">
@forelse(($lowStock ?? collect())->take(8) as $item)
<a href="{{ $item->url ?? '#' }}" class="flex justify-between px-5 py-3 text-sm hover:bg-slate-50">
<span class="truncate pr-4">{{ $item->name }}</span>
<span class="font-semibold text-rose-600">{{ (float)$item->qty }} {{ $item->unit }}</span>
</a>
@empty
<div class="p-5 text-sm text-slate-500">Tidak ada stok kritis.</div>
@endforelse
</div>
</section>
<section class="border border-slate-200 bg-white">
<div class="border-b border-slate-200 px-5 py-4"><h2 class="font-semibold text-slate-900">Mendekati kedaluwarsa</h2></div>
<div class="divide-y divide-slate-100">
@forelse($expiringItems as $item)
<a href="{{ $item->url ?? '#' }}" class="flex items-center justify-between gap-4 px-5 py-3 text-sm hover:bg-slate-50">
<div class="min-w-0">
<p class="truncate font-medium">{{ $item->name }}</p>
<p class="text-xs {{ $item->is_expired?'text-rose-600':'text-amber-600' }}">{{ $item->is_expired?'Sudah kedaluwarsa':$item->days_left.' hari lagi' }} · {{ $item->formatted_date }}</p>
</div>
<span class="shrink-0 text-xs text-slate-500">{{ (float)$item->qty }} {{ $item->unit }}</span>
</a>
@empty
<div class="p-5 text-sm text-slate-500">Tidak ada peringatan kedaluwarsa.</div>
@endforelse
</div>
</section>
2026-02-08 15:16:35 +07:00
</div>
@push('scripts')
2026-09-23 12:45:02 +07:00
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const formatKg = (value) => `${Number(value).toFixed(2).replace('.', ',')} kg`;
const formatMonth = (month) => {
const d = new Date(`${month}-01T00:00:00`);
return Number.isNaN(d.getTime()) ? month : d.toLocaleDateString('id-ID', { month: 'short', year: '2-digit' });
};
if (typeof Chart !== 'undefined') {
Chart.defaults.font.family = "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
Chart.defaults.color = '#64748b';
const categoryColors = {
Kitten: '#2563eb',
Junior: '#db2777',
Dewasa: '#059669'
};
const catPalette = ['#d97706', '#7c3aed', '#0891b2', '#2563eb', '#059669', '#be123c'];
const avgData = @json($categoryAverageTrend ?? ['labels'=>[], 'series'=>[]]);
const avgCanvas = document.getElementById('weightCategoryAverageChart');
if (avgCanvas && avgData.labels && avgData.labels.length) {
new Chart(avgCanvas, {
type: 'line',
data: {
labels: avgData.labels.map(formatMonth),
datasets: (avgData.series || []).map((series) => ({
label: series.name,
data: series.data,
borderColor: categoryColors[series.name] || '#64748b',
backgroundColor: categoryColors[series.name] || '#64748b',
borderWidth: 2.5,
pointRadius: 3,
pointHoverRadius: 5,
pointBorderWidth: 0,
tension: 0.28,
spanGaps: true
}))
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'index', intersect: false },
plugins: {
legend: {
position: 'bottom',
labels: { usePointStyle: true, boxWidth: 8, boxHeight: 8, padding: 18, font: { size: 11, weight: '600' } }
},
tooltip: {
backgroundColor: '#0f172a',
padding: 10,
cornerRadius: 10,
callbacks: { label: (ctx) => `${ctx.dataset.label}: ${formatKg(ctx.parsed.y)}` }
}
},
scales: {
x: { grid: { display: false }, border: { display: false }, ticks: { maxRotation: 0, autoSkip: true, maxTicksLimit: 8 } },
y: {
beginAtZero: true,
grid: { color: '#e2e8f0' },
border: { display: false },
title: { display: true, text: 'Berat (kg)', color: '#64748b', font: { size: 11, weight: '600' } }
}
}
}
});
}
const trendLabels = @json($weightTrendLabels ?? []);
const trendSeries = @json($weightTrendSeries ?? []);
const trendCanvas = document.getElementById('weightSelectedCategoryChart');
if (trendCanvas && trendLabels.length && trendSeries.length) {
new Chart(trendCanvas, {
type: 'line',
data: {
labels: trendLabels.map(formatMonth),
datasets: trendSeries.map((series, index) => ({
label: series.name,
data: series.data,
borderColor: catPalette[index % catPalette.length],
backgroundColor: catPalette[index % catPalette.length],
borderWidth: 2.2,
pointRadius: 3,
pointHoverRadius: 5,
pointBorderWidth: 0,
tension: 0.28,
spanGaps: true
}))
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'nearest', intersect: false },
plugins: {
legend: {
position: 'bottom',
labels: { usePointStyle: true, boxWidth: 8, boxHeight: 8, padding: 15, font: { size: 10, weight: '600' } }
},
tooltip: {
backgroundColor: '#0f172a',
padding: 10,
cornerRadius: 10,
callbacks: { label: (ctx) => `${ctx.dataset.label}: ${formatKg(ctx.parsed.y)}` }
}
},
scales: {
x: { grid: { display: false }, border: { display: false }, ticks: { maxRotation: 0, autoSkip: true, maxTicksLimit: 8 } },
y: {
beginAtZero: true,
grid: { color: '#e2e8f0' },
border: { display: false },
title: { display: true, text: 'Berat (kg)', color: '#64748b', font: { size: 11, weight: '600' } }
}
}
}
});
}
}
// Pencarian + pagination tabel berat terbaru (client-side, tanpa request tambahan).
const searchInput = document.getElementById('weightTableSearch');
const rowsPerPageSelect = document.getElementById('weightRowsPerPage');
const tableRows = Array.from(document.querySelectorAll('[data-weight-row]'));
const prevButton = document.getElementById('weightPrevPage');
const nextButton = document.getElementById('weightNextPage');
const pageLabel = document.getElementById('weightPageLabel');
const tableInfo = document.getElementById('weightTableInfo');
let currentPage = 1;
const renderWeightTable = () => {
if (!tableRows.length) {
if (tableInfo) tableInfo.textContent = '0 data';
if (pageLabel) pageLabel.textContent = '';
if (prevButton) prevButton.disabled = true;
if (nextButton) nextButton.disabled = true;
return;
}
const search = (searchInput?.value || '').trim().toLowerCase();
const pageSize = Number(rowsPerPageSelect?.value || 10);
const filtered = tableRows.filter((row) => (row.dataset.name || '').includes(search));
const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
currentPage = Math.min(currentPage, totalPages);
const start = (currentPage - 1) * pageSize;
const visible = new Set(filtered.slice(start, start + pageSize));
tableRows.forEach((row) => row.classList.toggle('hidden', !visible.has(row)));
if (tableInfo) {
const from = filtered.length ? start + 1 : 0;
const to = Math.min(start + pageSize, filtered.length);
tableInfo.textContent = `Menampilkan ${from}-${to} dari ${filtered.length} data`;
}
if (pageLabel) pageLabel.textContent = `Hal. ${currentPage} / ${totalPages}`;
if (prevButton) prevButton.disabled = currentPage <= 1;
if (nextButton) nextButton.disabled = currentPage >= totalPages;
};
searchInput?.addEventListener('input', () => { currentPage = 1; renderWeightTable(); });
rowsPerPageSelect?.addEventListener('change', () => { currentPage = 1; renderWeightTable(); });
prevButton?.addEventListener('click', () => { if (currentPage > 1) { currentPage--; renderWeightTable(); } });
nextButton?.addEventListener('click', () => { currentPage++; renderWeightTable(); });
renderWeightTable();
});
</script>
2026-02-08 15:16:35 +07:00
@endpush
2026-09-23 12:45:02 +07:00
</x-app-layout>