Products Tab Changes:
- Converted to product listing page similar to /admin/marketplace-products
- Added Import/Export buttons in header
- Added product stats cards (total, active, inactive, last sync)
- Added search and filter functionality
- Added product table with pagination
- Import modal for single URL or all languages
Settings Tab Changes:
- Moved batch size setting from products tab
- Moved include inactive checkbox from products tab
- Added export behavior info box
Export Changes:
- New POST endpoint exports all languages (FR, DE, EN)
- CSV files written to exports/letzshop/{vendor_code}/ for scheduler pickup
- Letzshop scheduler can fetch files from this location
API Changes:
- Added vendor_id filter to /admin/vendor-products/stats endpoint
- Added POST /admin/vendors/{id}/export/letzshop for folder export
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
411 lines
23 KiB
HTML
411 lines
23 KiB
HTML
{# app/templates/admin/partials/letzshop-settings-tab.html #}
|
|
{# Settings tab for admin Letzshop management - API credentials, CSV URLs, Import/Export settings #}
|
|
{% from 'shared/macros/inputs.html' import number_stepper %}
|
|
|
|
<div class="grid gap-6 lg:grid-cols-2">
|
|
<!-- API Configuration Card -->
|
|
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-6">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Letzshop API Configuration
|
|
</h3>
|
|
|
|
<form @submit.prevent="saveCredentials()">
|
|
<!-- API Key -->
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
API Key <span class="text-red-500">*</span>
|
|
</label>
|
|
<div class="relative">
|
|
<input
|
|
:type="showApiKey ? 'text' : 'password'"
|
|
x-model="settingsForm.api_key"
|
|
:placeholder="credentials ? credentials.api_key_masked : 'Enter Letzshop API key'"
|
|
class="block w-full px-3 py-2 pr-10 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 focus:shadow-outline-purple"
|
|
/>
|
|
<button
|
|
type="button"
|
|
@click="showApiKey = !showApiKey"
|
|
class="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 hover:text-gray-600"
|
|
>
|
|
<span x-html="$icon(showApiKey ? 'eye-off' : 'eye', 'w-4 h-4')"></span>
|
|
</button>
|
|
</div>
|
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
Get your API key from the Letzshop merchant portal
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Test Mode -->
|
|
<div class="mb-4">
|
|
<label class="flex items-center cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
x-model="settingsForm.test_mode_enabled"
|
|
class="form-checkbox h-5 w-5 text-orange-600 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:ring-orange-500"
|
|
/>
|
|
<span class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-400">Test Mode</span>
|
|
</label>
|
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 ml-8">
|
|
When enabled, operations (confirm, reject, tracking) will NOT be sent to Letzshop API
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Test Mode Warning -->
|
|
<div x-show="settingsForm.test_mode_enabled" class="mb-4 p-3 bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg">
|
|
<div class="flex items-center">
|
|
<span x-html="$icon('exclamation', 'w-5 h-5 text-orange-500 mr-2')"></span>
|
|
<span class="text-sm text-orange-700 dark:text-orange-300 font-medium">Test Mode Active</span>
|
|
</div>
|
|
<p class="mt-1 text-xs text-orange-600 dark:text-orange-400 ml-7">
|
|
All Letzshop API mutations are disabled. Orders can be imported but confirmations/rejections will only be saved locally.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Auto Sync -->
|
|
<div class="mb-4">
|
|
<label class="flex items-center cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
x-model="settingsForm.auto_sync_enabled"
|
|
class="form-checkbox h-5 w-5 text-purple-600 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:ring-purple-500"
|
|
/>
|
|
<span class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-400">Enable Auto-Sync</span>
|
|
</label>
|
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 ml-8">
|
|
Automatically import new orders periodically
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Sync Interval -->
|
|
<div class="mb-6" x-show="settingsForm.auto_sync_enabled">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
Sync Interval
|
|
</label>
|
|
<select
|
|
x-model="settingsForm.sync_interval_minutes"
|
|
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"
|
|
>
|
|
<option value="15">Every 15 minutes</option>
|
|
<option value="30">Every 30 minutes</option>
|
|
<option value="60">Every hour</option>
|
|
<option value="120">Every 2 hours</option>
|
|
<option value="360">Every 6 hours</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Last Sync Info -->
|
|
<div x-show="credentials" class="mb-6 p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Last Sync</h4>
|
|
<div class="grid gap-2 text-sm text-gray-600 dark:text-gray-400">
|
|
<p>
|
|
<span class="font-medium">Status:</span>
|
|
<span
|
|
class="ml-2 px-2 py-0.5 text-xs rounded-full"
|
|
:class="{
|
|
'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300': credentials?.last_sync_status === 'success',
|
|
'bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300': credentials?.last_sync_status === 'partial',
|
|
'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300': credentials?.last_sync_status === 'failed',
|
|
'bg-gray-100 text-gray-700 dark:bg-gray-600 dark:text-gray-300': !credentials?.last_sync_status
|
|
}"
|
|
x-text="credentials?.last_sync_status || 'Never'"
|
|
></span>
|
|
</p>
|
|
<p x-show="credentials?.last_sync_at">
|
|
<span class="font-medium">Time:</span>
|
|
<span class="ml-2" x-text="formatDate(credentials?.last_sync_at)"></span>
|
|
</p>
|
|
<p x-show="credentials?.last_sync_error" class="text-red-600 dark:text-red-400">
|
|
<span class="font-medium">Error:</span>
|
|
<span class="ml-2" x-text="credentials?.last_sync_error"></span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="flex flex-wrap gap-3">
|
|
<button
|
|
type="submit"
|
|
:disabled="savingCredentials"
|
|
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 disabled:opacity-50"
|
|
>
|
|
<span x-show="!savingCredentials" x-html="$icon('save', 'w-4 h-4 mr-2')"></span>
|
|
<span x-show="savingCredentials" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
|
<span x-text="savingCredentials ? 'Saving...' : 'Save Credentials'"></span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
@click="testConnection()"
|
|
:disabled="testingConnection || !letzshopStatus.is_configured"
|
|
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-800 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none disabled:opacity-50"
|
|
>
|
|
<span x-show="!testingConnection" x-html="$icon('lightning-bolt', 'w-4 h-4 mr-2')"></span>
|
|
<span x-show="testingConnection" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
|
<span x-text="testingConnection ? 'Testing...' : 'Test Connection'"></span>
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
x-show="credentials"
|
|
@click="deleteCredentials()"
|
|
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-red-600 transition-colors duration-150 bg-white dark:bg-gray-800 border border-red-300 rounded-lg hover:bg-red-50 dark:hover:bg-red-900/20 focus:outline-none"
|
|
>
|
|
<span x-html="$icon('trash', 'w-4 h-4 mr-2')"></span>
|
|
Remove
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CSV URLs Card -->
|
|
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-6">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Letzshop CSV URLs
|
|
</h3>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">
|
|
Configure the CSV feed URLs for product imports. These URLs are used for quick-fill in the Products tab.
|
|
</p>
|
|
|
|
<form @submit.prevent="saveCsvUrls()">
|
|
<!-- French CSV URL -->
|
|
<div class="mb-4">
|
|
<label class="flex items-center text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
<span class="fi fi-fr mr-2"></span>
|
|
French CSV URL
|
|
</label>
|
|
<input
|
|
x-model="settingsForm.letzshop_csv_url_fr"
|
|
type="url"
|
|
placeholder="https://letzshop.lu/feeds/products_fr.csv"
|
|
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 focus:shadow-outline-purple"
|
|
/>
|
|
</div>
|
|
|
|
<!-- English CSV URL -->
|
|
<div class="mb-4">
|
|
<label class="flex items-center text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
<span class="fi fi-gb mr-2"></span>
|
|
English CSV URL
|
|
</label>
|
|
<input
|
|
x-model="settingsForm.letzshop_csv_url_en"
|
|
type="url"
|
|
placeholder="https://letzshop.lu/feeds/products_en.csv"
|
|
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 focus:shadow-outline-purple"
|
|
/>
|
|
</div>
|
|
|
|
<!-- German CSV URL -->
|
|
<div class="mb-6">
|
|
<label class="flex items-center text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
<span class="fi fi-de mr-2"></span>
|
|
German CSV URL
|
|
</label>
|
|
<input
|
|
x-model="settingsForm.letzshop_csv_url_de"
|
|
type="url"
|
|
placeholder="https://letzshop.lu/feeds/products_de.csv"
|
|
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 focus:shadow-outline-purple"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Save Button -->
|
|
<button
|
|
type="submit"
|
|
:disabled="savingCsvUrls"
|
|
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 disabled:opacity-50"
|
|
>
|
|
<span x-show="!savingCsvUrls" x-html="$icon('save', 'w-4 h-4 mr-2')"></span>
|
|
<span x-show="savingCsvUrls" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
|
<span x-text="savingCsvUrls ? 'Saving...' : 'Save CSV URLs'"></span>
|
|
</button>
|
|
</form>
|
|
|
|
<!-- Info Box -->
|
|
<div class="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
|
<div class="flex">
|
|
<span x-html="$icon('information-circle', 'w-5 h-5 text-blue-500 mr-2 flex-shrink-0')"></span>
|
|
<div class="text-sm text-blue-700 dark:text-blue-300">
|
|
<p class="font-medium">About CSV URLs</p>
|
|
<p class="mt-1">These URLs should point to the vendor's product feed on Letzshop. The feed is typically provided by Letzshop as part of the merchant integration.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Import/Export Settings Card -->
|
|
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-6">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Import / Export Settings
|
|
</h3>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">
|
|
Configure settings for product import and export operations.
|
|
</p>
|
|
|
|
<!-- Import Settings -->
|
|
<div class="mb-6">
|
|
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3 flex items-center">
|
|
<span x-html="$icon('cloud-download', 'w-4 h-4 mr-2 text-purple-500')"></span>
|
|
Import Settings
|
|
</h4>
|
|
<div class="pl-6">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
Batch Size
|
|
</label>
|
|
{{ number_stepper(model='importForm.batch_size', min=100, max=5000, step=100, label='Batch Size') }}
|
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
Products processed per batch (100-5000). Higher = faster but more memory.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Export Settings -->
|
|
<div class="border-t border-gray-200 dark:border-gray-600 pt-6">
|
|
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3 flex items-center">
|
|
<span x-html="$icon('upload', 'w-4 h-4 mr-2 text-green-500')"></span>
|
|
Export Settings
|
|
</h4>
|
|
<div class="pl-6">
|
|
<label class="flex items-center cursor-pointer">
|
|
<input
|
|
type="checkbox"
|
|
x-model="exportIncludeInactive"
|
|
class="form-checkbox h-5 w-5 text-purple-600 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:ring-purple-500"
|
|
/>
|
|
<span class="ml-3 text-sm font-medium text-gray-700 dark:text-gray-400">Include inactive products</span>
|
|
</label>
|
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 ml-8">
|
|
Export products that are currently marked as inactive
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Export Info Box -->
|
|
<div class="mt-6 p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Export Behavior</h4>
|
|
<ul class="text-xs text-gray-500 dark:text-gray-400 space-y-1">
|
|
<li class="flex items-center">
|
|
<span x-html="$icon('check', 'w-3 h-3 mr-2 text-green-500')"></span>
|
|
Exports all languages (FR, DE, EN) automatically
|
|
</li>
|
|
<li class="flex items-center">
|
|
<span x-html="$icon('check', 'w-3 h-3 mr-2 text-green-500')"></span>
|
|
CSV files are placed in a folder for Letzshop pickup
|
|
</li>
|
|
<li class="flex items-center">
|
|
<span x-html="$icon('check', 'w-3 h-3 mr-2 text-green-500')"></span>
|
|
Letzshop scheduler fetches files periodically
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Carrier Settings Card -->
|
|
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800 lg:col-span-2">
|
|
<div class="p-6">
|
|
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
Carrier Settings
|
|
</h3>
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">
|
|
Configure default carrier and label URL prefixes for shipping labels.
|
|
</p>
|
|
|
|
<form @submit.prevent="saveCarrierSettings()">
|
|
<div class="grid gap-6 lg:grid-cols-2">
|
|
<!-- Default Carrier -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
Default Carrier
|
|
</label>
|
|
<select
|
|
x-model="settingsForm.default_carrier"
|
|
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"
|
|
>
|
|
<option value="">-- Select carrier --</option>
|
|
<option value="greco">Greco</option>
|
|
<option value="colissimo">Colissimo</option>
|
|
<option value="xpresslogistics">XpressLogistics</option>
|
|
</select>
|
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
Letzshop automatically assigns carriers based on shipment data
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Placeholder for alignment -->
|
|
<div></div>
|
|
|
|
<!-- Greco Label URL -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
<span class="inline-flex items-center">
|
|
<span class="w-3 h-3 rounded-full bg-blue-500 mr-2"></span>
|
|
Greco Label URL Prefix
|
|
</span>
|
|
</label>
|
|
<input
|
|
x-model="settingsForm.carrier_greco_label_url"
|
|
type="url"
|
|
placeholder="https://dispatchweb.fr/Tracky/Home/"
|
|
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"
|
|
/>
|
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
Label URL = Prefix + Shipment Number (e.g., H74683403433)
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Colissimo Label URL -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
<span class="inline-flex items-center">
|
|
<span class="w-3 h-3 rounded-full bg-yellow-500 mr-2"></span>
|
|
Colissimo Label URL Prefix
|
|
</span>
|
|
</label>
|
|
<input
|
|
x-model="settingsForm.carrier_colissimo_label_url"
|
|
type="url"
|
|
placeholder="https://..."
|
|
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"
|
|
/>
|
|
</div>
|
|
|
|
<!-- XpressLogistics Label URL -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-2">
|
|
<span class="inline-flex items-center">
|
|
<span class="w-3 h-3 rounded-full bg-green-500 mr-2"></span>
|
|
XpressLogistics Label URL Prefix
|
|
</span>
|
|
</label>
|
|
<input
|
|
x-model="settingsForm.carrier_xpresslogistics_label_url"
|
|
type="url"
|
|
placeholder="https://..."
|
|
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"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Save Button -->
|
|
<div class="mt-6">
|
|
<button
|
|
type="submit"
|
|
:disabled="savingCarrierSettings"
|
|
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 disabled:opacity-50"
|
|
>
|
|
<span x-show="!savingCarrierSettings" x-html="$icon('save', 'w-4 h-4 mr-2')"></span>
|
|
<span x-show="savingCarrierSettings" x-html="$icon('spinner', 'w-4 h-4 mr-2')"></span>
|
|
<span x-text="savingCarrierSettings ? 'Saving...' : 'Save Carrier Settings'"></span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|