Consolidate all tab-specific pagination into a unified pagination object and use the shared pagination macro for consistent look and feel across Orders, Products, Jobs, and Exceptions tabs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
211 lines
12 KiB
HTML
211 lines
12 KiB
HTML
{# app/templates/admin/partials/letzshop-exceptions-tab.html #}
|
|
{# Exceptions tab for admin Letzshop management - Order Item Exception Resolution #}
|
|
{% from 'shared/macros/pagination.html' import pagination %}
|
|
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">Product Exceptions</h3>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400" x-text="selectedVendor ? 'Resolve unmatched products from order imports' : 'All exceptions across vendors'"></p>
|
|
</div>
|
|
<button
|
|
@click="loadExceptions()"
|
|
:disabled="loadingExceptions"
|
|
class="flex items-center 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 dark:hover:bg-gray-700"
|
|
>
|
|
<span x-show="!loadingExceptions" x-html="$icon('refresh', 'w-4 h-4 mr-2')"></span>
|
|
<span x-show="loadingExceptions" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Status Cards -->
|
|
<div class="grid gap-6 mb-8 md:grid-cols-4">
|
|
<!-- Pending Exceptions -->
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-orange-500 bg-orange-100 rounded-full dark:bg-orange-900">
|
|
<span x-html="$icon('exclamation-circle', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Pending</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="exceptionStats.pending || 0"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resolved Exceptions -->
|
|
<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">Resolved</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="exceptionStats.resolved || 0"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ignored Exceptions -->
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-gray-500 bg-gray-100 rounded-full dark:bg-gray-700">
|
|
<span x-html="$icon('ban', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Ignored</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="exceptionStats.ignored || 0"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Orders Affected -->
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:bg-purple-900">
|
|
<span x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Orders Affected</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="exceptionStats.orders_with_exceptions || 0"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="mb-4 flex flex-wrap gap-4 items-center">
|
|
<!-- Search input -->
|
|
<div class="relative flex-1 min-w-[200px] max-w-md">
|
|
<span x-html="$icon('search', 'w-4 h-4 absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400')"></span>
|
|
<input
|
|
type="text"
|
|
x-model="exceptionsSearch"
|
|
@input.debounce.300ms="pagination.page = 1; loadExceptions()"
|
|
placeholder="Search by GTIN, product name, or order..."
|
|
class="w-full pl-9 pr-8 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md focus:border-purple-400 focus:outline-none"
|
|
/>
|
|
<button
|
|
x-show="exceptionsSearch"
|
|
@click="exceptionsSearch = ''; pagination.page = 1; loadExceptions()"
|
|
class="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
|
>
|
|
<span x-html="$icon('x', 'w-4 h-4')"></span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Status filter -->
|
|
<select
|
|
x-model="exceptionsFilter"
|
|
@change="pagination.page = 1; loadExceptions()"
|
|
class="px-3 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-md focus:border-purple-400 focus:outline-none"
|
|
>
|
|
<option value="">All Status</option>
|
|
<option value="pending">Pending</option>
|
|
<option value="resolved">Resolved</option>
|
|
<option value="ignored">Ignored</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Exceptions Table -->
|
|
<div class="w-full overflow-hidden rounded-lg shadow-xs">
|
|
<div class="w-full overflow-x-auto">
|
|
<table class="w-full whitespace-no-wrap">
|
|
<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 Info</th>
|
|
<th x-show="!selectedVendor" class="px-4 py-3">Vendor</th>
|
|
<th class="px-4 py-3">GTIN</th>
|
|
<th class="px-4 py-3">Order</th>
|
|
<th class="px-4 py-3">Status</th>
|
|
<th class="px-4 py-3">Created</th>
|
|
<th class="px-4 py-3">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
|
<template x-if="loadingExceptions && exceptions.length === 0">
|
|
<tr>
|
|
<td :colspan="selectedVendor ? 6 : 7" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
|
|
<span x-html="$icon('spinner', 'w-6 h-6 mx-auto mb-2')"></span>
|
|
<p>Loading exceptions...</p>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
<template x-if="!loadingExceptions && exceptions.length === 0">
|
|
<tr>
|
|
<td :colspan="selectedVendor ? 6 : 7" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
|
|
<span x-html="$icon('check-circle', 'w-12 h-12 mx-auto mb-2 text-green-300')"></span>
|
|
<p class="font-medium">No exceptions found</p>
|
|
<p class="text-sm mt-1">All order items are properly matched to products</p>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
<template x-for="exc in exceptions" :key="exc.id">
|
|
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700">
|
|
<td class="px-4 py-3">
|
|
<div class="flex items-center text-sm">
|
|
<div>
|
|
<p class="font-semibold text-gray-700 dark:text-gray-200" x-text="exc.original_product_name || 'Unknown Product'"></p>
|
|
<p class="text-xs text-gray-500" x-show="exc.original_sku" x-text="'SKU: ' + exc.original_sku"></p>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<!-- Vendor column (only in cross-vendor view) -->
|
|
<td x-show="!selectedVendor" class="px-4 py-3 text-sm">
|
|
<p class="font-medium text-gray-700 dark:text-gray-200" x-text="exc.vendor_name || 'N/A'"></p>
|
|
</td>
|
|
<td class="px-4 py-3 text-sm">
|
|
<code class="px-2 py-1 text-xs bg-gray-100 dark:bg-gray-700 rounded" x-text="exc.original_gtin || 'No GTIN'"></code>
|
|
</td>
|
|
<td class="px-4 py-3 text-sm">
|
|
<a
|
|
:href="'/admin/orders/' + exc.order_id"
|
|
class="text-purple-600 hover:text-purple-800 dark:text-purple-400"
|
|
x-text="exc.order_number"
|
|
></a>
|
|
<p class="text-xs text-gray-500" x-text="formatDate(exc.order_date)"></p>
|
|
</td>
|
|
<td class="px-4 py-3 text-xs">
|
|
<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': exc.status === 'pending',
|
|
'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100': exc.status === 'resolved',
|
|
'text-gray-700 bg-gray-100 dark:bg-gray-600 dark:text-gray-100': exc.status === 'ignored'
|
|
}"
|
|
x-text="exc.status.toUpperCase()"
|
|
></span>
|
|
</td>
|
|
<td class="px-4 py-3 text-sm">
|
|
<span x-text="formatDate(exc.created_at)"></span>
|
|
</td>
|
|
<td class="px-4 py-3">
|
|
<div class="flex items-center space-x-2 text-sm">
|
|
<template x-if="exc.status === 'pending'">
|
|
<button
|
|
@click="openResolveModal(exc)"
|
|
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="Resolve - Assign Product"
|
|
>
|
|
<span x-html="$icon('check', 'w-4 h-4 mr-1')"></span>
|
|
Resolve
|
|
</button>
|
|
</template>
|
|
<template x-if="exc.status === 'pending'">
|
|
<button
|
|
@click="ignoreException(exc)"
|
|
class="flex items-center justify-center px-2 py-1 text-sm text-gray-600 transition-colors duration-150 rounded-md hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
title="Ignore Exception"
|
|
>
|
|
<span x-html="$icon('ban', 'w-4 h-4')"></span>
|
|
</button>
|
|
</template>
|
|
<template x-if="exc.status === 'resolved'">
|
|
<span class="text-xs text-gray-500">
|
|
<span x-html="$icon('check', 'w-3 h-3 inline mr-1')"></span>
|
|
Resolved
|
|
</span>
|
|
</template>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{ pagination(show_condition="!loadingExceptions && pagination.total > 0") }}
|
|
</div>
|