1. 於 Order Model 新增收件人姓名遮罩邏輯 (masked_pickup_recipient),將 member_barcode 進行去識別化處理以保護隱私,並保留括號/換行等後綴資訊。 2. 新增 OrderPickupRecipientMaskTest 單元測試,驗證中文字元及包含換行/括號的後綴遮罩正確性。 3. 更新後台銷售管理介面 (order-detail-panel.blade.php、tab-orders.blade.php),將原先顯示明文的 member_barcode 改用遮罩後的屬性展示。 4. 將機器通訊協定與狀態代碼中的「便當 (Bento)」字詞統一調整為「商品 (Product)」,以符合各機型通用語意。 5. 更新相關語系翻譯檔案 (zh_TW.json, en.json, ja.json) 及 MQTT 通訊規範文件中的詞彙對應。 6. 修改 MachineLog 中的翻譯取得邏輯,確保能透過錯誤代碼表對應到最新的商品語意。
308 lines
20 KiB
PHP
308 lines
20 KiB
PHP
{{-- 側滑面板標題 --}}
|
||
<div class="px-5 py-6 sm:px-8 border-b border-slate-200 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/50">
|
||
<div class="flex items-start justify-between gap-4">
|
||
<div class="min-w-0 flex-1">
|
||
<h2 class="text-xl sm:text-2xl font-black text-slate-800 dark:text-white font-display flex items-center gap-2 sm:gap-3">
|
||
<div class="p-2 bg-cyan-500/10 rounded-xl">
|
||
<svg class="w-5 h-5 sm:w-6 sm:h-6 text-cyan-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||
</svg>
|
||
</div>
|
||
<span class="truncate">{{ __('Order Details') }}</span>
|
||
</h2>
|
||
<div class="mt-2 flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400 font-bold uppercase tracking-widest">
|
||
<span class="text-cyan-600 dark:text-cyan-400 font-mono tracking-tighter text-lg">{{ $order->order_no }}</span>
|
||
</div>
|
||
</div>
|
||
<button type="button" @click="showPanel = false"
|
||
class="bg-white dark:bg-slate-800 rounded-full p-2 text-slate-400 hover:text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-700 focus:outline-none transition duration-300 shadow-sm border border-slate-200 dark:border-slate-700">
|
||
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex-1 overflow-y-auto p-6 sm:p-8 space-y-10">
|
||
{{-- 基本狀態卡片 --}}
|
||
<div class="grid grid-cols-3 gap-6 p-6 bg-slate-50/50 dark:bg-slate-800/30 rounded-[2rem] border border-slate-100 dark:border-slate-800">
|
||
<div class="space-y-2">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Payment Status') }}</p>
|
||
<div>
|
||
@php
|
||
$statusMap = [
|
||
'pending' => 'pending',
|
||
'paid' => 'active',
|
||
'completed' => 'active',
|
||
'cancelled' => 'disabled',
|
||
'refunded' => 'error',
|
||
];
|
||
$labelMap = [
|
||
'pending' => __('Pending'),
|
||
'paid' => __('Paid'),
|
||
'completed' => __('Completed'),
|
||
'cancelled' => __('Cancelled'),
|
||
'refunded' => __('Refunded'),
|
||
];
|
||
$status = $statusMap[$order->status] ?? 'slate';
|
||
$label = $labelMap[$order->status] ?? $order->status;
|
||
@endphp
|
||
<x-status-badge :status="$status" :label="$label" size="sm" />
|
||
</div>
|
||
</div>
|
||
<div class="space-y-2">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Payment Type') }}</p>
|
||
<p class="text-sm font-extrabold text-slate-700 dark:text-slate-200">
|
||
{{ $order->payment_type_label }}
|
||
</p>
|
||
</div>
|
||
<div class="space-y-2">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Dispense Status') }}</p>
|
||
<div>
|
||
@php
|
||
$deliveryStatusMap = [
|
||
0 => ['label' => __('Dispense Failed'), 'color' => 'rose'],
|
||
1 => ['label' => __('Dispense Success'), 'color' => 'emerald'],
|
||
2 => ['label' => __('Partial Dispense Success'), 'color' => 'amber'],
|
||
];
|
||
$ds = $deliveryStatusMap[$order->delivery_status] ?? ['label' => __('Unknown'), 'color' => 'slate'];
|
||
@endphp
|
||
<x-status-badge :color="$ds['color']" :label="$ds['label']" size="sm" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- 交易資訊 --}}
|
||
<section class="space-y-4">
|
||
<div class="flex items-center gap-2 px-1">
|
||
<div class="w-1.5 h-4 bg-cyan-500 rounded-full"></div>
|
||
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Transaction Info') }}</h3>
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-8 p-8 bg-slate-50/50 dark:bg-slate-800/30 rounded-[2rem] border border-slate-100 dark:border-slate-800">
|
||
<div class="space-y-1">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Transaction Time') }}</p>
|
||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 font-mono tracking-tight">{{ $order->created_at->format('Y-m-d H:i:s') }}</p>
|
||
</div>
|
||
<div class="space-y-1">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Machine Name') }}</p>
|
||
<p class="text-sm font-extrabold text-slate-700 dark:text-slate-200">{{ $order->machine->name ?? __('Unknown') }}</p>
|
||
</div>
|
||
<div class="space-y-1">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Machine Serial') }}</p>
|
||
<p class="text-xs font-black text-slate-400 dark:text-slate-500 font-mono uppercase tracking-widest">{{ $order->machine->serial_no ?? '---' }}</p>
|
||
</div>
|
||
<div class="space-y-1">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Machine Flow ID') }}</p>
|
||
<p class="text-xs font-black text-slate-400 dark:text-slate-500 font-mono uppercase tracking-widest">{{ $order->flow_id }}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{{-- 員工卡資訊 --}}
|
||
@if($order->payment_type == 41 && $order->staffCardLog && $order->staffCardLog->staffCard)
|
||
<section class="space-y-4">
|
||
<div class="flex items-center gap-2 px-1">
|
||
<div class="w-1.5 h-4 bg-cyan-500 rounded-full"></div>
|
||
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Staff Card Info') }}</h3>
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-8 p-8 bg-slate-50/50 dark:bg-slate-800/30 rounded-[2rem] border border-slate-100 dark:border-slate-800">
|
||
<div class="space-y-1">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Staff Name') }}</p>
|
||
<p class="text-sm font-extrabold text-slate-700 dark:text-slate-200">{{ $order->staffCardLog->staffCard->name }}</p>
|
||
</div>
|
||
<div class="space-y-1">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Employee ID') }}</p>
|
||
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 font-mono tracking-tight">{{ $order->staffCardLog->staffCard->employee_id }}</p>
|
||
</div>
|
||
<div class="space-y-1 col-span-2">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Card UID') }}</p>
|
||
<p class="text-xs font-black text-slate-400 dark:text-slate-500 font-mono uppercase tracking-widest">{{ $order->staffCardLog->staffCard->card_uid }}</p>
|
||
</div>
|
||
@if($order->staffCardLog->staffCard->notes)
|
||
<div class="space-y-1 col-span-2">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Notes') }}</p>
|
||
<p class="text-xs font-bold text-slate-500 dark:text-slate-400">{{ $order->staffCardLog->staffCard->notes }}</p>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
</section>
|
||
@endif
|
||
|
||
@if($order->payment_type == 42 && !empty($order->member_barcode))
|
||
<section class="space-y-4">
|
||
<div class="flex items-center gap-2 px-1">
|
||
<div class="w-1.5 h-4 bg-cyan-500 rounded-full"></div>
|
||
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Pickup Recipient Info') }}</h3>
|
||
</div>
|
||
<div class="grid grid-cols-2 gap-8 p-8 bg-slate-50/50 dark:bg-slate-800/30 rounded-[2rem] border border-slate-100 dark:border-slate-800">
|
||
<div class="space-y-1 col-span-2">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Pickup Recipient (Prescription Slip)') }}</p>
|
||
<p class="text-sm font-extrabold text-slate-700 dark:text-slate-200">{{ $order->masked_pickup_recipient }}</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
@endif
|
||
|
||
{{-- 商品明細 --}}
|
||
<section class="space-y-4">
|
||
<div class="flex items-center justify-between px-1">
|
||
<div class="flex items-center gap-2">
|
||
<div class="w-1.5 h-4 bg-cyan-500 rounded-full"></div>
|
||
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Product Details') }}</h3>
|
||
</div>
|
||
<span class="text-[10px] font-black text-cyan-500 bg-cyan-500/10 px-2 py-0.5 rounded-md uppercase tracking-widest">{{ $order->items->count() }} {{ __('Items') }}</span>
|
||
</div>
|
||
|
||
<div class="space-y-3">
|
||
@foreach($order->items as $item)
|
||
<div class="luxury-card p-4 rounded-2xl flex items-center gap-4 group/item transition-all hover:border-cyan-500/30"
|
||
x-data="{ imgFailed: {{ (isset($item->product->image_url) && $item->product->image_url) ? 'false' : 'true' }} }">
|
||
<div class="w-16 h-16 rounded-xl bg-slate-100 dark:bg-slate-800 flex-shrink-0 overflow-hidden border border-slate-200 dark:border-slate-700 group-hover:scale-105 transition-transform flex items-center justify-center relative">
|
||
@if(isset($item->product->image_url) && $item->product->image_url)
|
||
<img src="{{ $item->product->image_url }}" class="w-full h-full object-cover" x-show="!imgFailed" x-on:error="imgFailed = true">
|
||
@endif
|
||
|
||
<div x-show="imgFailed"
|
||
class="absolute inset-0 flex items-center justify-center bg-slate-50 dark:bg-slate-800/50">
|
||
<svg class="w-8 h-8 text-slate-300 dark:text-slate-600 group-hover/item:text-cyan-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"/>
|
||
</svg>
|
||
</div>
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<p class="text-base font-extrabold text-slate-800 dark:text-white truncate tracking-tight">{{ $item->product_name }}</p>
|
||
<div class="flex items-center gap-3 mt-1">
|
||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-widest">${{ number_format($item->price, 0) }} × {{ $item->quantity }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="text-right">
|
||
<p class="text-base font-black text-slate-800 dark:text-white tracking-tight">${{ number_format($item->subtotal, 0) }}</p>
|
||
</div>
|
||
</div>
|
||
@endforeach
|
||
</div>
|
||
|
||
{{-- 金額彙總 --}}
|
||
<div class="mt-6 p-8 rounded-[2rem] bg-slate-50/50 dark:bg-slate-800/30 border border-slate-100 dark:border-slate-800 relative overflow-hidden group">
|
||
<!-- Decorative Gradient -->
|
||
<div class="absolute top-0 right-0 w-32 h-32 bg-cyan-500/10 blur-[50px] -mr-16 -mt-16 group-hover:bg-cyan-500/20 transition-colors duration-500"></div>
|
||
|
||
<div class="relative space-y-4">
|
||
<div class="flex justify-between items-center text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em]">
|
||
<span>{{ __('Subtotal') }}</span>
|
||
<span class="font-mono text-slate-600 dark:text-slate-300">${{ number_format($order->total_amount + $order->discount_amount, 0) }}</span>
|
||
</div>
|
||
<div class="flex justify-between items-center text-[10px] font-black text-rose-500 uppercase tracking-[0.2em]">
|
||
<span>{{ __('Discount') }}</span>
|
||
<span class="font-mono">-${{ number_format($order->discount_amount, 0) }}</span>
|
||
</div>
|
||
<div class="pt-4 mt-2 border-t border-slate-100 dark:border-slate-800/50 flex justify-between items-baseline">
|
||
<span class="text-sm font-black uppercase tracking-[0.2em] text-slate-500 dark:text-slate-400">{{ __('Total') }}</span>
|
||
<div class="text-right">
|
||
<span class="text-3xl font-black text-cyan-600 dark:text-cyan-400 tracking-tighter font-display">${{ number_format($order->total_amount, 0) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{{-- 出貨紀錄 --}}
|
||
@if($order->dispenseRecords->count() > 0)
|
||
<section class="space-y-4">
|
||
<div class="flex items-center justify-between px-1">
|
||
<div class="flex items-center gap-2">
|
||
<div class="w-1.5 h-4 bg-emerald-500 rounded-full"></div>
|
||
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Dispense Status') }}</h3>
|
||
</div>
|
||
</div>
|
||
<div class="bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 rounded-[2rem] overflow-hidden shadow-sm">
|
||
<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-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Slot') }}</th>
|
||
<th class="px-6 py-4 text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Product') }}</th>
|
||
<th class="px-6 py-4 text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Status') }}</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||
@foreach($order->dispenseRecords as $dispense)
|
||
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200">
|
||
<td class="px-6 py-4">
|
||
<span class="text-xs font-black text-cyan-600 dark:text-cyan-400 font-mono">#{{ str_pad($dispense->slot_no, 2, '0', STR_PAD_LEFT) }}</span>
|
||
</td>
|
||
<td class="px-6 py-4">
|
||
<p class="text-sm font-bold text-slate-700 dark:text-slate-200">{{ $dispense->product->localized_name ?? __('Unknown Product') }}</p>
|
||
</td>
|
||
<td class="px-6 py-4 text-right">
|
||
@php
|
||
$dStatusMap = [
|
||
'success' => 'active',
|
||
'failed' => 'error',
|
||
'pending' => 'pending',
|
||
'1' => 'active',
|
||
'0' => 'error',
|
||
];
|
||
$dLabelMap = [
|
||
'success' => __('Success'),
|
||
'failed' => __('Failed'),
|
||
'pending' => __('Pending'),
|
||
'1' => __('Success'),
|
||
'0' => __('Failed'),
|
||
];
|
||
$statusValue = (string)($dispense->dispense_status ?? 'pending');
|
||
$ds = $dStatusMap[$statusValue] ?? 'slate';
|
||
$dl = $dLabelMap[$statusValue] ?? $statusValue;
|
||
@endphp
|
||
<x-status-badge :status="$ds" :label="$dl" size="xs" />
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
@endif
|
||
|
||
{{-- 發票資訊 --}}
|
||
@if($order->invoice)
|
||
<section class="space-y-4">
|
||
<div class="flex items-center gap-2 px-1">
|
||
<div class="w-1.5 h-4 bg-indigo-500 rounded-full"></div>
|
||
<h3 class="text-sm font-black text-slate-800 dark:text-white uppercase tracking-widest">{{ __('Invoice Information') }}</h3>
|
||
</div>
|
||
<div class="bg-indigo-50/30 dark:bg-indigo-500/5 border border-indigo-100 dark:border-indigo-500/20 rounded-[2rem] p-8 group relative overflow-hidden">
|
||
<!-- Decorative Accent -->
|
||
<div class="absolute top-0 right-0 p-8 opacity-10 group-hover:opacity-20 transition-opacity duration-500">
|
||
<svg class="w-24 h-24 text-indigo-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||
</svg>
|
||
</div>
|
||
|
||
<div class="relative flex flex-col sm:flex-row sm:items-center justify-between gap-6">
|
||
<div class="space-y-3">
|
||
<div class="flex items-center gap-3">
|
||
<p class="text-xl font-black text-slate-800 dark:text-white font-mono tracking-tighter">{{ $order->invoice->invoice_no }}</p>
|
||
<x-status-badge status="active" :label="__('Valid')" size="xs" />
|
||
</div>
|
||
<div class="flex flex-col gap-1">
|
||
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('Issued At') }}</p>
|
||
<p class="text-sm font-bold text-slate-600 dark:text-slate-400 font-mono tracking-tight">
|
||
{{ $order->invoice->invoice_date }} {{ $order->invoice->invoice_time }}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex items-center gap-3">
|
||
<button class="flex-1 sm:flex-none px-6 py-3 rounded-xl bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-bold text-xs border border-slate-200 dark:border-slate-700 hover:bg-indigo-500 hover:text-white hover:border-indigo-500 transition-all duration-300 shadow-sm">
|
||
{{ __('Print Invoice') }}
|
||
</button>
|
||
<button class="flex-1 sm:flex-none px-6 py-3 rounded-xl bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-bold text-xs border border-slate-200 dark:border-slate-700 hover:bg-rose-500 hover:text-white hover:border-rose-500 transition-all duration-300 shadow-sm">
|
||
{{ __('Void Invoice') }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
@endif
|
||
</div>
|