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:
@@ -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) {
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
Carrier <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
x-model="trackingForm.tracking_carrier"
|
||||
x-model="trackingForm.tracking_provider"
|
||||
required
|
||||
class="block w-full px-3 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md focus:border-purple-400 focus:outline-none"
|
||||
>
|
||||
@@ -273,7 +273,7 @@
|
||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span class="font-medium text-gray-600 dark:text-gray-400">Order Number:</span>
|
||||
<span class="ml-2 text-gray-700 dark:text-gray-300" x-text="selectedOrder?.letzshop_order_number"></span>
|
||||
<span class="ml-2 text-gray-700 dark:text-gray-300" x-text="selectedOrder?.external_order_number || selectedOrder?.order_number"></span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="font-medium text-gray-600 dark:text-gray-400">Order Date:</span>
|
||||
@@ -284,12 +284,12 @@
|
||||
<span
|
||||
class="ml-2 px-2 py-0.5 text-xs rounded-full"
|
||||
:class="{
|
||||
'bg-orange-100 text-orange-700': selectedOrder?.sync_status === 'pending',
|
||||
'bg-green-100 text-green-700': selectedOrder?.sync_status === 'confirmed',
|
||||
'bg-red-100 text-red-700': selectedOrder?.sync_status === 'rejected',
|
||||
'bg-blue-100 text-blue-700': selectedOrder?.sync_status === 'shipped'
|
||||
'bg-orange-100 text-orange-700': selectedOrder?.status === 'pending',
|
||||
'bg-green-100 text-green-700': selectedOrder?.status === 'processing',
|
||||
'bg-red-100 text-red-700': selectedOrder?.status === 'cancelled',
|
||||
'bg-blue-100 text-blue-700': selectedOrder?.status === 'shipped'
|
||||
}"
|
||||
x-text="selectedOrder?.sync_status === 'rejected' ? 'DECLINED' : selectedOrder?.sync_status?.toUpperCase()"
|
||||
x-text="selectedOrder?.status === 'cancelled' ? 'DECLINED' : (selectedOrder?.status === 'processing' ? 'CONFIRMED' : selectedOrder?.status?.toUpperCase())"
|
||||
></span>
|
||||
</div>
|
||||
<div>
|
||||
@@ -337,7 +337,7 @@
|
||||
<div class="text-sm bg-blue-50 dark:bg-blue-900/20 rounded-lg p-3">
|
||||
<p class="text-gray-700 dark:text-gray-300">
|
||||
<span class="font-medium">Carrier:</span>
|
||||
<span class="ml-1" x-text="selectedOrder?.tracking_carrier"></span>
|
||||
<span class="ml-1" x-text="selectedOrder?.tracking_provider"></span>
|
||||
</p>
|
||||
<p class="text-gray-700 dark:text-gray-300">
|
||||
<span class="font-medium">Tracking #:</span>
|
||||
@@ -347,28 +347,28 @@
|
||||
</div>
|
||||
|
||||
<!-- Order Items -->
|
||||
<div x-show="selectedOrder?.inventory_units?.length > 0" class="border-t border-gray-200 dark:border-gray-600 pt-4">
|
||||
<div x-show="selectedOrder?.items?.length > 0" class="border-t border-gray-200 dark:border-gray-600 pt-4">
|
||||
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-2 flex items-center gap-2">
|
||||
<span x-html="$icon('shopping-bag', 'w-4 h-4')"></span>
|
||||
Items
|
||||
<span class="text-xs font-normal text-gray-500">
|
||||
(<span x-text="selectedOrder?.inventory_units?.length"></span> item<span x-show="selectedOrder?.inventory_units?.length > 1">s</span>)
|
||||
(<span x-text="selectedOrder?.items?.length"></span> item<span x-show="selectedOrder?.items?.length > 1">s</span>)
|
||||
</span>
|
||||
</h4>
|
||||
<div class="space-y-2">
|
||||
<template x-for="(unit, index) in selectedOrder?.inventory_units || []" :key="unit.id">
|
||||
<template x-for="(item, index) in selectedOrder?.items || []" :key="item.id">
|
||||
<div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
<p class="font-medium text-gray-700 dark:text-gray-200 text-sm" x-text="unit.product_name || 'Unknown Product'"></p>
|
||||
<p class="font-medium text-gray-700 dark:text-gray-200 text-sm" x-text="item.product_name || 'Unknown Product'"></p>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1 space-y-0.5">
|
||||
<p x-show="unit.ean">
|
||||
<span class="font-medium">EAN:</span> <span x-text="unit.ean"></span>
|
||||
<span x-show="unit.ean_type" class="text-gray-400" x-text="'(' + unit.ean_type + ')'"></span>
|
||||
<p x-show="item.gtin">
|
||||
<span class="font-medium">GTIN:</span> <span x-text="item.gtin"></span>
|
||||
<span x-show="item.gtin_type" class="text-gray-400" x-text="'(' + item.gtin_type + ')'"></span>
|
||||
</p>
|
||||
<p x-show="unit.sku"><span class="font-medium">SKU:</span> <span x-text="unit.sku"></span></p>
|
||||
<p x-show="unit.mpn"><span class="font-medium">MPN:</span> <span x-text="unit.mpn"></span></p>
|
||||
<p x-show="unit.price"><span class="font-medium">Price:</span> <span x-text="unit.price + ' EUR'"></span></p>
|
||||
<p x-show="item.product_sku"><span class="font-medium">SKU:</span> <span x-text="item.product_sku"></span></p>
|
||||
<p><span class="font-medium">Qty:</span> <span x-text="item.quantity"></span></p>
|
||||
<p x-show="item.unit_price"><span class="font-medium">Price:</span> <span x-text="item.unit_price + ' ' + selectedOrder?.currency"></span></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
@@ -376,25 +376,25 @@
|
||||
<span
|
||||
class="px-2 py-0.5 text-xs rounded-full whitespace-nowrap"
|
||||
: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>
|
||||
<!-- Item Actions (only for unconfirmed items) -->
|
||||
<template x-if="unit.state === 'unconfirmed' && selectedOrder?.sync_status === 'pending'">
|
||||
<template x-if="!item.item_state && selectedOrder?.status === 'pending'">
|
||||
<div class="flex gap-1">
|
||||
<button
|
||||
@click="confirmInventoryUnit(selectedOrder, unit, index)"
|
||||
@click="confirmInventoryUnit(selectedOrder, item, index)"
|
||||
class="p-1 text-green-600 hover:bg-green-100 rounded"
|
||||
title="Confirm this item"
|
||||
>
|
||||
<span x-html="$icon('check', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<button
|
||||
@click="declineInventoryUnit(selectedOrder, unit, index)"
|
||||
@click="declineInventoryUnit(selectedOrder, item, index)"
|
||||
class="p-1 text-red-600 hover:bg-red-100 rounded"
|
||||
title="Decline this item"
|
||||
>
|
||||
@@ -408,7 +408,7 @@
|
||||
</template>
|
||||
</div>
|
||||
<!-- Bulk Actions -->
|
||||
<div x-show="selectedOrder?.sync_status === 'pending'" class="mt-4 flex gap-2 justify-end">
|
||||
<div x-show="selectedOrder?.status === 'pending'" class="mt-4 flex gap-2 justify-end">
|
||||
<button
|
||||
@click="confirmAllItems(selectedOrder)"
|
||||
class="px-3 py-1.5 text-sm text-white bg-green-600 hover:bg-green-700 rounded-lg"
|
||||
|
||||
@@ -101,25 +101,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirmed Orders -->
|
||||
<!-- Confirmed/Processing Orders -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:bg-green-900">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Confirmed</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="orderStats.confirmed"></p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="orderStats.processing"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Declined Orders -->
|
||||
<!-- Declined/Cancelled Orders -->
|
||||
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<div class="p-3 mr-4 text-red-500 bg-red-100 rounded-full dark:bg-red-900">
|
||||
<span x-html="$icon('x-circle', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Declined</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="orderStats.rejected"></p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="orderStats.cancelled"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -164,8 +164,8 @@
|
||||
>
|
||||
<option value="">All Status</option>
|
||||
<option value="pending">Pending</option>
|
||||
<option value="confirmed">Confirmed</option>
|
||||
<option value="rejected">Declined</option>
|
||||
<option value="processing">Confirmed</option>
|
||||
<option value="cancelled">Declined</option>
|
||||
<option value="shipped">Shipped</option>
|
||||
</select>
|
||||
|
||||
@@ -230,7 +230,7 @@
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center text-sm">
|
||||
<div>
|
||||
<p class="font-semibold" x-text="order.letzshop_order_number || order.letzshop_order_id"></p>
|
||||
<p class="font-semibold" x-text="order.external_order_number || order.order_number"></p>
|
||||
<p class="text-xs text-gray-500" x-text="'#' + order.id"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -245,12 +245,12 @@
|
||||
<span
|
||||
class="px-2 py-1 font-semibold leading-tight rounded-full"
|
||||
:class="{
|
||||
'text-orange-700 bg-orange-100 dark:bg-orange-700 dark:text-orange-100': order.sync_status === 'pending',
|
||||
'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100': order.sync_status === 'confirmed',
|
||||
'text-red-700 bg-red-100 dark:bg-red-700 dark:text-red-100': order.sync_status === 'rejected',
|
||||
'text-blue-700 bg-blue-100 dark:bg-blue-700 dark:text-blue-100': order.sync_status === 'shipped'
|
||||
'text-orange-700 bg-orange-100 dark:bg-orange-700 dark:text-orange-100': order.status === 'pending',
|
||||
'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100': order.status === 'processing',
|
||||
'text-red-700 bg-red-100 dark:bg-red-700 dark:text-red-100': order.status === 'cancelled',
|
||||
'text-blue-700 bg-blue-100 dark:bg-blue-700 dark:text-blue-100': 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>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-sm">
|
||||
@@ -259,7 +259,7 @@
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center space-x-2 text-sm">
|
||||
<button
|
||||
x-show="order.sync_status === 'pending'"
|
||||
x-show="order.status === 'pending'"
|
||||
@click="confirmOrder(order)"
|
||||
class="flex items-center justify-center px-2 py-1 text-sm text-green-600 transition-colors duration-150 rounded-md hover:bg-green-100 dark:hover:bg-green-900"
|
||||
title="Confirm Order"
|
||||
@@ -267,7 +267,7 @@
|
||||
<span x-html="$icon('check', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<button
|
||||
x-show="order.sync_status === 'pending'"
|
||||
x-show="order.status === 'pending'"
|
||||
@click="declineOrder(order)"
|
||||
class="flex items-center justify-center px-2 py-1 text-sm text-red-600 transition-colors duration-150 rounded-md hover:bg-red-100 dark:hover:bg-red-900"
|
||||
title="Decline Order"
|
||||
@@ -275,7 +275,7 @@
|
||||
<span x-html="$icon('x', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<button
|
||||
x-show="order.sync_status === 'confirmed'"
|
||||
x-show="order.status === 'processing'"
|
||||
@click="openTrackingModal(order)"
|
||||
class="flex items-center justify-center px-2 py-1 text-sm text-blue-600 transition-colors duration-150 rounded-md hover:bg-blue-100 dark:hover:bg-blue-900"
|
||||
title="Set Tracking"
|
||||
|
||||
@@ -267,4 +267,9 @@ Each marketplace would use:
|
||||
- [x] Letzshop order service updated
|
||||
- [x] Letzshop schemas updated
|
||||
- [x] API endpoints updated
|
||||
- [ ] Frontend updated
|
||||
- [x] Frontend updated
|
||||
- [x] Orders tab template (status badges, filters, table)
|
||||
- [x] Order detail page (snapshots, items, tracking)
|
||||
- [x] JavaScript (API params, response handling)
|
||||
- [x] Tracking modal (tracking_provider field)
|
||||
- [x] Order items modal (items array, item_state)
|
||||
|
||||
@@ -92,7 +92,7 @@ function adminMarketplaceLetzshop() {
|
||||
ordersFilter: '',
|
||||
ordersSearch: '',
|
||||
ordersHasDeclinedItems: false,
|
||||
orderStats: { pending: 0, confirmed: 0, rejected: 0, shipped: 0, has_declined_items: 0 },
|
||||
orderStats: { pending: 0, processing: 0, shipped: 0, delivered: 0, cancelled: 0, total: 0, has_declined_items: 0 },
|
||||
|
||||
// Jobs
|
||||
jobs: [],
|
||||
@@ -103,7 +103,7 @@ function adminMarketplaceLetzshop() {
|
||||
showTrackingModal: false,
|
||||
showOrderModal: false,
|
||||
selectedOrder: null,
|
||||
trackingForm: { tracking_number: '', tracking_carrier: '' },
|
||||
trackingForm: { tracking_number: '', tracking_provider: '' },
|
||||
|
||||
async init() {
|
||||
marketplaceLetzshopLog.info('init() called');
|
||||
@@ -399,7 +399,7 @@ function adminMarketplaceLetzshop() {
|
||||
});
|
||||
|
||||
if (this.ordersFilter) {
|
||||
params.append('sync_status', this.ordersFilter);
|
||||
params.append('status', this.ordersFilter);
|
||||
}
|
||||
|
||||
if (this.ordersHasDeclinedItems) {
|
||||
@@ -437,13 +437,14 @@ function adminMarketplaceLetzshop() {
|
||||
*/
|
||||
updateOrderStats() {
|
||||
// Reset stats
|
||||
this.orderStats = { pending: 0, confirmed: 0, rejected: 0, shipped: 0, has_declined_items: 0 };
|
||||
this.orderStats = { pending: 0, processing: 0, shipped: 0, delivered: 0, cancelled: 0, total: 0, has_declined_items: 0 };
|
||||
|
||||
// Count from orders list (only visible page - not accurate for totals)
|
||||
for (const order of this.orders) {
|
||||
if (this.orderStats.hasOwnProperty(order.sync_status)) {
|
||||
this.orderStats[order.sync_status]++;
|
||||
if (this.orderStats.hasOwnProperty(order.status)) {
|
||||
this.orderStats[order.status]++;
|
||||
}
|
||||
this.orderStats.total++;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -669,7 +670,7 @@ function adminMarketplaceLetzshop() {
|
||||
this.selectedOrder = order;
|
||||
this.trackingForm = {
|
||||
tracking_number: order.tracking_number || '',
|
||||
tracking_carrier: order.tracking_carrier || ''
|
||||
tracking_provider: order.tracking_provider || ''
|
||||
};
|
||||
this.showTrackingModal = true;
|
||||
},
|
||||
@@ -707,17 +708,24 @@ function adminMarketplaceLetzshop() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm a single inventory unit
|
||||
* Confirm a single order item
|
||||
*/
|
||||
async confirmInventoryUnit(order, unit, index) {
|
||||
async confirmInventoryUnit(order, item, index) {
|
||||
if (!this.selectedVendor) return;
|
||||
|
||||
// Use external_item_id (Letzshop inventory unit ID)
|
||||
const itemId = item.external_item_id;
|
||||
if (!itemId) {
|
||||
this.error = 'Item has no external ID';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await apiClient.post(
|
||||
`/admin/letzshop/vendors/${this.selectedVendor.id}/orders/${order.id}/items/${unit.id}/confirm`
|
||||
`/admin/letzshop/vendors/${this.selectedVendor.id}/orders/${order.id}/items/${itemId}/confirm`
|
||||
);
|
||||
// Update local state
|
||||
this.selectedOrder.inventory_units[index].state = 'confirmed_available';
|
||||
this.selectedOrder.items[index].item_state = 'confirmed_available';
|
||||
this.successMessage = 'Item confirmed';
|
||||
// Reload orders to get updated status
|
||||
await this.loadOrders();
|
||||
@@ -728,17 +736,24 @@ function adminMarketplaceLetzshop() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Decline a single inventory unit
|
||||
* Decline a single order item
|
||||
*/
|
||||
async declineInventoryUnit(order, unit, index) {
|
||||
async declineInventoryUnit(order, item, index) {
|
||||
if (!this.selectedVendor) return;
|
||||
|
||||
// Use external_item_id (Letzshop inventory unit ID)
|
||||
const itemId = item.external_item_id;
|
||||
if (!itemId) {
|
||||
this.error = 'Item has no external ID';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await apiClient.post(
|
||||
`/admin/letzshop/vendors/${this.selectedVendor.id}/orders/${order.id}/items/${unit.id}/decline`
|
||||
`/admin/letzshop/vendors/${this.selectedVendor.id}/orders/${order.id}/items/${itemId}/decline`
|
||||
);
|
||||
// Update local state
|
||||
this.selectedOrder.inventory_units[index].state = 'confirmed_unavailable';
|
||||
this.selectedOrder.items[index].item_state = 'confirmed_unavailable';
|
||||
this.successMessage = 'Item declined';
|
||||
// Reload orders to get updated status
|
||||
await this.loadOrders();
|
||||
|
||||
Reference in New Issue
Block a user