Files
MarekWoandClaude Opus 5 88ca23bccb fix(i18n): finish the contacts templates and three Settings spinners
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>
2026-08-01 10:29:30 +02:00

196 lines
10 KiB
HTML

{% extends "contacts_base.html" %}
{% block title %}{{ t('contacts.manage.title') }} - mc-webui{% endblock %}
{% block page_content %}
<div id="managePageContent" class="p-3">
<!-- Navigation Section -->
<div class="mb-4">
<!-- Add Contact Card -->
<div class="nav-card" onclick="navigateTo('/contacts/add');" style="border-left: 4px solid #198754;">
<div>
<h6><i class="bi bi-person-plus"></i> {{ t('contacts.add.title') }}</h6>
<small class="text-muted">{{ t('contacts.card.add_desc') }}</small>
</div>
<i class="bi bi-chevron-right text-muted"></i>
</div>
<!-- Pending Contacts Card -->
<div class="nav-card" onclick="navigateTo('/contacts/pending');">
<div>
<h6><i class="bi bi-hourglass-split"></i> {{ t('contacts.pending.title') }}</h6>
<small class="text-muted">{{ t('contacts.card.pending_desc') }}</small>
</div>
<span class="badge bg-primary rounded-pill" id="pendingBadge" style="font-size: 1.1rem;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</span>
</div>
<!-- Existing Contacts Card -->
<div class="nav-card" onclick="navigateTo('/contacts/existing');">
<div>
<h6><i class="bi bi-person-lines-fill"></i> {{ t('contacts.existing.title') }}</h6>
<small class="text-muted">{{ t('contacts.card.existing_desc') }}</small>
</div>
<span class="badge counter-badge counter-ok rounded-pill" id="existingBadge" style="font-size: 1.1rem;">
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
</span>
</div>
</div>
<!-- Cleanup Contacts Section -->
<div class="cleanup-section">
<h6>
<i class="bi bi-trash"></i>
<span>{{ t('contacts.cleanup.title') }}</span>
<i class="bi bi-info-circle info-icon"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ t('contacts.cleanup.title_hint') }}"></i>
</h6>
<!-- Basic Filters (Always visible) -->
<div class="mb-3">
<label for="cleanupNameFilter" class="form-label">{{ t('contacts.cleanup.name_filter') }}</label>
<input type="text" class="form-control" id="cleanupNameFilter" placeholder="{{ t('contacts.cleanup.name_filter_ph') }}">
</div>
<!-- Advanced Filters (Collapsible) -->
<div class="mb-3">
<button class="btn btn-sm btn-outline-secondary w-100" type="button" data-bs-toggle="collapse" data-bs-target="#advancedFilters" aria-expanded="false" aria-controls="advancedFilters">
<i class="bi bi-sliders"></i> {{ t('contacts.filter.advanced') }}
</button>
</div>
<div class="collapse" id="advancedFilters">
<!-- Contact Types -->
<div class="mb-3">
<label class="form-label">{{ t('contacts.cleanup.types') }}</label>
<div class="d-flex flex-wrap gap-2">
<div class="form-check">
<input class="form-check-input cleanup-type-filter" type="checkbox" value="1" id="cleanupTypeCOM" checked>
<label class="form-check-label" for="cleanupTypeCOM">COM</label>
</div>
<div class="form-check">
<input class="form-check-input cleanup-type-filter" type="checkbox" value="2" id="cleanupTypeREP" checked>
<label class="form-check-label" for="cleanupTypeREP">REP</label>
</div>
<div class="form-check">
<input class="form-check-input cleanup-type-filter" type="checkbox" value="3" id="cleanupTypeROOM" checked>
<label class="form-check-label" for="cleanupTypeROOM">ROOM</label>
</div>
<div class="form-check">
<input class="form-check-input cleanup-type-filter" type="checkbox" value="4" id="cleanupTypeSENS" checked>
<label class="form-check-label" for="cleanupTypeSENS">SENS</label>
</div>
</div>
</div>
<!-- Date Field -->
<div class="mb-3">
<label class="form-label">{{ t('contacts.cleanup.date_field') }}</label>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input" type="radio" name="cleanupDateField" id="cleanupDateLastAdvert" value="last_advert" checked>
<label class="form-check-label" for="cleanupDateLastAdvert">{{ t('contacts.cleanup.last_advert') }}</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="cleanupDateField" id="cleanupDateLastMod" value="lastmod">
<label class="form-check-label" for="cleanupDateLastMod">{{ t('contacts.cleanup.last_modified') }}</label>
</div>
</div>
</div>
<!-- Days of Inactivity -->
<div class="mb-3">
<label for="cleanupDays" class="form-label">{{ t('contacts.cleanup.days') }}</label>
<input type="number" class="form-control" id="cleanupDays" value="0" min="0">
<small class="form-text text-muted">{{ t('contacts.cleanup.days_hint') }}</small>
</div>
<!-- Auto-Cleanup Toggle -->
<div class="mb-3 mt-4 pt-3 border-top">
<div class="form-check form-switch d-flex align-items-center gap-2">
<input class="form-check-input" type="checkbox" role="switch" id="autoCleanupSwitch" style="cursor: pointer; min-width: 3rem; min-height: 1.5rem;">
<label class="form-check-label" for="autoCleanupSwitch" style="cursor: pointer; font-weight: 500;">
<span id="autoCleanupLabel">{{ t('contacts.cleanup.enable') }}</span>
</label>
<i class="bi bi-info-circle info-icon"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ t('contacts.cleanup.enable_hint') }}"></i>
</div>
<small class="text-muted">{{ t('contacts.cleanup.status_label') }} <span id="autoCleanupStatusText">{{ t('common.loading') }}</span></small>
<!-- Cleanup Hour Selector -->
<div class="mt-2 d-flex align-items-center gap-2">
<label for="cleanupHour" class="form-label mb-0 small">{{ t('contacts.cleanup.run_at') }}</label>
<select class="form-select form-select-sm" id="cleanupHour" style="width: auto;" disabled>
<option value="0">00:00</option>
<option value="1" selected>01:00</option>
<option value="2">02:00</option>
<option value="3">03:00</option>
<option value="4">04:00</option>
<option value="5">05:00</option>
<option value="6">06:00</option>
<option value="7">07:00</option>
<option value="8">08:00</option>
<option value="9">09:00</option>
<option value="10">10:00</option>
<option value="11">11:00</option>
<option value="12">12:00</option>
<option value="13">13:00</option>
<option value="14">14:00</option>
<option value="15">15:00</option>
<option value="16">16:00</option>
<option value="17">17:00</option>
<option value="18">18:00</option>
<option value="19">19:00</option>
<option value="20">20:00</option>
<option value="21">21:00</option>
<option value="22">22:00</option>
<option value="23">23:00</option>
</select>
<span class="small text-muted" id="cleanupTimezoneLabel"></span>
</div>
</div>
</div>
<button class="btn btn-warning" id="cleanupPreviewBtn">
<i class="bi bi-eye"></i> {{ t('contacts.cleanup.preview') }}
</button>
</div>
</div>
<!-- Cleanup Confirmation Modal -->
<div class="modal fade" id="cleanupConfirmModal" tabindex="-1" aria-labelledby="cleanupConfirmModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header bg-warning">
<h5 class="modal-title" id="cleanupConfirmModalLabel">
<i class="bi bi-exclamation-triangle"></i> {{ t('contacts.cleanup.confirm_title') }}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ t('common.close') }}"></button>
</div>
<div class="modal-body">
<!-- The count element is inside the catalog value; it must survive translation. -->
<p class="fw-bold">{{ t_html('contacts.cleanup.delete_confirm') }}</p>
<div id="cleanupContactList" class="list-group mb-3" style="max-height: 300px; overflow-y: auto;">
<!-- Contact list will be populated here -->
</div>
<div class="alert alert-warning mb-0">
<i class="bi bi-exclamation-circle"></i>
<strong>{{ t('common.warning') }}</strong> {{ t('contacts.cleanup.undo_warning') }}
</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-danger" id="confirmCleanupBtn">
<i class="bi bi-trash"></i> {{ t('contacts.cleanup.delete_btn') }}
</button>
</div>
</div>
</div>
</div>
{% endblock %}