Files
orion/app/templates/admin/partials/letzshop-settings-tab.html
Samir Boulahtit d46b676e77 feat: redesign Letzshop products tab with product listing view
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>
2025-12-20 21:44:59 +01:00

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>