feat: add Exceptions tab to Letzshop management page
Adds a new "Exceptions" tab to the Letzshop marketplace page for managing unmatched product exceptions from order imports. Features: - Exception list with search and status filtering - Stats cards showing pending/resolved/ignored counts - Resolve modal with product search - Bulk resolve option for same GTIN - Ignore functionality Files: - New: letzshop-exceptions-tab.html partial template - Updated: marketplace-letzshop.html (tab button, panel, resolve modal) - Updated: marketplace-letzshop.js (exception state, methods) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,7 @@
|
||||
{% call tabs_nav(tab_var='activeTab') %}
|
||||
{{ tab_button('products', 'Products', tab_var='activeTab', icon='cube') }}
|
||||
{{ tab_button('orders', 'Orders', tab_var='activeTab', icon='shopping-cart', count_var='orderStats.pending') }}
|
||||
{{ tab_button('exceptions', 'Exceptions', tab_var='activeTab', icon='exclamation-circle', count_var='exceptionStats.pending') }}
|
||||
{{ tab_button('settings', 'Settings', tab_var='activeTab', icon='cog') }}
|
||||
{% endcall %}
|
||||
|
||||
@@ -137,6 +138,11 @@
|
||||
{% include 'admin/partials/letzshop-settings-tab.html' %}
|
||||
{{ endtab_panel() }}
|
||||
|
||||
<!-- Exceptions Tab -->
|
||||
{{ tab_panel('exceptions', tab_var='activeTab') }}
|
||||
{% include 'admin/partials/letzshop-exceptions-tab.html' %}
|
||||
{{ endtab_panel() }}
|
||||
|
||||
<!-- Unified Jobs Table (below all tabs) -->
|
||||
<div class="mt-8">
|
||||
{% include 'admin/partials/letzshop-jobs-table.html' %}
|
||||
@@ -426,6 +432,146 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Exception Resolve Modal -->
|
||||
<div
|
||||
x-show="showResolveModal"
|
||||
x-transition:enter="transition ease-out duration-150"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="fixed inset-0 z-30 flex items-end bg-black bg-opacity-50 sm:items-center sm:justify-center"
|
||||
@click.self="showResolveModal = false"
|
||||
x-cloak
|
||||
>
|
||||
<div
|
||||
x-transition:enter="transition ease-out duration-150"
|
||||
x-transition:enter-start="opacity-0 transform translate-y-1/2"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0 transform translate-y-1/2"
|
||||
class="w-full px-6 py-4 overflow-hidden bg-white rounded-t-lg dark:bg-gray-800 sm:rounded-lg sm:m-4 sm:max-w-lg"
|
||||
@click.stop
|
||||
>
|
||||
<header class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">Resolve Exception</h3>
|
||||
<button @click="showResolveModal = false" class="text-gray-400 hover:text-gray-600">
|
||||
<span x-html="$icon('x', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<!-- Exception Details -->
|
||||
<div class="mb-4 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-200" x-text="selectedExceptionForResolve?.original_product_name || 'Unknown Product'"></p>
|
||||
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400 space-y-1">
|
||||
<p x-show="selectedExceptionForResolve?.original_gtin">
|
||||
<span class="font-medium">GTIN:</span>
|
||||
<code class="ml-1 px-1 bg-gray-200 dark:bg-gray-600 rounded" x-text="selectedExceptionForResolve?.original_gtin"></code>
|
||||
</p>
|
||||
<p x-show="selectedExceptionForResolve?.original_sku">
|
||||
<span class="font-medium">SKU:</span> <span x-text="selectedExceptionForResolve?.original_sku"></span>
|
||||
</p>
|
||||
<p>
|
||||
<span class="font-medium">Order:</span>
|
||||
<a :href="'/admin/orders/' + selectedExceptionForResolve?.order_id" class="text-purple-600 hover:underline" x-text="selectedExceptionForResolve?.order_number"></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="submitResolveException()">
|
||||
<!-- Product Search -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Assign Product <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="relative">
|
||||
<input
|
||||
type="text"
|
||||
x-model="productSearchQuery"
|
||||
@input.debounce.300ms="searchProducts()"
|
||||
placeholder="Search by name, SKU, or GTIN..."
|
||||
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"
|
||||
/>
|
||||
<span x-show="searchingProducts" x-html="$icon('spinner', 'w-4 h-4 absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400')"></span>
|
||||
</div>
|
||||
<!-- Search Results -->
|
||||
<div x-show="productSearchResults.length > 0" class="mt-2 max-h-48 overflow-y-auto border border-gray-200 dark:border-gray-600 rounded-md">
|
||||
<template x-for="product in productSearchResults" :key="product.id">
|
||||
<button
|
||||
type="button"
|
||||
@click="selectProductForResolve(product)"
|
||||
class="w-full px-3 py-2 text-left text-sm hover:bg-purple-50 dark:hover:bg-purple-900/20 border-b border-gray-100 dark:border-gray-700 last:border-b-0"
|
||||
:class="resolveForm.product_id === product.id ? 'bg-purple-100 dark:bg-purple-900/30' : ''"
|
||||
>
|
||||
<p class="font-medium text-gray-700 dark:text-gray-200" x-text="product.name || product.title"></p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<span x-show="product.gtin" x-text="'GTIN: ' + product.gtin"></span>
|
||||
<span x-show="product.gtin && product.sku"> · </span>
|
||||
<span x-show="product.sku" x-text="'SKU: ' + product.sku"></span>
|
||||
</p>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Selected Product -->
|
||||
<div x-show="resolveForm.product_id" class="mt-2 p-2 bg-green-50 dark:bg-green-900/20 rounded-md flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-green-700 dark:text-green-300" x-text="resolveForm.product_name"></p>
|
||||
<p class="text-xs text-green-600 dark:text-green-400" x-text="'Product ID: ' + resolveForm.product_id"></p>
|
||||
</div>
|
||||
<button type="button" @click="resolveForm.product_id = null; resolveForm.product_name = ''" class="text-green-600 hover:text-green-800">
|
||||
<span x-html="$icon('x', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resolution Notes -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
||||
Notes (Optional)
|
||||
</label>
|
||||
<textarea
|
||||
x-model="resolveForm.notes"
|
||||
rows="2"
|
||||
placeholder="Add any notes about this resolution..."
|
||||
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"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Bulk Resolve Option -->
|
||||
<div x-show="selectedExceptionForResolve?.original_gtin" class="mb-4">
|
||||
<label class="flex items-center text-sm text-gray-700 dark:text-gray-300">
|
||||
<input
|
||||
type="checkbox"
|
||||
x-model="resolveForm.bulk_resolve"
|
||||
class="mr-2 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
|
||||
/>
|
||||
Resolve all pending exceptions with this GTIN
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="showResolveModal = false"
|
||||
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="!resolveForm.product_id || submittingResolve"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-lg hover:bg-green-700 disabled:opacity-50"
|
||||
>
|
||||
<span x-show="submittingResolve" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
||||
<span x-text="submittingResolve ? 'Resolving...' : 'Resolve Exception'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
|
||||
Reference in New Issue
Block a user