UniformFlow/resources/views/dashboard.blade.php

122 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@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