The override concept is no longer relevant with the independent copy architecture. Products are fully independent entities, not inherited from marketplace products. Removed all Override badges and updated the info banner text to reflect the new architecture. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
359 lines
20 KiB
HTML
359 lines
20 KiB
HTML
{# app/templates/admin/vendor-product-detail.html #}
|
|
{% extends "admin/base.html" %}
|
|
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
|
{% from 'shared/macros/headers.html' import detail_page_header %}
|
|
{% from 'shared/macros/modals.html' import modal_simple %}
|
|
|
|
{% block title %}Vendor Product Details{% endblock %}
|
|
|
|
{% block alpine_data %}adminVendorProductDetail(){% endblock %}
|
|
|
|
{% block content %}
|
|
{% call detail_page_header("product?.title || 'Product Details'", '/admin/vendor-products', subtitle_show='product') %}
|
|
<span x-text="product?.vendor_name || 'Unknown Vendor'"></span>
|
|
<span class="text-gray-400 mx-2">|</span>
|
|
<span x-text="product?.vendor_code || ''"></span>
|
|
{% endcall %}
|
|
|
|
{{ loading_state('Loading product details...') }}
|
|
|
|
{{ error_state('Error loading product') }}
|
|
|
|
<!-- Product Details -->
|
|
<div x-show="!loading && product">
|
|
<!-- Info Banner - adapts based on whether product has marketplace source -->
|
|
<div class="px-4 py-3 mb-6 rounded-lg shadow-md"
|
|
:class="product?.marketplace_product_id ? 'bg-purple-50 dark:bg-purple-900/20' : 'bg-blue-50 dark:bg-blue-900/20'">
|
|
<div class="flex items-start">
|
|
<span x-html="$icon('information-circle', product?.marketplace_product_id ? 'w-5 h-5 text-purple-500 mr-3 mt-0.5 flex-shrink-0' : 'w-5 h-5 text-blue-500 mr-3 mt-0.5 flex-shrink-0')"></span>
|
|
<div>
|
|
<!-- Marketplace-sourced product -->
|
|
<template x-if="product?.marketplace_product_id">
|
|
<div>
|
|
<p class="text-sm font-medium text-purple-700 dark:text-purple-300">Vendor Product Catalog Entry</p>
|
|
<p class="text-xs text-purple-600 dark:text-purple-400 mt-1">
|
|
This is a vendor-specific copy of a marketplace product. All fields are independently managed.
|
|
View the source product for comparison.
|
|
</p>
|
|
</div>
|
|
</template>
|
|
<!-- Directly created product -->
|
|
<template x-if="!product?.marketplace_product_id">
|
|
<div>
|
|
<p class="text-sm font-medium text-blue-700 dark:text-blue-300">Directly Created Product</p>
|
|
<p class="text-xs text-blue-600 dark:text-blue-400 mt-1">
|
|
This product was created directly for this vendor without a marketplace source.
|
|
All product information is managed independently.
|
|
</p>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions Card -->
|
|
<div class="px-4 py-3 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Quick Actions
|
|
</h3>
|
|
<div class="flex flex-wrap items-center gap-3">
|
|
<!-- View Source Product - only for marketplace-sourced products -->
|
|
<a
|
|
x-show="product?.marketplace_product_id"
|
|
:href="'/admin/marketplace-products/' + product?.marketplace_product_id"
|
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
|
|
<span x-html="$icon('database', 'w-4 h-4 mr-2')"></span>
|
|
View Source Product
|
|
</a>
|
|
<a
|
|
:href="'/admin/vendor-products/' + productId + '/edit'"
|
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 dark:text-gray-300 transition-colors duration-150 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600">
|
|
<span x-html="$icon('pencil', 'w-4 h-4 mr-2')"></span>
|
|
<span x-text="product?.marketplace_product_id ? 'Edit Overrides' : 'Edit Product'"></span>
|
|
</a>
|
|
<button
|
|
@click="toggleActive()"
|
|
:class="product?.is_active
|
|
? 'text-red-700 dark:text-red-300 border-red-300 dark:border-red-600 hover:bg-red-50 dark:hover:bg-red-900/20'
|
|
: 'text-green-700 dark:text-green-300 border-green-300 dark:border-green-600 hover:bg-green-50 dark:hover:bg-green-900/20'"
|
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 transition-colors duration-150 bg-white dark:bg-gray-700 border rounded-lg">
|
|
<span x-html="$icon(product?.is_active ? 'x-circle' : 'check-circle', 'w-4 h-4 mr-2')"></span>
|
|
<span x-text="product?.is_active ? 'Deactivate' : 'Activate'"></span>
|
|
</button>
|
|
<button
|
|
@click="confirmRemove()"
|
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-red-600 dark:text-red-400 transition-colors duration-150 bg-white dark:bg-gray-700 border border-red-300 dark:border-red-600 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20">
|
|
<span x-html="$icon('delete', 'w-4 h-4 mr-2')"></span>
|
|
Remove from Catalog
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Product Header with Image -->
|
|
<div class="grid gap-6 mb-8 md:grid-cols-3">
|
|
<!-- Product Image -->
|
|
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<div class="aspect-square bg-gray-100 dark:bg-gray-700 rounded-lg overflow-hidden">
|
|
<template x-if="product?.image_url">
|
|
<img :src="product?.image_url" :alt="product?.title" class="w-full h-full object-contain" />
|
|
</template>
|
|
<template x-if="!product?.image_url">
|
|
<div class="w-full h-full flex items-center justify-center">
|
|
<span x-html="$icon('photograph', 'w-16 h-16 text-gray-300')"></span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<!-- Additional Images -->
|
|
<div x-show="product?.additional_images?.length > 0" class="mt-4">
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Additional Images</p>
|
|
<div class="grid grid-cols-4 gap-2">
|
|
<template x-for="(img, index) in (product?.additional_images || [])" :key="index">
|
|
<div class="aspect-square bg-gray-100 dark:bg-gray-700 rounded overflow-hidden">
|
|
<img :src="img" :alt="'Image ' + (index + 1)" class="w-full h-full object-cover" />
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Product Info -->
|
|
<div class="md:col-span-2 space-y-6">
|
|
<!-- Vendor Info Card -->
|
|
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Vendor Information
|
|
</h3>
|
|
<div class="grid gap-4 md:grid-cols-2">
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Vendor</p>
|
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-300" x-text="product?.vendor_name || '-'">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Vendor Code</p>
|
|
<p class="text-sm font-mono text-gray-700 dark:text-gray-300" x-text="product?.vendor_code || '-'">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Vendor SKU</p>
|
|
<p class="text-sm font-mono text-gray-700 dark:text-gray-300" x-text="product?.vendor_sku || '-'">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Status</p>
|
|
<div class="flex items-center gap-2">
|
|
<span class="px-2 py-1 text-xs font-semibold rounded-full"
|
|
:class="product?.is_active ? 'text-green-700 bg-green-100 dark:bg-green-900/30 dark:text-green-400' : 'text-red-700 bg-red-100 dark:bg-red-900/30 dark:text-red-400'"
|
|
x-text="product?.is_active ? 'Active' : 'Inactive'">
|
|
</span>
|
|
<span x-show="product?.is_featured" class="px-2 py-1 text-xs font-semibold rounded-full text-yellow-700 bg-yellow-100 dark:bg-yellow-900/30 dark:text-yellow-400">
|
|
Featured
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pricing Card -->
|
|
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Pricing
|
|
</h3>
|
|
<div class="grid gap-4 md:grid-cols-3">
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Price</p>
|
|
<p class="text-lg font-bold text-gray-700 dark:text-gray-200" x-text="formatPrice(product?.effective_price, product?.effective_currency)">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Availability</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="product?.availability || 'Not specified'">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Product Information Card -->
|
|
<div class="px-4 py-3 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Product Information
|
|
</h3>
|
|
<div class="grid gap-4 md:grid-cols-3">
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Brand</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="product?.brand || 'No brand'">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Product Type</p>
|
|
<div class="flex items-center gap-2">
|
|
<span class="px-2 py-1 text-xs font-semibold rounded-full"
|
|
:class="product?.is_digital ? 'text-blue-700 bg-blue-100 dark:bg-blue-900/30 dark:text-blue-400' : 'text-orange-700 bg-orange-100 dark:bg-orange-900/30 dark:text-orange-400'"
|
|
x-text="product?.is_digital ? 'Digital' : 'Physical'">
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Condition</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="product?.condition || 'Not specified'">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Product Identifiers Card -->
|
|
<div class="px-4 py-3 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Product Identifiers
|
|
</h3>
|
|
<div class="grid gap-4" :class="product?.marketplace_product_id ? 'md:grid-cols-4' : 'md:grid-cols-3'">
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Product ID</p>
|
|
<p class="text-sm font-mono text-gray-700 dark:text-gray-300" x-text="product?.id || '-'">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">GTIN/EAN</p>
|
|
<p class="text-sm font-mono text-gray-700 dark:text-gray-300" x-text="product?.gtin || product?.source_gtin || '-'">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Vendor SKU</p>
|
|
<p class="text-sm font-mono text-gray-700 dark:text-gray-300" x-text="product?.vendor_sku || '-'">-</p>
|
|
</div>
|
|
<!-- Source SKU - only for marketplace-sourced products -->
|
|
<div x-show="product?.marketplace_product_id">
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Source SKU</p>
|
|
<p class="text-sm font-mono text-gray-700 dark:text-gray-300" x-text="product?.source_sku || '-'">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Source Information Card - only for marketplace-sourced products -->
|
|
<template x-if="product?.marketplace_product_id">
|
|
<div class="px-4 py-3 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Source Information
|
|
</h3>
|
|
<a
|
|
:href="'/admin/marketplace-products/' + product?.marketplace_product_id"
|
|
class="flex items-center text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400">
|
|
<span>View Source</span>
|
|
<span x-html="$icon('arrow-right', 'w-4 h-4 ml-1')"></span>
|
|
</a>
|
|
</div>
|
|
<div class="grid gap-4 md:grid-cols-3">
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Marketplace</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="product?.source_marketplace || '-'">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Source Vendor</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="product?.source_vendor || '-'">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Marketplace Product ID</p>
|
|
<p class="text-sm font-mono text-gray-700 dark:text-gray-300" x-text="product?.marketplace_product_id || '-'">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Product Origin Card - for directly created products -->
|
|
<template x-if="!product?.marketplace_product_id">
|
|
<div class="px-4 py-3 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Product Origin
|
|
</h3>
|
|
<div class="flex items-center gap-3">
|
|
<span class="flex items-center justify-center w-10 h-10 rounded-full bg-blue-100 dark:bg-blue-900/30">
|
|
<span x-html="$icon('plus-circle', 'w-5 h-5 text-blue-600 dark:text-blue-400')"></span>
|
|
</span>
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-300">Direct Creation</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">This product was created directly in the vendor's catalog</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Description Card -->
|
|
<div class="px-4 py-3 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800" x-show="product?.title || product?.description">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Product Content
|
|
</h3>
|
|
<div class="space-y-4">
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-1">Title</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="product?.title || '-'">-</p>
|
|
</div>
|
|
<div x-show="product?.description">
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-1">Description</p>
|
|
<div class="text-sm text-gray-700 dark:text-gray-300 prose prose-sm dark:prose-invert max-w-none" x-html="product?.description || '-'"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Category Information -->
|
|
<div class="px-4 py-3 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800" x-show="product?.google_product_category || product?.category_path">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Categories
|
|
</h3>
|
|
<div class="space-y-3">
|
|
<div x-show="product?.google_product_category">
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Google Product Category</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="product?.google_product_category">-</p>
|
|
</div>
|
|
<div x-show="product?.category_path">
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Category Path</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="product?.category_path">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Timestamps -->
|
|
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Record Information
|
|
</h3>
|
|
<div class="grid gap-4 md:grid-cols-2">
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Added to Catalog</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="formatDate(product?.created_at)">-</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Last Updated</p>
|
|
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="formatDate(product?.updated_at)">-</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Confirm Remove Modal -->
|
|
{% call modal_simple('confirmRemoveModal', 'Remove from Catalog', show_var='showRemoveModal', size='sm') %}
|
|
<div class="space-y-4">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400">
|
|
Are you sure you want to remove this product from the vendor's catalog?
|
|
</p>
|
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-300" x-text="product?.title || 'Untitled'"></p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
This will not delete the source product from the marketplace repository.
|
|
</p>
|
|
|
|
<div class="flex items-center justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
|
|
<button
|
|
@click="showRemoveModal = false"
|
|
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
@click="executeRemove()"
|
|
:disabled="removing"
|
|
class="px-4 py-2 text-sm font-medium text-white bg-red-600 rounded-lg hover:bg-red-700 transition-colors disabled:opacity-50"
|
|
>
|
|
<span x-text="removing ? 'Removing...' : 'Remove'"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% endcall %}
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script src="{{ url_for('static', path='admin/js/vendor-product-detail.js') }}"></script>
|
|
{% endblock %}
|