From 83234ab8cd184f4693a148dc55bddef300c49e71 Mon Sep 17 00:00:00 2001 From: Miguel Ribeiro Date: Tue, 5 Mar 2024 18:17:15 +0100 Subject: [PATCH] feat: allow sorting of categories in settings feat: add filters to statistics page feat: allow renaming / translation of payment methods feat: allow deletion of the default payment methods --- endpoints/categories/sort.php | 34 ++++ endpoints/payments/get.php | 2 +- endpoints/payments/rename.php | 41 +++++ images/siteicons/draggable.png | Bin 0 -> 987 bytes includes/getdbkeys.php | 2 +- includes/i18n/de.php | 8 + includes/i18n/el.php | 8 + includes/i18n/en.php | 8 + includes/i18n/es.php | 8 + includes/i18n/fr.php | 8 + includes/i18n/jp.php | 8 + includes/i18n/pt.php | 8 + includes/i18n/tr.php | 8 + includes/i18n/zh_cn.php | 9 ++ includes/i18n/zh_tw.php | 8 + includes/version.php | 2 +- migrations/000010.php | 14 ++ scripts/libs/sortable.min.js | 2 + scripts/settings.js | 122 +++++++++++++- scripts/stats.js | 88 +++++++++++ service-worker.js | 1 + settings.php | 19 ++- stats.php | 279 ++++++++++++++++++++++++--------- styles/dark-theme.css | 13 +- styles/styles.css | 152 +++++++++++++++++- 25 files changed, 754 insertions(+), 98 deletions(-) create mode 100644 endpoints/categories/sort.php create mode 100644 endpoints/payments/rename.php create mode 100644 images/siteicons/draggable.png create mode 100644 migrations/000010.php create mode 100644 scripts/libs/sortable.min.js diff --git a/endpoints/categories/sort.php b/endpoints/categories/sort.php new file mode 100644 index 000000000..955222635 --- /dev/null +++ b/endpoints/categories/sort.php @@ -0,0 +1,34 @@ +prepare($sql); + $stmt->bindParam(':order', $order, SQLITE3_INTEGER); + $stmt->bindParam(':categoryId', $categoryId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $order++; + } + + $response = [ + "success" => true, + "message" => translate("sort_order_saved", $i18n) + ]; + echo json_encode($response); +} else { + $response = [ + "success" => false, + "errorMessage" => translate("session_expired", $i18n) + ]; + echo json_encode($response); + die(); +} + +?> \ No newline at end of file diff --git a/endpoints/payments/get.php b/endpoints/payments/get.php index fdd5f4e06..902c64559 100644 --- a/endpoints/payments/get.php +++ b/endpoints/payments/get.php @@ -39,7 +39,7 @@ 31 && !$inUse) { + if (!$inUse) { ?>
x diff --git a/endpoints/payments/rename.php b/endpoints/payments/rename.php new file mode 100644 index 000000000..f7ba2507b --- /dev/null +++ b/endpoints/payments/rename.php @@ -0,0 +1,41 @@ + false, + "message" => translate('session_expired', $i18n) + ])); +} + +if (!isset($_POST['paymentId']) || !isset($_POST['name']) || $_POST['paymentId'] === '' || $_POST['name'] === '') { + die(json_encode([ + "success" => false, + "message" => translate('fields_missing', $i18n) + ])); +} + +$paymentId = $_POST['paymentId']; +$name = $_POST['name']; + +$sql = "UPDATE payment_methods SET name = :name WHERE id = :paymentId"; +$stmt = $db->prepare($sql); +$stmt->bindParam(':name', $name, SQLITE3_TEXT); +$stmt->bindParam(':paymentId', $paymentId, SQLITE3_INTEGER); +$result = $stmt->execute(); + +if ($result) { + echo json_encode([ + "success" => true, + "message" => translate('payment_renamed', $i18n) + ]); +} else { + echo json_encode([ + "success" => false, + "message" => translate('payment_not_renamed', $i18n) + ]); +} + +?> \ No newline at end of file diff --git a/images/siteicons/draggable.png b/images/siteicons/draggable.png new file mode 100644 index 0000000000000000000000000000000000000000..55d8da3e8ff07afa085954840b0816ecdf7481ae GIT binary patch literal 987 zcmeAS@N?(olHy`uVBq!ia0vp^Qb26L!3HGxYqRD7DVB6cUq=Rpjs4tz5?O(K#^NA% zCx&(BWL^R}3dtTpz6=aiY77hwEes65fIUO_QmvAUQh^kMk%5tcu7Rnp zfmw)wxs`#Dm8qe&fuWUwfuELH4T^@`{FKbJO57Uk%li%hHMoInD9%qSDNig)W$?^R zOi%SqOwUZtRxr~u*R#;IR4_6ywA3{)(lszt2sAWP$S5f(u+rBrFE7{21gQpMplZGR zqIAdVM<;+LN`OoZ$tcZDva)i?PcF?(%`3683IHlCW-wg-f44rG8nB&KRvD?8=@}&q zriMlbE(8Pu)rcdR1632AnNpGgQX*5sd!u-(8 z@eKj0VlXtdurN0C=;@ahKhrGnCYh!C8oy%Rpq3n#Ahbw=fxYE7+F}F z>j53r^xl*k7|C&-E{-7)?r*1__c~%Aa7;b3Soljq=(iX9dRDa8cE|l`U&9``fme=Y z<${V=!5TVE`kUp%?^FeR2)k{Oc`WJeqq9w?|LA2-%HOwMQJ0zLbL)bQYQDjybN8`% z{Z!(ZmX;{-Y|B^eZF;BgUH|mw@Rus>o9B1A1&FAw4w_{gb5HCLM{>fO->;HPtg6&b z#w>bKbolcV&4!z%5o#query($query); while ($row = $result->fetchArray(SQLITE3_ASSOC)) { $categoryId = $row['id']; diff --git a/includes/i18n/de.php b/includes/i18n/de.php index 632ed1a45..82edb037b 100644 --- a/includes/i18n/de.php +++ b/includes/i18n/de.php @@ -136,17 +136,24 @@ "experimental_info" => "Experimentelle Einstellungen funktionieren möglicherweise nicht perfekt.", "payment_methods" => "Zahlungsmethoden", "payment_methods_info" => "Zahlungsmethode zum (de-)aktivieren anklicken.", + "rename_payment_methods_info" => "Klicken Sie auf den Namen einer Zahlungsmethode, um sie umzubenennen", "cant_delete_payment_method_in_use" => "Genutzte Zahlungsmethoden können nicht deaktiviert werden", "add_custom_payment" => "Eigene Zahlungsmethode hinzufügen", "payment_method_name" => "Name der Zahlungsmethode", "payment_method_added_successfuly" => "Zahlungsmethode erfolgreich hinzugefügt", "disable" => "Deaktivieren", "enable" => "Aktivieren", + "rename_payment_method" => "Zahlungsmethode umbenennen", + "payment_renamed" => "Zahlungsmethode umbenannt", + "payment_not_renamed" => "Zahlungsmethode konnte nicht umbenannt werden", "test" => "Test", "add" => "Hinzufügen", "save" => "Speichern", "export_subscriptions" => "Abonnements exportieren", "export_to_json" => "Nach JSON exportieren", + // Filters menu + "filter" => "Filter", + "clear" => "Leeren", // Toast "success" => "Erfolgreich", // Endpoint responses @@ -162,6 +169,7 @@ "failed_remove_category" => "Kategorie konnte nicht gelöscht werden", "category_saved" => "Kategorie gespeichert", "category_removed" => "Kategorie gelöscht", + "sort_order_saved" => "Sortierung gespeichert", // Currency "currency_saved" => "wurde gespeichert.", "error_adding_currency" => "Fehler beim hinzufügen der Währung.", diff --git a/includes/i18n/el.php b/includes/i18n/el.php index f73753fdd..c4234c55f 100644 --- a/includes/i18n/el.php +++ b/includes/i18n/el.php @@ -136,17 +136,24 @@ "experimental_info" => "Οι πειραματικές ρυθμίσεις πιθανότατα δεν θα λειτουργούν τέλεια.", "payment_methods" => "Τρόποι πληρωμής", "payment_methods_info" => "Κάνε κλικ σε μια μέθοδο πληρωμής για να την απενεργοποιήσεις/ενεργοποιήσεις.", + "rename_payment_methods_info" => "Κάντε κλικ στο όνομα μιας μεθόδου πληρωμής για να τη μετονομάσετε.", "cant_delete_payment_method_in_use" => "Δεν είναι εφικτό να απενεργοποιηθεί η χρησιμοποιούμενη μέθοδο πληρωμής", "add_custom_payment" => "Προσθήκη προσαρμοσμένης μεθόδου πληρωμής", "payment_method_name" => "Όνομα μεθόδου πληρωμής", "payment_method_added_successfuly" => "Η μέθοδος πληρωμής προστέθηκε με επιτυχία", "disable" => "Ανενεργό", "enable" => "Ενεργό", + "rename_payment_method" => "Μετονομασία μεθόδου πληρωμής", + "payment_renamed" => "Η μέθοδος πληρωμής μετονομάστηκε", + "payment_not_renamed" => "Η μέθοδος πληρωμής δεν μετονομάστηκε", "test" => "Δοκιμή", "add" => "Προσθήκη", "save" => "Αποθήκευση", "export_subscriptions" => "Εξαγωγή συνδρομών", "export_to_json" => "Εξαγωγή σε JSON", + // Filters menu + "filter" => "Φίλτρο", + "clear" => "Καθαρισμός", // Toast "success" => "Επιτυχία", // Endpoint responses @@ -162,6 +169,7 @@ "failed_remove_category" => "Απέτυχε η διαγραφή κατηγορίας", "category_saved" => "Αποθήκευση κατηγορίας", "category_removed" => "Διαγραφή κατηγορίας", + "sort_order_saved" => "Η ταξινόμηση αποθηκεύτηκε", // Currency "currency_saved" => "αποθηκεύτηκε.", "error_adding_currency" => "Error adding currency entry.", diff --git a/includes/i18n/en.php b/includes/i18n/en.php index b6d06924f..cfcc650b8 100644 --- a/includes/i18n/en.php +++ b/includes/i18n/en.php @@ -136,17 +136,24 @@ "experimental_info" => "Experimental settings will probably not work perfectly.", "payment_methods" => "Payment Methods", "payment_methods_info" => "Click a payment method to disable / enable it.", + "rename_payment_methods_info" => "Click the name on a payment method to rename it.", "cant_delete_payment_method_in_use" => "Can't disable used payment method", "add_custom_payment" => "Add Custom Payment Method", "payment_method_name" => "Payment Method Name", "payment_method_added_successfuly" => "Payment method added successfully", "disable" => "Disable", "enable" => "Enable", + "rename_payment_method" => "Rename Payment Method", + "payment_renamed" => "Payment method renamed", + "payment_not_renamed" => "Payment method not renamed", "test" => "Test", "add" => "Add", "save" => "Save", "export_subscriptions" => "Export Subscriptions", "export_to_json" => "Export to JSON", + // Filters menu + "filter" => "Filter", + "clear" => "Clear", // Toast "success" => "Success", // Endpoint responses @@ -162,6 +169,7 @@ "failed_remove_category" => "Failed to remove category", "category_saved" => "Category saved", "category_removed" => "Category removed", + "sort_order_saved" => "Sort order saved", // Currency "currency_saved" => "was saved.", "error_adding_currency" => "Error adding currency entry.", diff --git a/includes/i18n/es.php b/includes/i18n/es.php index c0f60f843..44ee4e23c 100644 --- a/includes/i18n/es.php +++ b/includes/i18n/es.php @@ -136,17 +136,24 @@ "experimental_info" => "Las configuraciones experimentales probablemente no funcionarán perfectamente.", "payment_methods" => "Métodos de Pago", "payment_methods_info" => "Haz clic en un método de pago para deshabilitarlo/habilitarlo.", + "rename_payment_methods_info" => "Haz clic en el nombre de un método de pago para cambiarle el nombre.", "cant_delete_payment_method_in_use" => "No se puede desactivar el método de pago utilizado", "add_custom_payment" => "Añadir método de pago personalizado", "payment_method_name" => "Nombre del método de pago", "payment_method_added_successfuly" => "Método de pago añadido con éxito", "disable" => "Desactivar", "enable" => "Activar", + "rename_payment_method" => "Renombrar método de pago", + "payment_renamed" => "Método de pago renombrado", + "payment_not_renamed" => "Error al renombrar el método de pago", "test" => "Probar", "add" => "Agregar", "save" => "Guardar", "export_subscriptions" => "Exportar suscripciones", "export_to_json" => "Exportar a JSON", + // Filters menu + "filter" => "Filtrar", + "clear" => "Limpiar", // Toast "success" => "Éxito", // Endpoint responses @@ -162,6 +169,7 @@ "failed_remove_category" => "Error al eliminar la categoría", "category_saved" => "Categoría guardada", "category_removed" => "Categoría eliminada", + "sort_order_saved" => "Orden de clasificación guardado", // Currency "currency_saved" => "fue guardada.", "error_adding_currency" => "Error al añadir la entrada de la moneda.", diff --git a/includes/i18n/fr.php b/includes/i18n/fr.php index ba844e315..e53ba7f3f 100644 --- a/includes/i18n/fr.php +++ b/includes/i18n/fr.php @@ -136,17 +136,24 @@ "experimental_info" => "Les paramètres expérimentaux ne fonctionneront probablement pas parfaitement.", "payment_methods" => "Méthodes de paiement", "payment_methods_info" => "Cliquez sur une méthode de paiement pour la désactiver / l'activer.", + "rename_payment_methods_info" => "Cliquez sur le nom d'un mode de paiement pour le renommer.", "cant_delete_payment_method_in_use" => "Impossible de désactiver la méthode de paiement utilisée", "add_custom_payment" => "Ajouter un paiement personnalisé", "payment_method_name" => "Nom de la méthode de paiement", "payment_method_added_successfuly" => "Méthode de paiement ajoutée avec succès", "disable" => "Désactiver", "enable" => "Activer", + "rename_payment_method" => "Renommer la méthode de paiement", + "payment_renamed" => "Méthode de paiement renommée", + "payment_not_renamed" => "La méthode de paiement n'a pas été renommée", "test" => "Test", "add" => "Ajouter", "save" => "Enregistrer", "export_subscriptions" => "Exporter les abonnements", "export_to_json" => "Exporter en JSON", + // Menu des filtes + "filter" => "Filtre", + "clear" => "Effacer", // Toast "success" => "Succès", // Réponses de l'API @@ -162,6 +169,7 @@ "failed_remove_category" => "Échec de la suppression de la catégorie", "category_saved" => "Catégorie enregistrée", "category_removed" => "Catégorie supprimée", + "sort_order_saved" => "L'ordre de tri a été enregistré", // Devise "currency_saved" => "a été enregistrée.", "error_adding_currency" => "Erreur lors de l'ajout de l'entrée de devise.", diff --git a/includes/i18n/jp.php b/includes/i18n/jp.php index d0d0b4aaa..ec3afd0d5 100644 --- a/includes/i18n/jp.php +++ b/includes/i18n/jp.php @@ -136,17 +136,24 @@ "experimental_info" => "実験的な設定は、おそらく完全には機能しません。", "payment_methods" => "支払い方法", "payment_methods_info" => "支払い方法をクリックして無効/有効を切り替えます。", + "rename_payment_methods_info" => "支払い方法の名前をクリックして、名前を変更します。", "cant_delete_payment_method_in_use" => "支払い方法が使用中のため無効にできません。", "add_custom_payment" => "カスタム支払い方法を追加", "payment_method_name" => "支払い方法名", "payment_method_added_successfuly" => "支払い方法が追加されました", "disable" => "無効", "enable" => "有効", + "rename_payment_method" => "支払い方法の名前を変更", + "payment_renamed" => "支払い方法が変更されました", + "payment_not_renamed" => "支払い方法が変更されませんでした", "test" => "テスト", "add" => "追加", "save" => "保存", "export_subscriptions" => "購読をエクスポート", "export_to_json" => "JSONにエクスポート", + // Filters menu + "filter" => "フィルタ", + "clear" => "クリア", // Toast "success" => "成功", // Endpoint responses @@ -162,6 +169,7 @@ "failed_remove_category" => "カテゴリの削除に失敗", "category_saved" => "カテゴリの保存", "category_removed" => "カテゴリの削除", + "sort_order_saved" => "並べ替え順が保存されました", // Currency "currency_saved" => "通貨を保存", "error_adding_currency" => "通貨エントリの追加エラー.", diff --git a/includes/i18n/pt.php b/includes/i18n/pt.php index 3f8249030..3f5f5840a 100644 --- a/includes/i18n/pt.php +++ b/includes/i18n/pt.php @@ -136,17 +136,24 @@ "experimental_info" => "Definições experimentais provavelmente não funcionarão correctamente.", "payment_methods" => "Métodos de Pagamento", "payment_methods_info" => "Clique num método de pagamento para o activar / desactivar.", + "rename_payment_methods_info" => "Clique no nome do método de pagamento para o renomear.", "cant_delete_payment_method_in_use" => "Não pode desactivar metodo de pagamento em uso", "add_custom_payment" => "Adicionar método de pagamento personalizado", "payment_method_name" => "Nome do método de pagamento", "payment_method_added_successfuly" => "Método de pagamento adicionado com sucesso", "disable" => "Desactivar", "enable" => "Activar", + "rename_payment_method" => "Renomear método de pagamento", + "payment_renamed" => "Método de pagamento renomeado", + "payment_not_renamed" => "Método de pagamento não renomeado", "test" => "Testar", "add" => "Adicionar", "save" => "Guardar", "export_subscriptions" => "Exportar Subscrições", "export_to_json" => "Exportar para JSON", + // Filters menu + "filter" => "Filtro", + "clear" => "Limpar", // Toast "success" => "Sucesso", // Endpoint responses @@ -162,6 +169,7 @@ "failed_remove_category" => "Erro ao remover categoria", "category_saved" => "Categoria guardada", "category_removed" => "Categoria removida", + "sort_order_saved" => "Ordenação guardada", // Currency "currency_saved" => "guardada.", "error_adding_currency" => "Erro ao adicionar moeda.", diff --git a/includes/i18n/tr.php b/includes/i18n/tr.php index 15a7c9019..8d05ede92 100644 --- a/includes/i18n/tr.php +++ b/includes/i18n/tr.php @@ -136,17 +136,24 @@ "experimental_info" => "Deneysel ayarlar muhtemelen mükemmel çalışmayacak.", "payment_methods" => "Ödeme Yöntemleri", "payment_methods_info" => "Bir ödeme yöntemini devre dışı bırakmak / etkinleştirmek için tıklayın.", + "rename_payment_methods_info" => "Yeniden adlandırmak için bir ödeme yönteminin adına tıklayın.", "cant_delete_payment_method_in_use" => "Kullanımda olan ödeme yöntemini devre dışı bırakamazsınız", "add_custom_payment" => "Özel ödeme yöntemi ekle", "payment_method_name" => "Ödeme Yöntemi Adı", "payment_method_added_successfuly" => "Ödeme yöntemi başarıyla eklendi", "disable" => "Devre Dışı Bırak", "enable" => "Etkinleştir", + "rename_payment_method" => "Ödeme yöntemi adını değiştir", + "payment_renamed" => "Ödeme yöntemi adı değiştirildi", + "payment_not_renamed" => "Ödeme yöntemi adı değiştirilemedi", "test" => "Test Et", "add" => "Ekle", "save" => "Kaydet", "export_subscriptions" => "Abonelikleri Dışa Aktar", "export_to_json" => "JSON'a dışa aktar", + // Filters menu + "filter" => "Filtre", + "clear" => "Temizle", // Toast "success" => "Başarılı", // Endpoint responses @@ -162,6 +169,7 @@ "failed_remove_category" => "Kategori kaldırılamadı", "category_saved" => "Kategori kaydedildi", "category_removed" => "Kategori kaldırıldı", + "sort_order_saved" => "Sıralama düzeni kaydedildi", // Currency "currency_saved" => "kaydedildi.", "error_adding_currency" => "Para birimi girişi eklenirken hata oluştu.", diff --git a/includes/i18n/zh_cn.php b/includes/i18n/zh_cn.php index 859d7cbc0..405125cb5 100644 --- a/includes/i18n/zh_cn.php +++ b/includes/i18n/zh_cn.php @@ -143,17 +143,25 @@ "experimental_info" => "实验性设置,可能存在问题。", "payment_methods" => "支付方式", "payment_methods_info" => "点击支付方式以禁用/启用。", + "rename_payment_methods_info" => "点击付款方式名称,重新命名该付款方式。" "cant_delete_payment_method_in_use" => "不能禁用正在使用的支付方式", "add_custom_payment" => "添加自定义支付方式", "payment_method_name" => "支付方式名称", "payment_method_added_successfuly" => "支付方式已成功添加", "disable" => "禁用", "enable" => "启用", + "rename_payment_method" => "重命名支付方式", + "payment_renamed" => "支付方式已重命名", + "payment_not_renamed" => "支付方式未重命名", "test" => "测试", "add" => "添加", "save" => "保存", "export_subscriptions" => "导出订阅", "export_to_json" => "导出为 JSON", + + // Filters menu + "filter" => "筛选", + "clear" => "清除", // Toast "success" => "成功", @@ -172,6 +180,7 @@ "failed_remove_category" => "移除分类失败", "category_saved" => "分类已保存", "category_removed" => "分类已移除", + "sort_order_saved" => "排序顺序已保存", // Currency "currency_saved" => "货币已保存。", diff --git a/includes/i18n/zh_tw.php b/includes/i18n/zh_tw.php index 5cb233208..6838f4b93 100644 --- a/includes/i18n/zh_tw.php +++ b/includes/i18n/zh_tw.php @@ -136,17 +136,24 @@ "experimental_info" => "實驗性設定,可能存在問題。", "payment_methods" => "付款方式", "payment_methods_info" => "點選付款方式以停用/啟用。", + "rename_payment_methods_info" => "點選付款方式的名稱可對其進行重新命名。", "cant_delete_payment_method_in_use" => "無法停用正在使用的付款方式", "add_custom_payment" => "新增自訂付款方式", "payment_method_name" => "付款方式名稱", "payment_method_added_successfuly" => "付款方式已成功新增", "disable" => "停用", "enable" => "啟用", + "rename_payment_method" => "更改付款方式名稱", + "payment_renamed" => "付款方式名稱已更改", + "payment_not_renamed" => "付款方式名稱未更改", "test" => "測試", "add" => "新增", "save" => "儲存", "export_subscriptions" => "匯出訂閱", "export_to_json" => "匯出為 JSON 檔案", + // Filters menu + "filter" => "篩選", + "clear" => "清除", // Toast "success" => "成功", // Endpoint responses @@ -162,6 +169,7 @@ "failed_remove_category" => "移除分類失敗", "category_saved" => "分類已儲存", "category_removed" => "分類已移除", + "sort_order_saved" => "排序順序已儲存", // Currency "currency_saved" => "已儲存。", "error_adding_currency" => "新增貨幣時發生錯誤。", diff --git a/includes/version.php b/includes/version.php index ee4e48b5b..4a16bfe4a 100644 --- a/includes/version.php +++ b/includes/version.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/migrations/000010.php b/migrations/000010.php new file mode 100644 index 000000000..5c65e1976 --- /dev/null +++ b/migrations/000010.php @@ -0,0 +1,14 @@ +query("SELECT * FROM pragma_table_info('categories') WHERE name='order'"); + $columnRequired = $columnQuery->fetchArray(SQLITE3_ASSOC) === false; + + if ($columnRequired) { + $db->exec('ALTER TABLE categories ADD COLUMN `order` INTEGER DEFAULT 0'); + $db->exec('UPDATE categories SET `order` = id'); + } + + +?> \ No newline at end of file diff --git a/scripts/libs/sortable.min.js b/scripts/libs/sortable.min.js new file mode 100644 index 000000000..bb9953355 --- /dev/null +++ b/scripts/libs/sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.15.2 - MIT | git://github.com/SortableJS/Sortable.git */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function e(e,t){var n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function I(o){for(var t=1;tt.length)&&(e=t.length);for(var n=0,o=new Array(e);n"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&p(t,e)||o&&t===n)return t}while(t!==n&&(t=(i=t).host&&i!==document&&i.host.nodeType?i.host:i.parentNode))}var i;return null}var g,m=/\s+/g;function k(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(m," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(m," ")))}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function v(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function b(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=n.left-e&&i<=n.right+e,e=r>=n.top-e&&r<=n.bottom+e;return o&&e?a=t:void 0}}),a);if(e){var n,o={};for(n in t)t.hasOwnProperty(n)&&(o[n]=t[n]);o.target=o.rootEl=e,o.preventDefault=void 0,o.stopPropagation=void 0,e[K]._onDragOver(o)}}var i,r,a}function Bt(t){V&&V.parentNode[K]._isOutsideThisEl(t.target)}function Ft(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[K]=this;var n,o,i={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Pt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Ft.supportPointer&&"PointerEvent"in window&&!u,emptyInsertThreshold:5};for(n in W.initializePlugins(this,t,i),i)n in e||(e[n]=i[n]);for(o in kt(e),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&Nt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?h(t,"pointerdown",this._onTapStart):(h(t,"mousedown",this._onTapStart),h(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(h(t,"dragover",this),h(t,"dragenter",this)),Dt.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,x())}function jt(t,e,n,o,i,r,a,l){var s,c,u=t[K],d=u.options.onMove;return!window.CustomEvent||y||w?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||X(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),c=d?d.call(u,s,a):c}function Ht(t){t.draggable=!1}function Lt(){Tt=!1}function Kt(t){return setTimeout(t,0)}function Wt(t){return clearTimeout(t)}Ft.prototype={constructor:Ft,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(mt=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,V):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(!function(t){xt.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&xt.push(o)}}(o),!V&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=P(l,t.draggable,o,!1))&&l.animated||tt===l)){if(ot=j(l),rt=j(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return q({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),G("filter",n,{evt:e}),void(i&&e.cancelable&&e.preventDefault())}else if(c=c&&c.split(",").some(function(t){if(t=P(s,t.trim(),o,!1))return q({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),G("filter",n,{evt:e}),!0}))return void(i&&e.cancelable&&e.preventDefault());t.handle&&!P(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;n&&!V&&n.parentNode===r&&(o=X(n),Q=r,Z=(V=n).parentNode,J=V.nextSibling,tt=n,lt=a.group,ct={target:Ft.dragged=V,clientX:(e||t).clientX,clientY:(e||t).clientY},ft=ct.clientX-o.left,pt=ct.clientY-o.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,V.style["will-change"]="all",o=function(){G("delayEnded",i,{evt:t}),Ft.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!s&&i.nativeDraggable&&(V.draggable=!0),i._triggerDragStart(t,e),q({sortable:i,name:"choose",originalEvent:t}),k(V,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){b(V,t.trim(),Ht)}),h(l,"dragover",Yt),h(l,"mousemove",Yt),h(l,"touchmove",Yt),h(l,"mouseup",i._onDrop),h(l,"touchend",i._onDrop),h(l,"touchcancel",i._onDrop),s&&this.nativeDraggable&&(this.options.touchStartThreshold=4,V.draggable=!0),G("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(w||y)?o():Ft.eventCanceled?this._onDrop():(h(l,"mouseup",i._disableDelayedDrag),h(l,"touchend",i._disableDelayedDrag),h(l,"touchcancel",i._disableDelayedDrag),h(l,"mousemove",i._delayedDragTouchMoveHandler),h(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&h(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)))},_delayedDragTouchMoveHandler:function(t){t=t.touches?t.touches[0]:t;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){V&&Ht(V),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;f(t,"mouseup",this._disableDelayedDrag),f(t,"touchend",this._disableDelayedDrag),f(t,"touchcancel",this._disableDelayedDrag),f(t,"mousemove",this._delayedDragTouchMoveHandler),f(t,"touchmove",this._delayedDragTouchMoveHandler),f(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?h(document,"pointermove",this._onTouchMove):h(document,e?"touchmove":"mousemove",this._onTouchMove):(h(V,"dragend",this),h(Q,"dragstart",this._onDragStart));try{document.selection?Kt(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){var n;wt=!1,Q&&V?(G("dragStarted",this,{evt:e}),this.nativeDraggable&&h(document,"dragover",Bt),n=this.options,t||k(V,n.dragClass,!1),k(V,n.ghostClass,!0),Ft.active=this,t&&this._appendGhost(),q({sortable:this,name:"start",originalEvent:e})):this._nulling()},_emulateDragOver:function(){if(ut){this._lastX=ut.clientX,this._lastY=ut.clientY,Rt();for(var t=document.elementFromPoint(ut.clientX,ut.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(ut.clientX,ut.clientY))!==e;)e=t;if(V.parentNode[K]._isOutsideThisEl(t),e)do{if(e[K])if(e[K]._onDragOver({clientX:ut.clientX,clientY:ut.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}while(e=(t=e).parentNode);Xt()}},_onTouchMove:function(t){if(ct){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=$&&v($,!0),a=$&&r&&r.a,l=$&&r&&r.d,e=Mt&&yt&&E(yt),a=(i.clientX-ct.clientX+o.x)/(a||1)+(e?e[0]-Ct[0]:0)/(a||1),l=(i.clientY-ct.clientY+o.y)/(l||1)+(e?e[1]-Ct[1]:0)/(l||1);if(!Ft.active&&!wt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))D.right+10||S.clientY>x.bottom&&S.clientX>x.left:S.clientY>D.bottom+10||S.clientX>x.right&&S.clientY>x.top)||m.animated)){if(m&&(t=n,e=r,C=X(B((_=this).el,0,_.options,!0)),_=L(_.el,_.options,$),e?t.clientX<_.left-10||t.clientY { + if (!response.ok) { + throw new Error(translate('network_response_error')); + } + return response.json(); + }).then(data => { + if (data.success) { + showSuccessMessage(`${newName} ${data.message}`); + } else { + showErrorMessage(data.message); + } + }).catch(error => { + showErrorMessage(translate('unknown_error')); + }); +} + +document.body.addEventListener('keypress', function(e) { + let targetElement = e.target; + if (targetElement.classList && targetElement.classList.contains('payment-name')) { + if (e.key === 'Enter') { + e.preventDefault(); + targetElement.blur(); + } + } +}); + function handleFileSelect(event) { const fileInput = event.target; const iconPreview = document.querySelector('.icon-preview'); @@ -843,3 +911,41 @@ function setRemoveBackground() { function exportToJson() { window.location.href = "endpoints/subscriptions/export.php"; } + +function saveCategorySorting() { + const categories = document.getElementById('categories'); + const categoryIds = Array.from(categories.children).map(category => category.dataset.categoryid); + + const formData = new FormData(); + categoryIds.forEach(categoryId => { + formData.append('categoryIds[]', categoryId); + }); + + fetch('endpoints/categories/sort.php', { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + showSuccessMessage(data.message); + } else { + showErrorMessage(data.errorMessage); + } + }) + .catch(error => { + showErrorMessage(translate('unknown_error')); + }); +} + +var el = document.getElementById('categories'); +var sortable = Sortable.create(el, { + handle: '.drag-icon', + ghostClass: 'sortable-ghost', + delay: 500, + delayOnTouchOnly: true, + touchStartThreshold: 5, + onEnd: function (evt) { + saveCategorySorting(); + }, +}); \ No newline at end of file diff --git a/scripts/stats.js b/scripts/stats.js index d67db3097..cc8160e5e 100644 --- a/scripts/stats.js +++ b/scripts/stats.js @@ -25,3 +25,91 @@ function loadGraph(container, dataPoints, currency, run) { }); } } + +function closeSubMenus() { + var subMenus = document.querySelectorAll('.filtermenu-submenu-content'); + subMenus.forEach(subMenu => { + subMenu.classList.remove('is-open'); + }); + +} + +document.addEventListener("DOMContentLoaded", function() { + var filtermenu = document.querySelector('#filtermenu-button'); + 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); + + 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(); + } else { + closeSubMenus(); + subMenu.classList.add("is-open"); + } +} + +document.querySelectorAll('.filter-item').forEach(function(item) { + item.addEventListener('click', function(e) { + if (this.hasAttribute('data-categoryid')) { + const categoryId = this.getAttribute('data-categoryid'); + const urlParams = new URLSearchParams(window.location.search); + let newUrl = 'stats.php?'; + + if (urlParams.get('category') === categoryId) { + urlParams.delete('category'); + } else { + urlParams.set('category', categoryId); + } + + newUrl += urlParams.toString(); + window.location.href = newUrl; + } else if (this.hasAttribute('data-memberid')) { + const memberId = this.getAttribute('data-memberid'); + const urlParams = new URLSearchParams(window.location.search); + let newUrl = 'stats.php?'; + + if (urlParams.get('member') === memberId) { + urlParams.delete('member'); + } else { + urlParams.set('member', memberId); + } + + newUrl += urlParams.toString(); + window.location.href = newUrl; + } else if (this.hasAttribute('data-paymentid')) { + const paymentId = this.getAttribute('data-paymentid'); + const urlParams = new URLSearchParams(window.location.search); + let newUrl = 'stats.php?'; + + if (urlParams.get('payment') === paymentId) { + urlParams.delete('payment'); + } else { + urlParams.set('payment', paymentId); + } + + newUrl += urlParams.toString(); + window.location.href = newUrl; + } + }); +}); + +function clearFilters() { + window.location.href = 'stats.php'; +} \ No newline at end of file diff --git a/service-worker.js b/service-worker.js index 8fb587b95..ec834cd57 100644 --- a/service-worker.js +++ b/service-worker.js @@ -33,6 +33,7 @@ self.addEventListener('install', function(event) { 'scripts/i18n/zh_tw.js', 'scripts/i18n/getlang.js', 'scripts/libs/chart.js', + 'scripts/libs/sortable.min.js', 'images/icon/favicon.ico', 'images/wallossolid.png', 'images/wallossolidwhite.png', diff --git a/settings.php b/settings.php index 20f7a704c..c5a67fa1d 100644 --- a/settings.php +++ b/settings.php @@ -2,6 +2,7 @@ require_once 'includes/header.php'; ?> +