feat: update frontend for unified order model

Update all frontend templates and JavaScript to use new unified Order model:
- Orders tab: use status field, processing/cancelled values, items array
- Order detail: use snapshot fields, items array, tracking_provider
- JavaScript: update API params (status vs sync_status), orderStats fields
- Tracking modal: use tracking_provider instead of tracking_carrier
- Order items modal: use items array with item_state field

All status mappings:
- pending → pending (unconfirmed)
- processing → confirmed (at least one item available)
- cancelled → declined (all items unavailable)
- shipped → shipped (with tracking)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-19 21:38:02 +01:00
parent c49b80ce41
commit 2e6f8fdb8a
5 changed files with 126 additions and 110 deletions

View File

@@ -20,7 +20,7 @@
</a>
<div>
<h2 class="text-2xl font-semibold text-gray-700 dark:text-gray-200">
Order <span x-text="order?.letzshop_order_number || 'Loading...'"></span>
Order <span x-text="order?.external_order_number || order?.order_number || 'Loading...'"></span>
</h2>
<p class="text-sm text-gray-500 dark:text-gray-400">
Letzshop Order Details
@@ -32,12 +32,12 @@
x-show="order"
class="px-3 py-1 text-sm rounded-full font-medium"
:class="{
'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300': order?.sync_status === 'pending',
'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300': order?.sync_status === 'confirmed',
'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300': order?.sync_status === 'rejected',
'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300': order?.sync_status === 'shipped'
'bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300': order?.status === 'pending',
'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300': order?.status === 'processing',
'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300': order?.status === 'cancelled',
'bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300': order?.status === 'shipped'
}"
x-text="order?.sync_status === 'rejected' ? 'DECLINED' : order?.sync_status?.toUpperCase()"
x-text="order?.status === 'cancelled' ? 'DECLINED' : (order?.status === 'processing' ? 'CONFIRMED' : order?.status?.toUpperCase())"
></span>
</div>
</div>
@@ -61,7 +61,7 @@
<div class="space-y-3 text-sm">
<div class="flex justify-between">
<span class="text-gray-500 dark:text-gray-400">Order Number</span>
<span class="font-medium text-gray-700 dark:text-gray-300" x-text="order?.letzshop_order_number"></span>
<span class="font-medium text-gray-700 dark:text-gray-300" x-text="order?.external_order_number || order?.order_number"></span>
</div>
<div class="flex justify-between">
<span class="text-gray-500 dark:text-gray-400">Order Date</span>
@@ -69,7 +69,7 @@
</div>
<div class="flex justify-between">
<span class="text-gray-500 dark:text-gray-400">Shipment ID</span>
<span class="font-mono text-xs text-gray-600 dark:text-gray-400" x-text="order?.letzshop_shipment_id"></span>
<span class="font-mono text-xs text-gray-600 dark:text-gray-400" x-text="order?.external_shipment_id"></span>
</div>
<div class="flex justify-between">
<span class="text-gray-500 dark:text-gray-400">Total</span>
@@ -79,9 +79,9 @@
<span class="text-gray-500 dark:text-gray-400">Confirmed At</span>
<span class="text-gray-700 dark:text-gray-300" x-text="formatDate(order?.confirmed_at)"></span>
</div>
<div class="flex justify-between" x-show="order?.rejected_at">
<div class="flex justify-between" x-show="order?.cancelled_at">
<span class="text-gray-500 dark:text-gray-400">Declined At</span>
<span class="text-gray-700 dark:text-gray-300" x-text="formatDate(order?.rejected_at)"></span>
<span class="text-gray-700 dark:text-gray-300" x-text="formatDate(order?.cancelled_at)"></span>
</div>
</div>
</div>
@@ -109,20 +109,21 @@
</div>
<!-- Shipping Address -->
<div class="min-w-0 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800" x-show="shippingAddress">
<div class="min-w-0 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800" x-show="order">
<h4 class="mb-4 font-semibold text-gray-600 dark:text-gray-300 flex items-center gap-2">
<span x-html="$icon('location-marker', 'w-5 h-5')"></span>
Shipping Address
</h4>
<div class="text-sm text-gray-700 dark:text-gray-300 space-y-1">
<p x-text="shippingAddress?.firstName + ' ' + shippingAddress?.lastName"></p>
<p x-show="shippingAddress?.company" x-text="shippingAddress?.company"></p>
<p x-text="shippingAddress?.streetName + ' ' + shippingAddress?.streetNumber"></p>
<p x-text="shippingAddress?.zipCode + ' ' + shippingAddress?.city"></p>
<p x-text="shippingAddress?.country?.name || order?.shipping_country_iso"></p>
<p x-show="shippingAddress?.phone" class="mt-2">
<p x-text="order?.ship_first_name + ' ' + order?.ship_last_name"></p>
<p x-show="order?.ship_company" x-text="order?.ship_company"></p>
<p x-text="order?.ship_address_line_1"></p>
<p x-show="order?.ship_address_line_2" x-text="order?.ship_address_line_2"></p>
<p x-text="order?.ship_postal_code + ' ' + order?.ship_city"></p>
<p x-text="order?.ship_country_iso"></p>
<p x-show="order?.customer_phone" class="mt-2">
<span class="text-gray-500 dark:text-gray-400">Phone:</span>
<span x-text="shippingAddress?.phone"></span>
<span x-text="order?.customer_phone"></span>
</p>
</div>
</div>
@@ -136,15 +137,15 @@
<div x-show="order?.tracking_number" class="space-y-3 text-sm">
<div class="flex justify-between">
<span class="text-gray-500 dark:text-gray-400">Carrier</span>
<span class="font-medium text-gray-700 dark:text-gray-300" x-text="order?.tracking_carrier"></span>
<span class="font-medium text-gray-700 dark:text-gray-300" x-text="order?.tracking_provider"></span>
</div>
<div class="flex justify-between">
<span class="text-gray-500 dark:text-gray-400">Tracking Number</span>
<span class="font-mono text-gray-700 dark:text-gray-300" x-text="order?.tracking_number"></span>
</div>
<div class="flex justify-between" x-show="order?.tracking_set_at">
<span class="text-gray-500 dark:text-gray-400">Set At</span>
<span class="text-gray-700 dark:text-gray-300" x-text="formatDate(order?.tracking_set_at)"></span>
<div class="flex justify-between" x-show="order?.shipped_at">
<span class="text-gray-500 dark:text-gray-400">Shipped At</span>
<span class="text-gray-700 dark:text-gray-300" x-text="formatDate(order?.shipped_at)"></span>
</div>
</div>
<div x-show="!order?.tracking_number" class="text-sm text-gray-500 dark:text-gray-400 italic">
@@ -159,7 +160,7 @@
<span x-html="$icon('shopping-bag', 'w-5 h-5')"></span>
Order Items
<span class="text-sm font-normal text-gray-500">
(<span x-text="order?.inventory_units?.length || 0"></span> items)
(<span x-text="order?.items?.length || 0"></span> items)
</span>
</h4>
@@ -168,13 +169,14 @@
<thead>
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
<th class="px-4 py-3">Product</th>
<th class="px-4 py-3">EAN/SKU</th>
<th class="px-4 py-3">GTIN/SKU</th>
<th class="px-4 py-3">Qty</th>
<th class="px-4 py-3">Price</th>
<th class="px-4 py-3">Status</th>
</tr>
</thead>
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
<template x-for="unit in order?.inventory_units || []" :key="unit.id">
<template x-for="item in order?.items || []" :key="item.id">
<tr class="text-gray-700 dark:text-gray-400">
<td class="px-4 py-3">
<div class="flex items-center">
@@ -183,35 +185,38 @@
<span x-html="$icon('photograph', 'w-5 h-5 text-gray-400')"></span>
</div>
<div>
<p class="font-semibold text-gray-800 dark:text-gray-200" x-text="unit.product_name || 'Unknown Product'"></p>
<p class="text-xs text-gray-600 dark:text-gray-400" x-show="unit.mpn">
MPN: <span x-text="unit.mpn"></span>
</p>
<p class="font-semibold text-gray-800 dark:text-gray-200" x-text="item.product_name || 'Unknown Product'"></p>
</div>
</div>
</td>
<td class="px-4 py-3 text-sm">
<p x-show="unit.ean" class="font-mono">
<span x-text="unit.ean"></span>
<span x-show="unit.ean_type" class="text-xs text-gray-400" x-text="'(' + unit.ean_type + ')'"></span>
<p x-show="item.gtin" class="font-mono">
<span x-text="item.gtin"></span>
<span x-show="item.gtin_type" class="text-xs text-gray-400" x-text="'(' + item.gtin_type + ')'"></span>
</p>
<p x-show="unit.sku" class="text-xs text-gray-500">
SKU: <span x-text="unit.sku"></span>
<p x-show="item.product_sku" class="text-xs text-gray-500">
SKU: <span x-text="item.product_sku"></span>
</p>
</td>
<td class="px-4 py-3 text-sm">
<span x-text="item.quantity"></span>
</td>
<td class="px-4 py-3 text-sm font-medium">
<span x-text="unit.price ? (unit.price + ' EUR') : 'N/A'"></span>
<span x-text="item.unit_price ? (item.unit_price + ' ' + order?.currency) : 'N/A'"></span>
<p x-show="item.quantity > 1" class="text-xs text-gray-500">
Total: <span x-text="item.total_price + ' ' + order?.currency"></span>
</p>
</td>
<td class="px-4 py-3">
<span
class="px-2 py-1 text-xs rounded-full font-medium"
:class="{
'bg-orange-100 text-orange-700': unit.state === 'unconfirmed',
'bg-green-100 text-green-700': unit.state === 'confirmed_available' || unit.state === 'confirmed',
'bg-red-100 text-red-700': unit.state === 'confirmed_unavailable',
'bg-gray-100 text-gray-700': unit.state === 'returned'
'bg-orange-100 text-orange-700': !item.item_state,
'bg-green-100 text-green-700': item.item_state === 'confirmed_available',
'bg-red-100 text-red-700': item.item_state === 'confirmed_unavailable',
'bg-gray-100 text-gray-700': item.item_state === 'returned'
}"
x-text="unit.state === 'confirmed_unavailable' ? 'DECLINED' : (unit.state === 'confirmed_available' ? 'CONFIRMED' : unit.state?.toUpperCase())"
x-text="item.item_state === 'confirmed_unavailable' ? 'DECLINED' : (item.item_state === 'confirmed_available' ? 'CONFIRMED' : (item.item_state ? item.item_state.toUpperCase() : 'PENDING'))"
></span>
</td>
</tr>
@@ -229,12 +234,12 @@
>
<h4 class="font-semibold text-gray-600 dark:text-gray-300 flex items-center gap-2">
<span x-html="$icon('code', 'w-5 h-5')"></span>
Raw Order Data
Raw Marketplace Data
</h4>
<span x-html="showRawData ? $icon('chevron-up', 'w-5 h-5 text-gray-500') : $icon('chevron-down', 'w-5 h-5 text-gray-500')"></span>
</button>
<div x-show="showRawData" class="mt-4">
<pre class="text-xs bg-gray-100 dark:bg-gray-900 p-4 rounded overflow-x-auto max-h-96"><code x-text="JSON.stringify(order?.raw_order_data, null, 2)"></code></pre>
<pre class="text-xs bg-gray-100 dark:bg-gray-900 p-4 rounded overflow-x-auto max-h-96"><code x-text="JSON.stringify(order?.external_data, null, 2)"></code></pre>
</div>
</div>
</div>
@@ -253,14 +258,6 @@ function letzshopOrderDetail() {
error: null,
showRawData: false,
get shippingAddress() {
return this.order?.raw_order_data?.order?.shipAddress;
},
get billingAddress() {
return this.order?.raw_order_data?.order?.billAddress;
},
async init() {
await this.loadOrder();
},
@@ -270,8 +267,7 @@ function letzshopOrderDetail() {
this.error = null;
try {
// First, we need to find which vendor this order belongs to
// We'll fetch the order detail from the API
// Fetch the order detail from the API
const response = await apiClient.get(`/admin/letzshop/orders/${this.orderId}`);
this.order = response;
} catch (err) {