star-cloud/resources/views/admin/sales/pharmacy-pickup/index.blade.php
twsystem1004 f8c6da3acc [FIX] 領藥單建立頁批價單號欄位攔截 Enter
1. 批價單號 input 加上 @keydown.enter.prevent,避免掃碼器掃完批價單 QR 後自動送出的 Enter 誤觸「建立並產生 QR」、跳出「請至少勾選一項藥品並填寫數量」提示。
2. 單號仍正常自動帶入,攔截僅作用於此欄位,不影響其他欄位與按鈕送出。
2026-06-24 03:11:19 +00:00

460 lines
35 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.admin')
@section('content')
@php
$statusLabels = [
'awaiting_pickup' => __('Awaiting Pickup'),
'completed' => __('Picked Up'),
'failed' => __('Voided'),
'pending' => __('Pending'),
'abandoned' => __('Abandoned'),
];
$statusColors = [
'awaiting_pickup' => 'bg-amber-500/10 border border-amber-500/20 text-amber-600 dark:text-amber-400',
'completed' => 'bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 dark:text-emerald-400',
'failed' => 'bg-rose-500/10 border border-rose-500/20 text-rose-600 dark:text-rose-400',
'pending' => 'bg-slate-500/10 border border-slate-500/20 text-slate-500',
'abandoned' => 'bg-slate-500/10 border border-slate-500/20 text-slate-500',
];
@endphp
<div class="space-y-4 pb-20" x-data="pharmacyPickupManager()">
{{-- Page Header --}}
<x-page-header
:title="__('menu.sales.pharmacy-pickup')"
:subtitle="__('Issue a pharmacy pickup order, print the QR ticket, patient scans at the machine to dispense.')">
@if($machines->isNotEmpty())
<button @click="openCreateModal()"
class="btn-luxury-primary whitespace-nowrap flex items-center gap-2 transition-all duration-300">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14M5 12h14" />
</svg>
<span class="text-sm sm:text-base">{{ __('Create Pharmacy Pickup Order') }}</span>
</button>
@endif
</x-page-header>
{{-- Flash / Errors --}}
@if(session('success'))
<div class="p-5 bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 dark:text-emerald-400 rounded-2xl flex items-center gap-3 font-bold text-sm animate-luxury-in">
<svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="p-5 bg-rose-500/10 border border-rose-500/20 text-rose-600 dark:text-rose-400 rounded-2xl flex items-center gap-3 font-bold text-sm animate-luxury-in">
<svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01M5.07 19h13.86a2 2 0 001.74-3L13.74 4a2 2 0 00-3.48 0L3.33 16a2 2 0 001.74 3z"/></svg>
{{ session('error') }}
</div>
@endif
@if($errors->any())
<div class="p-5 bg-rose-500/10 border border-rose-500/20 text-rose-600 dark:text-rose-400 rounded-2xl text-sm animate-luxury-in">
<ul class="list-disc ps-5 space-y-1 font-bold">
@foreach($errors->all() as $err)<li>{{ $err }}</li>@endforeach
</ul>
</div>
@endif
@if($machines->isEmpty())
<div class="p-5 bg-amber-500/10 border border-amber-500/20 text-amber-600 dark:text-amber-400 rounded-2xl flex items-start gap-4 font-bold text-sm">
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01M5.07 19h13.86a2 2 0 001.74-3L13.74 4a2 2 0 00-3.48 0L3.33 16a2 2 0 001.74 3z"/></svg>
{{ __('No machines enabled for pharmacy pickup. Enable it under Machine System Settings (Pickup Sheet mode → Pharmacy Pickup switch).') }}
</div>
@endif
{{-- 領藥單列表 --}}
<div class="luxury-card rounded-3xl p-8 animate-luxury-in relative overflow-hidden">
{{-- Desktop Table --}}
<div class="hidden xl:block overflow-x-auto">
<table class="w-full text-left border-separate border-spacing-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 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Order No.') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 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 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Items') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Pickup Code') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Status') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Created') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 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-100 dark:divide-slate-800/80">
@forelse($orders as $order)
<tr class="group hover:bg-slate-50/50 dark:hover:bg-white/[0.02] transition-colors duration-200 {{ session('created_order_id') == $order->id ? 'bg-emerald-50/60 dark:bg-emerald-500/5' : '' }}">
<td class="px-6 py-6 whitespace-nowrap">
<div class="text-base font-black font-mono text-slate-800 dark:text-slate-100 tracking-tight">{{ $order->order_no }}</div>
@if($order->pricing_slip_no)
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] mt-0.5">{{ __('Pricing Slip No.') }}: {{ $order->pricing_slip_no }}</div>
@endif
</td>
<td class="px-6 py-6">
<div class="text-base font-extrabold text-slate-800 dark:text-slate-100 tracking-tight">{{ $order->machine?->name ?? '-' }}</div>
<div class="text-[10px] font-mono font-bold text-slate-400 uppercase tracking-[0.2em]">{{ $order->machine?->serial_no }}</div>
</td>
<td class="px-6 py-6">
<div class="space-y-0.5">
@foreach($order->items as $it)
<div class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ $it->product_name }} <span class="text-cyan-600 dark:text-cyan-400 font-black">× {{ $it->quantity }}</span></div>
@endforeach
</div>
</td>
<td class="px-6 py-6 text-center whitespace-nowrap">
@if($order->pickupCode?->code)
<span class="text-base font-black font-mono text-cyan-600 dark:text-cyan-400 tracking-widest bg-cyan-50 dark:bg-cyan-500/10 px-3 py-1.5 rounded-xl border border-cyan-100 dark:border-cyan-500/20">{{ $order->pickupCode->code }}</span>
@else
<span class="text-slate-300 dark:text-slate-600">-</span>
@endif
</td>
<td class="px-6 py-6 text-center whitespace-nowrap">
<span class="inline-block px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest {{ $statusColors[$order->status] ?? 'bg-slate-500/10 border border-slate-500/20 text-slate-500' }}">{{ $statusLabels[$order->status] ?? $order->status }}</span>
</td>
<td class="px-6 py-6 whitespace-nowrap">
<div class="text-sm font-black text-slate-600 dark:text-slate-300 font-mono">{{ $order->created_at?->format('Y-m-d H:i') }}</div>
<div class="text-[10px] font-bold text-slate-400 uppercase tracking-widest mt-0.5">{{ $order->creator?->name }}</div>
</td>
<td class="px-6 py-6 whitespace-nowrap text-right">
<div class="flex items-center justify-end gap-2">
@if($order->status !== 'failed' && $order->pickupCode)
<a href="{{ route('admin.sales.pharmacy-pickup.print', $order) }}" target="_blank"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all"
title="{{ __('Print') }}">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0110.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0l.229 2.523a1.125 1.125 0 01-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0021 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 00-1.913-.247M6.34 18H5.25A2.25 2.25 0 013 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 011.913-.247m10.5 0a48.536 48.536 0 00-10.5 0m10.5 0V3.375c0-.621-.504-1.125-1.125-1.125h-8.25c-.621 0-1.125.504-1.125 1.125v3.659M18 10.5h.008v.008H18V10.5zm-3 0h.008v.008H15V10.5z"/></svg>
</a>
@endif
@if($order->status === 'awaiting_pickup')
<form id="pp-cancel-desktop-{{ $order->id }}" method="POST" action="{{ route('admin.sales.pharmacy-pickup.cancel', $order) }}">
@csrf
<button type="button" @click="confirmCancel('pp-cancel-desktop-{{ $order->id }}')"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 hover:bg-rose-500/5 border border-transparent hover:border-rose-500/20 transition-all"
title="{{ __('Cancel') }}">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-3.38a2.25 2.25 0 00-2.25-2.25h-3.51a2.25 2.25 0 00-2.25 2.25v3.38"/></svg>
</button>
</form>
@endif
@if($order->status !== 'awaiting_pickup' && !($order->status !== 'failed' && $order->pickupCode))
<span class="text-slate-300 dark:text-slate-600 pr-2">-</span>
@endif
</div>
</td>
</tr>
@empty
<x-empty-state mode="table" :colspan="7" :message="__('No pharmacy pickup orders yet.')" />
@endforelse
</tbody>
</table>
</div>
{{-- Mobile Card Grid --}}
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
@forelse($orders as $order)
<div class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group {{ session('created_order_id') == $order->id ? 'ring-2 ring-emerald-500/30' : '' }}">
{{-- Header --}}
<div class="flex items-start justify-between gap-4 mb-6">
<div class="flex items-center gap-4 min-w-0">
<div class="w-14 h-14 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 shadow-sm shrink-0">
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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="min-w-0">
<h3 class="text-base font-extrabold text-slate-800 dark:text-slate-100 truncate tracking-tight font-mono">{{ $order->order_no }}</h3>
<p class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">{{ $order->machine?->name ?? '-' }}</p>
</div>
</div>
<span class="inline-block px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest shrink-0 {{ $statusColors[$order->status] ?? 'bg-slate-500/10 border border-slate-500/20 text-slate-500' }}">{{ $statusLabels[$order->status] ?? $order->status }}</span>
</div>
{{-- Info Grid --}}
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Pickup Code') }}</p>
<p class="text-sm font-black font-mono text-cyan-600 dark:text-cyan-400 tracking-widest">{{ $order->pickupCode?->code ?? '-' }}</p>
</div>
<div>
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Pricing Slip No.') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ $order->pricing_slip_no ?: '-' }}</p>
</div>
<div class="col-span-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Items') }}</p>
<div class="space-y-0.5">
@foreach($order->items as $it)
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ $it->product_name }} <span class="text-cyan-600 dark:text-cyan-400 font-black">× {{ $it->quantity }}</span></p>
@endforeach
</div>
</div>
<div class="col-span-2">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Created') }}</p>
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 font-mono">{{ $order->created_at?->format('Y-m-d H:i') }} <span class="text-slate-400 font-sans">· {{ $order->creator?->name }}</span></p>
</div>
</div>
{{-- Actions --}}
@if(($order->status !== 'failed' && $order->pickupCode) || $order->status === 'awaiting_pickup')
<div class="flex items-center gap-3">
@if($order->status !== 'failed' && $order->pickupCode)
<a href="{{ route('admin.sales.pharmacy-pickup.print', $order) }}" target="_blank"
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-black text-xs uppercase tracking-widest border border-slate-100 dark:border-slate-800 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all duration-300">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0110.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0l.229 2.523a1.125 1.125 0 01-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0021 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 00-1.913-.247M6.34 18H5.25A2.25 2.25 0 013 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 011.913-.247"/></svg>
{{ __('Print') }}
</a>
@endif
@if($order->status === 'awaiting_pickup')
<form id="pp-cancel-mobile-{{ $order->id }}" method="POST" action="{{ route('admin.sales.pharmacy-pickup.cancel', $order) }}" class="flex-1">
@csrf
<button type="button" @click="confirmCancel('pp-cancel-mobile-{{ $order->id }}')"
class="w-full flex items-center justify-center gap-2 py-3 rounded-xl bg-rose-500/5 text-rose-500 text-xs font-black uppercase tracking-widest border border-rose-500/20 hover:bg-rose-500 hover:text-white transition-all duration-300">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79"/></svg>
{{ __('Cancel') }}
</button>
</form>
@endif
</div>
@endif
</div>
@empty
<div class="col-span-full">
<x-empty-state :message="__('No pharmacy pickup orders yet.')" />
</div>
@endforelse
</div>
{{-- Pagination --}}
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
{{ $orders->appends(request()->query())->links('vendor.pagination.luxury') }}
</div>
</div>
{{-- Create Pharmacy Pickup Order Modal --}}
<div x-show="showCreateModal" class="fixed inset-0 z-50 overflow-y-auto"
x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" x-cloak style="display:none;">
<div class="flex items-center justify-center min-h-screen px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity bg-slate-900/60 backdrop-blur-sm" @click="showCreateModal = false"></div>
<div x-show="showCreateModal" x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:scale-95"
class="inline-block px-8 py-10 text-left align-bottom transition-all transform luxury-card rounded-3xl dark:bg-slate-900 border-slate-200/50 dark:border-slate-700/50 shadow-2xl sm:my-8 sm:align-middle sm:max-w-3xl sm:w-full overflow-visible">
<div class="flex justify-between items-center mb-8">
<h3 class="text-2xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ __('Create Pharmacy Pickup Order') }}</h3>
<button @click="showCreateModal = false" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 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>
<form action="{{ route('admin.sales.pharmacy-pickup.store') }}" method="POST" class="space-y-6"
@submit.prevent="validateAndSubmit" x-ref="createForm">
@csrf
<input type="hidden" name="machine_id" :value="selectedMachine">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
{{-- Machine --}}
<div class="space-y-2 relative focus-within:z-[60]">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Select Machine') }} <span class="text-rose-500">*</span></label>
<x-searchable-select name="machine_select" id="pp-machine"
placeholder="{{ __('-- Select Machine --') }}" x-model="selectedMachine"
@change="selectedMachine = $event.target.value; fetchProducts()">
@foreach($machines as $m)
<option value="{{ $m->id }}" data-title="{{ $m->name }} ({{ $m->serial_no }})">{{ $m->name }} ({{ $m->serial_no }})</option>
@endforeach
</x-searchable-select>
</div>
{{-- Pricing Slip No. --}}
<div class="space-y-2">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Pricing Slip No.') }} <span class="text-slate-300 normal-case font-bold">({{ __('optional') }})</span></label>
<input type="text" name="pricing_slip_no" x-model="pricingSlipNo" maxlength="64"
@keydown.enter.prevent
class="luxury-input w-full py-3 px-4 text-sm" placeholder="{{ __('e.g. the hospital pricing slip number') }}">
</div>
</div>
{{-- Medicine selection --}}
<div class="space-y-3">
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Medicine') }} <span class="text-rose-500">*</span></label>
{{-- Empty: no machine selected --}}
<div x-show="!selectedMachine" class="rounded-[2rem] border border-dashed border-slate-200 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-900/30 p-10 text-center">
<p class="text-sm font-bold text-slate-400">{{ __('Please select a machine') }}</p>
</div>
{{-- Loading --}}
<div x-show="selectedMachine && loadingProducts" class="rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/30 p-10 flex items-center justify-center">
<x-luxury-spinner show="loadingProducts" />
</div>
{{-- No products --}}
<div x-show="selectedMachine && !loadingProducts && products.length === 0" class="rounded-[2rem] border border-amber-500/20 bg-amber-500/5 p-8 text-center">
<p class="text-sm font-bold text-amber-600 dark:text-amber-400">{{ __('This machine currently has no medicine in stock.') }}</p>
</div>
{{-- Product table --}}
<div x-show="selectedMachine && !loadingProducts && products.length > 0"
class="overflow-hidden rounded-[2rem] border border-slate-100 dark:border-slate-800">
<table class="w-full text-sm">
<thead class="bg-slate-50/70 dark:bg-slate-900/40">
<tr>
<th class="py-3 px-4 text-left w-10"></th>
<th class="py-3 px-4 text-left text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Medicine') }}</th>
<th class="py-3 px-4 text-left text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Spec') }}</th>
<th class="py-3 px-4 text-center text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Available') }}</th>
<th class="py-3 px-4 text-center text-[10px] font-black text-slate-400 uppercase tracking-widest w-40">{{ __('Quantity') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100 dark:divide-slate-800/80">
<template x-for="p in products" :key="p.product_id">
<tr class="transition-colors duration-200"
:class="p.checked ? 'bg-cyan-50/40 dark:bg-cyan-500/5' : 'hover:bg-slate-50/50 dark:hover:bg-white/[0.02]'">
<td class="py-3 px-4 text-center">
<input type="checkbox" x-model="p.checked"
class="w-4 h-4 text-cyan-500 rounded border-slate-300 dark:border-slate-600 focus:ring-cyan-500/30 cursor-pointer">
</td>
<td class="py-3 px-4 font-bold text-slate-700 dark:text-slate-200" x-text="p.name"></td>
<td class="py-3 px-4 text-slate-500" x-text="p.spec || '-'"></td>
<td class="py-3 px-4 text-center">
<span class="font-black text-slate-700 dark:text-slate-200" x-text="p.available"></span>
<template x-if="p.reserved > 0">
<span class="block text-[10px] font-bold text-slate-400" x-text="'({{ __('physical') }} ' + p.physical + ' {{ __('reserved') }} ' + p.reserved + ')'"></span>
</template>
</td>
<td class="py-3 px-4">
{{-- Luxury +/- counter --}}
<div class="flex items-center h-10 mx-auto rounded-xl border border-slate-200/70 dark:border-slate-700/50 bg-white dark:bg-slate-900/50 overflow-hidden w-[140px] transition-all"
:class="{ 'opacity-40 pointer-events-none': !p.checked }">
<button type="button" @click="p.qty > 1 ? p.qty-- : 1"
class="shrink-0 w-10 h-full flex items-center justify-center text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 active:scale-90 transition-all">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M20 12H4"/></svg>
</button>
<div class="h-5 w-px bg-slate-200 dark:bg-slate-700/50"></div>
<input type="text" x-model.number="p.qty" readonly
class="flex-1 w-full min-w-0 bg-transparent border-none text-center text-base font-black text-slate-800 dark:text-white focus:ring-0 p-0">
<div class="h-5 w-px bg-slate-200 dark:bg-slate-700/50"></div>
<button type="button" @click="p.qty < p.available ? p.qty++ : p.available"
class="shrink-0 w-10 h-full flex items-center justify-center text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 active:scale-90 transition-all">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 5v14M5 12h14"/></svg>
</button>
</div>
{{-- Submitted only when checked (empty name = skipped) --}}
<input type="hidden" :name="p.checked ? 'products[]' : ''" :value="p.product_id">
<input type="hidden" :name="p.checked ? `qty[${p.product_id}]` : ''" :value="p.qty">
</td>
</tr>
</template>
</tbody>
</table>
</div>
<p class="text-[10px] font-bold text-slate-400 pl-1">{{ __('(deducted by issued pickup orders)') }}</p>
</div>
<div class="flex justify-end gap-x-4 pt-4">
<button type="button" @click="showCreateModal = false" class="btn-luxury-ghost px-8">{{ __('Cancel') }}</button>
<button type="submit" class="btn-luxury-primary px-12 flex items-center gap-2">
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4"/></svg>
{{ __('Create & Generate QR') }}
</button>
</div>
</form>
</div>
</div>
</div>
{{-- Confirm Cancel Modal --}}
<x-confirm-modal alpineVar="showCancelModal" :title="__('Cancel Pickup Code')"
:message="__('Cancel this pharmacy pickup order?')"
:confirmText="__('Yes, Cancel')" confirmAction="submitCancel()" confirmColor="rose" iconType="danger" />
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('pharmacyPickupManager', () => ({
showCreateModal: false,
selectedMachine: '',
pricingSlipNo: '',
products: [],
loadingProducts: false,
showCancelModal: false,
cancelTargetForm: null,
openCreateModal() {
this.selectedMachine = '';
this.pricingSlipNo = '';
this.products = [];
this.showCreateModal = true;
this.syncSelect('pp-machine', ' ');
},
async fetchProducts() {
if (!this.selectedMachine) {
this.products = [];
return;
}
this.loadingProducts = true;
try {
const res = await fetch(`/admin/sales/pharmacy-pickup/${this.selectedMachine}/products-ajax`, {
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }
});
const data = await res.json();
this.products = (data.products || []).map(p => ({
...p,
checked: false,
qty: 1,
}));
} catch (e) {
console.error('Error fetching products:', e);
window.showToast?.('{{ __("Loading failed") }}', 'error');
this.products = [];
} finally {
this.loadingProducts = false;
}
},
validateAndSubmit() {
if (!this.selectedMachine || this.selectedMachine.toString().trim() === '') {
window.showToast?.('{{ __("Please select a machine") }}', 'error');
return;
}
if (!this.products.some(p => p.checked)) {
window.showToast?.('{{ __("Please select at least one medicine with quantity.") }}', 'error');
return;
}
this.$refs.createForm.submit();
},
confirmCancel(formId) {
this.cancelTargetForm = formId;
this.showCancelModal = true;
},
submitCancel() {
if (this.cancelTargetForm) {
const form = document.getElementById(this.cancelTargetForm);
if (form) form.submit();
}
this.showCancelModal = false;
},
syncSelect(id, value) {
this.$nextTick(() => {
const el = document.getElementById(id);
if (el) {
const valStr = (value !== undefined && value !== null && value.toString().trim() !== '') ? value.toString() : ' ';
el.value = valStr;
window.HSSelect?.getInstance(el)?.setValue(valStr);
}
});
},
init() {
this.$watch('selectedMachine', (val) => {
this.syncSelect('pp-machine', val);
});
}
}));
});
</script>
@endsection