feat: enhance headers and modals shared macros

- headers.html: Add new header layout macros
- modals.html: Improve modal component styling and functionality

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-07 17:04:34 +01:00
parent 95a8ffc645
commit 2ec150e8a5
2 changed files with 339 additions and 2 deletions

View File

@@ -6,12 +6,12 @@
Usage:
{% from 'shared/macros/modals.html' import modal, confirm_modal, form_modal %}
{# Basic modal #}
Basic modal:
{% call modal('editModal', 'Edit User', 'isEditModalOpen') %}
<p>Modal content here</p>
{% endcall %}
{# Confirmation modal #}
Confirmation modal:
{{ confirm_modal('deleteModal', 'Delete User', 'Are you sure?', 'deleteUser()', 'isDeleteModalOpen') }}
Required Alpine.js:
@@ -475,3 +475,139 @@
</div>
</div>
{% endmacro %}
{#
Job Details Modal
=================
A mobile-friendly modal for displaying import job details.
Used in marketplace and imports admin pages.
Parameters:
- show_var: Alpine.js variable controlling visibility (default: 'showJobModal')
- job_var: Alpine.js variable containing the job data (default: 'selectedJob')
- close_action: Alpine.js action to close modal (default: 'closeJobModal()')
- get_vendor_name: Function to get vendor name from ID (default: 'getVendorName')
- show_created_by: Whether to show Created By field (default: false)
Required Alpine.js state:
- showJobModal: boolean
- selectedJob: object with job data
- closeJobModal(): function to close and clear
- getVendorName(id): function to resolve vendor name
- formatDate(date): function to format dates
#}
{% macro job_details_modal(show_var='showJobModal', job_var='selectedJob', close_action='closeJobModal()', get_vendor_name='getVendorName', show_created_by=false) %}
<div x-show="{{ show_var }}"
x-cloak
@click.away="{{ close_action }}"
class="fixed inset-0 z-30 flex items-end bg-black bg-opacity-50 sm:items-center sm:justify-center"
x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0">
<div @click.away="{{ close_action }}"
class="w-full px-6 py-4 overflow-hidden bg-white rounded-t-lg dark:bg-gray-800 sm:rounded-lg sm:m-4 sm:max-w-2xl"
x-transition:enter="transition ease-out duration-150"
x-transition:enter-start="opacity-0 transform translate-y-1/2"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0 transform translate-y-1/2">
{# Modal Header #}
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
Import Job Details
</h3>
<button @click="{{ close_action }}" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<span x-html="$icon('close', 'w-5 h-5')"></span>
</button>
</div>
{# Modal Content #}
<div x-show="{{ job_var }}" class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Job ID</p>
<p class="text-sm text-gray-900 dark:text-gray-100" x-text="{{ job_var }}?.id"></p>
</div>
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Vendor</p>
<p class="text-sm text-gray-900 dark:text-gray-100" x-text="{{ get_vendor_name }}({{ job_var }}?.vendor_id)"></p>
</div>
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Marketplace</p>
<p class="text-sm text-gray-900 dark:text-gray-100" x-text="{{ job_var }}?.marketplace"></p>
</div>
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Status</p>
<span class="px-2 py-1 font-semibold leading-tight rounded-full text-xs"
:class="{
'text-green-700 bg-green-100': {{ job_var }}?.status === 'completed',
'text-blue-700 bg-blue-100': {{ job_var }}?.status === 'processing',
'text-yellow-700 bg-yellow-100': {{ job_var }}?.status === 'pending',
'text-red-700 bg-red-100': {{ job_var }}?.status === 'failed',
'text-orange-700 bg-orange-100': {{ job_var }}?.status === 'completed_with_errors'
}"
x-text="{{ job_var }}?.status.replace('_', ' ').toUpperCase()">
</span>
</div>
<div class="col-span-2">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Source URL</p>
<p class="text-sm text-gray-900 dark:text-gray-100 break-all" x-text="{{ job_var }}?.source_url"></p>
</div>
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Imported</p>
<p class="text-sm text-green-600 dark:text-green-400" x-text="{{ job_var }}?.imported_count"></p>
</div>
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Updated</p>
<p class="text-sm text-blue-600 dark:text-blue-400" x-text="{{ job_var }}?.updated_count"></p>
</div>
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Errors</p>
<p class="text-sm text-red-600 dark:text-red-400" x-text="{{ job_var }}?.error_count"></p>
</div>
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Total Processed</p>
<p class="text-sm text-gray-900 dark:text-gray-100" x-text="{{ job_var }}?.total_processed"></p>
</div>
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Started At</p>
<p class="text-sm text-gray-900 dark:text-gray-100" x-text="{{ job_var }}?.started_at ? formatDate({{ job_var }}.started_at) : 'Not started'"></p>
</div>
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Completed At</p>
<p class="text-sm text-gray-900 dark:text-gray-100" x-text="{{ job_var }}?.completed_at ? formatDate({{ job_var }}.completed_at) : 'Not completed'"></p>
</div>
{% if show_created_by %}
<div>
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">Created By</p>
<p class="text-sm text-gray-900 dark:text-gray-100" x-text="{{ job_var }}?.created_by_name || 'System'"></p>
</div>
{% endif %}
</div>
{# Error Details #}
<div x-show="{{ job_var }}?.error_details?.length > 0" class="mt-4">
<p class="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">Error Details</p>
<div class="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg max-h-48 overflow-y-auto">
<pre class="text-xs text-red-700 dark:text-red-300 whitespace-pre-wrap" x-text="JSON.stringify({{ job_var }}?.error_details, null, 2)"></pre>
</div>
</div>
</div>
{# Modal Footer #}
<div class="flex justify-end mt-6">
<button
@click="{{ close_action }}"
class="px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 border border-gray-300 rounded-lg dark:text-gray-400 dark:border-gray-700 hover:border-gray-500 focus:outline-none"
>
Close
</button>
</div>
</div>
</div>
{% endmacro %}