Files
orion/app/templates/admin/partials/letzshop-settings-tab.html
Samir Boulahtit 45b09d6d90 feat: add unified admin Marketplace Letzshop page
- Add new Marketplace section in admin sidebar with Letzshop sub-item
- Remove old Import and Letzshop Orders items from Product Catalog
- Create unified Letzshop management page with 3 tabs:
  - Products tab: Import/Export functionality
  - Orders tab: Order management with confirm/reject/tracking
  - Settings tab: API credentials and CSV URLs
- Add unified jobs table showing imports, exports, and order syncs
- Implement vendor autocomplete using Tom Select library (CDN + fallback)
- Add /vendors/{vendor_id}/jobs API endpoint for unified job listing
- Move database queries to service layer (LetzshopOrderService)
- Add LetzshopJobItem and LetzshopJobsListResponse schemas
- Include Tom Select CSS/JS assets as local fallback

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 18:27:04 +01:00

213 lines
12 KiB
HTML

{# app/templates/admin/partials/letzshop-settings-tab.html #}
{# Settings tab for admin Letzshop management - API credentials and CSV URLs #}
<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>
<!-- 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>
</div>