122 lines
12 KiB
PHP
122 lines
12 KiB
PHP
@extends('layouts.app')
|
||
@section('title', 'Admin Dashboard - UniformFlow')
|
||
@section('page-heading', 'Admin dashboard')
|
||
@section('content')
|
||
<section class="page-intro admin-intro">
|
||
<div>
|
||
<div class="dashboard-datetime"><span class="eyebrow">{{ now()->timezone('Asia/Colombo')->format('l, d F Y') }}</span><span id="dashboard-live-time">--:--:--</span><small>SLST</small></div>
|
||
<h1>Operations at a <em>glance.</em></h1>
|
||
<p>Stock health, employee accountability, and handover performance in one place.</p>
|
||
</div>
|
||
<div class="quick-actions">
|
||
<a class="button subtle" href="{{ route('employees.index') }}">+ Add employee</a>
|
||
<a class="button subtle" href="{{ route('inventory.index') }}">+ Receive stock</a>
|
||
<a class="button primary" href="{{ route('issues.index') }}">+ Issue uniform</a>
|
||
</div>
|
||
</section>
|
||
|
||
<div class="stats-grid admin-stats">
|
||
<article class="stat-card mint"><span class="stat-icon">E</span><small>Active employees</small><strong>{{ number_format($activeEmployees) }}</strong><span class="stat-note">{{ $employeesWithIssues }} currently hold uniforms</span></article>
|
||
<button class="stat-card blue stock-card-button" type="button" onclick="document.getElementById('stock-chart-dialog').showModal()"><span class="stat-icon">S</span><small>Available stock</small><strong>{{ number_format($unitsInStock) }}</strong><span class="stat-note">Across {{ $uniformTypes }} uniform types / click for graph</span></button>
|
||
<article class="stat-card amber"><span class="stat-icon">I</span><small>Units with employees</small><strong>{{ number_format($unitsIssued) }}</strong><span class="stat-note">{{ $issuedThisMonth }} issued this month</span></article>
|
||
<article class="stat-card rose"><span class="stat-icon">!</span><small>Stock alerts</small><strong>{{ $lowStock->count() }}</strong><span class="stat-note">{{ $outOfStock->count() }} sizes are out of stock</span></article>
|
||
</div>
|
||
|
||
<div class="admin-summary-grid">
|
||
<section class="panel health-panel">
|
||
<div class="panel-head"><div><span class="eyebrow">Inventory control</span><h2>Stock health</h2></div><strong class="health-score">{{ $stockHealth }}%</strong></div>
|
||
<div class="progress-track"><span style="width: {{ $stockHealth }}%"></span></div>
|
||
<div class="health-legend"><span><i class="good-dot"></i> Healthy sizes</span><span><i class="warn-dot"></i> At or below reorder level</span></div>
|
||
<div class="mini-metrics"><div><small>Uniform types</small><strong>{{ $uniformTypes }}</strong></div><div><small>Low stock</small><strong>{{ $lowStock->count() }}</strong></div><div><small>Out of stock</small><strong>{{ $outOfStock->count() }}</strong></div></div>
|
||
</section>
|
||
<section class="panel monthly-panel monthly-panel-clickable" role="button" tabindex="0" onclick="document.getElementById('monthly-movement-dialog').showModal()" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();document.getElementById('monthly-movement-dialog').showModal()}">
|
||
<div class="panel-head"><div><span class="eyebrow">This month</span><h2>Movement summary</h2></div><span class="view-detail-hint">View all →</span></div>
|
||
<div class="monthly-metric"><span>Uniforms issued</span><strong>{{ $issuedThisMonth }}</strong></div>
|
||
<div class="monthly-metric"><span>Exit handovers completed</span><strong>{{ $handoversThisMonth }}</strong></div>
|
||
<div class="monthly-metric"><span>Reusable returns</span><strong>{{ $handoverOutcomes['reusable'] ?? 0 }}</strong></div>
|
||
<div class="monthly-metric danger-text"><span>Damaged or not returned</span><strong>{{ ($handoverOutcomes['damaged'] ?? 0) + ($handoverOutcomes['not_returned'] ?? 0) }}</strong></div>
|
||
</section>
|
||
</div>
|
||
|
||
<div class="dashboard-main-grid">
|
||
<section class="panel attention-panel">
|
||
<div class="panel-head"><div><span class="eyebrow">Action required</span><h2>Stock alerts</h2></div><a href="{{ route('inventory.index') }}">Manage stock</a></div>
|
||
@forelse($lowStock->take(7) as $variant)
|
||
<div class="list-row"><div class="item-symbol">{{ strtoupper(substr($variant->item->name, 0, 2)) }}</div><div class="grow"><strong>{{ $variant->item->name }}</strong><small>Size {{ $variant->size }} / reorder at {{ $variant->reorder_level }}</small></div><span class="pill {{ $variant->quantity === 0 ? 'danger' : 'warning' }}">{{ $variant->quantity }} available</span></div>
|
||
@empty<div class="empty">All size balances are above their reorder levels.</div>@endforelse
|
||
</section>
|
||
<section class="panel replacement-panel">
|
||
<div class="panel-head"><div><span class="eyebrow">Employee welfare</span><h2>Held over 12 months</h2></div><span class="pill {{ $replacementDue->count() ? 'warning' : 'good' }}">{{ $replacementDue->count() }} due</span></div>
|
||
@forelse($replacementDue as $issue)
|
||
<div class="list-row"><div class="person-avatar">{{ strtoupper(substr($issue->employee->name, 0, 1)) }}</div><div class="grow"><strong>{{ $issue->employee->name }}</strong><small>{{ $issue->variant->item->name }} / {{ $issue->variant->size }} / issued {{ $issue->issued_at->format('d M Y') }}</small></div><a class="text-link" href="{{ route('employees.show', $issue->employee) }}">Review</a></div>
|
||
@empty<div class="empty">No employee-held uniforms are over 12 months old.</div>@endforelse
|
||
</section>
|
||
</div>
|
||
|
||
<div class="dashboard-insights-grid">
|
||
<section class="panel">
|
||
<div class="panel-head"><div><span class="eyebrow">Accountability</span><h2>Issued by department</h2></div></div>
|
||
@php($maxDepartmentUnits = max(1, (int) ($departmentAllocation->max('units') ?? 1)))
|
||
@forelse($departmentAllocation as $department)
|
||
<div class="bar-row"><div><span>{{ $department->department }}</span><strong>{{ $department->units }}</strong></div><div class="bar-track"><i style="width: {{ round(($department->units / $maxDepartmentUnits) * 100) }}%"></i></div></div>
|
||
@empty<div class="empty">Department allocation appears after uniforms are issued.</div>@endforelse
|
||
</section>
|
||
<section class="panel">
|
||
<div class="panel-head"><div><span class="eyebrow">Demand planning</span><h2>Most issued uniforms</h2></div></div>
|
||
@forelse($popularUniforms as $index => $uniform)
|
||
<div class="rank-row"><span class="rank">{{ $index + 1 }}</span><div class="grow"><strong>{{ $uniform->name }}</strong><small>Total units issued</small></div><b>{{ $uniform->units }}</b></div>
|
||
@empty<div class="empty">Demand rankings appear after uniforms are issued.</div>@endforelse
|
||
</section>
|
||
<section class="panel">
|
||
<div class="panel-head"><div><span class="eyebrow">Audit trail</span><h2>Recent activity</h2></div></div>
|
||
@forelse($recentMovements->take(6) as $movement)
|
||
<div class="activity"><span class="activity-dot {{ $movement->quantity_change > 0 ? 'in' : 'out' }}"></span><div class="grow"><strong>{{ ucfirst(str_replace('_', ' ', $movement->type)) }} / {{ $movement->variant->item->name }}</strong><small>Size {{ $movement->variant->size }} / {{ $movement->created_at->diffForHumans() }}</small></div><b>{{ $movement->quantity_change > 0 ? '+' : '' }}{{ $movement->quantity_change }}</b></div>
|
||
@empty<div class="empty">Stock activity will appear here.</div>@endforelse
|
||
</section>
|
||
</div>
|
||
|
||
<dialog class="stock-chart-dialog" id="stock-chart-dialog">
|
||
<div class="chart-dialog-head"><div><span class="eyebrow">Available inventory</span><h2>Stock across uniform types</h2><p>New and employee-returned units currently available.</p></div><button type="button" onclick="this.closest('dialog').close()" aria-label="Close">×</button></div>
|
||
<div class="chart-legend"><span><i class="chart-new"></i>New stock</span><span><i class="chart-returned"></i>Returned stock</span></div>
|
||
@php($maxStockTotal = max(1, (int) ($stockDistribution->max('total') ?? 1)))
|
||
<div class="stock-chart">
|
||
@forelse($stockDistribution as $stock)
|
||
<div class="stock-chart-row">
|
||
<div class="chart-label"><strong>{{ $stock->name }}</strong><small>{{ number_format($stock->stock_value, 2) }} total value</small></div>
|
||
<div class="chart-bar-area"><div class="chart-bar" style="width: {{ round(($stock->total / $maxStockTotal) * 100) }}%"><span class="chart-new" style="width: {{ $stock->total ? round(($stock->new_units / $stock->total) * 100) : 0 }}%"></span><span class="chart-returned" style="width: {{ $stock->total ? round(($stock->returned_units / $stock->total) * 100) : 0 }}%"></span></div></div>
|
||
<b>{{ $stock->total }}</b>
|
||
</div>
|
||
@empty<div class="empty">No available stock to graph.</div>@endforelse
|
||
</div>
|
||
<div class="chart-dialog-foot"><a class="button primary" href="{{ route('inventory.index') }}">Open inventory</a><button class="button subtle" type="button" onclick="this.closest('dialog').close()">Close</button></div>
|
||
</dialog>
|
||
<dialog class="stock-chart-dialog monthly-movement-dialog" id="monthly-movement-dialog">
|
||
<div class="chart-dialog-head"><div><span class="eyebrow">{{ now()->timezone('Asia/Colombo')->format('F Y') }}</span><h2>Detailed monthly movement summary</h2><p>Every inventory movement recorded during the current month.</p></div><button type="button" onclick="this.closest('dialog').close()" aria-label="Close">×</button></div>
|
||
<div class="monthly-dialog-metrics">
|
||
<div><small>Movement records</small><strong>{{ $monthlyMovements->count() }}</strong></div>
|
||
<div><small>Units received / returned</small><strong class="positive">+{{ $monthlyMovements->where('quantity_change', '>', 0)->sum('quantity_change') }}</strong></div>
|
||
<div><small>Units issued</small><strong class="negative">{{ $monthlyMovements->where('quantity_change', '<', 0)->sum('quantity_change') }}</strong></div>
|
||
<div><small>Net stock movement</small><strong>{{ $monthlyMovements->sum('quantity_change') > 0 ? '+' : '' }}{{ $monthlyMovements->sum('quantity_change') }}</strong></div>
|
||
</div>
|
||
<div class="monthly-movement-table table-wrap"><table><thead><tr><th>Date & time</th><th>Movement</th><th>Uniform</th><th>Size</th><th>Category</th><th>Quantity</th><th>Unit value</th><th>Employee / reference</th></tr></thead><tbody>
|
||
@forelse($monthlyMovements as $movement)<tr><td>{{ $movement->created_at->timezone('Asia/Colombo')->format('d M Y') }}<small class="block">{{ $movement->created_at->timezone('Asia/Colombo')->format('h:i:s A') }}</small></td><td><strong>{{ ucfirst(str_replace('_', ' ', $movement->type)) }}</strong></td><td>{{ $movement->variant->item->name }}</td><td><span class="size-badge">{{ $movement->variant->size }}</span></td><td>{{ $movement->stock_category ? ucfirst($movement->stock_category) : '—' }}</td><td><strong class="{{ $movement->quantity_change > 0 ? 'movement-in' : ($movement->quantity_change < 0 ? 'movement-out' : '') }}">{{ $movement->quantity_change > 0 ? '+' : '' }}{{ $movement->quantity_change }}</strong></td><td>{{ $movement->unit_value !== null ? number_format($movement->unit_value, 2) : '—' }}</td><td>{{ $movement->employee?->name ?: 'Stock operation' }}<small class="block">{{ $movement->notes ?: 'No reference' }}</small></td></tr>
|
||
@empty<tr><td colspan="8" class="empty">No stock movements have been recorded this month.</td></tr>@endforelse
|
||
</tbody></table></div>
|
||
<div class="chart-dialog-foot"><button class="button subtle" type="button" onclick="this.closest('dialog').close()">Close</button></div>
|
||
</dialog>
|
||
<script>
|
||
document.getElementById('stock-chart-dialog').addEventListener('click',function(event){if(event.target===this)this.close()});
|
||
document.getElementById('monthly-movement-dialog').addEventListener('click',function(event){if(event.target===this)this.close()});
|
||
(() => {
|
||
const clock = document.getElementById('dashboard-live-time');
|
||
const updateClock = () => {
|
||
clock.textContent = new Intl.DateTimeFormat('en-GB', {
|
||
timeZone: 'Asia/Colombo', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true
|
||
}).format(new Date());
|
||
};
|
||
updateClock();
|
||
setInterval(updateClock, 1000);
|
||
})();
|
||
</script>
|
||
@endsection
|