418 lines
13 KiB
JavaScript
418 lines
13 KiB
JavaScript
// Заміна вмісту таблиці стилів на надані 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>
|
||
*/ |