feat(admin): separate platform CRUD from CMS, add entity selector macro
Some checks failed
CI / ruff (push) Successful in 11s
CI / docs (push) Has been cancelled
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

- Move platforms menu from CMS to Platform Admin section with create/edit
- Add platform create page, API endpoint, and service method
- Remove CMS-specific content from platform list and detail pages
- Create shared entity_selector + entity_selected_badge Jinja macros
- Create entity-selector.js generalizing store-selector.js for any entity
- Add Tom Select merchant filter to stores page with localStorage persistence
- Migrate store-products page to use shared macros (remove 53 lines of duped CSS)
- Fix broken icons: puzzle→puzzle-piece, building-storefront→store, language→translate, server→cube

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 22:40:15 +01:00
parent fa758b7e31
commit 45260b6b82
22 changed files with 943 additions and 267 deletions

View File

@@ -284,6 +284,102 @@
{% endmacro %}
{#
Entity Selector (Tom Select)
============================
A generic async searchable entity selector using Tom Select.
Works for stores, merchants, platforms, or any entity with a search API.
Includes Tom Select dark mode CSS overrides (no need to add per page).
Parameters:
- ref_name: Alpine.js x-ref name for the select element (default: 'entitySelect')
- id: HTML id attribute (default: 'entity-select')
- placeholder: Placeholder text (default: 'Search...')
- width: CSS width class (default: 'w-80')
- label: Accessible label (default: 'Entity selector')
Usage:
{% from 'shared/macros/inputs.html' import entity_selector, entity_selected_badge %}
{{ entity_selector(ref_name='merchantSelect', id='merchant-select', placeholder='Filter by merchant...') }}
{{ entity_selected_badge(entity_var='selectedMerchant', clear_fn='clearMerchantFilter()', color='blue') }}
#}
{% macro entity_selector(
ref_name='entitySelect',
id='entity-select',
placeholder='Search...',
width='w-80',
label='Entity selector'
) %}
{# Dark mode CSS is in admin/base.html — no need to duplicate here #}
<div class="{{ width }}">
<select
id="{{ id }}"
x-ref="{{ ref_name }}"
placeholder="{{ placeholder }}"
aria-label="{{ label }}"
></select>
</div>
{% endmacro %}
{#
Entity Selected Badge
=====================
Displays the currently selected entity as a badge with avatar, name, optional code, and clear button.
Parameters:
- entity_var: Alpine.js variable name holding the selected entity (e.g. 'selectedStore')
- clear_fn: Alpine.js function to call on clear (e.g. 'clearStoreFilter()')
- name_field: Field name for entity name (default: 'name')
- code_field: Field name for secondary code display (default: None, omitted if None)
- color: Color scheme - 'purple', 'blue', 'teal', 'green' (default: 'purple')
Usage:
{{ entity_selected_badge(
entity_var='selectedStore',
clear_fn='clearStoreFilter()',
code_field='store_code',
color='purple'
) }}
#}
{% macro entity_selected_badge(
entity_var='selectedEntity',
clear_fn='clearEntityFilter()',
name_field='name',
code_field=None,
color='purple'
) %}
{% set bg = 'bg-' ~ color ~ '-50 dark:bg-' ~ color ~ '-900/20' %}
{% set border = 'border-' ~ color ~ '-200 dark:border-' ~ color ~ '-800' %}
{% set avatar_bg = 'bg-' ~ color ~ '-100 dark:bg-' ~ color ~ '-900' %}
{% set avatar_text = 'text-' ~ color ~ '-600 dark:text-' ~ color ~ '-300' %}
{% set name_text = 'text-' ~ color ~ '-800 dark:text-' ~ color ~ '-200' %}
{% set code_text = 'text-' ~ color ~ '-600 dark:text-' ~ color ~ '-400' %}
{% set btn_text = 'text-' ~ color ~ '-600 dark:text-' ~ color ~ '-400 hover:text-' ~ color ~ '-800 dark:hover:text-' ~ color ~ '-200' %}
<div x-show="{{ entity_var }}" x-transition class="mb-6 p-3 {{ bg }} rounded-lg border {{ border }}">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-8 h-8 rounded-full {{ avatar_bg }} flex items-center justify-center">
<span class="text-sm font-semibold {{ avatar_text }}" x-text="{{ entity_var }}?.{{ name_field }}?.charAt(0).toUpperCase()"></span>
</div>
<div>
<span class="font-medium {{ name_text }}" x-text="{{ entity_var }}?.{{ name_field }}"></span>
{% if code_field %}
<span class="ml-2 text-xs {{ code_text }} font-mono" x-text="{{ entity_var }}?.{{ code_field }}"></span>
{% endif %}
</div>
</div>
<button @click="{{ clear_fn }}" class="{{ btn_text }} text-sm flex items-center gap-1">
<span x-html="$icon('x', 'w-4 h-4')"></span>
Clear filter
</button>
</div>
</div>
{% endmacro %}
{#
Toggle Switch
=============