mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-11-05 10:12:20 +01:00
Stage 5a translated the contacts pages but the browser diff was only run against /contacts/existing, so the other three kept most of their page headers, action buttons and confirmation modals in English: page titles, "Add Contact", "Pending Contacts", Refresh, Filters, Approve/Ignore and both batch-confirmation dialogs, plus Advanced Filters, Preview Cleanup and the cleanup confirmation on /contacts/manage. Most of the keys they needed already existed from 5a - the template sites were simply never wired to them. Three spinners in the Settings modal (Regions, Analyzer, Observer lists) were missed in 8b for the opposite reason: their containers are excluded from the stage-8 browser check as JS-populated, but the spinner inside is server-rendered, so excluding the container hid it. Root cause of both, and the reason for the new tool: a browser diff only sees what is visible on the page it is pointed at. Add i18n-template-diff.py, which fetches each entry point's HTML in en and pl and compares text nodes directly. No browser, no per-page script, and it covers closed modals and every page in one run - it found all of the above in a single pass. It cannot see anything JavaScript writes, so it complements the Playwright scripts rather than replacing them. Every server-rendered template in the project is now translated. The whole-project run reports 11 identical strings, all correct: the device name, version strings, the language names (which are supposed to match), "1 hop" (identical in Polish) and the glossary terms Hash, HB, Repeater. Catalog: 760 -> 781 keys, pl at 100%. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
148 lines
6.9 KiB
HTML
148 lines
6.9 KiB
HTML
{% extends "contacts_base.html" %}
|
|
|
|
{% block title %}{{ t('contacts.pending.title') }} - mc-webui{% endblock %}
|
|
|
|
{% block page_content %}
|
|
<div id="pendingPageContent" class="p-3">
|
|
<!-- Page Header -->
|
|
<div class="mb-3">
|
|
<h4 class="mb-2">
|
|
<i class="bi bi-hourglass-split"></i> {{ t('contacts.pending.title') }}
|
|
<span class="badge bg-primary rounded-pill" id="pendingCountBadge" style="display: none; font-size: 0.8rem;">0</span>
|
|
</h4>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="d-flex gap-2 mb-3">
|
|
<button class="btn btn-outline-secondary btn-sm" onclick="navigateTo('/contacts/manage');">
|
|
<i class="bi bi-arrow-left"></i> {{ t('common.back') }}
|
|
</button>
|
|
<button class="btn btn-outline-primary btn-sm" id="refreshPendingBtn">
|
|
<i class="bi bi-arrow-clockwise"></i> {{ t('common.refresh') }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Filter Toolbar -->
|
|
<div class="mb-3">
|
|
<div class="card">
|
|
<div class="card-body p-3">
|
|
<h6 class="mb-2">
|
|
<i class="bi bi-funnel"></i> {{ t('pa.filters') }}
|
|
<i class="bi bi-info-circle text-muted ms-1" role="button" tabindex="0"
|
|
data-bs-toggle="tooltip" data-bs-placement="right"
|
|
title="{{ t('contacts.pending.filter_hint') }}"></i>
|
|
</h6>
|
|
|
|
<!-- Type Filter Badges -->
|
|
<div class="d-flex flex-wrap gap-2 mb-2">
|
|
<span class="type-filter-badge active" data-type="COM" id="typeFilterCOM">COM</span>
|
|
<span class="type-filter-badge" data-type="REP" id="typeFilterREP">REP</span>
|
|
<span class="type-filter-badge" data-type="ROOM" id="typeFilterROOM">ROOM</span>
|
|
<span class="type-filter-badge" data-type="SENS" id="typeFilterSENS">SENS</span>
|
|
</div>
|
|
|
|
<!-- Search + Batch Action Buttons -->
|
|
<div class="d-flex gap-2">
|
|
<input type="text" class="form-control form-control-sm" id="pendingSearchInput"
|
|
placeholder="{{ t('logs.filter.search_ph') }}" style="min-width: 0;">
|
|
<button class="btn btn-success btn-sm flex-shrink-0" id="addFilteredBtn">
|
|
<i class="bi bi-check-circle-fill"></i> {{ t('contacts.btn.approve') }}
|
|
</button>
|
|
<button class="btn btn-outline-secondary btn-sm flex-shrink-0" id="ignoreFilteredBtn">
|
|
<i class="bi bi-eye-slash"></i> {{ t('contacts.btn.ignore') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div id="pendingLoading" class="text-center py-5" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">{{ t('common.loading') }}</span>
|
|
</div>
|
|
<p class="mt-3 text-muted">{{ t('contacts.pending.loading') }}</p>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div id="pendingEmpty" class="empty-state" style="display: none;">
|
|
<i class="bi bi-check-circle"></i>
|
|
<p class="mb-0">{{ t('contacts.pending.none') }}</p>
|
|
<small class="text-muted">{{ t('contacts.pending.none_hint') }}</small>
|
|
</div>
|
|
|
|
<!-- Pending Contacts List (Full-screen) -->
|
|
<div id="pendingList" class="contacts-list-fullscreen">
|
|
<!-- Dynamically populated by contacts.js -->
|
|
</div>
|
|
|
|
<!-- Error State -->
|
|
<div id="pendingError" class="alert alert-danger" style="display: none;" role="alert">
|
|
<i class="bi bi-exclamation-triangle"></i>
|
|
<span id="pendingErrorMessage">{{ t('contacts.pending.load_failed') }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Batch Approval Confirmation Modal -->
|
|
<div class="modal fade" id="batchApprovalModal" tabindex="-1" aria-labelledby="batchApprovalModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-success text-white">
|
|
<h5 class="modal-title" id="batchApprovalModalLabel">
|
|
<i class="bi bi-check-circle"></i> {{ t('contacts.pending.confirm_approve_title') }}
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="{{ t('common.close') }}"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="mb-2">{{ t_html('contacts.pending.approve_confirm') }}</p>
|
|
|
|
<!-- List of contacts to approve -->
|
|
<div class="list-group mb-3" id="batchApprovalList" style="max-height: 300px; overflow-y: auto;">
|
|
<!-- Populated dynamically by JavaScript -->
|
|
</div>
|
|
|
|
<div class="alert alert-info mb-0">
|
|
<i class="bi bi-info-circle"></i> {{ t('contacts.pending.approve_note') }}
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('common.cancel') }}</button>
|
|
<button type="button" class="btn btn-success" id="confirmBatchApprovalBtn">
|
|
<i class="bi bi-check-circle-fill"></i> {{ t('contacts.btn.approve_all') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Batch Ignore Confirmation Modal -->
|
|
<div class="modal fade" id="batchIgnoreModal" tabindex="-1" aria-labelledby="batchIgnoreModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-dialog-scrollable">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-secondary text-white">
|
|
<h5 class="modal-title" id="batchIgnoreModalLabel">
|
|
<i class="bi bi-eye-slash"></i> {{ t('contacts.pending.confirm_ignore_title') }}
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="{{ t('common.close') }}"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p class="mb-2">{{ t_html('contacts.pending.ignore_confirm') }}</p>
|
|
|
|
<div class="list-group mb-3" id="batchIgnoreList" style="max-height: 300px; overflow-y: auto;">
|
|
</div>
|
|
|
|
<div class="alert alert-warning mb-0">
|
|
<i class="bi bi-info-circle"></i> {{ t('contacts.pending.ignore_note') }}
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t('common.cancel') }}</button>
|
|
<button type="button" class="btn btn-outline-secondary" id="confirmBatchIgnoreBtn">
|
|
<i class="bi bi-eye-slash"></i> {{ t('contacts.btn.ignore_all') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|