fix(loyalty): terminal icons, server-side i18n, category in transactions
Some checks failed
CI / ruff (push) Successful in 22s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has been cancelled

- Fix icons: plus-circle → plus, backspace → arrow-left
- Convert terminal $t() calls to server-side _() for card_label,
  stamps_until_reward, reward_label, not_enough_stamps
- Inject transaction type labels as server-rendered window._txLabels
  object (eliminates all async i18n warnings on terminal page)
- Resolve category_names in store transactions list endpoint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 22:20:52 +02:00
parent 29593f4c61
commit 39e02f0d9b
3 changed files with 37 additions and 8 deletions

View File

@@ -652,11 +652,22 @@ def list_store_transactions(
db, merchant_id, skip=skip, limit=limit
)
from app.modules.loyalty.services.category_service import category_service
tx_responses = []
for t in transactions:
tx = TransactionResponse.model_validate(t)
if t.card and t.card.customer:
tx.customer_name = t.card.customer.full_name
if t.category_ids and isinstance(t.category_ids, list):
names = []
for cid in t.category_ids:
name = category_service.validate_category_for_store(
db, cid, t.store_id or 0
)
if name:
names.append(name)
tx.category_names = names if names else None
tx_responses.append(tx)
return TransactionListResponse(transactions=tx_responses, total=total)

View File

@@ -369,9 +369,11 @@ function storeLoyaltyTerminal() {
getTransactionLabel(tx) {
const type = tx.transaction_type;
if (type) {
return I18n.t('loyalty.transactions.' + type, {defaultValue: type.replace(/_/g, ' ')});
// Use server-rendered labels (no async flicker)
if (window._txLabels && window._txLabels[type]) return window._txLabels[type];
return type.replace(/_/g, ' ');
}
return I18n.t('loyalty.common.unknown');
return 'Unknown';
},
getTransactionColor(tx) {

View File

@@ -129,7 +129,7 @@
<div class="ml-4 flex-1">
<p class="font-semibold text-gray-700 dark:text-gray-200" x-text="selectedCard?.customer_name || 'Unknown'"></p>
<p class="text-sm text-gray-500 dark:text-gray-400" x-text="selectedCard?.customer_email"></p>
<p class="text-xs text-gray-400 dark:text-gray-500" x-text="$t('loyalty.store.terminal.card_label') + ': ' + selectedCard?.card_number"></p>
<p class="text-xs text-gray-400 dark:text-gray-500" x-text="'{{ _('loyalty.store.terminal.card_label') }}' + ': ' + selectedCard?.card_number"></p>
</div>
<button @click="clearCustomer()" type="button" aria-label="{{ _('loyalty.common.close') }}" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<span x-html="$icon('x', 'w-5 h-5')"></span>
@@ -156,7 +156,7 @@
:style="'color: ' + (program?.card_color || '#4F46E5')"
x-text="(selectedCard?.stamp_count || 0) + ' / ' + (program?.stamps_target || 10)"></p>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1"
x-text="selectedCard?.stamps_until_reward > 0 ? $t('loyalty.store.terminal.more_for_reward', {count: selectedCard.stamps_until_reward}) : $t('loyalty.store.terminal.ready_to_redeem')"></p>
x-text="selectedCard?.stamps_until_reward > 0 ? '{{ _('loyalty.store.terminal.more_for_reward') }}'.replace('{count}', selectedCard.stamps_until_reward) : '{{ _('loyalty.store.terminal.ready_to_redeem') }}'"></p>
</div>
</template>
</div>
@@ -168,7 +168,7 @@
<template x-if="program?.is_stamps_enabled">
<div class="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-3">
<span x-html="$icon('plus-circle', 'inline w-4 h-4 mr-1 text-green-500')"></span>
<span x-html="$icon('plus', 'inline w-4 h-4 mr-1 text-green-500')"></span>
{{ _('loyalty.store.terminal.add_stamp') }}
</h4>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
@@ -192,7 +192,7 @@
{{ _('loyalty.store.terminal.redeem_stamps') }}
</h4>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3"
x-text="selectedCard?.can_redeem_stamps ? $t('loyalty.store.terminal.reward_label') + ': ' + (selectedCard?.stamp_reward_description || program?.stamps_reward_description || $t('loyalty.store.terminal.free_item')) : $t('loyalty.store.terminal.not_enough_stamps')"></p>
x-text="selectedCard?.can_redeem_stamps ? '{{ _('loyalty.store.terminal.reward_label') }}' + ': ' + (selectedCard?.stamp_reward_description || program?.stamps_reward_description || '{{ _('loyalty.store.terminal.free_item') }}') : '{{ _('loyalty.store.terminal.not_enough_stamps') }}'"></p>
<button @click="showPinModal('redeemStamps')"
:disabled="!selectedCard?.can_redeem_stamps"
class="w-full px-4 py-2 text-sm font-medium text-white bg-orange-600 rounded-lg hover:bg-orange-700 disabled:opacity-50">
@@ -205,7 +205,7 @@
<template x-if="program?.is_points_enabled">
<div class="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-3">
<span x-html="$icon('plus-circle', 'inline w-4 h-4 mr-1 text-green-500')"></span>
<span x-html="$icon('plus', 'inline w-4 h-4 mr-1 text-green-500')"></span>
{{ _('loyalty.store.terminal.earn_points') }}
</h4>
<div class="mb-3">
@@ -370,7 +370,7 @@
</button>
<button @click="removePinDigit()" type="button" aria-label="{{ _('loyalty.common.back') }}"
class="h-14 text-sm font-medium rounded-lg bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600">
<span x-html="$icon('backspace', 'w-6 h-6 mx-auto')"></span>
<span x-html="$icon('arrow-left', 'w-6 h-6 mx-auto')"></span>
</button>
</div>
<div class="mt-4 flex justify-end gap-3">
@@ -390,5 +390,21 @@
{% endblock %}
{% block extra_scripts %}
<script>
// Server-rendered transaction type labels (avoids async i18n flicker)
window._txLabels = {
card_created: {{ _('loyalty.transactions.card_created')|tojson }},
welcome_bonus: {{ _('loyalty.transactions.welcome_bonus')|tojson }},
stamp_earned: {{ _('loyalty.transactions.stamp_earned')|tojson }},
stamp_redeemed: {{ _('loyalty.transactions.stamp_redeemed')|tojson }},
stamp_voided: {{ _('loyalty.transactions.stamp_voided')|tojson }},
points_earned: {{ _('loyalty.transactions.points_earned')|tojson }},
points_redeemed: {{ _('loyalty.transactions.points_redeemed')|tojson }},
points_voided: {{ _('loyalty.transactions.points_voided')|tojson }},
points_adjustment: {{ _('loyalty.transactions.points_adjustment')|tojson }},
points_expired: {{ _('loyalty.transactions.points_expired')|tojson }},
reward_redeemed: {{ _('loyalty.transactions.reward_redeemed')|tojson }},
};
</script>
<script defer src="{{ url_for('loyalty_static', path='store/js/loyalty-terminal.js') }}"></script>
{% endblock %}