feat: add maintenance tasks to admin page

feat: add support to upload svg logos
This commit is contained in:
Miguel Ribeiro
2024-07-09 18:45:23 +02:00
committed by GitHub
parent c4ba1a7d1c
commit 9f7f47b5d1
25 changed files with 435 additions and 178 deletions

View File

@@ -224,6 +224,76 @@ $loginDisabledAllowed = $userCount == 1 && $settings['registrations_open'] == 0;
</div>
</section>
<?php
// find unused upload logos
// Get all logos in the subscriptions table
$query = 'SELECT logo FROM subscriptions';
$stmt = $db->prepare($query);
$result = $stmt->execute();
$logosOnDB = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$logosOnDB[] = $row['logo'];
}
// Get all logos in the payment_methods table
$query = 'SELECT icon FROM payment_methods';
$stmt = $db->prepare($query);
$result = $stmt->execute();
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
if (!strstr($row['icon'], "images/uploads/icons/")) {
$logosOnDB[] = $row['icon'];
}
}
$logosOnDB = array_unique($logosOnDB);
// Get all logos in the uploads folder
$uploadDir = 'images/uploads/logos/';
$uploadFiles = scandir($uploadDir);
foreach ($uploadFiles as $file) {
if ($file != '.'&& $file != '..' && $file != 'avatars') {
$logosOnDisk[] = ['logo' => $file];
}
}
// Find unused logos
$unusedLogos = [];
foreach ($logosOnDisk as $disk) {
$found = false;
foreach ($logosOnDB as $dbLogo) {
if ($disk['logo'] == $dbLogo) {
$found = true;
break;
}
}
if (!$found) {
$unusedLogos[] = $disk;
}
}
$logosToDelete = count($unusedLogos);
?>
<section class="account-section">
<header>
<h2>
<?= translate('maintenance_tasks', $i18n) ?>
</h2>
</header>
<div class="maintenance-tasks">
<h3><?= translate('orphaned_logos', $i18n) ?></h3>
<div class="form-group-inline">
<input type="button" class="button thin mobile-grow" value="<?= translate('delete', $i18n) ?>" id="deleteUnusedLogos"
onClick="deleteUnusedLogos()" <?= $logosToDelete == 0 ? 'disabled' : '' ?> />
<span class="number-of-logos bold"><?= $logosToDelete ?></span> <?= translate('orphaned_logos', $i18n) ?>
</div>
</div>
</section>
<section class="account-section">
<header>

View File

@@ -0,0 +1,76 @@
<?php
require_once '../../includes/connect_endpoint.php';
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
die(json_encode([
"success" => false,
"message" => translate('session_expired', $i18n)
]));
}
// Check that user is an admin
if ($userId !== 1) {
die(json_encode([
"success" => false,
"message" => translate('error', $i18n)
]));
}
$query = 'SELECT logo FROM subscriptions';
$stmt = $db->prepare($query);
$result = $stmt->execute();
$logosOnDB = [];
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$logosOnDB[] = $row['logo'];
}
$logosOnDB = array_unique($logosOnDB);
$uploadDir = '../../images/uploads/logos/';
$uploadFiles = scandir($uploadDir);
foreach ($uploadFiles as $file) {
if ($file != '.' && $file != '..' && $file != 'avatars') {
$logosOnDisk[] = ['logo' => $file];
}
}
// Get all logos in the payment_methods table
$query = 'SELECT icon FROM payment_methods';
$stmt = $db->prepare($query);
$result = $stmt->execute();
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
if (!strstr($row['icon'], "images/uploads/icons/")) {
$logosOnDB[] = $row['icon'];
}
}
$logosOnDB = array_unique($logosOnDB);
// Find and delete unused logos
$count = 0;
foreach ($logosOnDisk as $disk) {
foreach ($logosOnDB as $db) {
$found = false;
if ($disk['logo'] == $db) {
$found = true;
break;
}
}
if (!$found) {
unlink($uploadDir . $disk['logo']);
$count++;
}
}
echo json_encode([
"success" => true,
"message" => translate('success', $i18n),
'count' => $count
]);
?>

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Benutzer erstellen",
"smtp_settings" => "SMTP Einstellungen",
"smtp_usage_info" => "Wird für die Passwortwiederherstellung und andere System-E-Mails verwendet",
"maintenance_tasks" => "Wartungsaufgaben",
"orphaned_logos" => "Verwaiste Logos",
// Email Verification
"email_verified" => "E-Mail verifiziert",
"email_verification_failed" => "E-Mail konnte nicht verifiziert werden",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Δημιουργία χρήστη",
"smtp_settings" => "SMTP ρυθμίσεις",
"smtp_usage_info" => "Θα χρησιμοποιηθεί για ανάκτηση κωδικού πρόσβασης και άλλα μηνύματα ηλεκτρονικού ταχυδρομείου συστήματος.",
"maintenance_tasks" => "Εργασίες συντήρησης",
"orphaned_logos" => "Ορφανά λογότυπα",
// Email Verification
"email_verified" => "Το email επιβεβαιώθηκε",
"email_verification_failed" => "Η επαλήθευση email απέτυχε",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Create User",
"smtp_settings" => "SMTP Settings",
"smtp_usage_info" => "Will be used for password recovery and other system emails.",
"maintenance_tasks" => "Maintenance Tasks",
"orphaned_logos" => "Orphaned Logos",
// Email Verification
"email_verified" => "Email verified successfully",
"email_verification_failed" => "Email verification failed",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Crear Usuario",
"smtp_settings" => "Configuración SMTP",
"smtp_usage_info" => "Se utilizará para recuperar contraseñas y otros correos electrónicos del sistema.",
"maintenance_tasks" => "Tareas de Mantenimiento",
"orphaned_logos" => "Logotipos huérfanos",
// Email Verification
"email_verified" => "Correo electrónico verificado",
"email_verification_failed" => "Error al verificar el correo electrónico",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Créer un utilisateur",
"smtp_settings" => "Paramètres SMTP",
"smtp_usage_info" => "Sera utilisé pour la récupération du mot de passe et d'autres e-mails système.",
"maintenance_tasks" => "Tâches de maintenance",
"orphaned_logos" => "Logos orphelins",
// Email Verification
"email_verified" => "Votre adresse courriel a été vérifiée avec succès",
"email_verification_failed" => "La vérification de l'adresse courriel a échoué",

View File

@@ -315,6 +315,8 @@ $i18n = [
"create_user" => "Crea utente",
"smtp_settings" => "Impostazioni SMTP",
"smtp_usage_info" => "Verrà utilizzato per il recupero della password e altre e-mail di sistema.",
"maintenance_tasks" => "Compiti di manutenzione",
"orphaned_logos" => "Loghi orfani",
// Email Verification
"email_verified" => "L'indirizzo email è stato verificato con successo",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "ユーザーを作成",
"smtp_settings" => "SMTP設定",
"smtp_usage_info" => "パスワードの回復やその他のシステム電子メールに使用されます。",
"maintenance_tasks" => "メンテナンスタスク",
"orphaned_logos" => "孤立したロゴ",
// Email Verification
"email_verified" => "メールアドレスが確認されました",
"email_verification_failed" => "メールアドレスの確認に失敗しました",

View File

@@ -298,6 +298,8 @@ $i18n = [
"create_user" => "유저 생성",
"smtp_settings" => "SMTP 설정",
"smtp_usage_info" => "비밀번호 복구 및 기타 시스템 이메일에 사용됩니다.",
"maintenance_tasks" => "유지보수 작업",
"orphaned_logos" => "고아 로고",
// Email Verification
"email_verified" => "이메일 인증 완료",
"email_verification_failed" => "이메일 인증 실패",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Utwórz użytkownika",
"smtp_settings" => "Ustawienia SMTP",
"smtp_usage_info" => "Będzie używany do odzyskiwania hasła i innych e-maili systemowych.",
"maintenance_tasks" => "Zadania konserwacyjne",
"orphaned_logos" => "Osierocone logo",
// Email Verification
"email_verified" => "E-mail został zweryfikowany",
"email_verification_failed" => "Weryfikacja e-maila nie powiodła się",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Criar Utilizador",
"smtp_settings" => "Definições SMTP",
"smtp_usage_info" => "Será usado para recuperações de password e outros emails do sistema.",
"maintenance_tasks" => "Tarefas de Manutenção",
"orphaned_logos" => "Logos Órfãos",
// Email Verification
"email_verified" => "Email verificado",
"email_verification_failed" => "Verificação de email falhou",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Criar usuário",
"smtp_settings" => "Configurações SMTP",
"smtp_usage_info" => "Será usado para recuperação de senha e outros e-mails do sistema.",
"maintenance_tasks" => "Tarefas de manutenção",
"orphaned_logos" => "Logos órfãos",
// Email Verification
"email_verified" => "Email verificado",
"email_verification_failed" => "Falha na verificação do email",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Создать пользователя",
"smtp_settings" => "Настройки SMTP",
"smtp_usage_info" => "Будет использоваться для восстановления пароля и других системных писем.",
"maintenance_tasks" => "Задачи обслуживания",
"orphaned_logos" => "Одинокие логотипы",
// Email Verification
"email_verified" => "Ваш адрес электронной почты подтвержден. Теперь вы можете войти.",
"email_verification_failed" => "Не удалось подтвердить ваш адрес электронной почты.",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Ustvari uporabnika",
"smtp_settings" => "Nastavitve SMTP",
"smtp_usage_info" => "Uporabljeno bo za obnovitev gesla in druge sistemske e-pošte.",
"maintenance_tasks" => "Vzdrževalne naloge",
"orphaned_logos" => "Osamljeni logotipi",
// Email Verification
"email_verified" => "E-pošta je bila uspešno preverjena",
"email_verification_failed" => "Preverjanje e-pošte ni uspelo",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Креирај корисника",
"smtp_settings" => "SMTP подешавања",
"smtp_usage_info" => "SMTP се користи за слање е-поште за обавештења.",
"maintenance_tasks" => "Одржавање",
"orphaned_logos" => "Породични логотипови",
// Email Verification
"email_verified" => "Е-пошта је верификована",
"email_verification_failed" => "Верификација е-поште није успела",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Kreiraj korisnika",
"smtp_settings" => "SMTP podešavanja",
"smtp_usage_info" => "Koristiće se za oporavak lozinke i druge sistemske e-poruke.",
"maintenance_tasks" => "Održavanje",
"orphaned_logos" => "Nepovezani logotipi",
// Email Verification
"email_verified" => "E-pošta je uspešno verifikovana",
"email_verification_failed" => "Verifikacija e-pošte nije uspela",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "Kullanıcı Oluştur",
"smtp_settings" => "SMTP Ayarları",
"smtp_usage_info" => "Şifre kurtarma ve diğer sistem e-postaları için kullanılacaktır.",
"maintenance_tasks" => "Bakım Görevleri",
"orphaned_logos" => "Yetim Logolar",
// Email Verification
"email_verified" => "E-posta doğrulandı",
"email_verification_failed" => "E-posta doğrulaması başarısız oldu",

View File

@@ -315,6 +315,8 @@ $i18n = [
"create_user" => "创建用户",
"smtp_settings" => "SMTP 设置",
"smtp_usage_info" => "将用于密码恢复和其他系统电子邮件。",
"maintenance_tasks" => "维护任务",
"orphaned_logos" => "孤立的 Logo",
// Email Verification
"email_verified" => "电子邮件已验证",

View File

@@ -297,6 +297,8 @@ $i18n = [
"create_user" => "建立使用者",
"smtp_settings" => "SMTP 設定",
"smtp_usage_info" => "將用於密碼恢復和其他系統電子郵件。",
"maintenance_tasks" => "維護任務",
"orphaned_logos" => "孤立的圖示",
// Email Verification
"email_verified" => "電子郵件已驗證",
"email_verification_failed" => "電子郵件驗證失敗",

View File

@@ -1,3 +1,3 @@
<?php
$version = "v2.14.2";
$version = "v2.15.0";
?>

View File

@@ -237,7 +237,7 @@ $headerClass = count($subscriptions) > 0 ? "main-actions" : "main-actions hidden
<label for="logo" class="logo-preview">
<img src="" alt="<?= translate('logo_preview', $i18n) ?>" id="form-logo">
</label>
<input type="file" id="logo" name="logo" accept="image/jpeg, image/png, image/gif, image/webp"
<input type="file" id="logo" name="logo" accept="image/jpeg, image/png, image/gif, image/webp, image/svg+xml"
onchange="handleFileSelect(event)" class="hidden-input">
<input type="hidden" id="logo-url" name="logo-url">
<div id="logo-search-button" class="image-button medium disabled" title="<?= translate('search_logo', $i18n) ?>"

View File

@@ -262,4 +262,26 @@ function addUserButton() {
showErrorMessage(error);
button.disabled = false;
});
}
function deleteUnusedLogos() {
const button = document.getElementById('deleteUnusedLogos');
button.disabled = true;
fetch('endpoints/admin/deleteunusedlogos.php')
.then(response => response.json())
.then(data => {
if (data.success) {
showSuccessMessage(data.message);
const numberOfLogos = document.querySelector('.number-of-logos');
numberOfLogos.innerText = '0';
} else {
showErrorMessage(data.message);
button.disabled = false;
}
})
.catch(error => {
showErrorMessage(error);
button.disabled = false;
});
}

View File

@@ -20,27 +20,27 @@ function toggleNotificationDays() {
}
function resetForm() {
const id = document.querySelector("#id");
id.value = "";
const formTitle = document.querySelector("#form-title");
formTitle.textContent = translate('add_subscription');
const logo = document.querySelector("#form-logo");
logo.src = "";
logo.style = 'display: none';
const logoUrl = document.querySelector("#logo-url");
logoUrl.value = "";
const logoSearchButton = document.querySelector("#logo-search-button");
logoSearchButton.classList.add("disabled");
const submitButton = document.querySelector("#save-button");
submitButton.disabled = false;
const notifyDaysBefore = document.querySelector("#notify_days_before");
notifyDaysBefore.disabled = true;
const form = document.querySelector("#subs-form");
form.reset();
closeLogoSearch();
const deleteButton = document.querySelector("#deletesub");
deleteButton.style = 'display: none';
deleteButton.removeAttribute("onClick");
const id = document.querySelector("#id");
id.value = "";
const formTitle = document.querySelector("#form-title");
formTitle.textContent = translate('add_subscription');
const logo = document.querySelector("#form-logo");
logo.src = "";
logo.style = 'display: none';
const logoUrl = document.querySelector("#logo-url");
logoUrl.value = "";
const logoSearchButton = document.querySelector("#logo-search-button");
logoSearchButton.classList.add("disabled");
const submitButton = document.querySelector("#save-button");
submitButton.disabled = false;
const notifyDaysBefore = document.querySelector("#notify_days_before");
notifyDaysBefore.disabled = true;
const form = document.querySelector("#subs-form");
form.reset();
closeLogoSearch();
const deleteButton = document.querySelector("#deletesub");
deleteButton.style = 'display: none';
deleteButton.removeAttribute("onClick");
}
function fillEditFormFields(subscription) {
@@ -106,12 +106,12 @@ function fillEditFormFields(subscription) {
}
function openEditSubscription(event, id) {
event.stopPropagation();
scrollTopBeforeOpening = window.scrollY;
const body = document.querySelector('body');
body.classList.add('no-scroll');
const url = `endpoints/subscription/get.php?id=${id}`;
fetch(url)
event.stopPropagation();
scrollTopBeforeOpening = window.scrollY;
const body = document.querySelector('body');
body.classList.add('no-scroll');
const url = `endpoints/subscription/get.php?id=${id}`;
fetch(url)
.then((response) => {
if (response.ok) {
return response.json();
@@ -133,41 +133,41 @@ function openEditSubscription(event, id) {
}
function addSubscription() {
resetForm();
const modal = document.getElementById('subscription-form');
modal.classList.add("is-open");
const body = document.querySelector('body');
body.classList.add('no-scroll');
resetForm();
const modal = document.getElementById('subscription-form');
modal.classList.add("is-open");
const body = document.querySelector('body');
body.classList.add('no-scroll');
}
function closeAddSubscription() {
const modal = document.getElementById('subscription-form');
modal.classList.remove("is-open");
const body = document.querySelector('body');
body.classList.remove('no-scroll');
if (shouldScroll) {
window.scrollTo(0, scrollTopBeforeOpening);
}
resetForm();
const modal = document.getElementById('subscription-form');
modal.classList.remove("is-open");
const body = document.querySelector('body');
body.classList.remove('no-scroll');
if (shouldScroll) {
window.scrollTo(0, scrollTopBeforeOpening);
}
resetForm();
}
function handleFileSelect(event) {
const fileInput = event.target;
const logoPreview = document.querySelector('.logo-preview');
const logoImg = logoPreview.querySelector('img');
const logoUrl = document.querySelector("#logo-url");
logoUrl.value = "";
const fileInput = event.target;
const logoPreview = document.querySelector('.logo-preview');
const logoImg = logoPreview.querySelector('img');
const logoUrl = document.querySelector("#logo-url");
logoUrl.value = "";
if (fileInput.files && fileInput.files[0]) {
const reader = new FileReader();
if (fileInput.files && fileInput.files[0]) {
const reader = new FileReader();
reader.onload = function (e) {
logoImg.src = e.target.result;
logoImg.style.display = 'block';
};
reader.onload = function (e) {
logoImg.src = e.target.result;
logoImg.style.display = 'block';
};
reader.readAsDataURL(fileInput.files[0]);
}
reader.readAsDataURL(fileInput.files[0]);
}
}
function deleteSubscription(event, id) {
@@ -177,7 +177,7 @@ function deleteSubscription(event, id) {
fetch(`endpoints/subscription/delete.php?id=${id}`, {
method: 'DELETE',
})
.then(response => {
.then(response => {
if (response.ok) {
showSuccessMessage(translate('subscription_deleted'));
fetchSubscriptions();
@@ -185,10 +185,10 @@ function deleteSubscription(event, id) {
} else {
showErrorMessage(translate('error_deleting_subscription'));
}
})
.catch(error => {
})
.catch(error => {
console.error('Error:', error);
});
});
}
}
@@ -216,7 +216,7 @@ function cloneSubscription(event, id) {
.catch(error => {
showErrorMessage(error.message || translate('error'));
});
}
}
function setSearchButtonStatus() {
@@ -239,17 +239,17 @@ function searchLogo() {
logoSearchPopup.classList.add("is-open");
const imageSearchUrl = `endpoints/logos/search.php?search=${searchTerm}`;
fetch(imageSearchUrl)
.then(response => response.json())
.then(data => {
if (data.imageUrls) {
displayImageResults(data.imageUrls);
} else if (data.error) {
console.error(data.error);
}
})
.catch(error => {
console.error(translate('error_fetching_image_results'), error);
});
.then(response => response.json())
.then(data => {
if (data.imageUrls) {
displayImageResults(data.imageUrls);
} else if (data.error) {
console.error(data.error);
}
})
.catch(error => {
console.error(translate('error_fetching_image_results'), error);
});
} else {
nameInput.focus();
}
@@ -260,15 +260,15 @@ function displayImageResults(imageSources) {
logoResults.innerHTML = "";
imageSources.forEach(src => {
const img = document.createElement("img");
img.src = src;
img.onclick = function() {
selectWebLogo(src);
};
img.onerror = function() {
this.parentNode.removeChild(this);
};
logoResults.appendChild(img);
const img = document.createElement("img");
img.src = src;
img.onclick = function () {
selectWebLogo(src);
};
img.onerror = function () {
this.parentNode.removeChild(this);
};
logoResults.appendChild(img);
});
}
@@ -323,7 +323,7 @@ function fetchSubscriptions() {
function setSortOption(sortOption) {
const sortOptionsContainer = document.querySelector("#sort-options");
const sortOptionsList = sortOptionsContainer.querySelectorAll("li");
sortOptionsList.forEach((option) => {
sortOptionsList.forEach((option) => {
if (option.getAttribute("id") === "sort-" + sortOption) {
option.classList.add("selected");
} else {
@@ -339,21 +339,46 @@ function setSortOption(sortOption) {
toggleSortOptions();
}
document.addEventListener('DOMContentLoaded', function() {
const subscriptionForm = document.querySelector("#subs-form");
const submitButton = document.querySelector("#save-button");
const endpoint = "endpoints/subscription/add.php";
function convertSvgToPng(file, callback) {
const reader = new FileReader();
subscriptionForm.addEventListener("submit", function (e) {
e.preventDefault();
submitButton.disabled = true;
const formData = new FormData(subscriptionForm);
reader.onload = function (e) {
const img = new Image();
img.src = e.target.result;
img.onload = function() {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const pngDataUrl = canvas.toDataURL('image/png');
const pngFile = dataURLtoFile(pngDataUrl, file.name.replace(".svg", ".png"));
callback(pngFile);
};
};
fetch(endpoint, {
method: "POST",
body: formData,
})
reader.readAsDataURL(file);
}
function dataURLtoFile(dataurl, filename) {
let arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while(n--){
u8arr[n] = bstr.charCodeAt(n);
}
return new File([u8arr], filename, {type:mime});
}
function submitFormData(formData, submitButton, endpoint) {
fetch(endpoint, {
method: "POST",
body: formData,
})
.then((response) => response.json())
.then((data) => {
if (data.status === "Success") {
@@ -363,45 +388,69 @@ document.addEventListener('DOMContentLoaded', function() {
}
})
.catch((error) => {
showErrorMessage(error);
submitButton.disabled = false;
});
showErrorMessage(error);
submitButton.disabled = false;
});
}
document.addEventListener('mousedown', function(event) {
const sortOptions = document.querySelector('#sort-options');
const sortButton = document.querySelector("#sort-button");
document.addEventListener('DOMContentLoaded', function () {
const subscriptionForm = document.querySelector("#subs-form");
const submitButton = document.querySelector("#save-button");
const endpoint = "endpoints/subscription/add.php";
if (!sortOptions.contains(event.target) && !sortButton.contains(event.target) && isSortOptionsOpen) {
sortOptions.classList.remove('is-open');
isSortOptionsOpen = false;
}
});
subscriptionForm.addEventListener("submit", function (e) {
e.preventDefault();
submitButton.disabled = true;
const formData = new FormData(subscriptionForm);
document.querySelector('#sort-options').addEventListener('focus', function() {
isSortOptionsOpen = true;
});
const fileInput = document.querySelector("#logo");
const file = fileInput.files[0];
if (file && file.type === "image/svg+xml") {
convertSvgToPng(file, function(pngFile) {
formData.set("logo", pngFile);
submitFormData(formData, submitButton, endpoint);
});
} else {
submitFormData(formData, submitButton, endpoint);
}
});
document.addEventListener('mousedown', function (event) {
const sortOptions = document.querySelector('#sort-options');
const sortButton = document.querySelector("#sort-button");
if (!sortOptions.contains(event.target) && !sortButton.contains(event.target) && isSortOptionsOpen) {
sortOptions.classList.remove('is-open');
isSortOptionsOpen = false;
}
});
document.querySelector('#sort-options').addEventListener('focus', function () {
isSortOptionsOpen = true;
});
});
function searchSubscriptions() {
const searchInput = document.querySelector("#search");
const searchTerm = searchInput.value.trim().toLowerCase();
const searchInput = document.querySelector("#search");
const searchTerm = searchInput.value.trim().toLowerCase();
const subscriptions = document.querySelectorAll(".subscription");
subscriptions.forEach(subscription => {
const name = subscription.getAttribute('data-name').toLowerCase();
if (!name.includes(searchTerm)) {
subscription.classList.add("hide");
} else {
subscription.classList.remove("hide");
}
});
const subscriptions = document.querySelectorAll(".subscription");
subscriptions.forEach(subscription => {
const name = subscription.getAttribute('data-name').toLowerCase();
if (!name.includes(searchTerm)) {
subscription.classList.add("hide");
} else {
subscription.classList.remove("hide");
}
});
}
function closeSubMenus() {
var subMenus = document.querySelectorAll('.filtermenu-submenu-content');
subMenus.forEach(subMenu => {
subMenu.classList.remove('is-open');
subMenu.classList.remove('is-open');
});
}
@@ -411,84 +460,84 @@ activeFilters['category'] = "";
activeFilters['member'] = "";
activeFilters['payment'] = "";
document.addEventListener("DOMContentLoaded", function() {
document.addEventListener("DOMContentLoaded", function () {
var filtermenu = document.querySelector('#filtermenu-button');
filtermenu.addEventListener('click', function() {
this.parentElement.querySelector('.filtermenu-content').classList.toggle('is-open');
closeSubMenus();
filtermenu.addEventListener('click', function () {
this.parentElement.querySelector('.filtermenu-content').classList.toggle('is-open');
closeSubMenus();
});
document.addEventListener('click', function(e) {
var filtermenuContent = document.querySelector('.filtermenu-content');
if (filtermenuContent.classList.contains('is-open')) {
var subMenus = document.querySelectorAll('.filtermenu-submenu');
var clickedInsideSubmenu = Array.from(subMenus).some(subMenu => subMenu.contains(e.target) || subMenu === e.target);
document.addEventListener('click', function (e) {
var filtermenuContent = document.querySelector('.filtermenu-content');
if (filtermenuContent.classList.contains('is-open')) {
var subMenus = document.querySelectorAll('.filtermenu-submenu');
var clickedInsideSubmenu = Array.from(subMenus).some(subMenu => subMenu.contains(e.target) || subMenu === e.target);
if (!filtermenu.contains(e.target) && !clickedInsideSubmenu) {
closeSubMenus();
filtermenuContent.classList.remove('is-open');
}
if (!filtermenu.contains(e.target) && !clickedInsideSubmenu) {
closeSubMenus();
filtermenuContent.classList.remove('is-open');
}
}
});
});
function toggleSubMenu(subMenu) {
var subMenu = document.getElementById("filter-" + subMenu);
if (subMenu.classList.contains("is-open")) {
closeSubMenus();
closeSubMenus();
} else {
closeSubMenus();
subMenu.classList.add("is-open");
closeSubMenus();
subMenu.classList.add("is-open");
}
}
document.querySelectorAll('.filter-item').forEach(function(item) {
item.addEventListener('click', function(e) {
document.querySelectorAll('.filter-item').forEach(function (item) {
item.addEventListener('click', function (e) {
const searchInput = document.querySelector("#search");
searchInput.value = "";
if (this.hasAttribute('data-categoryid')) {
const categoryId = this.getAttribute('data-categoryid');
if (activeFilters['category'] === categoryId) {
activeFilters['category'] = "";
this.classList.remove('selected');
} else {
activeFilters['category'] = categoryId;
Array.from(this.parentNode.children).forEach(sibling => {
sibling.classList.remove('selected');
});
this.classList.add('selected');
}
const categoryId = this.getAttribute('data-categoryid');
if (activeFilters['category'] === categoryId) {
activeFilters['category'] = "";
this.classList.remove('selected');
} else {
activeFilters['category'] = categoryId;
Array.from(this.parentNode.children).forEach(sibling => {
sibling.classList.remove('selected');
});
this.classList.add('selected');
}
} else if (this.hasAttribute('data-memberid')) {
const memberId = this.getAttribute('data-memberid');
if (activeFilters['member'] === memberId) {
activeFilters['member'] = "";
this.classList.remove('selected');
} else {
activeFilters['member'] = memberId;
Array.from(this.parentNode.children).forEach(sibling => {
sibling.classList.remove('selected');
});
this.classList.add('selected');
}
const memberId = this.getAttribute('data-memberid');
if (activeFilters['member'] === memberId) {
activeFilters['member'] = "";
this.classList.remove('selected');
} else {
activeFilters['member'] = memberId;
Array.from(this.parentNode.children).forEach(sibling => {
sibling.classList.remove('selected');
});
this.classList.add('selected');
}
} else if (this.hasAttribute('data-paymentid')) {
const paymentId = this.getAttribute('data-paymentid');
if (activeFilters['payment'] === paymentId) {
activeFilters['payment'] = "";
this.classList.remove('selected');
} else {
activeFilters['payment'] = paymentId;
Array.from(this.parentNode.children).forEach(sibling => {
sibling.classList.remove('selected');
});
this.classList.add('selected');
}
const paymentId = this.getAttribute('data-paymentid');
if (activeFilters['payment'] === paymentId) {
activeFilters['payment'] = "";
this.classList.remove('selected');
} else {
activeFilters['payment'] = paymentId;
Array.from(this.parentNode.children).forEach(sibling => {
sibling.classList.remove('selected');
});
this.classList.add('selected');
}
}
if (activeFilters['category'] !== "" || activeFilters['member'] !== "" || activeFilters['payment'] !== "") {
document.querySelector('#clear-filters').classList.remove('hide');
document.querySelector('#clear-filters').classList.remove('hide');
} else {
document.querySelector('#clear-filters').classList.add('hide');
document.querySelector('#clear-filters').classList.add('hide');
}
fetchSubscriptions();
@@ -501,7 +550,7 @@ function clearFilters() {
activeFilters['category'] = "";
activeFilters['member'] = "";
activeFilters['payment'] = "";
document.querySelectorAll('.filter-item').forEach(function(item) {
document.querySelectorAll('.filter-item').forEach(function (item) {
item.classList.remove('selected');
});
document.querySelector('#clear-filters').classList.add('hide');
@@ -510,7 +559,7 @@ function clearFilters() {
let currentActions = null;
document.addEventListener('click', function(event) {
document.addEventListener('click', function (event) {
// Check if click was outside currentActions
if (currentActions && !currentActions.contains(event.target)) {
// Click was outside currentActions, close currentActions

View File

@@ -117,7 +117,7 @@ header .logo .logo-image svg {
.dropbtn > img {
width: 35px;
height: 35px;
object-fit: contain;
object-fit: cover;
}
.dropdown-content {
@@ -630,7 +630,7 @@ header #avatar {
cursor: pointer;
width: 80px;
height: 80px;
object-fit: contain;
object-fit: cover;
max-width: 80px;
border-radius: 50%;
border: 1px solid #ccc;
@@ -701,7 +701,7 @@ header #avatar {
.avatar-select .avatar-list .avatar-container > img {
width: 60px;
height: 60px;
object-fit: contain;
object-fit: cover;
cursor: pointer;
border: 1px solid #ccc;
box-sizing: border-box
@@ -2249,6 +2249,10 @@ button.dark-theme-button i {
}
}
.bold {
font-weight: 700;
}
/* Checkbox */
input[type="checkbox"] {
opacity: 0;