Files
HickmeisterandClaude Sonnet 5.5 04be9802dc
Deploy HickLab3D Dashboard / Deploy to production (push) Successful in 27s
Style label downloads as buttons
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 20:42:57 +01:00

42 lines
14 KiB
PHP
Raw Permalink 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.app')
@section('title', 'Shipping')
@section('content')
<div><p class="text-xs font-bold uppercase tracking-[.2em] text-brand">Dispatch</p><h2 class="mt-2 text-2xl font-bold">Orders and shipping</h2><p class="mt-1 text-slate-400">Prepare pending Etsy orders and follow Parcel2Go shipments.</p></div>
@isset($integrationError)<div role="alert" class="rounded-xl border border-amber-500/30 bg-amber-500/10 p-4 text-sm text-amber-200">{{ $integrationError }}</div>@endisset
@if($etsyNotice)<div role="status" class="rounded-xl border border-amber-500/30 bg-amber-500/10 p-4 text-sm text-amber-200">{{ $etsyNotice }} <a href="{{ route('settings.index') }}" class="font-semibold underline">Settings</a></div>@endif
@if($profileNotice)<div role="status" class="rounded-xl border border-slate-700 bg-slate-900 p-4 text-sm text-slate-300">{{ $profileNotice }} See the access steps in <a href="{{ route('settings.index') }}" class="font-semibold text-brand underline">Settings</a>.</div>@endif
@if($bookingPending)<section role="alert" class="rounded-2xl border border-amber-500/40 bg-amber-500/10 p-6"><h3 class="font-semibold text-amber-200">Check Parcel2Go before retrying</h3><p class="mt-2 text-sm text-amber-100/80">The last booking request had no confirmed result. Check your account for the collection order, then reset this form.</p><form method="post" action="{{ route('shipping.reset-booking') }}" class="mt-4 flex flex-wrap items-center gap-3">@csrf<label class="text-sm"><input type="checkbox" name="checked_account" value="1" required> I checked the Parcel2Go account</label><button class="rounded-xl border border-amber-500/50 px-4 py-2 text-sm font-semibold text-amber-200">Reset booking form</button></form></section>@endif
@if($bookingResult)
@php $paymentUrl = $bookingResult['Links']['payment'] ?? null; $paymentHost = is_string($paymentUrl) ? parse_url($paymentUrl, PHP_URL_HOST) : null; @endphp
<section class="rounded-2xl border border-brand/40 bg-brand/10 p-6"><h3 class="text-lg font-semibold">Collection order #{{ $bookingResult['OrderId'] ?? '—' }} created</h3><p class="mt-2 text-sm text-slate-300">Total: £{{ number_format((float) ($bookingResult['TotalPrice'] ?? 0), 2) }}. Payment and collection confirmation are completed with Parcel2Go.</p>@if($paymentHost && ($paymentHost === 'parcel2go.com' || str_ends_with($paymentHost, '.parcel2go.com')))<a href="{{ $paymentUrl }}" target="_blank" rel="noopener noreferrer" class="mt-4 inline-flex rounded-xl bg-brand px-5 py-3 font-semibold text-slate-950">Complete payment ↗</a>@endif</section>
@endif
@if($account || $balance)<div class="grid gap-4 md:grid-cols-2"><div class="rounded-2xl border border-slate-800 bg-slate-900 p-6"><p class="text-sm text-slate-400">Account</p><p class="mt-4 text-xl font-semibold">{{ trim(($account['Forename'] ?? '').' '.($account['Surname'] ?? '')) ?: 'Unavailable' }}</p><p class="mt-2 text-sm text-slate-400">{{ $account['Email'] ?? '' }}</p></div><div class="rounded-2xl border border-slate-800 bg-slate-900 p-6"><p class="text-sm text-slate-400">Prepay balance</p><p class="mt-4 text-3xl font-bold">{{ isset($balance['Balance']) ? '£'.number_format((float) $balance['Balance'], 2) : 'Unavailable' }}</p></div></div>@endif
<section class="rounded-2xl border border-slate-800 bg-slate-900 p-6">
<div class="flex flex-wrap items-start justify-between gap-3"><div><h3 class="text-lg font-semibold">Pending Etsy orders</h3><p class="mt-1 text-sm text-slate-400">Quotes use the Etsy delivery postcode and order value. Enter the packed parcel size and weight.</p></div><span class="rounded-full bg-brand/10 px-3 py-1 text-sm font-semibold text-brand">{{ count($pendingOrders) }} pending</span></div>
@if(blank($collectionPostcode))<p class="mt-4 rounded-xl border border-amber-500/30 bg-amber-500/10 p-4 text-sm text-amber-200">Set your collection postcode once in <a class="font-semibold underline" href="{{ route('settings.index') }}">Parcel2Go settings</a> to quote these orders.</p>@endif
<div class="mt-5 grid gap-4">
@forelse($pendingOrders as $receipt)
@php $canQuote = filled($collectionPostcode) && filled($receipt['zip'] ?? null) && ($receipt['country_iso'] ?? 'GB') === 'GB' && ($receipt['grandtotal']['currency_code'] ?? 'GBP') === 'GBP'; @endphp
<article class="rounded-xl border border-slate-700 bg-slate-950 p-4">
<div class="flex flex-wrap items-start justify-between gap-3"><div><p class="font-semibold">Order #{{ $receipt['receipt_id'] ?? '—' }}</p><p class="mt-1 text-sm text-slate-300">{{ $receipt['name'] ?? 'Buyer' }} · {{ $receipt['city'] ?? 'City unavailable' }} · {{ $receipt['zip'] ?? 'Postcode unavailable' }}</p></div>@if(isset($receipt['grandtotal']['amount']))<p class="text-sm font-semibold text-brand">{{ $receipt['grandtotal']['currency_code'] ?? 'GBP' }} {{ number_format((float) $receipt['grandtotal']['amount'] / max(1, (int) ($receipt['grandtotal']['divisor'] ?? 1)), 2) }}</p>@endif</div>
@if($canQuote)
<form method="post" action="{{ route('shipping.quote') }}" class="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-5">@csrf<input type="hidden" name="receipt_id" value="{{ $receipt['receipt_id'] }}">
@foreach([['weight','Weight (kg)','0.1'],['length','Length (cm)','1'],['width','Width (cm)','1'],['height','Height (cm)','1']] as [$field,$label,$step])<label class="grid gap-1 text-xs text-slate-400">{{ $label }}<input type="number" name="{{ $field }}" value="{{ old($field) }}" min="0.01" step="{{ $step }}" required class="rounded-lg border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-white"></label>@endforeach
<div class="flex items-end"><button class="w-full rounded-lg bg-brand px-4 py-2 text-sm font-semibold text-slate-950">Quote this order</button></div>
</form>
@else<p class="mt-3 text-sm text-amber-200">{{ blank($collectionPostcode) ? 'Add a collection postcode in Settings.' : 'A UK delivery postcode and GBP order value are needed from Etsy before this order can be quoted.' }}</p>@endif
</article>
@empty<p class="rounded-xl border border-slate-700 bg-slate-950 p-5 text-sm text-slate-400">No paid, unshipped Etsy orders to prepare.</p>@endforelse
</div>
</section>@if(count($quotes) && $selectedReceipt)<section class="rounded-2xl border border-slate-800 bg-slate-900 p-6"><h3 class="text-lg font-semibold">Collection services for Etsy order #{{ $selectedReceipt['receipt_id'] }}</h3><div class="mt-4 grid gap-3 sm:grid-cols-2 xl:grid-cols-3">@foreach($quotes as $quote)@php $service = $quote['Service'] ?? []; @endphp<article class="rounded-xl border border-slate-700 bg-slate-950 p-4"><p class="font-semibold">{{ $service['Name'] ?? 'Courier service' }}</p><p class="mt-1 text-sm text-slate-400">{{ $service['CourierName'] ?? '' }} · {{ $service['CollectionType'] ?? 'Unknown' }}</p><p class="mt-3 text-2xl font-bold text-brand">{{ isset($quote['TotalPrice']) ? '£'.number_format((float) $quote['TotalPrice'], 2) : 'See details' }}</p>@if(($service['CollectionType'] ?? null) === 'Collection' && ! empty($service['Slug']))<form method="post" action="{{ route('shipping.collection-dates') }}" class="mt-4 grid gap-3">@csrf<input type="hidden" name="service" value="{{ $service['Slug'] }}"><label class="grid gap-1 text-xs text-slate-400">Collection street<input name="sender_street" value="{{ old('sender_street') }}" required class="rounded-lg border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-white"></label><label class="grid gap-1 text-xs text-slate-400">Collection town<input name="sender_town" value="{{ old('sender_town') }}" required class="rounded-lg border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-white"></label><button class="rounded-lg border border-brand/40 px-3 py-2 text-sm font-semibold text-brand">Check collection dates</button></form>@endif</article>@endforeach</div></section>@endif
@if(count($collectionDates) && $selectedService && $selectedReceipt)<section class="rounded-2xl border border-brand/30 bg-slate-900 p-6"><h3 class="text-lg font-semibold">Arrange collection for Etsy order #{{ $selectedReceipt['receipt_id'] }}</h3><p class="mt-1 text-sm text-slate-400">Creating the order does not take payment. Parcel2Go will provide a payment link.</p><form method="post" action="{{ route('shipping.book-collection') }}" class="mt-5 grid gap-5">@csrf<label class="grid max-w-md gap-2 text-sm">Collection date<select name="collection_date" required class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3"><option value="">Choose a date</option>@foreach($collectionDates as $date)<option value="{{ $date['CollectionDate'] ?? '' }}">{{ isset($date['CollectionDate']) ? \Illuminate\Support\Carbon::parse($date['CollectionDate'])->format('D d M Y') : 'Available date' }}{{ ! empty($date['Surcharge']) ? ' · £'.number_format((float) $date['Surcharge'], 2).' surcharge' : '' }}</option>@endforeach</select></label>
<div class="grid gap-5 lg:grid-cols-2"><fieldset class="grid gap-3 rounded-xl border border-slate-700 p-4"><legend class="px-2 font-semibold">Sender and collection</legend>@foreach([['sender_name','Name'],['sender_email','Email'],['sender_phone','Phone'],['sender_property','Property'],['sender_street','Street'],['sender_town','Town']] as [$field,$label])<label class="grid gap-1 text-sm">{{ $label }}<input name="{{ $field }}" value="{{ old($field, session('shipping_sender_location.'.$field)) }}" required class="rounded-lg border border-slate-700 bg-slate-950 px-3 py-2"></label>@endforeach</fieldset><fieldset class="grid gap-3 rounded-xl border border-slate-700 p-4"><legend class="px-2 font-semibold">Recipient and delivery</legend>@foreach([['recipient_name','Name'],['recipient_email','Email'],['recipient_phone','Phone'],['recipient_property','Property'],['recipient_street','Street'],['recipient_town','Town']] as [$field,$label])<label class="grid gap-1 text-sm">{{ $label }}<input name="{{ $field }}" value="{{ old($field, ['recipient_name' => $selectedReceipt['name'] ?? '', 'recipient_email' => $selectedReceipt['buyer_email'] ?? '', 'recipient_property' => $selectedReceipt['first_line'] ?? '', 'recipient_street' => $selectedReceipt['second_line'] ?? '', 'recipient_town' => $selectedReceipt['city'] ?? ''][$field] ?? '') }}" required class="rounded-lg border border-slate-700 bg-slate-950 px-3 py-2"></label>@endforeach</fieldset></div><label class="grid max-w-xl gap-2 text-sm">Parcel contents<input name="contents" value="{{ old('contents') }}" required maxlength="200" class="rounded-xl border border-slate-700 bg-slate-950 px-4 py-3"></label><button class="w-fit rounded-xl bg-brand px-5 py-3 font-semibold text-slate-950">Create unpaid order</button></form></section>@endif
<section class="overflow-x-auto rounded-2xl border border-slate-800 bg-slate-900">
<div class="p-6"><h3 class="text-lg font-semibold">Existing shipments</h3><p class="mt-1 text-sm text-slate-400">Current Parcel2Go progress and labels from your account history.</p></div>
<table class="w-full min-w-[800px] text-left text-sm"><thead class="border-y border-slate-800 text-slate-400"><tr><th class="p-4">Order</th><th class="p-4">Courier</th><th class="p-4">Service</th><th class="p-4">Progress</th><th class="p-4">Estimated delivery</th><th class="p-4">Recipient</th><th class="p-4">Destination</th><th class="p-4">Label</th></tr></thead><tbody class="divide-y divide-slate-800">
@forelse(($orders['Orders'] ?? []) as $order)
<tr><td class="p-4">{{ $order['OrderLineId'] ?? '—' }}</td><td class="p-4">{{ $order['Courier'] ?? '—' }}</td><td class="p-4">{{ $order['Service'] ?? '—' }}</td><td class="p-4"><span @class(['rounded-full px-2 py-1 text-xs font-semibold', 'bg-emerald-500/10 text-emerald-300' => $order['Tracking']['Delivered'] ?? false, 'bg-sky-500/10 text-sky-300' => ! ($order['Tracking']['Delivered'] ?? false)])>{{ ($order['Tracking']['Delivered'] ?? false) ? 'Delivered' : 'In transit' }}</span></td><td class="p-4">{{ app(\App\Services\Display::class)->dateTime($order['EstimatedDeliveryDate'] ?? null) }}</td><td class="p-4">{{ $order['DeliveryAddress']['ContactName'] ?? '—' }}</td><td class="p-4">{{ app(\App\Services\Display::class)->flag($order['DeliveryAddress'] ?? []) }}</td><td class="p-4">@if(app(\App\Services\Parcel2Go::class)->labelHash($order) && ! empty($order['OrderLineId']))<span class="flex items-center gap-2"><a class="inline-flex items-center gap-1.5 rounded-lg bg-brand px-3 py-1.5 text-xs font-bold text-slate-950 transition hover:brightness-110 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white" href="{{ route('shipping.label', [$order['OrderLineId'], 'size' => '4x6']) }}" aria-label="Download 4 by 6 inch label for order {{ $order['OrderLineId'] }}" title="Thermal printer label (100 × 150 mm)"><svg class="size-3.5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M10 2a1 1 0 0 1 1 1v8.59l2.3-2.3a1 1 0 1 1 1.4 1.42l-4 4a1 1 0 0 1-1.4 0l-4-4a1 1 0 1 1 1.4-1.42L9 11.59V3a1 1 0 0 1 1-1ZM4 15a1 1 0 0 1 1 1v1h10v-1a1 1 0 1 1 2 0v1a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-1a1 1 0 0 1 1-1Z"/></svg>4×6 label</a><a class="inline-flex items-center gap-1.5 rounded-lg border border-slate-700 px-3 py-1.5 text-xs font-semibold text-slate-300 transition hover:border-brand hover:text-brand focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white" href="{{ route('shipping.label', $order['OrderLineId']) }}" aria-label="Download A4 label for order {{ $order['OrderLineId'] }}" title="A4 sheet">A4</a></span>@else<span class="text-slate-500">Unavailable</span>@endif</td></tr>
@empty<tr><td colspan="8" class="p-6 text-slate-400">{{ $historyAvailable ? 'No shipments in Parcel2Go account history.' : 'Shipment history is unavailable. Check the Client Credentials in Settings.' }}</td></tr>@endforelse
</tbody></table>
</section>@endsection