feat: add Letzshop vendor directory with sync and admin management
- Add LetzshopVendorCache model to store cached vendor data from Letzshop API - Create LetzshopVendorSyncService for syncing vendor directory - Add Celery task for background vendor sync - Create admin page at /admin/letzshop/vendor-directory with: - Stats dashboard (total, claimed, unclaimed vendors) - Searchable/filterable vendor list - "Sync Now" button to trigger sync - Ability to create platform vendors from Letzshop cache - Add API endpoints for vendor directory management - Add Pydantic schemas for API responses Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
430
app/templates/admin/letzshop-vendor-directory.html
Normal file
430
app/templates/admin/letzshop-vendor-directory.html
Normal file
@@ -0,0 +1,430 @@
|
||||
{# app/templates/admin/letzshop-vendor-directory.html #}
|
||||
{% extends "admin/base.html" %}
|
||||
{% from 'shared/macros/alerts.html' import alert_dynamic, error_state %}
|
||||
{% from 'shared/macros/headers.html' import page_header_flex, refresh_button %}
|
||||
{% from 'shared/macros/pagination.html' import pagination_controls %}
|
||||
|
||||
{% block title %}Letzshop Vendor Directory{% endblock %}
|
||||
{% block alpine_data %}letzshopVendorDirectory(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
{% call page_header_flex(title='Letzshop Vendor Directory', subtitle='Browse and import vendors from Letzshop marketplace') %}
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
@click="triggerSync()"
|
||||
:disabled="syncing"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg transition-colors"
|
||||
>
|
||||
<span x-show="!syncing" x-html="$icon('arrow-path', 'w-4 h-4 mr-2')"></span>
|
||||
<span x-show="syncing" class="w-4 h-4 mr-2 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
|
||||
<span x-text="syncing ? 'Syncing...' : 'Sync from Letzshop'"></span>
|
||||
</button>
|
||||
{{ refresh_button(loading_var='loading', onclick='loadVendors()', variant='secondary') }}
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
<!-- Success/Error Messages -->
|
||||
<div x-show="successMessage" x-transition class="mb-6 p-4 bg-green-100 dark:bg-green-900/30 border border-green-400 dark:border-green-600 text-green-700 dark:text-green-300 rounded-lg flex items-center">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5 mr-3 flex-shrink-0')"></span>
|
||||
<span x-text="successMessage"></span>
|
||||
<button @click="successMessage = ''" class="ml-auto">
|
||||
<span x-html="$icon('x', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{ error_state('Error', show_condition='error && !loading') }}
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Total Vendors</p>
|
||||
<p class="text-2xl font-bold text-gray-900 dark:text-white" x-text="stats.total_vendors || 0"></p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-blue-100 dark:bg-blue-900/30 rounded-lg flex items-center justify-center">
|
||||
<span x-html="$icon('building-storefront', 'w-5 h-5 text-blue-600 dark:text-blue-400')"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Active</p>
|
||||
<p class="text-2xl font-bold text-green-600 dark:text-green-400" x-text="stats.active_vendors || 0"></p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-green-100 dark:bg-green-900/30 rounded-lg flex items-center justify-center">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5 text-green-600 dark:text-green-400')"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Claimed</p>
|
||||
<p class="text-2xl font-bold text-purple-600 dark:text-purple-400" x-text="stats.claimed_vendors || 0"></p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center">
|
||||
<span x-html="$icon('user-check', 'w-5 h-5 text-purple-600 dark:text-purple-400')"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Unclaimed</p>
|
||||
<p class="text-2xl font-bold text-amber-600 dark:text-amber-400" x-text="stats.unclaimed_vendors || 0"></p>
|
||||
</div>
|
||||
<div class="w-10 h-10 bg-amber-100 dark:bg-amber-900/30 rounded-lg flex items-center justify-center">
|
||||
<span x-html="$icon('user-plus', 'w-5 h-5 text-amber-600 dark:text-amber-400')"></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2" x-show="stats.last_synced_at">
|
||||
Last sync: <span x-text="formatDate(stats.last_synced_at)"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 mb-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<!-- Search -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Search</label>
|
||||
<input
|
||||
type="text"
|
||||
x-model="filters.search"
|
||||
@input.debounce.300ms="loadVendors()"
|
||||
placeholder="Search by name..."
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
</div>
|
||||
<!-- City -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">City</label>
|
||||
<input
|
||||
type="text"
|
||||
x-model="filters.city"
|
||||
@input.debounce.300ms="loadVendors()"
|
||||
placeholder="Filter by city..."
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
</div>
|
||||
<!-- Category -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Category</label>
|
||||
<input
|
||||
type="text"
|
||||
x-model="filters.category"
|
||||
@input.debounce.300ms="loadVendors()"
|
||||
placeholder="Filter by category..."
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
</div>
|
||||
<!-- Only Unclaimed -->
|
||||
<div class="flex items-end">
|
||||
<label class="inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
x-model="filters.only_unclaimed"
|
||||
@change="loadVendors()"
|
||||
class="sr-only peer"
|
||||
>
|
||||
<div class="relative w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-purple-300 dark:peer-focus:ring-purple-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-purple-600"></div>
|
||||
<span class="ms-3 text-sm font-medium text-gray-700 dark:text-gray-300">Only Unclaimed</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div x-show="loading" class="flex justify-center items-center py-12">
|
||||
<div class="w-8 h-8 border-4 border-purple-600 border-t-transparent rounded-full animate-spin"></div>
|
||||
</div>
|
||||
|
||||
<!-- Vendors Table -->
|
||||
<div x-show="!loading" x-cloak class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<!-- Empty State -->
|
||||
<div x-show="vendors.length === 0" class="text-center py-12">
|
||||
<span x-html="$icon('building-storefront', 'w-12 h-12 mx-auto text-gray-400')"></span>
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-white">No vendors found</h3>
|
||||
<p class="mt-2 text-gray-500 dark:text-gray-400">
|
||||
<span x-show="stats.total_vendors === 0">Click "Sync from Letzshop" to import vendors.</span>
|
||||
<span x-show="stats.total_vendors > 0">Try adjusting your filters.</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Table -->
|
||||
<div x-show="vendors.length > 0" class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<thead class="bg-gray-50 dark:bg-gray-900/50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Vendor</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Contact</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Location</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Categories</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Status</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<template x-for="vendor in vendors" :key="vendor.id">
|
||||
<tr class="hover:bg-gray-50 dark:hover:bg-gray-700/50 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 w-10 h-10 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center">
|
||||
<span class="text-sm font-semibold text-purple-600 dark:text-purple-400" x-text="vendor.name?.charAt(0).toUpperCase()"></span>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="text-sm font-medium text-gray-900 dark:text-white" x-text="vendor.name"></div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400" x-text="vendor.company_name"></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="text-sm text-gray-900 dark:text-white" x-text="vendor.email || '-'"></div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400" x-text="vendor.phone || ''"></div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="text-sm text-gray-900 dark:text-white" x-text="vendor.city || '-'"></div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<template x-for="cat in (vendor.categories || []).slice(0, 2)" :key="cat">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200" x-text="cat"></span>
|
||||
</template>
|
||||
<span x-show="(vendor.categories || []).length > 2" class="text-xs text-gray-500">+<span x-text="vendor.categories.length - 2"></span></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<span
|
||||
x-show="vendor.is_claimed"
|
||||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300"
|
||||
>
|
||||
<span x-html="$icon('check', 'w-3 h-3 mr-1')"></span>
|
||||
Claimed
|
||||
</span>
|
||||
<span
|
||||
x-show="!vendor.is_claimed"
|
||||
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300"
|
||||
>
|
||||
Available
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<a
|
||||
:href="vendor.letzshop_url"
|
||||
target="_blank"
|
||||
class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
title="View on Letzshop"
|
||||
>
|
||||
<span x-html="$icon('arrow-top-right-on-square', 'w-5 h-5')"></span>
|
||||
</a>
|
||||
<button
|
||||
@click="showVendorDetail(vendor)"
|
||||
class="text-purple-600 hover:text-purple-800 dark:text-purple-400 dark:hover:text-purple-300"
|
||||
title="View Details"
|
||||
>
|
||||
<span x-html="$icon('eye', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
<button
|
||||
x-show="!vendor.is_claimed"
|
||||
@click="openCreateVendorModal(vendor)"
|
||||
class="text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-300"
|
||||
title="Create Platform Vendor"
|
||||
>
|
||||
<span x-html="$icon('plus-circle', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div x-show="vendors.length > 0" class="px-6 py-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Showing <span x-text="((page - 1) * limit) + 1"></span> to <span x-text="Math.min(page * limit, total)"></span> of <span x-text="total"></span> vendors
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
@click="page--; loadVendors()"
|
||||
:disabled="page <= 1"
|
||||
class="px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<span class="px-3 py-1 text-sm">Page <span x-text="page"></span></span>
|
||||
<button
|
||||
@click="page++; loadVendors()"
|
||||
:disabled="!hasMore"
|
||||
class="px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Vendor Detail Modal -->
|
||||
<div
|
||||
x-show="showDetailModal"
|
||||
x-cloak
|
||||
class="fixed inset-0 z-50 overflow-y-auto"
|
||||
@keydown.escape.window="showDetailModal = false"
|
||||
>
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
||||
<div x-show="showDetailModal" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="fixed inset-0 bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-75" @click="showDetailModal = false"></div>
|
||||
|
||||
<div x-show="showDetailModal" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" class="relative inline-block w-full max-w-2xl p-6 my-8 text-left align-middle bg-white dark:bg-gray-800 rounded-xl shadow-xl">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white" x-text="selectedVendor?.name"></h3>
|
||||
<button @click="showDetailModal = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<span x-html="$icon('x', 'w-6 h-6')"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div x-show="selectedVendor" class="space-y-4">
|
||||
<!-- Company Name -->
|
||||
<div x-show="selectedVendor?.company_name">
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Company</p>
|
||||
<p class="text-gray-900 dark:text-white" x-text="selectedVendor?.company_name"></p>
|
||||
</div>
|
||||
|
||||
<!-- Contact -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Email</p>
|
||||
<p class="text-gray-900 dark:text-white" x-text="selectedVendor?.email || '-'"></p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Phone</p>
|
||||
<p class="text-gray-900 dark:text-white" x-text="selectedVendor?.phone || '-'"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Address</p>
|
||||
<p class="text-gray-900 dark:text-white">
|
||||
<span x-text="selectedVendor?.city || '-'"></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Categories -->
|
||||
<div x-show="selectedVendor?.categories?.length">
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400 mb-2">Categories</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<template x-for="cat in (selectedVendor?.categories || [])" :key="cat">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300" x-text="cat"></span>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Website -->
|
||||
<div x-show="selectedVendor?.website">
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Website</p>
|
||||
<a :href="selectedVendor?.website" target="_blank" class="text-purple-600 hover:text-purple-800 dark:text-purple-400" x-text="selectedVendor?.website"></a>
|
||||
</div>
|
||||
|
||||
<!-- Letzshop URL -->
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">Letzshop Page</p>
|
||||
<a :href="selectedVendor?.letzshop_url" target="_blank" class="text-purple-600 hover:text-purple-800 dark:text-purple-400" x-text="selectedVendor?.letzshop_url"></a>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="pt-4 border-t border-gray-200 dark:border-gray-700 flex justify-end gap-3">
|
||||
<button @click="showDetailModal = false" class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg">
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
x-show="!selectedVendor?.is_claimed"
|
||||
@click="showDetailModal = false; openCreateVendorModal(selectedVendor)"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 rounded-lg"
|
||||
>
|
||||
Create Vendor
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Vendor Modal -->
|
||||
<div
|
||||
x-show="showCreateModal"
|
||||
x-cloak
|
||||
class="fixed inset-0 z-50 overflow-y-auto"
|
||||
@keydown.escape.window="showCreateModal = false"
|
||||
>
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
||||
<div x-show="showCreateModal" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" class="fixed inset-0 bg-gray-500 dark:bg-gray-900 bg-opacity-75 dark:bg-opacity-75" @click="showCreateModal = false"></div>
|
||||
|
||||
<div x-show="showCreateModal" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" class="relative inline-block w-full max-w-md p-6 my-8 text-left align-middle bg-white dark:bg-gray-800 rounded-xl shadow-xl">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">Create Vendor from Letzshop</h3>
|
||||
<button @click="showCreateModal = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<span x-html="$icon('x', 'w-6 h-6')"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Create a platform vendor from <strong x-text="createVendorData?.name"></strong>
|
||||
</p>
|
||||
|
||||
<!-- Company Selection -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Select Company <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
x-model="createVendorData.company_id"
|
||||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="">-- Select a company --</option>
|
||||
<template x-for="company in companies" :key="company.id">
|
||||
<option :value="company.id" x-text="company.name"></option>
|
||||
</template>
|
||||
</select>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">The vendor will be created under this company</p>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div x-show="createError" class="p-3 bg-red-100 dark:bg-red-900/30 border border-red-400 dark:border-red-600 text-red-700 dark:text-red-300 rounded-lg text-sm">
|
||||
<span x-text="createError"></span>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="pt-4 flex justify-end gap-3">
|
||||
<button @click="showCreateModal = false" class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="createVendor()"
|
||||
:disabled="!createVendorData.company_id || creating"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed rounded-lg"
|
||||
>
|
||||
<span x-show="!creating">Create Vendor</span>
|
||||
<span x-show="creating" class="flex items-center">
|
||||
<span class="w-4 h-4 mr-2 border-2 border-white border-t-transparent rounded-full animate-spin"></span>
|
||||
Creating...
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_scripts %}
|
||||
<script src="{{ url_for('static', path='admin/js/letzshop-vendor-directory.js') }}"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user