- Zentrale, wiederverwendbare Design-Partial wawi-ui.blade.php (gescoptes Inline-CSS, kein Build noetig) - Bausteine: Seitenkopf, Kennzahlen-Kacheln, Karten, Toolbar/Suche, aufgeraeumte Tabellen, Status-Pills, Datenblatt-Definitionsliste, Name-Zelle mit fester Icon-Spalte, Leer-Zustaende - Umgestellt: alle Uebersicht-/Listen- und Detailseiten unter admin/inventory - Responsive: Detail-Datenblaetter brechen unter 768px gestapelt um (Label oben, Wert linksbuendig); Icon-Spalte verhindert Versatz bei Zeilenumbruch - Entwicklungsplan um AP-19 + UI-Konvention ergaenzt Co-authored-by: Cursor <cursoragent@cursor.com>
216 lines
11 KiB
PHP
216 lines
11 KiB
PHP
@extends('layouts.layout-2')
|
|
|
|
@php
|
|
$total = $rows->count();
|
|
$criticalCount = $rows->whereIn('status', ['critical', 'critical_ordered'])->count();
|
|
$warningCount = $rows->where('status', 'warning')->count();
|
|
$okCount = $total - $criticalCount - $warningCount;
|
|
@endphp
|
|
|
|
@section('content')
|
|
@include('admin.inventory.partials.wawi-ui')
|
|
<style>
|
|
#rms-table tbody tr.rms-row { cursor: pointer; }
|
|
#rms-table tbody tr.rms-row:hover .fa-edit { color: var(--wawi-accent) !important; }
|
|
</style>
|
|
|
|
<div class="wawi-page">
|
|
<div class="wawi-page-head">
|
|
<div>
|
|
<h1 class="wawi-page-head__title">{{ __('Rohstoffbestand') }}</h1>
|
|
<p class="wawi-page-head__subtitle">{{ __('Verfügbare Rohstoffe inkl. Reichweite und Nachbestellung') }}</p>
|
|
</div>
|
|
<div class="wawi-page-head__actions">
|
|
<a href="{{ route('admin.inventory.stock-entries.create') }}" class="btn btn-outline-secondary btn-sm">
|
|
<span class="fas fa-truck mr-1"></span>{{ __('Einkauf erfassen') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wawi-stats">
|
|
<div class="wawi-stat">
|
|
<div class="wawi-stat__icon"><span class="fas fa-flask"></span></div>
|
|
<div class="wawi-stat__body">
|
|
<div class="wawi-stat__value">{{ $total }}</div>
|
|
<div class="wawi-stat__label">{{ __('Rohstoffe') }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="wawi-stat wawi-stat--ok">
|
|
<div class="wawi-stat__icon"><span class="fas fa-circle-check"></span></div>
|
|
<div class="wawi-stat__body">
|
|
<div class="wawi-stat__value">{{ $okCount }}</div>
|
|
<div class="wawi-stat__label">{{ __('Bestand OK') }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="wawi-stat wawi-stat--warning is-clickable" data-filter="warning">
|
|
<div class="wawi-stat__icon"><span class="fas fa-triangle-exclamation"></span></div>
|
|
<div class="wawi-stat__body">
|
|
<div class="wawi-stat__value">{{ $warningCount }}</div>
|
|
<div class="wawi-stat__label">{{ __('Bald nachbestellen') }}</div>
|
|
</div>
|
|
</div>
|
|
<div class="wawi-stat wawi-stat--danger is-clickable" data-filter="critical">
|
|
<div class="wawi-stat__icon"><span class="fas fa-circle-exclamation"></span></div>
|
|
<div class="wawi-stat__body">
|
|
<div class="wawi-stat__value">{{ $criticalCount }}</div>
|
|
<div class="wawi-stat__label">{{ __('Kritisch') }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wawi-card">
|
|
<div class="wawi-toolbar">
|
|
<div class="wawi-search">
|
|
<span class="fas fa-search"></span>
|
|
<input type="text" id="rms-search" class="form-control" autocomplete="off"
|
|
placeholder="{{ __('Rohstoff suchen …') }}">
|
|
</div>
|
|
<div class="wawi-toolbar__spacer"></div>
|
|
<label class="form-check d-inline-flex align-items-center mb-0">
|
|
<input type="checkbox" id="rms-only-critical" class="form-check-input mr-2">
|
|
<span>{{ __('nur kritische anzeigen') }}</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table wawi-table" id="rms-table">
|
|
<thead>
|
|
<tr>
|
|
<th>{{ __('Name') }}</th>
|
|
<th>{{ __('Qualität') }}</th>
|
|
<th class="text-right">{{ __('Bestand') }}</th>
|
|
<th class="text-right">{{ __('Offen bestellt') }}</th>
|
|
<th class="text-right">{{ __('Verbrauch / Tag') }}</th>
|
|
<th>{{ __('Voraussichtlich auf Null') }}</th>
|
|
<th class="text-right" style="min-width: 13rem;">
|
|
<select id="rms-horizon" class="form-control form-control-sm">
|
|
@foreach($horizonOptions as $days => $label)
|
|
<option value="{{ $days }}" @selected($days === $defaultHorizon)>{{ $label }}</option>
|
|
@endforeach
|
|
</select>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@forelse($rows as $row)
|
|
@php
|
|
$ingredient = $row['ingredient'];
|
|
$rowClass = $row['status'] === 'critical'
|
|
? 'is-danger'
|
|
: (in_array($row['status'], ['warning', 'critical_ordered'], true) ? 'is-warning' : '');
|
|
@endphp
|
|
<tr class="rms-row {{ $rowClass }}"
|
|
data-name="{{ Str::lower($ingredient->name) }} {{ Str::lower($ingredient->inci ?? '') }}"
|
|
data-status="{{ $row['status'] }}"
|
|
data-href="{{ route('admin.inventory.raw-material-stock.show', $ingredient) }}">
|
|
<td>
|
|
<span class="wawi-name-cell">
|
|
<i class="far fa-edit text-muted wawi-name-cell__icon" title="{{ __('Bestellung öffnen') }}"></i>
|
|
<span class="wawi-item-name">{{ $ingredient->name }}</span>
|
|
</span>
|
|
</td>
|
|
<td class="text-muted">{{ $ingredient->materialQuality?->name ?? '—' }}</td>
|
|
<td class="text-right {{ in_array($row['status'], ['critical', 'critical_ordered'], true) ? 'text-danger font-weight-bold' : '' }}">
|
|
{{ \App\Services\Util::formatNumber($row['remaining'], 0) }} g
|
|
@if($row['status'] === 'critical_ordered')
|
|
<span class="wawi-pill wawi-pill--warning ml-1">{{ __('bestellt') }}</span>
|
|
@endif
|
|
</td>
|
|
<td class="text-right">
|
|
@if($row['open_order'] > 0)
|
|
{{ \App\Services\Util::formatNumber($row['open_order'], 0) }} g
|
|
@else
|
|
<span class="text-muted">—</span>
|
|
@endif
|
|
</td>
|
|
<td class="text-right">
|
|
@if($row['daily'] !== null && $row['daily'] > 0)
|
|
{{ \App\Services\Util::formatNumber($row['daily'], 0) }} g
|
|
@else
|
|
<span class="text-muted">—</span>
|
|
@endif
|
|
</td>
|
|
<td>
|
|
@if($row['expected_empty'] !== null)
|
|
{{ $row['expected_empty']->format('d.m.Y') }}
|
|
<span class="text-muted">({{ $row['days_until_empty'] }} {{ trans_choice('Tag|Tagen', $row['days_until_empty']) }})</span>
|
|
@else
|
|
<span class="text-muted">—</span>
|
|
@endif
|
|
</td>
|
|
<td class="text-right rms-forecast" data-daily="{{ $row['daily'] !== null ? $row['daily'] : 0 }}">
|
|
@if($row['daily'] !== null && $row['daily'] > 0)
|
|
{{ \App\Services\Util::formatNumber($row['daily'] * $defaultHorizon, 0) }} g
|
|
@else
|
|
<span class="text-muted">—</span>
|
|
@endif
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr>
|
|
<td colspan="7">
|
|
<div class="wawi-empty">
|
|
<div><span class="fas fa-flask"></span></div>
|
|
{{ __('Keine aktiven Rohstoffe vorhanden.') }}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
$(function () {
|
|
var $rows = $('#rms-table tbody tr.rms-row');
|
|
|
|
function applyFilter() {
|
|
var term = ($('#rms-search').val() || '').toLowerCase().trim();
|
|
var onlyCritical = $('#rms-only-critical').is(':checked');
|
|
|
|
$rows.each(function () {
|
|
var $row = $(this);
|
|
var matchesTerm = term === '' || ($row.data('name') || '').toString().indexOf(term) !== -1;
|
|
var status = $row.data('status');
|
|
var matchesCritical = !onlyCritical || (status === 'critical' || status === 'critical_ordered' || status === 'warning');
|
|
$row.toggle(matchesTerm && matchesCritical);
|
|
});
|
|
}
|
|
|
|
function applyForecast() {
|
|
var days = parseInt($('#rms-horizon').val(), 10) || 0;
|
|
$('.rms-forecast').each(function () {
|
|
var daily = parseFloat($(this).data('daily')) || 0;
|
|
if (daily > 0) {
|
|
var total = Math.round(daily * days);
|
|
$(this).text(total.toLocaleString('de-DE') + ' g');
|
|
} else {
|
|
$(this).html('<span class="text-muted">—</span>');
|
|
}
|
|
});
|
|
}
|
|
|
|
$('#rms-search').on('keyup', applyFilter);
|
|
$('#rms-only-critical').on('change', applyFilter);
|
|
$('#rms-horizon').on('change', applyForecast);
|
|
|
|
$('.wawi-stat.is-clickable').on('click', function () {
|
|
var $this = $(this);
|
|
var wasActive = $this.hasClass('is-active');
|
|
$('.wawi-stat').removeClass('is-active');
|
|
$('#rms-only-critical').prop('checked', !wasActive);
|
|
if (!wasActive) { $this.addClass('is-active'); }
|
|
applyFilter();
|
|
});
|
|
|
|
$rows.on('click', function () {
|
|
var href = $(this).data('href');
|
|
if (href) {
|
|
window.location = href;
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
@endsection
|