Files
Sheep-Service/web/lib/customElements/territoryCard.js

418 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Заміна вмісту таблиці стилів на надані CSS-правила.
const CARD_STYLES_CSS = `
:host {
display: inline-block;
box-sizing: border-box;
width: 300px;
height: 200px;
}
@media (max-width: 2300px) {
:host {
width: calc((100% / 5) - 40px);
}
}
@media (max-width: 1960px) {
:host {
width: calc((100% / 4) - 40px);
}
}
@media (max-width: 1640px) {
:host {
width: calc((100% / 3) - 40px);
}
}
@media (max-width: 1280px) {
:host {
width: calc((100% / 2) - 40px);
}
}
@media (max-width: 650px) {
:host {
width: 100%;
}
}
.card {
position: relative;
width: 100%;
height: 200px;
background-color: var(--ColorThemes2, #525151);
overflow: hidden;
cursor: pointer;
border-radius: calc(var(--border-radius, 15px) - 5px);
}
@media(hover: hover) {
.card:hover {
opacity: 0.8;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
}
img {
width: 100%;
height: 100%;
object-fit: cover;
position: relative;
z-index: 1;
filter: blur(3px);
border-radius: calc(var(--border-radius, 15px) - 5px);
}
.contents {
position: absolute;
top: 0;
left: 0;
z-index: 2;
background: rgb(64 64 64 / 0.7);
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
justify-content: space-between;
border-radius: calc(var(--border-radius, 15px) - 5px);
}
.address {
margin: 10px;
height: 35px;
display: flex;
background: var(--ColorThemes0, #1c1c19);
align-items: center;
justify-content: center;
font-size: var(--FontSize3, 14px);
color: var(--ColorThemes3, #f3f3f3);
border-radius: calc(var(--border-radius, 15px) - 5px - 4px);
position: relative;
overflow: hidden;
font-weight: 400;
}
/* Стилі для режиму 'sheep' */
.sheep {
margin: 10px;
max-height: 50px;
border-radius: calc(var(--border-radius, 15px) - 5px - 4px);
padding: 10px 0;
margin-top: 10px;
display: flex;
background: var(--PrimaryColor, #cb9e44);
align-items: center;
flex-direction: column;
justify-content: space-around;
}
.sheep span {
color: var(--PrimaryColorText, #2e2e2e);
font-size: var(--FontSize3, 14px);
font-weight: 400;
opacity: 0.8;
}
.sheep p {
color: var(--PrimaryColorText, #2e2e2e);
font-size: var(--FontSize4, 15px);
font-weight: 400;
margin: 5px 0 0 0;
}
/* Стилі для режиму 'info' (прогресс) */
.info {
margin: 10px;
}
.info > div {
position: relative;
background-color: var(--ColorThemes0, #1c1c19);;
border-radius: calc(var(--border-radius, 15px) - 5px - 4px);
overflow: hidden;
padding: 5px 10px;
display: flex;
justify-content: space-between;
align-items: center;
min-height: 25px;
}
.info span {
z-index: 2;
font-size: var(--FontSize3, 14px);
color: var(--ColorThemes3, #f3f3f3);
}
.info p {
z-index: 2;
margin: 0;
font-weight: 500;
font-size: var(--FontSize3, 14px);
color: var(--ColorThemes3, #f3f3f3);
}
.progress {
position: absolute;
top: 0;
left: 0;
height: 100%;
background-color: var(--PrimaryColor, #cb9e44);
transition: width 0.3s ease;
}
a {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10;
border-radius: calc(var(--border-radius, 15px) - 5px);
}
`;
// Створення об'єкта CSSStyleSheet (якщо підтримується)
let appTerritoryCardStyles = null;
if (typeof CSSStyleSheet !== 'undefined' && CSSStyleSheet.prototype.replaceSync) {
appTerritoryCardStyles = new CSSStyleSheet(); // (2) Визначення об'єкта тут
appTerritoryCardStyles.replaceSync(CARD_STYLES_CSS);
}
/**
* Веб-компонент AppTerritoryCard.
* Відображає картку території з фоновим зображенням та різними режимами відображення.
*/
class AppTerritoryCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// Додаємо стилі в конструкторі, якщо це adoptable
if (this.shadowRoot.adoptedStyleSheets) {
this.shadowRoot.adoptedStyleSheets = [appTerritoryCardStyles];
} else {
// FALLBACK для старих браузерів (наприклад, iOS < 16.4)
const style = document.createElement('style');
style.textContent = CARD_STYLES_CSS;
this.shadowRoot.appendChild(style);
}
}
// Вказуємо, які атрибути ми хочемо відстежувати
static get observedAttributes() {
return ['image', 'address', 'sheep', 'link', 'atWork', 'quantity', 'overdue'];
}
// Геттери та Сеттери для атрибутів
// Вони спрощують роботу з атрибутами як з властивостями DOM-елемента
get image() {
return this.getAttribute('image');
}
set image(newValue) {
if (newValue === null) {
this.removeAttribute('image');
} else {
this.setAttribute('image', newValue);
}
}
get address() {
return this.getAttribute('address');
}
set address(newValue) {
if (newValue === null) {
this.removeAttribute('address');
} else {
this.setAttribute('address', newValue);
}
}
/** * Атрибут 'sheep' може приймати три стани:
* 1. null / відсутній: відключення блоку sheep та info
* 2. порожній рядок ('') / присутній без значення: Режим "Територія не опрацьовується"
* 3. рядок зі значенням: Режим "Територію опрацьовує: [значення]"
*/
get sheep() {
return this.getAttribute('sheep');
}
set sheep(newValue) {
if (newValue === null) {
this.removeAttribute('sheep');
} else {
this.setAttribute('sheep', newValue);
}
}
get link() {
return this.getAttribute('link');
}
set link(newValue) {
if (newValue === null) {
this.removeAttribute('link');
} else {
this.setAttribute('link', newValue);
}
}
get atWork() {
return this.getAttribute('atWork');
}
set atWork(newValue) {
if (newValue === null) {
this.removeAttribute('atWork');
} else {
// Приводимо до рядка, оскільки атрибути завжди є рядками
this.setAttribute('atWork', String(newValue));
}
}
get quantity() {
return this.getAttribute('quantity');
}
set quantity(newValue) {
if (newValue === null) {
this.removeAttribute('quantity');
} else {
// Приводимо до рядка
this.setAttribute('quantity', String(newValue));
}
}
get overdue() {
return this.getAttribute('address');
}
set overdue(newValue) {
if (newValue === null) {
this.removeAttribute('overdue');
} else {
this.setAttribute('overdue', newValue);
}
}
/**
* connectedCallback викликається, коли елемент додається в DOM.
* Тут ми викликаємо початкове рендеринг.
*/
connectedCallback() {
this.render();
}
/**
* attributeChangedCallback викликається при зміні одного зі спостережуваних атрибутів.
*/
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render(); // Перерендеринг при зміні атрибута
}
}
/**
* Логіка рендерингу (відображення) вмісту компонента.
*/
render() {
const image = this.getAttribute('image') || '';
const address = this.getAttribute('address') || '';
const sheep = this.getAttribute('sheep');
const link = this.getAttribute('link') || '#';
const atWork = this.getAttribute('atWork');
const quantity = this.getAttribute('quantity');
const overdue = this.getAttribute('overdue') == 'true' ? true : false;
this.shadowRoot.innerHTML = ``;
// Додаємо стилі для старих браузерів
if (!this.shadowRoot.adoptedStyleSheets) {
const style = document.createElement('style');
style.textContent = CARD_STYLES_CSS;
this.shadowRoot.appendChild(style);
}
let contentHTML = '';
// Перевіряємо, чи має бути увімкнений режим прогресу ('info'):
// обидва атрибути 'atWork' та 'quantity' присутні і є коректними числами.
const isProgressMode = atWork !== null && quantity !== null && !isNaN(parseInt(atWork)) && !isNaN(parseInt(quantity));
if (isProgressMode) {
// Режим прогресу (вільні під'їзди)
const atWorkNum = parseInt(atWork);
const quantityNum = parseInt(quantity);
const free = quantityNum - atWorkNum;
// Обчислення відсотка прогресу. Уникнення ділення на нуль.
const progressPercent = quantityNum > 0 ? (atWorkNum / quantityNum) * 100 : 100;
contentHTML = `
<div class="info">
<div>
<div class="progress" style="width: ${progressPercent}%"></div>
<span>Вільні під'їзди:</span>
<p>${free} / ${quantityNum}</p>
</div>
</div>
`;
} else if (sheep !== null && sheep !== '') {
// Режим опрацювання (значення атрибута 'sheep' є ім'ям опрацювача)
contentHTML = `
<div class="sheep" ${overdue ? `style="background: #bb4444;"` : ``}>
<span>Територію опрацьовує:</span>
<p>${sheep}</p>
</div>
`;
} else if (sheep !== null && sheep === '') {
// Режим "не опрацьовується" (атрибут 'sheep' присутній, але порожній)
contentHTML = `
<div class="sheep">
<span>Територія не опрацьовується</span>
</div>
`;
}
// --- Складання всього шаблону ---
this.shadowRoot.innerHTML += `
<div class="card" ${overdue ? `title="Термін опрацювання минув!"` : ``}>
<img src="${image}" alt="${address}" />
<div class="contents">
<h1 class="address">${address}</h1>
${contentHTML}
</div>
<a href="${link}" data-route=""></a>
</div>
`;
}
}
// Реєструємо веб-компонент у браузері
customElements.define('app-territory-card', AppTerritoryCard);
/*
============================
ПРИКЛАД ВИКОРИСТАННЯ
============================
*/
/*
<app-territory-card
address="Вул. Прикладна, 15А"
image="https://example.com/images/territory-1.jpg"
link="/territory/15a"
atWork="12"
quantity="20"
></app-territory-card>
<app-territory-card
address="Просп. Науковий, 5"
image="https://example.com/images/territory-2.jpg"
link="/territory/naukovyi-5"
sheep="Іван Петренко"
></app-territory-card>
<app-territory-card
address="Майдан Свободи, 1"
image="https://example.com/images/territory-3.jpg"
link="/territory/svobody-1"
sheep=""
></app-territory-card>
<app-territory-card
address="Вул. Безіменна, 99"
image="https://example.com/images/territory-4.jpg"
link="/territory/bezymenna-99"
></app-territory-card>
*/