From 8304ed7b54f50ed7fa5ab520ff4d8d54f3ef34df Mon Sep 17 00:00:00 2001 From: Miguel Ribeiro Date: Fri, 21 Jun 2024 02:00:27 +0200 Subject: [PATCH] feat: edit and delete options now available directly on the subscription list fix: typo on webhook payload refactor: split currency endpoint feat: add option to clone subscription --- endpoints/currency/add.php | 33 ++++++++++++++ endpoints/currency/edit.php | 48 ++++++++++++++++++++ endpoints/currency/remove.php | 70 ++++++++++++++++++++++++++++++ endpoints/subscription/clone.php | 59 +++++++++++++++++++++++++ images/siteicons/blue/clone.png | Bin 0 -> 1106 bytes images/siteicons/green/clone.png | Bin 0 -> 1092 bytes images/siteicons/red/clone.png | Bin 0 -> 1054 bytes images/siteicons/yellow/clone.png | Bin 0 -> 1006 bytes includes/i18n/de.php | 1 + includes/i18n/el.php | 1 + includes/i18n/en.php | 1 + includes/i18n/es.php | 1 + includes/i18n/fr.php | 1 + includes/i18n/it.php | 1 + includes/i18n/jp.php | 1 + includes/i18n/ko.php | 1 + includes/i18n/pl.php | 1 + includes/i18n/pt.php | 1 + includes/i18n/pt_br.php | 1 + includes/i18n/ru.php | 1 + includes/i18n/sl.php | 1 + includes/i18n/sr.php | 1 + includes/i18n/sr_lat.php | 1 + includes/i18n/tr.php | 1 + includes/i18n/zh_cn.php | 1 + includes/i18n/zh_tw.php | 1 + includes/list_subscriptions.php | 20 +++++++-- includes/version.php | 2 +- scripts/dashboard.js | 68 ++++++++++++++++++++++++++++- scripts/settings.js | 6 +-- settings.php | 2 +- styles/dark-theme.css | 26 ++++++++++- styles/styles.css | 59 ++++++++++++++++++++++++- 33 files changed, 398 insertions(+), 13 deletions(-) create mode 100644 endpoints/currency/add.php create mode 100644 endpoints/currency/edit.php create mode 100644 endpoints/currency/remove.php create mode 100644 endpoints/subscription/clone.php create mode 100644 images/siteicons/blue/clone.png create mode 100644 images/siteicons/green/clone.png create mode 100644 images/siteicons/red/clone.png create mode 100644 images/siteicons/yellow/clone.png diff --git a/endpoints/currency/add.php b/endpoints/currency/add.php new file mode 100644 index 0000000..7af026b --- /dev/null +++ b/endpoints/currency/add.php @@ -0,0 +1,33 @@ +prepare($sqlInsert); + $stmtInsert->bindParam(':name', $currencyName, SQLITE3_TEXT); + $stmtInsert->bindParam(':symbol', $currencySymbol, SQLITE3_TEXT); + $stmtInsert->bindParam(':code', $currencyCode, SQLITE3_TEXT); + $stmtInsert->bindParam(':rate', $currencyRate, SQLITE3_TEXT); + $stmtInsert->bindParam(':userId', $userId, SQLITE3_INTEGER); + $resultInsert = $stmtInsert->execute(); + + if ($resultInsert) { + $currencyId = $db->lastInsertRowID(); + echo $currencyId; + } else { + echo translate('error_adding_currency', $i18n); + } +} else { + $response = [ + "success" => false, + "message" => translate('session_expired', $i18n) + ]; + echo json_encode($response); +} + +?> diff --git a/endpoints/currency/edit.php b/endpoints/currency/edit.php new file mode 100644 index 0000000..4ea3c9c --- /dev/null +++ b/endpoints/currency/edit.php @@ -0,0 +1,48 @@ +prepare($sql); + $stmt->bindParam(':name', $name, SQLITE3_TEXT); + $stmt->bindParam(':symbol', $symbol, SQLITE3_TEXT); + $stmt->bindParam(':code', $code, SQLITE3_TEXT); + $stmt->bindParam(':currencyId', $currencyId, SQLITE3_INTEGER); + $stmt->bindParam(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + + if ($result) { + $response = [ + "success" => true, + "message" => $name . " " . translate('currency_saved', $i18n) + ]; + echo json_encode($response); + } else { + $response = [ + "success" => false, + "message" => translate('failed_to_store_currency', $i18n) + ]; + echo json_encode($response); + } + } else { + $response = [ + "success" => false, + "message" => translate('fields_missing', $i18n) + ]; + echo json_encode($response); + } +} else { + $response = [ + "success" => false, + "message" => translate('session_expired', $i18n) + ]; + echo json_encode($response); +} + +?> diff --git a/endpoints/currency/remove.php b/endpoints/currency/remove.php new file mode 100644 index 0000000..b65a293 --- /dev/null +++ b/endpoints/currency/remove.php @@ -0,0 +1,70 @@ +prepare($query); + $stmt->bindParam(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $row = $result->fetchArray(SQLITE3_ASSOC); + $mainCurrencyId = $row['main_currency']; + + $currencyId = $_GET['currencyId']; + $checkQuery = "SELECT COUNT(*) FROM subscriptions WHERE currency_id = :currencyId AND user_id = :userId"; + $checkStmt = $db->prepare($checkQuery); + $checkStmt->bindParam(':currencyId', $currencyId, SQLITE3_INTEGER); + $checkStmt->bindParam(':userId', $userId, SQLITE3_INTEGER); + $checkResult = $checkStmt->execute(); + $row = $checkResult->fetchArray(); + $count = $row[0]; + + if ($count > 0) { + $response = [ + "success" => false, + "message" => translate('currency_in_use', $i18n) + ]; + echo json_encode($response); + exit; + } else { + if ($currencyId == $mainCurrencyId) { + $response = [ + "success" => false, + "message" => translate('currency_is_main', $i18n) + ]; + echo json_encode($response); + exit; + } else { + $sql = "DELETE FROM currencies WHERE id = :currencyId AND user_id = :userId"; + $stmt = $db->prepare($sql); + $stmt->bindParam(':currencyId', $currencyId, SQLITE3_INTEGER); + $stmt->bindParam(':userId', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + if ($result) { + echo json_encode(["success" => true, "message" => translate('currency_removed', $i18n)]); + } else { + $response = [ + "success" => false, + "message" => translate('failed_to_remove_currency', $i18n) + ]; + echo json_encode($response); + } + } + } + } else { + $response = [ + "success" => false, + "message" => translate('fields_missing', $i18n) + ]; + echo json_encode($response); + } +} else { + $response = [ + "success" => false, + "message" => translate('session_expired', $i18n) + ]; + echo json_encode($response); +} + +?> diff --git a/endpoints/subscription/clone.php b/endpoints/subscription/clone.php new file mode 100644 index 0000000..bb96822 --- /dev/null +++ b/endpoints/subscription/clone.php @@ -0,0 +1,59 @@ +prepare($query); + $stmt->bindValue(':id', $subscriptionId, SQLITE3_INTEGER); + $stmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + $result = $stmt->execute(); + $subscriptionToClone = $result->fetchArray(SQLITE3_ASSOC); + if ($subscriptionToClone === false) { + die(json_encode([ + "success" => false, + "message" => translate("error", $i18n) + ])); + } + + $query = "INSERT INTO subscriptions (name, logo, price, currency_id, next_payment, cycle, frequency, notes, payment_method_id, payer_user_id, category_id, notify, url, inactive, notify_days_before, user_id) VALUES (:name, :logo, :price, :currency_id, :next_payment, :cycle, :frequency, :notes, :payment_method_id, :payer_user_id, :category_id, :notify, :url, :inactive, :notify_days_before, :user_id)"; + $cloneStmt = $db->prepare($query); + $cloneStmt->bindValue(':name', $subscriptionToClone['name'], SQLITE3_TEXT); + $cloneStmt->bindValue(':logo', $subscriptionToClone['logo'], SQLITE3_TEXT); + $cloneStmt->bindValue(':price', $subscriptionToClone['price'], SQLITE3_TEXT); + $cloneStmt->bindValue(':currency_id', $subscriptionToClone['currency_id'], SQLITE3_INTEGER); + $cloneStmt->bindValue(':next_payment', $subscriptionToClone['next_payment'], SQLITE3_TEXT); + $cloneStmt->bindValue(':cycle', $subscriptionToClone['cycle'], SQLITE3_TEXT); + $cloneStmt->bindValue(':frequency', $subscriptionToClone['frequency'], SQLITE3_INTEGER); + $cloneStmt->bindValue(':notes', $subscriptionToClone['notes'], SQLITE3_TEXT); + $cloneStmt->bindValue(':payment_method_id', $subscriptionToClone['payment_method_id'], SQLITE3_INTEGER); + $cloneStmt->bindValue(':payer_user_id', $subscriptionToClone['payer_user_id'], SQLITE3_INTEGER); + $cloneStmt->bindValue(':category_id', $subscriptionToClone['category_id'], SQLITE3_INTEGER); + $cloneStmt->bindValue(':notify', $subscriptionToClone['notify'], SQLITE3_INTEGER); + $cloneStmt->bindValue(':url', $subscriptionToClone['url'], SQLITE3_TEXT); + $cloneStmt->bindValue(':inactive', $subscriptionToClone['inactive'], SQLITE3_INTEGER); + $cloneStmt->bindValue(':notify_days_before', $subscriptionToClone['notify_days_before'], SQLITE3_INTEGER); + $cloneStmt->bindValue(':user_id', $userId, SQLITE3_INTEGER); + + if ($cloneStmt->execute()) { + $response = [ + "success" => true, + "message" => translate('success', $i18n) + ]; + echo json_encode($response); + } else { + die(json_encode([ + "success" => false, + "message" => translate("error", $i18n) + ])); + } + } else { + die(json_encode([ + "success" => false, + "message" => translate('invalid_request_method', $i18n) + ])); + } + } + $db->close(); +?> \ No newline at end of file diff --git a/images/siteicons/blue/clone.png b/images/siteicons/blue/clone.png new file mode 100644 index 0000000000000000000000000000000000000000..022e3d6ed9db53fe7488fae4772ece910084a2d4 GIT binary patch literal 1106 zcmV-Y1g-mtP)aSZehye zvZE(svh0Ay%pJOHMn}?FIY`Qa;gK|Bl+yOSpKs6&vM_jkeUutMAO7nEO-jF?HEg?@ zP$)wnbfugm&xiyN;b>)0tJDc03kp>e2ussKtM|XlSiFG1hoOnv5m|Aj9K|Fs$npD2 z3a&U|Ih&gwEwhiHK|}A}N&q)eq-iz5ec;X9^$V-iX*6JH;&udE{emLR$vA^x+&K5q zx7Kf0YPci~5ZlfaiZmxlkrvy|6m^mYD43)TD43*9;!*m8tN{$t9tMZryB)(N@yNDo z3EGHIBymYRQpz!CBT6|2E}cVs{j+TdoP_OQ-1_zJbJdE?Q>UUKbWL)Un(eSvs`Aw1 z%?e5>%3C!`TUE;C8h!}Sap^o~WT4P@hFB%vS*SuFf`PMM=@Ly*UW-#0E4uF?(PmcIyAhJ>~Ipa zk2wGvPD^+hqw7=B41@JeS_5})qgdq_6B!t*kj zvy2S)<45P|FkVTTl+UkokbHifR**EwwiO4lZAB|cb|vs>V`H0xluA|n3WqhklI)5Q zFxz(Ut0S!AK@pcu1Mc2?NIt)gZ7Vi5wmHhB%UPc2>gqt#1gTVP#(eiSU zY<2}dHa50-BAZ>oh(|Ha7(tPEw6<0v2-bRuAW2$GIRruo3b-UKCM1DSP{SqhsIMbJwX~qbG5)^RhG+<=7 zpON8y_PcaB%Zq&SafV~El)E-s#3k8PCSaN-@oT$M<1j)nN1bHXDn4~}b#RbqG=yK3 zip?PwRankaCux$V2@axZf@83V;9_omv`n2&lTxWT+3X6>OQqrjXh{m@oUokD&5xGZ zM`zb6K8+0bW5lCa>m`%}LV^*GVwy4hdc}O{(+7WAcZ86Z5)^XRMvJ_F&JI-ufnTN> z!!%%>s*@tVIaT(g2OR8&^NCjZb{`J@6KAhSpmIE>4f>MH-;7&yA~JJ|3U=;u~-_ zfdQbG+?*Eio>!&7`bOIvArUn;wjL;K_t};E5l>ZiwJZi})!#XvCd3JNFF) z@kn;!5JXs|OsovnIvJ6rl4&+pnkG-~%cnM-Wwh&)r)eAU_v61#*rgAqPZYhiN+wnW zoEalLVkIEM&Ke+9Iz*@dftb`(lGRV9eyOnyA%@B6;~7sd**2R1#@Krc36aZ+E0?Ek zHrPby(tFd#N@jM|uyOX8AU})9YoAWts?w353zO5wGv54nhK+MDhJbTt&JA7p;hS1O zNa;eGVA8O04v=BvZGuTUlrDI)3HpFHo1jC9=>6#vMPQ8nF!t{BSe}p)k*8Urzl;!4 zqVg2!C!Qi9#}izhd!^X+t0ln~f4|jusqKvoE+wc6JMrEKn#P9ast1FhWdu!6&@y5X z93jVJuFRdzv@Dp!{6yQYj1e;6*<%^@!s?3W&uWuerHS`O7e)vGlz=-R?y?~X6Z8l^F6XXhxxI~D|@edmzrlmWpRBtzUTI z)R$U#Z!{a8o!q!~pEaqBqAKKvlMEk8(<604j))%I3#i_15J$7&SyR2;COeQIS8()- z64A=THodU2+@_b5{buw^g^;q}gx)X$LP|v0!34dK9Z1jCN@LrHQ4M~_qpIl3@Bnr3)3&1N^Q-Df*5zWNkeke%~Eha=}6jv@!-E6RlV~t1^GvXK?O_Lo=u(I3+0|c@I333HTT)9KZ zkN&#bnnkGrm5Wmgb++LMnOQ@KFIRBn3XWdMJ>zCCez@>05hx)QsnJJNq(($aNF`mR zpNLe5NC{1x{JsV((jOKtoSdx_k@ASl&=+KeE+`#IO_qUq4%U29lV!S)pyR90D#06X zAN~?}9T;N&%>!r8pSoVB3kf#)`qJHpZ@x47g~yh`jsl0+dy9yEoYdr7=TBX)(Hs literal 0 HcmV?d00001 diff --git a/images/siteicons/red/clone.png b/images/siteicons/red/clone.png new file mode 100644 index 0000000000000000000000000000000000000000..239f8768d4fbb878c5907a6afac4b97d1040e65a GIT binary patch literal 1054 zcmV+(1mXLMP)VTMO{5V2mm&@p03S(NObN%QCT_^wS$8D0D4ZJI{>eE6>u4ygY5`^mZ^ZB0ue zcm^4^7-=BJ{;CK?S_BFh4T(r8suZK&{Z!%&gb*qhren<-v%m~6#o>DhT#xjz5WktL zvV+j37oSY0fxAGK?)x41VDNUXNK1q^R4z=%tjW(nmZNbJIRE9DQ#Y>u`lx|VX+yKd zERf}BWt%l-X;IoULvNU&MG0vAv*{!-MQ@mT{^|4xJ|&>KBTWwx$ly}~GDnggVvZy} zk3H0{f0b-D8)-9=?%^M2Xg1i^*N>>OA9D_B>ug)Ef*Dq?L9GV$8es@{_&i=yxiKG; zy3U%}OruesqD80PCJbS3R`~}j7O=u2aE_o1GWa}psC+%2rY3g{%hDwhKZZ=ag`FJ3 z{q_sg)@g^*Qn@i7!{jcI z5YPxd#aF+6EeTA~10;S7yeL5_zGkzTrkC``2ukspS&|+i(I`QAydvUZgl2y~f#*-z zHgp_0ks>q^g7SF9ECWIq_BfAQS>|;*aT*z)B$WObK`961)>he#TU(_Yl!J1PT{%ZL zC>tbASk(%=b&0)Kh67h*bs|@Dmprb zj8D=D24#F60oh+qa4XA%QmyccTUka2hLJ>yPEmq-@sy5u`HW6dy3LGE5GdeNx=n;m zFlgXYf*L$dM+^+p38Iwnc?2Yh6gqJlx3&t-!Fm(vpGO;H ze3HocBs(?l-{DOn@15t6~oJu{gWx>nF5Eojoi? z`dFYvIVe?jrOF{#0?x&6=Bl)K9FUHV;Z~M;osN#tL3v;?(#JylX0FN(9-E4UB;%9l zz%aag24)}v1H(unMOczZU3SGkABa*iC=A`om3ae?O;rv+5-CWe=oCqgJ{Y>4%MpO$ z6Oj@phKQ6*tPsC6SLXlh YUqPx#07*qoM6N<$f>NyBt^fc4 literal 0 HcmV?d00001 diff --git a/images/siteicons/yellow/clone.png b/images/siteicons/yellow/clone.png new file mode 100644 index 0000000000000000000000000000000000000000..25c1f5532ff86f89c64a0c20a0b348092a817062 GIT binary patch literal 1006 zcmVD)U7=YpT|9n@RCXs@BwJc(*ATk+?B@7IRsg^>;Vpy2T#1;=-kdP=$s6*0;CBV$m zY>}!g2s$9tY)z+Th~gp>+|+6l#mU+K$5nAiR3-7<<>DCeJp9)g&PW&VfE?ff4oaRw zIUr4MEuhW;Lk(yH4WMo+G&q4F23;VHG6PHl6P&-tzz-PaOobLl7$VXIawtnciT-JD=}HlSE|5m~4JdIjvcS~WpYhYJpJ@{iqEKdl5*Mq4GDDXT%`}5ynl2$EUBCk- z7z`7-z!(7`yc~liPe2IE;xR}p9s!3_be`VvtTj34c=qw#N$f8Faz{a2da^$FFF0U! zo1hl_ZSbFC{B2?}I0PJCq0eU1zRwIW-Ral~x;k%v!g(+|4dk!ikw1S0>k~nqfWr}c z8?!mzX9+0LCpzTOdGljz_C1`3-^1Q>q7b_D*=!nE0!mx}y_}<6`U1oy3ZcvQnE^^% zAibQ!zVRU;jS&zIO)~&&?tMZUBOnAiPiH+~f&rju51b*w5(3teV}R(TF~Sl8*5WZp z++o6U2pEII8JNvS$ksXs>_`F46p76UVL9x}6O*hjky=~iC;>XxZeR;#;?UQ|FoYwV zk!&n;9NAc=AB3|~pJS;{KM4DB#AI5V9LMavAhy&HVF~-vB+dH7<_r-+lYm16vc80D zEQ9)()+W8U_p7|5=?oYbo1MfK$|R&oz#$~F`H0loB5|13CNHJd7SZ8xG*cugLP)Z; zP7=)a6OttKnNdqZY6OHnQzIloX%i4avLgkOpu^)NLTC_h2uU+VI@fL>8_S?RX157y z=}BDl(kK`go1H{6MG{gY;1Gc=l(B^}N4fW_oW%cNmUFUdDl`cQ`&uMTTw-hQ(8D}k z!oE5&X*$Dk^wKD?b#^$#3WhnlgtIa($1*PGU "Leere Seite", "clear_filters" => "Filter zurücksetzen", "no_matching_subscriptions" => "Keine passenden Abonnements gefunden", + "clone" => "Klonen", // Subscription form "add_subscription" => "Abonnement hinzufügen", "edit_subscription" => "Abonnement editieren", diff --git a/includes/i18n/el.php b/includes/i18n/el.php index e79ec94..9372d2a 100644 --- a/includes/i18n/el.php +++ b/includes/i18n/el.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Κενή σελίδα", "clear_filters" => "Καθαρισμός φίλτρων", "no_matching_subscriptions" => "Δεν υπάρχουν συνδρομές που ταιριάζουν με τα φίλτρα σου", + "clone" => "Κλώνος", // Subscription form "add_subscription" => "Προσθήκη συνδρομής", "edit_subscription" => "Επεξεργασία συνδρομής", diff --git a/includes/i18n/en.php b/includes/i18n/en.php index 1a717ee..eef0e69 100644 --- a/includes/i18n/en.php +++ b/includes/i18n/en.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Empty Page", "clear_filters" => "Clear Filters", "no_matching_subscriptions" => "No matching subscriptions", + "clone" => "Clone", // Subscription form "add_subscription" => "Add subscription", "edit_subscription" => "Edit subscription", diff --git a/includes/i18n/es.php b/includes/i18n/es.php index 42e78dc..1fcbc91 100644 --- a/includes/i18n/es.php +++ b/includes/i18n/es.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Página Vacía", "clear_filters" => "Limpiar Filtros", "no_matching_subscriptions" => "No hay suscripciones que coincidan con los filtros", + "clone" => "Clonar", // Subscription form "add_subscription" => "Añadir suscripción", "edit_subscription" => "Editar suscripción", diff --git a/includes/i18n/fr.php b/includes/i18n/fr.php index a52110a..e850bf9 100644 --- a/includes/i18n/fr.php +++ b/includes/i18n/fr.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Page vide", "clear_filters" => "Effacer les filtres", "no_matching_subscriptions" => "Aucun abonnement ne correspond à vos critères de recherche", + "clone" => "Cloner", // Formulaire d'abonnement "add_subscription" => "Ajouter un abonnement", "edit_subscription" => "Modifier l'abonnement", diff --git a/includes/i18n/it.php b/includes/i18n/it.php index 78bec30..c9805d6 100644 --- a/includes/i18n/it.php +++ b/includes/i18n/it.php @@ -69,6 +69,7 @@ $i18n = [ 'empty_page' => 'Pagina vuota', 'clear_filters' => 'Pulisci filtri', 'no_matching_subscriptions' => 'Nessun abbonamento corrispondente', + "clone" => "Clona", // Add/Edit Subscription 'add_subscription' => 'Aggiungi abbonamento', diff --git a/includes/i18n/jp.php b/includes/i18n/jp.php index 78944a5..9fad453 100644 --- a/includes/i18n/jp.php +++ b/includes/i18n/jp.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "空のページ", "clear_filters" => "フィルタをクリア", "no_matching_subscriptions" => "一致する定期購入がありません", + "clone" => "複製", // Subscription form "add_subscription" => "定期購入の追加", "edit_subscription" => "定期購入の編集", diff --git a/includes/i18n/ko.php b/includes/i18n/ko.php index cc08498..fbccfa0 100644 --- a/includes/i18n/ko.php +++ b/includes/i18n/ko.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "빈 페이지", "clear_filters" => "필터 제거", "no_matching_subscriptions" => "해당하는 구독이 없습니다.", + "clone" => "복제", // Subscription form "add_subscription" => "구독 추가", "edit_subscription" => "구독 편집", diff --git a/includes/i18n/pl.php b/includes/i18n/pl.php index 84596a6..8601f52 100644 --- a/includes/i18n/pl.php +++ b/includes/i18n/pl.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Pusta strona", "clear_filters" => "Wyczyść filtry", "no_matching_subscriptions" => "Brak pasujących subskrypcji", + "clone" => "Klonuj", // Subscription form "add_subscription" => "Dodaj subskrypcję", "edit_subscription" => "Edytuj subskrypcję", diff --git a/includes/i18n/pt.php b/includes/i18n/pt.php index f5e6fbb..e97bf49 100644 --- a/includes/i18n/pt.php +++ b/includes/i18n/pt.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Página Vazia", "clear_filters" => "Limpar Filtros", "no_matching_subscriptions" => "Sem subscrições correspondentes", + "clone" => "Clonar", // Subscription form "add_subscription" => "Adicionar subscrição", "edit_subscription" => "Modificar subscrição", diff --git a/includes/i18n/pt_br.php b/includes/i18n/pt_br.php index 94e7695..8dc2a9d 100644 --- a/includes/i18n/pt_br.php +++ b/includes/i18n/pt_br.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Página vazia", "clear_filters" => "Limpar filtros", "no_matching_subscriptions" => "Nenhuma assinatura encontrada", + "clone" => "Clonar", // Subscription form "add_subscription" => "Adicionar assinatura", "edit_subscription" => "Editar assinatura", diff --git a/includes/i18n/ru.php b/includes/i18n/ru.php index e468542..4912147 100644 --- a/includes/i18n/ru.php +++ b/includes/i18n/ru.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Пустая страница", "clear_filters" => "Очистить фильтры", "no_matching_subscriptions" => "Нет подходящих подписок", + "clone" => "Клонировать", // Subscription form "add_subscription" => "Добавить подписку", "edit_subscription" => "Изменить подписку", diff --git a/includes/i18n/sl.php b/includes/i18n/sl.php index 8505102..7296765 100644 --- a/includes/i18n/sl.php +++ b/includes/i18n/sl.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Prazna stran", "clear_filters" => "Počisti filter", "no_matching_subscriptions" => "Ni ustreznih naročnin", + "clone" => "Klon", // Subscription form "add_subscription" => "Dodaj naročnino", "edit_subscription" => "Uredi naročnino", diff --git a/includes/i18n/sr.php b/includes/i18n/sr.php index e448508..be46353 100644 --- a/includes/i18n/sr.php +++ b/includes/i18n/sr.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Празна страница", "clear_filters" => "Очисти филтере", "no_matching_subscriptions" => "Нема подударајућих претплата", + "clone" => "Клонирај", // Форма за претплату "add_subscription" => "Додај претплату", "edit_subscription" => "Уреди претплату", diff --git a/includes/i18n/sr_lat.php b/includes/i18n/sr_lat.php index 707da24..c02b11c 100644 --- a/includes/i18n/sr_lat.php +++ b/includes/i18n/sr_lat.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Prazna stranica", "clear_filters" => "Očisti filtere", "no_matching_subscriptions" => "Nema podudarajućih pretplata", + "clone" => "Kloniraj", // Forma za pretplatu "add_subscription" => "Dodaj pretplatu", "edit_subscription" => "Uredi pretplatu", diff --git a/includes/i18n/tr.php b/includes/i18n/tr.php index 2d39c96..152b442 100644 --- a/includes/i18n/tr.php +++ b/includes/i18n/tr.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "Boş Sayfa", "clear_filters" => "Filtreleri Temizle", "no_matching_subscriptions" => "Eşleşen abonelik bulunamadı", + "clone" => "Kopyala", // Subscription form "add_subscription" => "Abonelik ekle", "edit_subscription" => "Aboneliği düzenle", diff --git a/includes/i18n/zh_cn.php b/includes/i18n/zh_cn.php index eb41ddc..df27c97 100644 --- a/includes/i18n/zh_cn.php +++ b/includes/i18n/zh_cn.php @@ -69,6 +69,7 @@ $i18n = [ "empty_page" => "空白页面", "clear_filters" => "清除筛选", "no_matching_subscriptions" => "没有匹配的订阅", + "clone" => "克隆", // 订阅表单 "add_subscription" => "添加订阅", diff --git a/includes/i18n/zh_tw.php b/includes/i18n/zh_tw.php index 28bb06b..d772c42 100644 --- a/includes/i18n/zh_tw.php +++ b/includes/i18n/zh_tw.php @@ -65,6 +65,7 @@ $i18n = [ "empty_page" => "空白頁面", "clear_filters" => "清除篩選", "no_matching_subscriptions" => "沒有符合的訂閱", + "clone" => "複製", // 訂閱表單 "add_subscription" => "新增訂閱", "edit_subscription" => "編輯訂閱", diff --git a/includes/list_subscriptions.php b/includes/list_subscriptions.php index fd40448..0fcdfae 100644 --- a/includes/list_subscriptions.php +++ b/includes/list_subscriptions.php @@ -108,11 +108,23 @@ - - - +
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
<?= translate('subscription', $i18n) ?> diff --git a/includes/version.php b/includes/version.php index 3b21718..a1b4c47 100644 --- a/includes/version.php +++ b/includes/version.php @@ -1,3 +1,3 @@ diff --git a/scripts/dashboard.js b/scripts/dashboard.js index f794972..9e098b9 100644 --- a/scripts/dashboard.js +++ b/scripts/dashboard.js @@ -160,7 +160,9 @@ function handleFileSelect(event) { } } -function deleteSubscription(id) { +function deleteSubscription(event, id) { + event.stopPropagation(); + event.preventDefault(); if (confirm(translate('confirm_delete_subscription'))) { fetch(`endpoints/subscription/delete.php?id=${id}`, { method: 'DELETE', @@ -171,7 +173,7 @@ function deleteSubscription(id) { fetchSubscriptions(); closeAddSubscription(); } else { - alert(translate('error_deleting_subscription')); + showErrorMessage(translate('error_deleting_subscription')); } }) .catch(error => { @@ -180,6 +182,32 @@ function deleteSubscription(id) { } } +function cloneSubscription(event, id) { + event.stopPropagation(); + event.preventDefault(); + + const url = `endpoints/subscription/clone.php?id=${id}`; + + fetch(url) + .then(response => { + if (!response.ok) { + throw new Error(translate('network_response_error')); + } + return response.json(); + }) + .then(data => { + if (data.success) { + fetchSubscriptions(); + showSuccessMessage(decodeURI(data.message)); + } else { + showErrorMessage(data.message || translate('error')); + } + }) + .catch(error => { + showErrorMessage(error.message || translate('error')); + }); + } + function setSearchButtonStatus() { const nameInput = document.querySelector("#name"); @@ -468,4 +496,40 @@ function clearFilters() { }); document.querySelector('#clear-filters').classList.add('hide'); fetchSubscriptions(); +} + +let currentActions = null; + +document.addEventListener('click', function(event) { + // Check if click was outside currentActions + if (currentActions && !currentActions.contains(event.target)) { + // Click was outside currentActions, close currentActions + currentActions.classList.remove('is-open'); + currentActions = null; + } +}); + +function expandActions(event, subscriptionId) { + event.stopPropagation(); + event.preventDefault(); + const subscriptionDiv = document.querySelector(`.subscription[data-id="${subscriptionId}"]`); + const actions = subscriptionDiv.querySelector('.actions'); + + // Close all other open actions + const allActions = document.querySelectorAll('.actions.is-open'); + allActions.forEach((openAction) => { + if (openAction !== actions) { + openAction.classList.remove('is-open'); + } + }); + + // Toggle the clicked actions + actions.classList.toggle('is-open'); + + // Update currentActions + if (actions.classList.contains('is-open')) { + currentActions = actions; + } else { + currentActions = null; + } } \ No newline at end of file diff --git a/scripts/settings.js b/scripts/settings.js index 4db2f17..7498313 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -360,7 +360,7 @@ function editCategory(categoryId) { function addCurrencyButton(currencyId) { document.getElementById("addCurrency").disabled = true; - const url = 'endpoints/currency/currency.php?action=add'; + const url = 'endpoints/currency/add.php'; fetch(url) .then(response => { if (!response.ok) { @@ -442,7 +442,7 @@ function addCurrencyButton(currencyId) { } function removeCurrency(currencyId) { - let url = `endpoints/currency/currency.php?action=delete¤cyId=${currencyId}`; + let url = `endpoints/currency/remove.php?currencyId=${currencyId}`; fetch(url) .then(response => { if (!response.ok) { @@ -477,7 +477,7 @@ function editCurrency(currencyId) { var currencyName = encodeURIComponent(inputNameElement.value); var currencySymbol = encodeURIComponent(inputSymbolElement.value); var currencyCode = encodeURIComponent(inputCodeElement.value); - var url = `endpoints/currency/currency.php?action=edit¤cyId=${currencyId}&name=${currencyName}&symbol=${currencySymbol}&code=${currencyCode}`; + var url = `endpoints/currency/edit.php?currencyId=${currencyId}&name=${currencyName}&symbol=${currencySymbol}&code=${currencyCode}`; fetch(url) .then(response => { diff --git a/settings.php b/settings.php index 9249ff1..0fa53eb 100644 --- a/settings.php +++ b/settings.php @@ -364,7 +364,7 @@ "category": "{{subscription_category}}", "date": "{{subscription_date}}", "payer": "{{subscription_payer}}" - "dyas": "{{subscription_days_until_payment}}" + "days": "{{subscription_days_until_payment}}" } ] diff --git a/styles/dark-theme.css b/styles/dark-theme.css index 2680802..8360897 100644 --- a/styles/dark-theme.css +++ b/styles/dark-theme.css @@ -25,7 +25,8 @@ header .logo .logo-image { .sort-options, .statistic, .graph, -.filtermenu-content { +.filtermenu-content, +.subscription-main .actions { background-color: #222; border: 1px solid #333; box-shadow: 0 2px 5px rgba(120, 120, 120, 0.1); @@ -53,6 +54,29 @@ header .logo .logo-image { border-bottom: 1px solid #EEE; } +.subscription.inactive { + background-color: rgba(24,24,24,0.3); + color: rgba(200,200,200,0.6); + box-shadow: 0 2px 5px rgba(50, 50, 50, 0.1); +} + +.subscription-main .actions { + color: #E0E0E0 +} + +.subscription-main .actions > li { + border-bottom: 1px solid #555; + border-color: #666; +} + +.subscription-main .actions > li:hover { + background-color: #333; +} + +.subscription-main .actions > li:last-of-type { + border: none; +} + .close-form { color: #EEE; } diff --git a/styles/styles.css b/styles/styles.css index aa7e802..23dab49 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -262,7 +262,9 @@ main > .contain { } .subscription.inactive { - opacity: 0.6; + background-color: rgba(255,255,255,0.6); + color: rgba(100,100,100,0.6); + box-shadow: 0 2px 5px rgba(100, 100, 100, 0.1); } .subscription.inactive span.price { @@ -274,8 +276,63 @@ main > .contain { flex-direction: row; align-items: center; gap: 12px; + position: relative; } +.subscription-main .actions-expand { + font-size: 21px; + padding: 8px 16px; + color: var(--main-color); + background-color: transparent; + border: none; + cursor: pointer; +} + +.subscription-main .actions-expand:hover { + color: var(--hover-color); +} + +.subscription-main .actions { + display: none; + position: absolute; + right: -16px; + top: 60px; + z-index: 2; + flex-direction: column; + color: #202020; + background-color: #FFFFFF; + border: 1px solid #eee; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + border-radius: 16px; + padding: 0px; + margin: 0px; +} + +.subscription-main .actions.is-open { + display: flex; +} + +.subscription-main .actions > li { + display: flex; + align-items: center; + justify-content: flex-start; + padding: 14px 35px 14px 18px; + gap: 12px; + cursor: pointer; + border-bottom: 1px solid #eee; +} + +.subscription-main .actions > li:hover { + background-color: #f9f9f9; +} + +.subscription-main .actions > li > i { + color: var(--main-color); +} + +.subscription-main .actions > li:hover > i { + color: var(--hover-color); +} .subscription-secondary { display: none;