Warenwirtschaft: einheitliches UI-Design (AP-19)

- 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>
This commit is contained in:
Kevin Adametz 2026-06-03 13:09:55 +00:00
parent 3ee2d756e9
commit a8f6fef38e
21 changed files with 1609 additions and 955 deletions

View file

@ -1,69 +1,147 @@
@extends('layouts.layout-2')
@php
$total = $rows->count();
$criticalCount = $rows->where('status', 'critical')->count();
$warningCount = $rows->where('status', 'warning')->count();
$okCount = $total - $criticalCount - $warningCount;
@endphp
@section('content')
@include('admin.inventory.partials.table-actions-style')
<div class="card">
<div class="card-header">
<div class="d-flex flex-wrap justify-content-between align-items-center">
<h6 class="mb-0">{{ __('Produktbestand') }}</h6>
@include('admin.inventory.partials.wawi-ui')
<div class="wawi-page">
<div class="wawi-page-head">
<div>
<h1 class="wawi-page-head__title">{{ __('Produktbestand') }}</h1>
<p class="wawi-page-head__subtitle">{{ __('Aktueller Lagerbestand aller aktiven Produkte') }}</p>
</div>
<div class="wawi-page-head__actions">
<a href="{{ route('admin.inventory.product-stock.history') }}" class="btn btn-outline-secondary btn-sm">
<span class="far fa-clock mr-1"></span>{{ __('Historie') }}
</a>
</div>
</div>
<div class="wawi-stats">
<div class="wawi-stat" data-filter="all">
<div class="wawi-stat__icon"><span class="fas fa-boxes-stacked"></span></div>
<div class="wawi-stat__body">
<div class="wawi-stat__value">{{ $total }}</div>
<div class="wawi-stat__label">{{ __('Produkte') }}</div>
</div>
</div>
<div class="wawi-stat wawi-stat--ok" data-filter="all">
<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">{{ __('Niedrig') }}</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="ps-search" class="form-control" autocomplete="off"
placeholder="{{ __('Produkt suchen …') }}">
</div>
<div class="wawi-toolbar__spacer"></div>
<label class="form-check d-inline-flex align-items-center mb-0">
<input type="checkbox" id="ps-only-critical" class="form-check-input mr-2">
<span>{{ __('nur kritische anzeigen') }}</span>
</label>
</div>
<div class="form-group row mt-3 mb-0 align-items-center">
<label for="ps-search" class="col-sm-1 col-form-label">{{ __('Suchen') }}</label>
<div class="col-sm-5">
<input type="text" id="ps-search" class="form-control" autocomplete="off">
</div>
</div>
</div>
<div class="card-datatable table-responsive">
<table class="table table-striped wawi-table mb-0" id="ps-table">
<thead>
<tr>
<th style="width:4rem"></th>
<th>{{ __('Name') }}</th>
<th class="text-right">{{ __('Bestand') }}</th>
<th style="width:18rem">{{ __('Aktion') }}</th>
</tr>
</thead>
<tbody>
@forelse($rows as $row)
@php
$product = $row['product'];
$rowClass = $row['status'] === 'critical' ? 'table-danger' : ($row['status'] === 'warning' ? 'table-warning' : '');
$stockClass = $row['status'] === 'critical' ? 'text-danger font-weight-bold' : ($row['status'] === 'warning' ? 'text-warning font-weight-bold' : '');
@endphp
<tr class="ps-row {{ $rowClass }}" data-name="{{ Str::lower($product->name) }} {{ Str::lower($product->number ?? '') }}" data-status="{{ $row['status'] }}">
<td>
@if ($product->images->count())
<img class="img-fluid" alt="" style="max-height: 42px"
src="{{ route('product_image', [$product->images->first()->slug]) }}">
@endif
</td>
<td>{{ $product->name }}</td>
<td class="text-right {{ $stockClass }}">{{ \App\Services\Util::formatNumber($row['stock'], 0) }} {{ __('Stück') }}</td>
<td>
@if (Auth::user()->isAdmin())
<button type="button" class="btn icon-btn btn-sm btn-success js-ps-move"
data-product="{{ $product->id }}" data-name="{{ $product->name }}" data-direction="in"
title="{{ __('Eingang buchen') }}"><span class="fas fa-plus"></span></button>
<button type="button" class="btn icon-btn btn-sm btn-outline-danger js-ps-move"
data-product="{{ $product->id }}" data-name="{{ $product->name }}" data-direction="out"
title="{{ __('Ausgang buchen') }}"><span class="fas fa-minus"></span></button>
@endif
<a href="{{ route('admin.inventory.productions.create', ['product_id' => $product->id]) }}"
class="btn btn-sm btn-dark">{{ __('Produzieren') }}</a>
</td>
</tr>
@empty
<div class="table-responsive">
<table class="table wawi-table" id="ps-table">
<thead>
<tr>
<td colspan="4" class="text-center text-muted py-4">{{ __('Keine Produkte vorhanden.') }}</td>
<th style="width:3.5rem"></th>
<th>{{ __('Name') }}</th>
<th class="text-right">{{ __('Bestand') }}</th>
<th>{{ __('Status') }}</th>
<th class="text-right" style="width:16rem">{{ __('Aktion') }}</th>
</tr>
@endforelse
</tbody>
</table>
</thead>
<tbody>
@forelse($rows as $row)
@php
$product = $row['product'];
$rowClass = $row['status'] === 'critical' ? 'is-danger' : ($row['status'] === 'warning' ? 'is-warning' : '');
$stockClass = $row['status'] === 'critical' ? 'text-danger font-weight-bold' : ($row['status'] === 'warning' ? 'text-warning font-weight-bold' : '');
@endphp
<tr class="ps-row {{ $rowClass }}" data-name="{{ Str::lower($product->name) }} {{ Str::lower($product->number ?? '') }}" data-status="{{ $row['status'] }}">
<td>
@if ($product->images->count())
<img class="wawi-thumb" alt=""
src="{{ route('product_image', [$product->images->first()->slug]) }}">
@else
<span class="wawi-thumb wawi-thumb--empty"><span class="far fa-image"></span></span>
@endif
</td>
<td>
<div class="wawi-item-name">{{ $product->name }}</div>
@if (!empty($product->number))
<div class="wawi-item-sub">{{ $product->number }}</div>
@endif
</td>
<td class="text-right {{ $stockClass }}">
{{ \App\Services\Util::formatNumber($row['stock'], 0) }} <span class="text-muted">{{ __('Stück') }}</span>
</td>
<td>
@if ($row['status'] === 'critical')
<span class="wawi-pill wawi-pill--danger">{{ __('Kritisch') }}</span>
@elseif ($row['status'] === 'warning')
<span class="wawi-pill wawi-pill--warning">{{ __('Niedrig') }}</span>
@else
<span class="wawi-pill wawi-pill--ok">{{ __('OK') }}</span>
@endif
</td>
<td class="text-right">
<div class="wawi-actions">
@if (Auth::user()->isAdmin())
<button type="button" class="btn icon-btn btn-sm btn-success js-ps-move"
data-product="{{ $product->id }}" data-name="{{ $product->name }}" data-direction="in"
title="{{ __('Eingang buchen') }}"><span class="fas fa-plus"></span></button>
<button type="button" class="btn icon-btn btn-sm btn-outline-danger js-ps-move"
data-product="{{ $product->id }}" data-name="{{ $product->name }}" data-direction="out"
title="{{ __('Ausgang buchen') }}"><span class="fas fa-minus"></span></button>
@endif
<a href="{{ route('admin.inventory.productions.create', ['product_id' => $product->id]) }}"
class="btn btn-sm btn-outline-secondary">
<span class="fas fa-flask mr-1"></span>{{ __('Produzieren') }}</a>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5">
<div class="wawi-empty">
<div><span class="fas fa-boxes-stacked"></span></div>
{{ __('Keine Produkte vorhanden.') }}
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
@ -126,6 +204,18 @@
$('#ps-search').on('keyup', applyFilter);
$('#ps-only-critical').on('change', applyFilter);
$('.wawi-stat.is-clickable').on('click', function () {
var filter = $(this).data('filter');
var $this = $(this);
var wasActive = $this.hasClass('is-active');
$('.wawi-stat').removeClass('is-active');
if (filter === 'critical' || filter === 'warning') {
$('#ps-only-critical').prop('checked', !wasActive);
if (!wasActive) { $this.addClass('is-active'); }
applyFilter();
}
});
var moveTemplate = "{{ route('admin.inventory.product-stock.movement', ['product' => '__ID__']) }}";
$('.js-ps-move').on('click', function () {
var id = $(this).data('product');