Додан фільтр на сторінці керування вісниками.

Виправлено помилки.
This commit is contained in:
2026-06-07 01:16:17 +03:00
parent 49b559f03e
commit 0b38f137bf
6 changed files with 160 additions and 49 deletions

View File

@@ -19,6 +19,8 @@ class SwipeUpdater extends HTMLElement {
// 3. Внутрішній стан
this._isReadyToReload = false; // Прапорець, що вказує на готовність до оновлення
this._isStandalone = false; // Кеш для перевірки PWA режиму
this._isTouching = false; // Чи тримає користувач палець на екрані
// 4. Створення елементів (Внутрішній HTML)
shadow.innerHTML = `
@@ -111,8 +113,10 @@ class SwipeUpdater extends HTMLElement {
this._animID = shadow.getElementById('swipe_updater');
this._animIconID = shadow.getElementById('swipe_icon');
// 6. Прив'язка контексту `this` для обробника подій (важливо для коректної роботи `this.handleScroll`)
// 6. Прив'язка контексту `this` для обробників подій
this.handleScroll = this.handleScroll.bind(this);
this.handleTouchStart = this.handleTouchStart.bind(this);
this.handleTouchEnd = this.handleTouchEnd.bind(this);
}
/**
@@ -128,51 +132,81 @@ class SwipeUpdater extends HTMLElement {
}
}
// Фіксуємо, що користувач торкнувся екрана
handleTouchStart() {
this._isTouching = true;
}
// Обробка події, коли користувач відпускає екран
handleTouchEnd() {
this._isTouching = false;
const threshold = -165;
// Якщо палець відпустили, а ефект «гумового скролу» вже повернувся вище порогу
// (тобто користувач передумав і потягнув назад вгору перед тим як відпустити)
if (window.scrollY > threshold && this._isReadyToReload) {
this._resetRefreshState();
}
}
/**
* Обробник події скролу (головна логіка Pull-to-Refresh).
* Відстежує прокручування вище верхньої межі сторінки (`window.scrollY < 0`).
*/
handleScroll() {
// Перевірка на режим Standalone (PWA) - функціональність актуальна переважно тут
const isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (!this._isStandalone) return;
if (isStandalone) {
let scrollY = window.scrollY;
// 1. Анімація іконки під час прокручування за верхню межу (scrollY < 0)
if (scrollY <= -10) {
// Зміщення іконки разом із прокруткою
this._animIconID.style.top = `${scrollY / 1.5}px`;
this._animID.style.zIndex = '115'; // Піднімаємо z-index для видимості над контентом
} else {
this._animID.style.zIndex = '0'; // Повертаємо базовий z-index
const scrollY = window.scrollY;
const threshold = -165; // Поріг прокрутки (наприклад, -125px) для активації оновлення
// 1. Анімація іконки під час прокручування за верхню межу (scrollY < 0)
if (scrollY <= -10) {
// Зміщення іконки разом із прокруткою
this._animIconID.style.top = `${scrollY / 1.5}px`;
this._animID.style.zIndex = '115'; // Піднімаємо z-index для видимості над контентом
} else {
this._animIconID.style.top = '0px';
this._animID.style.zIndex = '0'; // Повертаємо базовий z-index
}
// 2. Логіка активації "готовий до оновлення"
if (scrollY <= threshold) {
if (!this._isReadyToReload) {
this._isReadyToReload = true;
// Поворот іконки на 180 градусів
this._animIconID.style.transform = 'rotate(180deg)';
this._animIconID.setAttribute('data-state', ''); // Деактивація стану "active"
}
const threshold = -125; // Поріг прокрутки (наприклад, -125px) для активації оновлення
// 2. Логіка активації "готовий до оновлення"
if (scrollY <= threshold) {
if (!this._isReadyToReload) {
this._isReadyToReload = true;
// Поворот іконки на 180 градусів
this._animIconID.style.transform = 'rotate(180deg)';
this._animIconID.setAttribute('data-state', ''); // Деактивація стану "active"
}
}
// 3. Логіка виклику оновлення та скидання стану
// Якщо користувач відпускає свайп (scrollY повертається до >= 0) І був готовий до оновлення
else if (scrollY >= 0) {
if (this._isReadyToReload) {
// Виклик користувацької функції (або стандартного window.location.reload())
this._appReload();
// Скидання стану та анімації
this._isReadyToReload = false;
this._animIconID.style.transform = 'rotate(0deg)';
this._animIconID.setAttribute('data-state', 'active');
}
}
// Скасування жесту під час руху пальцем вгору (ще до відпускання)
else if (scrollY > threshold && scrollY < 0 && this._isTouching) {
if (this._isReadyToReload) {
this._isReadyToReload = false;
this._animIconID.style.transform = 'rotate(0deg)';
this._animIconID.setAttribute('data-state', 'active');
}
}
// 3. Логіка виклику оновлення, коли сторінка повернулась до нуля
else if (scrollY >= 0) {
if (this._isReadyToReload) {
this._appReload();
this._resetRefreshState();
}
}
}
/**
* Допоміжний метод для повного скидання графічного стану
*/
_resetRefreshState() {
this._isReadyToReload = false;
this._animIconID.style.transform = 'rotate(0deg)';
this._animIconID.setAttribute('data-state', 'active');
this._animIconID.style.top = '0px';
this._animID.style.zIndex = '0';
}
@@ -181,8 +215,15 @@ class SwipeUpdater extends HTMLElement {
* Додаємо обробник події прокручування.
*/
connectedCallback() {
// Прослуховування глобальної події скролу
window.addEventListener('scroll', this.handleScroll);
// Перевіряємо PWA режим ОДИН раз при додаванні компонента на сторінку
this._isStandalone = window.matchMedia('(display-mode: standalone)').matches;
if (this._isStandalone) {
window.addEventListener('scroll', this.handleScroll);
// passive: true покращує продуктивність скролу на мобільних пристроях
window.addEventListener('touchstart', this.handleTouchStart, { passive: true });
window.addEventListener('touchend', this.handleTouchEnd, { passive: true });
}
}
/**
@@ -190,12 +231,17 @@ class SwipeUpdater extends HTMLElement {
* Видаляємо обробник події прокручування для запобігання витоку пам'яті.
*/
disconnectedCallback() {
// Чистимо абсолютно всі додані слухачі
window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('touchstart', this.handleTouchStart);
window.removeEventListener('touchend', this.handleTouchEnd);
}
}
// Реєстрація веб-компонента
customElements.define('swipe-updater', SwipeUpdater);
if (!customElements.get('swipe-updater')) {
customElements.define('swipe-updater', SwipeUpdater);
}
/*

View File

@@ -11,7 +11,6 @@
/>
</svg>
</button>
${butt_add}
</div>
</div>

View File

@@ -162,7 +162,7 @@ const Sheeps = {
let html = await fetch('/lib/pages/sheeps/index.html').then((response) => response.text());
app.innerHTML = html;
await Sheeps.sheeps_list.setHTML();
await Sheeps.sheeps_list.setHTML({});
if (id) Sheeps.editor.setHTML(id);
SheepsEvents.init();
@@ -182,7 +182,7 @@ const Sheeps = {
return Sheeps.sheeps_list.list
},
setHTML: async (search_value = null) => {
setHTML: async ({search, filter}) => {
let block_sheep_list = document.getElementById('block-sheeps-list');
let block_sheep_info = document.getElementById('block-sheep-info');
block_sheep_list.style.display = "flex";
@@ -210,6 +210,9 @@ const Sheeps = {
<div class="header">
<h1>Всі вісники</h1>
<div>
<button title="Фільтрувати" onclick="Sheeps.filter.open()">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svgjs="http://svgjs.com/svgjs" x="0" y="0" viewBox="0 0 24 24" xml:space="preserve" style="padding: 6px;"><g><path xmlns="http://www.w3.org/2000/svg" d="m21 2h-18a1.0007 1.0007 0 0 0 -.8193 1.5732l6.8193 9.7422v7.6846a1.0015 1.0015 0 0 0 1.53.8481l4-2.5a1.0014 1.0014 0 0 0 .47-.8481v-5.1846l6.8193-9.7422a1.0007 1.0007 0 0 0 -.8193-1.5732z"></path></g></svg>
</button>
<button title="Пошук" onclick="Sheeps.search.open()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72"><path d="M 31 11 C 19.973 11 11 19.973 11 31 C 11 42.027 19.973 51 31 51 C 34.974166 51 38.672385 49.821569 41.789062 47.814453 L 54.726562 60.751953 C 56.390563 62.415953 59.088953 62.415953 60.751953 60.751953 C 62.415953 59.087953 62.415953 56.390563 60.751953 54.726562 L 47.814453 41.789062 C 49.821569 38.672385 51 34.974166 51 31 C 51 19.973 42.027 11 31 11 z M 31 19 C 37.616 19 43 24.384 43 31 C 43 37.616 37.616 43 31 43 C 24.384 43 19 37.616 19 31 C 19 24.384 24.384 19 31 19 z"/></svg>
</button>
@@ -224,6 +227,27 @@ const Sheeps = {
</div>
`;
let filter_selected = Number(filter)
html += `
<div class="filter" id="block-sheeps-list-filter" data-state="closed">
<select title="Оберіть групу" onchange="Sheeps.filter.input(this.value)">
<option value="0" ${Number(filter) == 0 ? 'selected' : ''}>Всі</option>
<option value="1" ${Number(filter) === 1 ? 'selected' : ''}>Група 1</option>
<option value="2" ${Number(filter) === 2 ? 'selected' : ''}>Група 2</option>
<option value="3" ${Number(filter) === 3 ? 'selected' : ''}>Група 3</option>
<option value="4" ${Number(filter) === 4 ? 'selected' : ''}>Група 4</option>
<option value="5" ${Number(filter) === 5 ? 'selected' : ''}>Група 5</option>
<option value="6" ${Number(filter) === 6 ? 'selected' : ''}>Група 6</option>
<option value="7" ${Number(filter) === 7 ? 'selected' : ''}>Група 7</option>
<option value="8" ${Number(filter) === 8 ? 'selected' : ''}>Група 8</option>
</select>
</div>
`;
html += `
<br>
`;
const accessTemplate = (p) => {
let perms = [];
if (p.can_view_sheeps) perms.push("View Sheeps");
@@ -242,7 +266,11 @@ const Sheeps = {
};
for (const element of list) {
if (search_value && !element.name.toLowerCase().includes(search_value)) {
if (search && !element.name.toLowerCase().includes(search)) {
continue; // пропустити, якщо ім'я не містить рядок пошуку
}
if (filter && element.group_id !== Number(filter) && Number(filter) !== 0) {
continue; // пропустити, якщо ім'я не містить рядок пошуку
}
@@ -446,18 +474,42 @@ const Sheeps = {
}, 10)
}
},
filter: {
open: () => {
const sheepFilterForm = document.getElementById("block-sheeps-list-filter");
const current = sheepFilterForm.dataset.state;
if(current === 'closed') Sheeps.search.close();
sheepFilterForm.dataset.state = current === 'open' ? 'closed' : 'open';
},
close: () => {
const sheepFilterForm = document.getElementById("block-sheeps-list-filter");
sheepFilterForm.dataset.state = 'closed'
},
input: (value) => {
console.log(value);
filter_value = value || "";
Sheeps.sheeps_list.setHTML({filter: filter_value});
}
},
search: {
open: () => {
const sheepSearchForm = document.getElementById("block-sheeps-list-search");
sheepSearchForm.classList.toggle('active');
const current = sheepSearchForm.dataset.state;
if(current === 'closed') Sheeps.filter.close();
sheepSearchForm.dataset.state = current === 'open' ? 'closed' : 'open';
},
close: () => {
const sheepSearchForm = document.getElementById("block-sheeps-list-search");
sheepSearchForm.dataset.state = 'closed'
},
input: (value) => {
console.log(value);
search_value = value?.trim()?.toLowerCase() || "";
Sheeps.sheeps_list.setHTML(search_value);
Sheeps.sheeps_list.setHTML({search: search_value});
}
},
territory: {

View File

@@ -43,6 +43,7 @@
border-radius: calc(var(--border-radius) - 5px);
position: relative;
z-index: 10;
margin-bottom: -10px;
}
#block-sheeps-list>.header>h1 {
@@ -83,11 +84,12 @@
}
#block-sheeps-list>.filter,
#block-sheeps-list>.search {
width: calc(100% - 30px);
background-color: var(--PrimaryColor);
border-radius: 0px 0px 10px 10px;
margin: -12px 10px 20px 10px;
border-radius: 10px;
margin: -2px 10px 0px 10px;
display: flex;
flex-direction: row;
align-items: center;
@@ -102,10 +104,22 @@
}
#block-sheeps-list>.filter[data-state="open"],
#block-sheeps-list>.search[data-state="open"] {
max-height: 60px;
padding: 22px 5px 5px 5px;
opacity: 1;
border-radius: 0px 0px 10px 10px;
}
#block-sheeps-list>.filter>select{
width: 100%;
padding: 0 5px;
border-radius: calc(var(--border-radius) - 5px - 4px);
height: 30px;
background-color: var(--ColorThemes2);
color: var(--ColorThemes3);
font-size: var(--FontSize2);
}
#block-sheeps-list>.search>input {

View File

@@ -77,7 +77,7 @@
.page-territory>.list-controls>button {
display: flex;
position: relative;
background: var(--ColorThemes3);
background: var(--PrimaryColor);
font-size: 18px;
cursor: pointer;
min-width: 30px;
@@ -93,7 +93,7 @@
.page-territory>.list-controls>button>svg {
width: 25px;
height: 25px;
fill: var(--ColorThemes0);
fill: var(--PrimaryColorText);
}
#page-territory-search {

View File

@@ -1,4 +1,4 @@
const STATIC_CACHE_NAME = 'v2.2.4';
const STATIC_CACHE_NAME = 'v2.2.9';
const FILES_TO_CACHE = [
'/',