1. 實作倉儲管理系統 (Warehouse Management):包含總覽、庫存、調撥、機台庫存與補貨模組。 2. 建立倉儲相關資料結構:新增 warehouses, warehouse_stocks, stock_in_orders, stock_movements, transfer_orders, replenishment_orders 等資料表。 3. 優化商品管理 (Product Management) 介面:調整商品與類別管理的分頁、搜尋與編輯邏輯,並導入極簡奢華風 UI。 4. 增強遠端機台管理 (Remote Machine Management):優化機台清單、庫存監控與遠端出貨介面,提升載入效能。 5. 更新全站導覽選單 (Sidebar):整合倉儲管理入口,並修復側邊欄語法錯誤。 6. 標準化分頁組件 (Pagination):套用 luxury 奢華風分頁樣式。 7. 補齊多語系語系檔 (i18n):更新 zh_TW.json,包含倉儲與商品模組相關詞彙。
140 lines
9.7 KiB
PHP
140 lines
9.7 KiB
PHP
@extends('layouts.admin')
|
|
|
|
@section('content')
|
|
<div class="space-y-3 pb-20" x-data="{
|
|
showSlotDrawer: false,
|
|
selectedMachine: null,
|
|
slots: [],
|
|
loading: false,
|
|
async viewSlots(machine) {
|
|
this.selectedMachine = machine;
|
|
this.showSlotDrawer = true;
|
|
this.loading = true;
|
|
try {
|
|
const res = await fetch(`/admin/warehouses/machine-inventory/${machine.id}/slots`);
|
|
const data = await res.json();
|
|
this.slots = data.slots || [];
|
|
} catch(e) { this.slots = []; }
|
|
this.loading = false;
|
|
}
|
|
}">
|
|
{{-- Header --}}
|
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
<div>
|
|
<h1 class="text-3xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ __('Machine Inventory Overview') }}</h1>
|
|
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Real-time slot-level inventory across all machines') }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Table --}}
|
|
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
|
<form action="{{ route('admin.warehouses.machine-inventory') }}" method="GET" class="mb-8">
|
|
<div class="relative group">
|
|
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none"><svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg></span>
|
|
<input type="text" name="search" value="{{ request('search') }}" class="py-2.5 pl-12 pr-6 block w-64 luxury-input" placeholder="{{ __('Search machines...') }}">
|
|
</div>
|
|
</form>
|
|
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full text-left border-separate border-spacing-y-0">
|
|
<thead>
|
|
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
|
<th class="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Machine') }}</th>
|
|
<th class="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Serial No.') }}</th>
|
|
<th class="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Slots') }}</th>
|
|
<th class="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Total Stock') }}</th>
|
|
<th class="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Fill Rate') }}</th>
|
|
<th class="px-6 py-4 text-xs font-bold text-slate-500 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
|
@forelse($machines as $machine)
|
|
@php
|
|
$maxStock = $machine->slots->sum('max_stock') ?: 1;
|
|
$currentStock = (int)($machine->total_stock ?? 0);
|
|
$fillRate = round(($currentStock / $maxStock) * 100);
|
|
$fillColor = $fillRate >= 50 ? 'emerald' : ($fillRate >= 20 ? 'amber' : 'rose');
|
|
@endphp
|
|
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
|
<td class="px-6 py-5">
|
|
<span class="font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">{{ $machine->name }}</span>
|
|
</td>
|
|
<td class="px-6 py-5">
|
|
<span class="font-mono font-bold text-slate-500">{{ $machine->serial_no }}</span>
|
|
</td>
|
|
<td class="px-6 py-5 text-center">
|
|
<span class="font-black text-slate-800 dark:text-white">{{ $machine->total_slots ?? 0 }}</span>
|
|
</td>
|
|
<td class="px-6 py-5 text-center">
|
|
<span class="font-black text-slate-800 dark:text-white">{{ $currentStock }}</span>
|
|
</td>
|
|
<td class="px-6 py-5 text-center">
|
|
<div class="flex items-center justify-center gap-3">
|
|
<div class="w-24 h-2 bg-slate-100 dark:bg-slate-800 rounded-full overflow-hidden">
|
|
<div class="h-full bg-{{ $fillColor }}-500 rounded-full transition-all" style="width: {{ $fillRate }}%"></div>
|
|
</div>
|
|
<span class="text-xs font-black text-{{ $fillColor }}-500">{{ $fillRate }}%</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-6 py-5 text-right">
|
|
<button @click="viewSlots({{ json_encode($machine->only(['id', 'name', 'serial_no'])) }})"
|
|
class="px-4 py-2 rounded-xl text-xs font-black bg-cyan-500/10 text-cyan-500 border border-cyan-500/20 hover:bg-cyan-500 hover:text-white transition-all">
|
|
{{ __('View Slots') }}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
@empty
|
|
<tr><td colspan="6" class="px-6 py-20 text-center text-slate-400 font-bold">{{ __('No machines found') }}</td></tr>
|
|
@endforelse
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div class="mt-6 py-6 border-t border-slate-50 dark:border-slate-800/50">{{ $machines->links('vendor.pagination.luxury') }}</div>
|
|
</div>
|
|
|
|
{{-- Slot Drawer --}}
|
|
<div x-show="showSlotDrawer" class="fixed inset-0 z-[100] overflow-hidden" x-cloak>
|
|
<div x-show="showSlotDrawer" x-transition class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm" @click="showSlotDrawer = false"></div>
|
|
<div x-show="showSlotDrawer" x-transition:enter="transform transition ease-in-out duration-300" x-transition:enter-start="translate-x-full" x-transition:enter-end="translate-x-0" x-transition:leave="transform transition ease-in-out duration-300" x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-full"
|
|
class="fixed inset-y-0 right-0 w-full sm:max-w-lg luxury-card rounded-l-3xl shadow-2xl overflow-y-auto z-10">
|
|
<div class="p-8">
|
|
<div class="flex justify-between items-center mb-8">
|
|
<div>
|
|
<h3 class="text-xl font-black text-slate-800 dark:text-white font-display" x-text="selectedMachine?.name"></h3>
|
|
<p class="text-xs font-mono font-bold text-slate-500 mt-1" x-text="selectedMachine?.serial_no"></p>
|
|
</div>
|
|
<button @click="showSlotDrawer = false" class="text-slate-400 hover:text-slate-600 transition-colors">
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12" /></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div x-show="loading" class="py-20 text-center text-slate-400 font-bold">{{ __('Loading...') }}</div>
|
|
|
|
<div x-show="!loading" class="space-y-3">
|
|
<template x-for="slot in slots" :key="slot.id">
|
|
<div class="p-4 bg-slate-50 dark:bg-slate-900/50 rounded-xl border border-slate-100 dark:border-slate-800">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-8 h-8 rounded-lg bg-slate-200 dark:bg-slate-800 flex items-center justify-center text-xs font-black text-slate-500" x-text="slot.slot_no"></div>
|
|
<div>
|
|
<p class="font-extrabold text-slate-800 dark:text-slate-100 text-sm" x-text="slot.product?.name || '{{ __('Empty') }}'"></p>
|
|
<p class="text-xs font-bold text-slate-400 mt-0.5" x-show="slot.expiry_date" x-text="'{{ __('Expiry') }}: ' + (slot.expiry_date || '')"></p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<div class="flex items-center gap-1">
|
|
<span class="text-lg font-black" :class="slot.stock <= 0 ? 'text-rose-500' : (slot.stock <= (slot.max_stock * 0.2) ? 'text-amber-500' : 'text-emerald-500')" x-text="slot.stock"></span>
|
|
<span class="text-xs font-bold text-slate-400">/ <span x-text="slot.max_stock"></span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<div x-show="slots.length === 0" class="py-10 text-center text-slate-400 font-bold">{{ __('No slot data') }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endsection
|