Refine loan tab filtering and date formats
This commit is contained in:
+21
-4
@@ -87,9 +87,11 @@ function isDateOnlyString(value) {
|
|||||||
return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value);
|
return typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VALID_DATE_FORMATS = ['mdy', 'dmy', 'ymd', 'mdy_dot', 'dmy_dot', 'ymd_dot', 'ymd_slash'];
|
||||||
|
|
||||||
function getDateFormatPreference() {
|
function getDateFormatPreference() {
|
||||||
const stored = localStorage.getItem(DATE_FORMAT_KEY);
|
const stored = localStorage.getItem(DATE_FORMAT_KEY);
|
||||||
return ['mdy', 'dmy', 'ymd'].includes(stored) ? stored : DEFAULT_DATE_FORMAT;
|
return VALID_DATE_FORMATS.includes(stored) ? stored : DEFAULT_DATE_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDateFormat() {
|
export function getDateFormat() {
|
||||||
@@ -112,8 +114,12 @@ function formatDateParts(date, useUtc = false) {
|
|||||||
const month = String((useUtc ? d.getUTCMonth() : d.getMonth()) + 1).padStart(2, '0');
|
const month = String((useUtc ? d.getUTCMonth() : d.getMonth()) + 1).padStart(2, '0');
|
||||||
const day = String(useUtc ? d.getUTCDate() : d.getDate()).padStart(2, '0');
|
const day = String(useUtc ? d.getUTCDate() : d.getDate()).padStart(2, '0');
|
||||||
switch (getDateFormatPreference()) {
|
switch (getDateFormatPreference()) {
|
||||||
case 'dmy': return `${day}.${month}.${year}`;
|
case 'dmy': return `${day}/${month}/${year}`;
|
||||||
|
case 'mdy_dot': return `${month}.${day}.${year}`;
|
||||||
|
case 'dmy_dot': return `${day}.${month}.${year}`;
|
||||||
case 'ymd': return `${year}-${month}-${day}`;
|
case 'ymd': return `${year}-${month}-${day}`;
|
||||||
|
case 'ymd_dot': return `${year}.${month}.${day}`;
|
||||||
|
case 'ymd_slash': return `${year}/${month}/${day}`;
|
||||||
default: return `${month}/${day}/${year}`;
|
default: return `${month}/${day}/${year}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,8 +145,12 @@ export function formatDate(date) {
|
|||||||
|
|
||||||
export function dateInputPlaceholder() {
|
export function dateInputPlaceholder() {
|
||||||
switch (getDateFormatPreference()) {
|
switch (getDateFormatPreference()) {
|
||||||
case 'dmy': return 'DD.MM.YYYY';
|
case 'dmy': return 'DD/MM/YYYY';
|
||||||
|
case 'mdy_dot': return 'MM.DD.YYYY';
|
||||||
|
case 'dmy_dot': return 'DD.MM.YYYY';
|
||||||
case 'ymd': return 'YYYY-MM-DD';
|
case 'ymd': return 'YYYY-MM-DD';
|
||||||
|
case 'ymd_dot': return 'YYYY.MM.DD';
|
||||||
|
case 'ymd_slash': return 'YYYY/MM/DD';
|
||||||
default: return 'MM/DD/YYYY';
|
default: return 'MM/DD/YYYY';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,11 +167,18 @@ export function parseDateInput(value) {
|
|||||||
const isoMatch = raw.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
const isoMatch = raw.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||||||
if (isoMatch) return isValidDateParts(isoMatch[1], isoMatch[2], isoMatch[3]) ? raw : '';
|
if (isoMatch) return isValidDateParts(isoMatch[1], isoMatch[2], isoMatch[3]) ? raw : '';
|
||||||
|
|
||||||
|
const ymdSeparatorMatch = raw.match(/^(\d{4})[\/.](\d{1,2})[\/.](\d{1,2})$/);
|
||||||
|
if (ymdSeparatorMatch && getDateFormatPreference().startsWith('ymd')) {
|
||||||
|
const [, year, month, day] = ymdSeparatorMatch;
|
||||||
|
if (!isValidDateParts(year, month, day)) return '';
|
||||||
|
return `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
const slashMatch = raw.match(/^(\d{1,2})[\/.](\d{1,2})[\/.](\d{4})$/);
|
const slashMatch = raw.match(/^(\d{1,2})[\/.](\d{1,2})[\/.](\d{4})$/);
|
||||||
if (!slashMatch) return '';
|
if (!slashMatch) return '';
|
||||||
|
|
||||||
const [, first, second, year] = slashMatch;
|
const [, first, second, year] = slashMatch;
|
||||||
const [month, day] = getDateFormatPreference() === 'dmy'
|
const [month, day] = getDateFormatPreference().startsWith('dmy')
|
||||||
? [second, first]
|
? [second, first]
|
||||||
: [first, second];
|
: [first, second];
|
||||||
|
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "تعديل القرض",
|
"editLoan": "تعديل القرض",
|
||||||
"deleteLoan": "حذف القرض",
|
"deleteLoan": "حذف القرض",
|
||||||
"deleteLoanConfirm": "هل تريد حذف القرض \"{{title}}\"؟ ستتم إزالة الدفعات المسجلة في الميزانية أيضًا.",
|
"deleteLoanConfirm": "هل تريد حذف القرض \"{{title}}\"؟ ستتم إزالة الدفعات المسجلة في الميزانية أيضًا.",
|
||||||
|
"deleteLoanPaymentConfirm": "هل تريد حذف دفعة القرض هذه؟",
|
||||||
"loanRemainingAmount": "المتبقي",
|
"loanRemainingAmount": "المتبقي",
|
||||||
"loanRemainingInstallments": "الأقساط المتبقية",
|
"loanRemainingInstallments": "الأقساط المتبقية",
|
||||||
"loanPaidAmount": "المدفوع",
|
"loanPaidAmount": "المدفوع",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "تم حفظ القرض",
|
"loanSavedToast": "تم حفظ القرض",
|
||||||
"loanDeletedToast": "تم حذف القرض",
|
"loanDeletedToast": "تم حذف القرض",
|
||||||
"loanPaymentAddedToast": "تم تسجيل الدفع",
|
"loanPaymentAddedToast": "تم تسجيل الدفع",
|
||||||
|
"loanPaymentTitle": "سداد القرض: {{borrower}}",
|
||||||
"typeLoan": "قرض",
|
"typeLoan": "قرض",
|
||||||
"tabsLabel": "أقسام الميزانية",
|
"tabsLabel": "أقسام الميزانية",
|
||||||
"budgetTab": "الميزانية",
|
"budgetTab": "الميزانية",
|
||||||
|
|||||||
@@ -598,6 +598,7 @@
|
|||||||
"editLoan": "Darlehen bearbeiten",
|
"editLoan": "Darlehen bearbeiten",
|
||||||
"deleteLoan": "Darlehen löschen",
|
"deleteLoan": "Darlehen löschen",
|
||||||
"deleteLoanConfirm": "Darlehen \"{{title}}\" löschen? Bereits im Budget verbuchte Zahlungen werden ebenfalls entfernt.",
|
"deleteLoanConfirm": "Darlehen \"{{title}}\" löschen? Bereits im Budget verbuchte Zahlungen werden ebenfalls entfernt.",
|
||||||
|
"deleteLoanPaymentConfirm": "Diese Darlehenszahlung löschen?",
|
||||||
"loanRemainingAmount": "Offen",
|
"loanRemainingAmount": "Offen",
|
||||||
"loanRemainingInstallments": "Raten offen",
|
"loanRemainingInstallments": "Raten offen",
|
||||||
"loanPaidAmount": "Bezahlt",
|
"loanPaidAmount": "Bezahlt",
|
||||||
@@ -622,6 +623,7 @@
|
|||||||
"loanSavedToast": "Darlehen gespeichert",
|
"loanSavedToast": "Darlehen gespeichert",
|
||||||
"loanDeletedToast": "Darlehen gelöscht",
|
"loanDeletedToast": "Darlehen gelöscht",
|
||||||
"loanPaymentAddedToast": "Zahlung erfasst",
|
"loanPaymentAddedToast": "Zahlung erfasst",
|
||||||
|
"loanPaymentTitle": "Darlehensrückzahlung: {{borrower}}",
|
||||||
"typeLoan": "Darlehen",
|
"typeLoan": "Darlehen",
|
||||||
"tabsLabel": "Budgetbereiche",
|
"tabsLabel": "Budgetbereiche",
|
||||||
"budgetTab": "Budget",
|
"budgetTab": "Budget",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Επεξεργασία δανείου",
|
"editLoan": "Επεξεργασία δανείου",
|
||||||
"deleteLoan": "Διαγραφή δανείου",
|
"deleteLoan": "Διαγραφή δανείου",
|
||||||
"deleteLoanConfirm": "Να διαγραφεί το δάνειο «{{title}}»; Οι πληρωμές που έχουν ήδη περαστεί στον προϋπολογισμό θα αφαιρεθούν επίσης.",
|
"deleteLoanConfirm": "Να διαγραφεί το δάνειο «{{title}}»; Οι πληρωμές που έχουν ήδη περαστεί στον προϋπολογισμό θα αφαιρεθούν επίσης.",
|
||||||
|
"deleteLoanPaymentConfirm": "Διαγραφή αυτής της πληρωμής δανείου;",
|
||||||
"loanRemainingAmount": "Υπόλοιπο",
|
"loanRemainingAmount": "Υπόλοιπο",
|
||||||
"loanRemainingInstallments": "Δόσεις που απομένουν",
|
"loanRemainingInstallments": "Δόσεις που απομένουν",
|
||||||
"loanPaidAmount": "Πληρωμένο",
|
"loanPaidAmount": "Πληρωμένο",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Το δάνειο αποθηκεύτηκε",
|
"loanSavedToast": "Το δάνειο αποθηκεύτηκε",
|
||||||
"loanDeletedToast": "Το δάνειο διαγράφηκε",
|
"loanDeletedToast": "Το δάνειο διαγράφηκε",
|
||||||
"loanPaymentAddedToast": "Η πληρωμή καταγράφηκε",
|
"loanPaymentAddedToast": "Η πληρωμή καταγράφηκε",
|
||||||
|
"loanPaymentTitle": "Πληρωμή δανείου: {{borrower}}",
|
||||||
"typeLoan": "Δάνειο",
|
"typeLoan": "Δάνειο",
|
||||||
"tabsLabel": "Ενότητες προϋπολογισμού",
|
"tabsLabel": "Ενότητες προϋπολογισμού",
|
||||||
"budgetTab": "Προϋπολογισμός",
|
"budgetTab": "Προϋπολογισμός",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Edit loan",
|
"editLoan": "Edit loan",
|
||||||
"deleteLoan": "Delete loan",
|
"deleteLoan": "Delete loan",
|
||||||
"deleteLoanConfirm": "Delete loan \"{{title}}\"? Payments already posted to the budget will also be removed.",
|
"deleteLoanConfirm": "Delete loan \"{{title}}\"? Payments already posted to the budget will also be removed.",
|
||||||
|
"deleteLoanPaymentConfirm": "Delete this loan payment?",
|
||||||
"loanRemainingAmount": "Remaining",
|
"loanRemainingAmount": "Remaining",
|
||||||
"loanRemainingInstallments": "Installments left",
|
"loanRemainingInstallments": "Installments left",
|
||||||
"loanPaidAmount": "Paid",
|
"loanPaidAmount": "Paid",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Loan saved",
|
"loanSavedToast": "Loan saved",
|
||||||
"loanDeletedToast": "Loan deleted",
|
"loanDeletedToast": "Loan deleted",
|
||||||
"loanPaymentAddedToast": "Payment recorded",
|
"loanPaymentAddedToast": "Payment recorded",
|
||||||
|
"loanPaymentTitle": "Loan repayment: {{borrower}}",
|
||||||
"typeLoan": "Loan",
|
"typeLoan": "Loan",
|
||||||
"tabsLabel": "Budget sections",
|
"tabsLabel": "Budget sections",
|
||||||
"budgetTab": "Budget",
|
"budgetTab": "Budget",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Editar préstamo",
|
"editLoan": "Editar préstamo",
|
||||||
"deleteLoan": "Eliminar préstamo",
|
"deleteLoan": "Eliminar préstamo",
|
||||||
"deleteLoanConfirm": "¿Eliminar el préstamo \"{{title}}\"? También se eliminarán los pagos ya registrados en el presupuesto.",
|
"deleteLoanConfirm": "¿Eliminar el préstamo \"{{title}}\"? También se eliminarán los pagos ya registrados en el presupuesto.",
|
||||||
|
"deleteLoanPaymentConfirm": "¿Eliminar este pago del préstamo?",
|
||||||
"loanRemainingAmount": "Restante",
|
"loanRemainingAmount": "Restante",
|
||||||
"loanRemainingInstallments": "Cuotas restantes",
|
"loanRemainingInstallments": "Cuotas restantes",
|
||||||
"loanPaidAmount": "Pagado",
|
"loanPaidAmount": "Pagado",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Préstamo guardado",
|
"loanSavedToast": "Préstamo guardado",
|
||||||
"loanDeletedToast": "Préstamo eliminado",
|
"loanDeletedToast": "Préstamo eliminado",
|
||||||
"loanPaymentAddedToast": "Pago registrado",
|
"loanPaymentAddedToast": "Pago registrado",
|
||||||
|
"loanPaymentTitle": "Pago del préstamo: {{borrower}}",
|
||||||
"typeLoan": "Préstamo",
|
"typeLoan": "Préstamo",
|
||||||
"tabsLabel": "Secciones del presupuesto",
|
"tabsLabel": "Secciones del presupuesto",
|
||||||
"budgetTab": "Presupuesto",
|
"budgetTab": "Presupuesto",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Modifier le prêt",
|
"editLoan": "Modifier le prêt",
|
||||||
"deleteLoan": "Supprimer le prêt",
|
"deleteLoan": "Supprimer le prêt",
|
||||||
"deleteLoanConfirm": "Supprimer le prêt \"{{title}}\" ? Les paiements déjà enregistrés dans le budget seront aussi supprimés.",
|
"deleteLoanConfirm": "Supprimer le prêt \"{{title}}\" ? Les paiements déjà enregistrés dans le budget seront aussi supprimés.",
|
||||||
|
"deleteLoanPaymentConfirm": "Supprimer ce paiement de prêt ?",
|
||||||
"loanRemainingAmount": "Restant",
|
"loanRemainingAmount": "Restant",
|
||||||
"loanRemainingInstallments": "Échéances restantes",
|
"loanRemainingInstallments": "Échéances restantes",
|
||||||
"loanPaidAmount": "Payé",
|
"loanPaidAmount": "Payé",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Prêt enregistré",
|
"loanSavedToast": "Prêt enregistré",
|
||||||
"loanDeletedToast": "Prêt supprimé",
|
"loanDeletedToast": "Prêt supprimé",
|
||||||
"loanPaymentAddedToast": "Paiement enregistré",
|
"loanPaymentAddedToast": "Paiement enregistré",
|
||||||
|
"loanPaymentTitle": "Remboursement du prêt : {{borrower}}",
|
||||||
"typeLoan": "Prêt",
|
"typeLoan": "Prêt",
|
||||||
"tabsLabel": "Sections du budget",
|
"tabsLabel": "Sections du budget",
|
||||||
"budgetTab": "Budget",
|
"budgetTab": "Budget",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "उधार संपादित करें",
|
"editLoan": "उधार संपादित करें",
|
||||||
"deleteLoan": "उधार हटाएं",
|
"deleteLoan": "उधार हटाएं",
|
||||||
"deleteLoanConfirm": "उधार \"{{title}}\" हटाएं? बजट में दर्ज भुगतान भी हटा दिए जाएंगे।",
|
"deleteLoanConfirm": "उधार \"{{title}}\" हटाएं? बजट में दर्ज भुगतान भी हटा दिए जाएंगे।",
|
||||||
|
"deleteLoanPaymentConfirm": "यह ऋण भुगतान हटाएँ?",
|
||||||
"loanRemainingAmount": "बाकी",
|
"loanRemainingAmount": "बाकी",
|
||||||
"loanRemainingInstallments": "बाकी किस्तें",
|
"loanRemainingInstallments": "बाकी किस्तें",
|
||||||
"loanPaidAmount": "भुगतान किया",
|
"loanPaidAmount": "भुगतान किया",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "उधार सहेजा गया",
|
"loanSavedToast": "उधार सहेजा गया",
|
||||||
"loanDeletedToast": "उधार हटाया गया",
|
"loanDeletedToast": "उधार हटाया गया",
|
||||||
"loanPaymentAddedToast": "भुगतान दर्ज किया गया",
|
"loanPaymentAddedToast": "भुगतान दर्ज किया गया",
|
||||||
|
"loanPaymentTitle": "ऋण भुगतान: {{borrower}}",
|
||||||
"typeLoan": "उधार",
|
"typeLoan": "उधार",
|
||||||
"tabsLabel": "बजट अनुभाग",
|
"tabsLabel": "बजट अनुभाग",
|
||||||
"budgetTab": "बजट",
|
"budgetTab": "बजट",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Modifica prestito",
|
"editLoan": "Modifica prestito",
|
||||||
"deleteLoan": "Elimina prestito",
|
"deleteLoan": "Elimina prestito",
|
||||||
"deleteLoanConfirm": "Eliminare il prestito \"{{title}}\"? Verranno rimossi anche i pagamenti già registrati nel bilancio.",
|
"deleteLoanConfirm": "Eliminare il prestito \"{{title}}\"? Verranno rimossi anche i pagamenti già registrati nel bilancio.",
|
||||||
|
"deleteLoanPaymentConfirm": "Eliminare questo pagamento del prestito?",
|
||||||
"loanRemainingAmount": "Rimanente",
|
"loanRemainingAmount": "Rimanente",
|
||||||
"loanRemainingInstallments": "Rate rimanenti",
|
"loanRemainingInstallments": "Rate rimanenti",
|
||||||
"loanPaidAmount": "Pagato",
|
"loanPaidAmount": "Pagato",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Prestito salvato",
|
"loanSavedToast": "Prestito salvato",
|
||||||
"loanDeletedToast": "Prestito eliminato",
|
"loanDeletedToast": "Prestito eliminato",
|
||||||
"loanPaymentAddedToast": "Pagamento registrato",
|
"loanPaymentAddedToast": "Pagamento registrato",
|
||||||
|
"loanPaymentTitle": "Rimborso del prestito: {{borrower}}",
|
||||||
"typeLoan": "Prestito",
|
"typeLoan": "Prestito",
|
||||||
"tabsLabel": "Sezioni del bilancio",
|
"tabsLabel": "Sezioni del bilancio",
|
||||||
"budgetTab": "Bilancio",
|
"budgetTab": "Bilancio",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "貸付を編集",
|
"editLoan": "貸付を編集",
|
||||||
"deleteLoan": "貸付を削除",
|
"deleteLoan": "貸付を削除",
|
||||||
"deleteLoanConfirm": "貸付「{{title}}」を削除しますか?予算に記録済みの返済も削除されます。",
|
"deleteLoanConfirm": "貸付「{{title}}」を削除しますか?予算に記録済みの返済も削除されます。",
|
||||||
|
"deleteLoanPaymentConfirm": "このローン支払いを削除しますか?",
|
||||||
"loanRemainingAmount": "残額",
|
"loanRemainingAmount": "残額",
|
||||||
"loanRemainingInstallments": "残り回数",
|
"loanRemainingInstallments": "残り回数",
|
||||||
"loanPaidAmount": "返済済み",
|
"loanPaidAmount": "返済済み",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "貸付を保存しました",
|
"loanSavedToast": "貸付を保存しました",
|
||||||
"loanDeletedToast": "貸付を削除しました",
|
"loanDeletedToast": "貸付を削除しました",
|
||||||
"loanPaymentAddedToast": "返済を記録しました",
|
"loanPaymentAddedToast": "返済を記録しました",
|
||||||
|
"loanPaymentTitle": "ローン返済: {{borrower}}",
|
||||||
"typeLoan": "貸付",
|
"typeLoan": "貸付",
|
||||||
"tabsLabel": "予算セクション",
|
"tabsLabel": "予算セクション",
|
||||||
"budgetTab": "予算",
|
"budgetTab": "予算",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Editar empréstimo",
|
"editLoan": "Editar empréstimo",
|
||||||
"deleteLoan": "Excluir empréstimo",
|
"deleteLoan": "Excluir empréstimo",
|
||||||
"deleteLoanConfirm": "Excluir empréstimo \"{{title}}\"? Pagamentos já lançados no orçamento também serão removidos.",
|
"deleteLoanConfirm": "Excluir empréstimo \"{{title}}\"? Pagamentos já lançados no orçamento também serão removidos.",
|
||||||
|
"deleteLoanPaymentConfirm": "Excluir este pagamento do empréstimo?",
|
||||||
"loanRemainingAmount": "Restante",
|
"loanRemainingAmount": "Restante",
|
||||||
"loanRemainingInstallments": "Parcelas restantes",
|
"loanRemainingInstallments": "Parcelas restantes",
|
||||||
"loanPaidAmount": "Pago",
|
"loanPaidAmount": "Pago",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Empréstimo salvo",
|
"loanSavedToast": "Empréstimo salvo",
|
||||||
"loanDeletedToast": "Empréstimo excluído",
|
"loanDeletedToast": "Empréstimo excluído",
|
||||||
"loanPaymentAddedToast": "Pagamento registrado",
|
"loanPaymentAddedToast": "Pagamento registrado",
|
||||||
|
"loanPaymentTitle": "Pagamento do empréstimo: {{borrower}}",
|
||||||
"typeLoan": "Empréstimo",
|
"typeLoan": "Empréstimo",
|
||||||
"tabsLabel": "Seções do orçamento",
|
"tabsLabel": "Seções do orçamento",
|
||||||
"budgetTab": "Orçamento",
|
"budgetTab": "Orçamento",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Изменить займ",
|
"editLoan": "Изменить займ",
|
||||||
"deleteLoan": "Удалить займ",
|
"deleteLoan": "Удалить займ",
|
||||||
"deleteLoanConfirm": "Удалить займ «{{title}}»? Платежи, уже добавленные в бюджет, тоже будут удалены.",
|
"deleteLoanConfirm": "Удалить займ «{{title}}»? Платежи, уже добавленные в бюджет, тоже будут удалены.",
|
||||||
|
"deleteLoanPaymentConfirm": "Удалить этот платеж по займу?",
|
||||||
"loanRemainingAmount": "Осталось",
|
"loanRemainingAmount": "Осталось",
|
||||||
"loanRemainingInstallments": "Осталось платежей",
|
"loanRemainingInstallments": "Осталось платежей",
|
||||||
"loanPaidAmount": "Оплачено",
|
"loanPaidAmount": "Оплачено",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Займ сохранён",
|
"loanSavedToast": "Займ сохранён",
|
||||||
"loanDeletedToast": "Займ удалён",
|
"loanDeletedToast": "Займ удалён",
|
||||||
"loanPaymentAddedToast": "Платёж записан",
|
"loanPaymentAddedToast": "Платёж записан",
|
||||||
|
"loanPaymentTitle": "Платеж по займу: {{borrower}}",
|
||||||
"typeLoan": "Займ",
|
"typeLoan": "Займ",
|
||||||
"tabsLabel": "Разделы бюджета",
|
"tabsLabel": "Разделы бюджета",
|
||||||
"budgetTab": "Бюджет",
|
"budgetTab": "Бюджет",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Redigera lån",
|
"editLoan": "Redigera lån",
|
||||||
"deleteLoan": "Ta bort lån",
|
"deleteLoan": "Ta bort lån",
|
||||||
"deleteLoanConfirm": "Ta bort lånet \"{{title}}\"? Betalningar som redan bokförts i budgeten tas också bort.",
|
"deleteLoanConfirm": "Ta bort lånet \"{{title}}\"? Betalningar som redan bokförts i budgeten tas också bort.",
|
||||||
|
"deleteLoanPaymentConfirm": "Ta bort den här lånebetalningen?",
|
||||||
"loanRemainingAmount": "Kvar",
|
"loanRemainingAmount": "Kvar",
|
||||||
"loanRemainingInstallments": "Delbetalningar kvar",
|
"loanRemainingInstallments": "Delbetalningar kvar",
|
||||||
"loanPaidAmount": "Betalt",
|
"loanPaidAmount": "Betalt",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Lån sparat",
|
"loanSavedToast": "Lån sparat",
|
||||||
"loanDeletedToast": "Lån borttaget",
|
"loanDeletedToast": "Lån borttaget",
|
||||||
"loanPaymentAddedToast": "Betalning registrerad",
|
"loanPaymentAddedToast": "Betalning registrerad",
|
||||||
|
"loanPaymentTitle": "Låneåterbetalning: {{borrower}}",
|
||||||
"typeLoan": "Lån",
|
"typeLoan": "Lån",
|
||||||
"tabsLabel": "Budgetsektioner",
|
"tabsLabel": "Budgetsektioner",
|
||||||
"budgetTab": "Budget",
|
"budgetTab": "Budget",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Borcu düzenle",
|
"editLoan": "Borcu düzenle",
|
||||||
"deleteLoan": "Borcu sil",
|
"deleteLoan": "Borcu sil",
|
||||||
"deleteLoanConfirm": "\"{{title}}\" borcu silinsin mi? Bütçeye işlenmiş ödemeler de kaldırılır.",
|
"deleteLoanConfirm": "\"{{title}}\" borcu silinsin mi? Bütçeye işlenmiş ödemeler de kaldırılır.",
|
||||||
|
"deleteLoanPaymentConfirm": "Bu kredi ödemesi silinsin mi?",
|
||||||
"loanRemainingAmount": "Kalan",
|
"loanRemainingAmount": "Kalan",
|
||||||
"loanRemainingInstallments": "Kalan taksit",
|
"loanRemainingInstallments": "Kalan taksit",
|
||||||
"loanPaidAmount": "Ödenen",
|
"loanPaidAmount": "Ödenen",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Borç kaydedildi",
|
"loanSavedToast": "Borç kaydedildi",
|
||||||
"loanDeletedToast": "Borç silindi",
|
"loanDeletedToast": "Borç silindi",
|
||||||
"loanPaymentAddedToast": "Ödeme kaydedildi",
|
"loanPaymentAddedToast": "Ödeme kaydedildi",
|
||||||
|
"loanPaymentTitle": "Kredi geri ödemesi: {{borrower}}",
|
||||||
"typeLoan": "Borç",
|
"typeLoan": "Borç",
|
||||||
"tabsLabel": "Bütçe bölümleri",
|
"tabsLabel": "Bütçe bölümleri",
|
||||||
"budgetTab": "Bütçe",
|
"budgetTab": "Bütçe",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "Редагувати позику",
|
"editLoan": "Редагувати позику",
|
||||||
"deleteLoan": "Видалити позику",
|
"deleteLoan": "Видалити позику",
|
||||||
"deleteLoanConfirm": "Видалити позику «{{title}}»? Платежі, вже додані до бюджету, також буде видалено.",
|
"deleteLoanConfirm": "Видалити позику «{{title}}»? Платежі, вже додані до бюджету, також буде видалено.",
|
||||||
|
"deleteLoanPaymentConfirm": "Видалити цей платіж за позикою?",
|
||||||
"loanRemainingAmount": "Залишилось",
|
"loanRemainingAmount": "Залишилось",
|
||||||
"loanRemainingInstallments": "Залишилось платежів",
|
"loanRemainingInstallments": "Залишилось платежів",
|
||||||
"loanPaidAmount": "Сплачено",
|
"loanPaidAmount": "Сплачено",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "Позику збережено",
|
"loanSavedToast": "Позику збережено",
|
||||||
"loanDeletedToast": "Позику видалено",
|
"loanDeletedToast": "Позику видалено",
|
||||||
"loanPaymentAddedToast": "Платіж записано",
|
"loanPaymentAddedToast": "Платіж записано",
|
||||||
|
"loanPaymentTitle": "Платіж за позикою: {{borrower}}",
|
||||||
"typeLoan": "Позика",
|
"typeLoan": "Позика",
|
||||||
"tabsLabel": "Розділи бюджету",
|
"tabsLabel": "Розділи бюджету",
|
||||||
"budgetTab": "Бюджет",
|
"budgetTab": "Бюджет",
|
||||||
|
|||||||
@@ -573,6 +573,7 @@
|
|||||||
"editLoan": "编辑借款",
|
"editLoan": "编辑借款",
|
||||||
"deleteLoan": "删除借款",
|
"deleteLoan": "删除借款",
|
||||||
"deleteLoanConfirm": "删除借款“{{title}}”?已记入预算的还款也会被删除。",
|
"deleteLoanConfirm": "删除借款“{{title}}”?已记入预算的还款也会被删除。",
|
||||||
|
"deleteLoanPaymentConfirm": "删除这笔借款还款?",
|
||||||
"loanRemainingAmount": "剩余金额",
|
"loanRemainingAmount": "剩余金额",
|
||||||
"loanRemainingInstallments": "剩余期数",
|
"loanRemainingInstallments": "剩余期数",
|
||||||
"loanPaidAmount": "已还金额",
|
"loanPaidAmount": "已还金额",
|
||||||
@@ -597,6 +598,7 @@
|
|||||||
"loanSavedToast": "借款已保存",
|
"loanSavedToast": "借款已保存",
|
||||||
"loanDeletedToast": "借款已删除",
|
"loanDeletedToast": "借款已删除",
|
||||||
"loanPaymentAddedToast": "还款已记录",
|
"loanPaymentAddedToast": "还款已记录",
|
||||||
|
"loanPaymentTitle": "借款还款:{{borrower}}",
|
||||||
"typeLoan": "借款",
|
"typeLoan": "借款",
|
||||||
"tabsLabel": "预算分区",
|
"tabsLabel": "预算分区",
|
||||||
"budgetTab": "预算",
|
"budgetTab": "预算",
|
||||||
|
|||||||
+97
-47
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { api } from '/api.js';
|
import { api } from '/api.js';
|
||||||
import { openModal as openSharedModal, closeModal } from '/components/modal.js';
|
import { openModal as openSharedModal, closeModal, confirmModal } from '/components/modal.js';
|
||||||
import { stagger, vibrate } from '/utils/ux.js';
|
import { stagger, vibrate } from '/utils/ux.js';
|
||||||
import { t, formatDate, getLocale, dateInputPlaceholder, formatDateInput, parseDateInput, isDateInputValid } from '/i18n.js';
|
import { t, formatDate, getLocale, dateInputPlaceholder, formatDateInput, parseDateInput, isDateInputValid } from '/i18n.js';
|
||||||
import { esc } from '/utils/html.js';
|
import { esc } from '/utils/html.js';
|
||||||
@@ -164,11 +164,8 @@ function setHtml(element, html) {
|
|||||||
async function loadMonth(month) {
|
async function loadMonth(month) {
|
||||||
const prevMonth = addMonths(month, -1);
|
const prevMonth = addMonths(month, -1);
|
||||||
try {
|
try {
|
||||||
const entriesPath = state.loanFilterId
|
|
||||||
? `/budget?loan_id=${encodeURIComponent(state.loanFilterId)}`
|
|
||||||
: `/budget?month=${month}`;
|
|
||||||
const [entriesRes, summaryRes, prevSummaryRes, loansRes] = await Promise.all([
|
const [entriesRes, summaryRes, prevSummaryRes, loansRes] = await Promise.all([
|
||||||
api.get(entriesPath),
|
api.get(`/budget?month=${month}`),
|
||||||
api.get(`/budget/summary?month=${month}`),
|
api.get(`/budget/summary?month=${month}`),
|
||||||
api.get(`/budget/summary?month=${prevMonth}`),
|
api.get(`/budget/summary?month=${prevMonth}`),
|
||||||
api.get('/budget/loans'),
|
api.get('/budget/loans'),
|
||||||
@@ -357,16 +354,10 @@ function renderBody() {
|
|||||||
<div class="budget-list-section">
|
<div class="budget-list-section">
|
||||||
<div class="budget-list-header">
|
<div class="budget-list-header">
|
||||||
<div>
|
<div>
|
||||||
<span class="budget-list-header__title">${state.loanFilterId ? t('budget.filteredTransactions') : t('budget.transactions')}</span>
|
<span class="budget-list-header__title">${t('budget.transactions')}</span>
|
||||||
${state.loanFilterId ? `<div class="budget-list-header__filter">${esc(activeLoanLabel())}</div>` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="budget-list-header__actions">
|
<div class="budget-list-header__actions">
|
||||||
${state.loanFilterId ? `
|
${state.entries.length ? `
|
||||||
<button class="btn btn--secondary" id="budget-clear-loan-filter"
|
|
||||||
style="font-size:var(--text-sm);padding:var(--space-1) var(--space-3);">
|
|
||||||
<i data-lucide="x" style="width:14px;height:14px;margin-right:4px;" aria-hidden="true"></i>${t('budget.clearLoanFilter')}
|
|
||||||
</button>` : ''}
|
|
||||||
${state.entries.length && !state.loanFilterId ? `
|
|
||||||
<a href="/api/v1/budget/export?month=${state.month}" class="btn btn--secondary"
|
<a href="/api/v1/budget/export?month=${state.month}" class="btn btn--secondary"
|
||||||
style="font-size:var(--text-sm);padding:var(--space-1) var(--space-3);">
|
style="font-size:var(--text-sm);padding:var(--space-1) var(--space-3);">
|
||||||
<i data-lucide="download" style="width:14px;height:14px;margin-right:4px;" aria-hidden="true"></i>CSV
|
<i data-lucide="download" style="width:14px;height:14px;margin-right:4px;" aria-hidden="true"></i>CSV
|
||||||
@@ -384,11 +375,6 @@ function renderBody() {
|
|||||||
_container.querySelector('#empty-cta-budget')?.addEventListener('click', () => {
|
_container.querySelector('#empty-cta-budget')?.addEventListener('click', () => {
|
||||||
document.querySelector('.page-fab')?.click();
|
document.querySelector('.page-fab')?.click();
|
||||||
});
|
});
|
||||||
_container.querySelector('#budget-clear-loan-filter')?.addEventListener('click', async () => {
|
|
||||||
state.loanFilterId = null;
|
|
||||||
await loadMonth(state.month);
|
|
||||||
renderBody();
|
|
||||||
});
|
|
||||||
stagger(_container.querySelector('#budget-list')?.querySelectorAll('.budget-entry') ?? []);
|
stagger(_container.querySelector('#budget-list')?.querySelectorAll('.budget-entry') ?? []);
|
||||||
|
|
||||||
_container.querySelector('#budget-list')?.addEventListener('click', async (e) => {
|
_container.querySelector('#budget-list')?.addEventListener('click', async (e) => {
|
||||||
@@ -411,11 +397,6 @@ function updateTabs() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function activeLoanLabel() {
|
|
||||||
const loan = state.loans.loans.find((item) => item.id === state.loanFilterId);
|
|
||||||
return loan ? t('budget.loanFilterActive', { title: loan.title }) : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderCategoryBars(byCategory) {
|
function renderCategoryBars(byCategory) {
|
||||||
const maxAbs = Math.max(...byCategory.map((c) => Math.abs(c.total)), 1);
|
const maxAbs = Math.max(...byCategory.map((c) => Math.abs(c.total)), 1);
|
||||||
|
|
||||||
@@ -498,8 +479,13 @@ function renderLoansDashboard() {
|
|||||||
count: summary.active_count ?? 0,
|
count: summary.active_count ?? 0,
|
||||||
amount: formatAmount(summary.remaining_amount ?? 0),
|
amount: formatAmount(summary.remaining_amount ?? 0),
|
||||||
})}</div>
|
})}</div>
|
||||||
|
${state.loanFilterId ? `<div class="budget-list-header__filter">${esc(activeLoanLabel())}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="budget-loans__filters" role="group" aria-label="${t('budget.loanStatusFilterLabel')}">
|
<div class="budget-loans__filters" role="group" aria-label="${t('budget.loanStatusFilterLabel')}">
|
||||||
|
${state.loanFilterId ? `
|
||||||
|
<button class="budget-loans__filter" type="button" id="budget-clear-loan-filter">
|
||||||
|
<i data-lucide="x" aria-hidden="true"></i>${t('budget.clearLoanFilter')}
|
||||||
|
</button>` : ''}
|
||||||
<button class="budget-loans__filter ${state.loanStatusFilter === 'active' ? 'budget-loans__filter--active' : ''}"
|
<button class="budget-loans__filter ${state.loanStatusFilter === 'active' ? 'budget-loans__filter--active' : ''}"
|
||||||
type="button" data-loan-status="active">${t('budget.loanStatusActive')}</button>
|
type="button" data-loan-status="active">${t('budget.loanStatusActive')}</button>
|
||||||
<button class="budget-loans__filter ${state.loanStatusFilter === 'paid' ? 'budget-loans__filter--active' : ''}"
|
<button class="budget-loans__filter ${state.loanStatusFilter === 'paid' ? 'budget-loans__filter--active' : ''}"
|
||||||
@@ -536,8 +522,16 @@ function renderLoansDashboard() {
|
|||||||
|
|
||||||
function filteredLoans() {
|
function filteredLoans() {
|
||||||
const loans = state.loans?.loans ?? [];
|
const loans = state.loans?.loans ?? [];
|
||||||
if (state.loanStatusFilter === 'all') return loans;
|
return loans.filter((loan) => {
|
||||||
return loans.filter((loan) => loan.status === state.loanStatusFilter);
|
const matchesStatus = state.loanStatusFilter === 'all' || loan.status === state.loanStatusFilter;
|
||||||
|
const matchesLoan = !state.loanFilterId || loan.id === state.loanFilterId;
|
||||||
|
return matchesStatus && matchesLoan;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function activeLoanLabel() {
|
||||||
|
const loan = state.loans.loans.find((item) => item.id === state.loanFilterId);
|
||||||
|
return loan ? t('budget.loanFilterActive', { title: loan.title }) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function loanPaymentsFor(loans) {
|
function loanPaymentsFor(loans) {
|
||||||
@@ -552,25 +546,53 @@ function renderLoanTransactions(loans) {
|
|||||||
return `<div class="budget-loan-transactions">
|
return `<div class="budget-loan-transactions">
|
||||||
<div class="budget-loan-transactions__title">${t('budget.loanTransactions')}</div>
|
<div class="budget-loan-transactions__title">${t('budget.loanTransactions')}</div>
|
||||||
<div class="budget-loan-transactions__list">
|
<div class="budget-loan-transactions__list">
|
||||||
${payments.map(({ loan, ...payment }) => `
|
${payments.map(({ loan, ...payment }) => renderLoanPaymentEntry(loan, payment)).join('')}
|
||||||
<div class="budget-loan-transaction">
|
|
||||||
<div>
|
|
||||||
<strong>${esc(loan.title)}</strong>
|
|
||||||
<span>${esc(loan.borrower)} · ${t('budget.loanInstallmentNumber', {
|
|
||||||
number: payment.installment_number,
|
|
||||||
total: loan.installment_count,
|
|
||||||
})}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>${formatAmount(payment.amount)}</strong>
|
|
||||||
<span>${formatEntryDate(payment.paid_date)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`).join('')}
|
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loanPaymentToEntry(loan, payment) {
|
||||||
|
if (!payment.budget_entry_id) return null;
|
||||||
|
return {
|
||||||
|
id: payment.budget_entry_id,
|
||||||
|
title: payment.entry_title || `Loan repayment: ${loan.borrower}`,
|
||||||
|
amount: Number(payment.amount || 0),
|
||||||
|
category: payment.entry_category || 'Geschenke & Transfers',
|
||||||
|
subcategory: payment.entry_subcategory || '',
|
||||||
|
date: payment.paid_date,
|
||||||
|
is_recurring: payment.entry_is_recurring || 0,
|
||||||
|
recurrence_parent_id: payment.entry_recurrence_parent_id || null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLoanPaymentEntry(loan, payment) {
|
||||||
|
const entry = loanPaymentToEntry(loan, payment);
|
||||||
|
const meta = `${formatEntryDate(payment.paid_date)} · ${esc(loan.title)} · ${t('budget.loanInstallmentNumber', {
|
||||||
|
number: payment.installment_number,
|
||||||
|
total: loan.installment_count,
|
||||||
|
})}`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="budget-entry budget-entry--loan" data-loan-payment-id="${payment.id}" data-loan-id="${loan.id}" ${entry ? `data-entry-id="${entry.id}"` : ''}>
|
||||||
|
<div class="budget-entry__indicator budget-entry__indicator--income"></div>
|
||||||
|
<div class="budget-entry__body">
|
||||||
|
<div class="budget-entry__title">${esc(payment.entry_title || t('budget.loanPaymentTitle', { borrower: loan.borrower }))}</div>
|
||||||
|
<div class="budget-entry__meta">${meta}</div>
|
||||||
|
</div>
|
||||||
|
<div class="budget-entry__amount budget-entry__amount--income">+${formatAmount(payment.amount)}</div>
|
||||||
|
<div class="budget-entry__actions">
|
||||||
|
${entry ? `
|
||||||
|
<button class="budget-entry__delete" data-action="loan-payment-edit" data-loan-id="${loan.id}" data-payment-id="${payment.id}" data-entry-id="${entry.id}" aria-label="${t('common.edit')}">
|
||||||
|
<i data-lucide="pencil" style="width:14px;height:14px;" aria-hidden="true"></i>
|
||||||
|
</button>` : ''}
|
||||||
|
<button class="budget-entry__delete" data-action="loan-payment-delete" data-loan-id="${loan.id}" data-payment-id="${payment.id}" data-entry-id="${entry?.id ?? ''}" aria-label="${t('budget.deleteLabel')}">
|
||||||
|
<i data-lucide="trash-2" style="width:14px;height:14px;" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderLoansPage() {
|
function renderLoansPage() {
|
||||||
const loans = state.loans?.loans ?? [];
|
const loans = state.loans?.loans ?? [];
|
||||||
if (!loans.length) {
|
if (!loans.length) {
|
||||||
@@ -594,6 +616,10 @@ function renderLoansPage() {
|
|||||||
|
|
||||||
function wireLoansPage() {
|
function wireLoansPage() {
|
||||||
_container.querySelector('#budget-empty-loan')?.addEventListener('click', () => openBudgetModal({ mode: 'create', initialType: 'loan' }));
|
_container.querySelector('#budget-empty-loan')?.addEventListener('click', () => openBudgetModal({ mode: 'create', initialType: 'loan' }));
|
||||||
|
_container.querySelector('#budget-clear-loan-filter')?.addEventListener('click', () => {
|
||||||
|
state.loanFilterId = null;
|
||||||
|
renderBody();
|
||||||
|
});
|
||||||
_container.querySelectorAll('[data-loan-status]').forEach((btn) => {
|
_container.querySelectorAll('[data-loan-status]').forEach((btn) => {
|
||||||
btn.addEventListener('click', () => {
|
btn.addEventListener('click', () => {
|
||||||
state.loanStatusFilter = btn.dataset.loanStatus;
|
state.loanStatusFilter = btn.dataset.loanStatus;
|
||||||
@@ -624,13 +650,25 @@ function wireLoansPage() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
_container.querySelectorAll('[data-action="loan-filter"]').forEach((btn) => {
|
_container.querySelectorAll('[data-action="loan-filter"]').forEach((btn) => {
|
||||||
btn.addEventListener('click', async () => {
|
btn.addEventListener('click', () => {
|
||||||
state.loanFilterId = parseInt(btn.dataset.id, 10);
|
const id = parseInt(btn.dataset.id, 10);
|
||||||
state.activeTab = 'budget';
|
state.loanFilterId = state.loanFilterId === id ? null : id;
|
||||||
await loadMonth(state.month);
|
|
||||||
renderBody();
|
renderBody();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
_container.querySelectorAll('[data-action="loan-payment-edit"]').forEach((btn) => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
const loan = state.loans.loans.find((item) => item.id === parseInt(btn.dataset.loanId, 10));
|
||||||
|
const payment = loan?.payments?.find((item) => item.id === parseInt(btn.dataset.paymentId, 10));
|
||||||
|
const entry = loan && payment ? loanPaymentToEntry(loan, payment) : null;
|
||||||
|
if (entry) openBudgetModal({ mode: 'edit', entry });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
_container.querySelectorAll('[data-action="loan-payment-delete"]').forEach((btn) => {
|
||||||
|
btn.addEventListener('click', async () => {
|
||||||
|
await deleteLoanPayment(parseInt(btn.dataset.loanId, 10), parseInt(btn.dataset.paymentId, 10));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openLoanReport(loan) {
|
function openLoanReport(loan) {
|
||||||
@@ -695,7 +733,7 @@ function renderLoanCard(loan) {
|
|||||||
<div class="budget-loan-card__main">
|
<div class="budget-loan-card__main">
|
||||||
<div class="budget-loan-card__title-row">
|
<div class="budget-loan-card__title-row">
|
||||||
<div class="budget-loan-card__title">${esc(loan.title)}</div>
|
<div class="budget-loan-card__title">${esc(loan.title)}</div>
|
||||||
<button class="budget-loan-card__filter" data-action="loan-filter" data-id="${loan.id}" aria-label="${t('budget.filterLoanTransactions')}">
|
<button class="budget-loan-card__filter ${state.loanFilterId === loan.id ? 'budget-loan-card__filter--active' : ''}" data-action="loan-filter" data-id="${loan.id}" aria-label="${t('budget.filterLoanTransactions')}">
|
||||||
<i data-lucide="filter" aria-hidden="true"></i>
|
<i data-lucide="filter" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -752,7 +790,7 @@ function renderTrend(current, prev, prevLabel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatEntryDate(dateStr) {
|
function formatEntryDate(dateStr) {
|
||||||
return formatDate(new Date(dateStr + 'T00:00:00'));
|
return formatDate(dateStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
@@ -1198,7 +1236,7 @@ async function markLoanPayment(id) {
|
|||||||
async function deleteLoan(id) {
|
async function deleteLoan(id) {
|
||||||
const loan = state.loans.loans.find((item) => item.id === id);
|
const loan = state.loans.loans.find((item) => item.id === id);
|
||||||
if (!loan) return;
|
if (!loan) return;
|
||||||
if (!window.confirm(t('budget.deleteLoanConfirm', { title: loan.title }))) return;
|
if (!await confirmModal(t('budget.deleteLoanConfirm', { title: loan.title }), { danger: true, confirmLabel: t('common.delete') })) return;
|
||||||
try {
|
try {
|
||||||
await api.delete(`/budget/loans/${id}`);
|
await api.delete(`/budget/loans/${id}`);
|
||||||
await loadMonth(state.month);
|
await loadMonth(state.month);
|
||||||
@@ -1209,6 +1247,18 @@ async function deleteLoan(id) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteLoanPayment(loanId, paymentId) {
|
||||||
|
if (!await confirmModal(t('budget.deleteLoanPaymentConfirm'), { danger: true, confirmLabel: t('common.delete') })) return;
|
||||||
|
try {
|
||||||
|
await api.delete(`/budget/loans/${loanId}/payments/${paymentId}`);
|
||||||
|
await loadMonth(state.month);
|
||||||
|
renderBody();
|
||||||
|
window.oikos?.showToast(t('budget.deletedToast'), 'success');
|
||||||
|
} catch (err) {
|
||||||
|
window.oikos?.showToast(err.data?.error ?? t('common.unknownError'), 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
// Eintrag löschen
|
// Eintrag löschen
|
||||||
// --------------------------------------------------------
|
// --------------------------------------------------------
|
||||||
|
|||||||
@@ -340,6 +340,10 @@ export async function render(container, { user }) {
|
|||||||
<option value="mdy"${prefs.date_format === 'mdy' ? ' selected' : ''}>MM/DD/YYYY</option>
|
<option value="mdy"${prefs.date_format === 'mdy' ? ' selected' : ''}>MM/DD/YYYY</option>
|
||||||
<option value="dmy"${prefs.date_format === 'dmy' ? ' selected' : ''}>DD/MM/YYYY</option>
|
<option value="dmy"${prefs.date_format === 'dmy' ? ' selected' : ''}>DD/MM/YYYY</option>
|
||||||
<option value="ymd"${prefs.date_format === 'ymd' ? ' selected' : ''}>YYYY-MM-DD</option>
|
<option value="ymd"${prefs.date_format === 'ymd' ? ' selected' : ''}>YYYY-MM-DD</option>
|
||||||
|
<option value="mdy_dot"${prefs.date_format === 'mdy_dot' ? ' selected' : ''}>MM.DD.YYYY</option>
|
||||||
|
<option value="dmy_dot"${prefs.date_format === 'dmy_dot' ? ' selected' : ''}>DD.MM.YYYY</option>
|
||||||
|
<option value="ymd_dot"${prefs.date_format === 'ymd_dot' ? ' selected' : ''}>YYYY.MM.DD</option>
|
||||||
|
<option value="ymd_slash"${prefs.date_format === 'ymd_slash' ? ' selected' : ''}>YYYY/MM/DD</option>
|
||||||
</select>
|
</select>
|
||||||
<label class="form-label" for="time-format-select" style="margin-top:var(--space-3)">${t('settings.timeFormatLabel')}</label>
|
<label class="form-label" for="time-format-select" style="margin-top:var(--space-3)">${t('settings.timeFormatLabel')}</label>
|
||||||
<select class="form-input" id="time-format-select">
|
<select class="form-input" id="time-format-select">
|
||||||
|
|||||||
+27
-19
@@ -84,9 +84,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.budget-tab {
|
.budget-tab {
|
||||||
min-height: 34px;
|
min-height: 30px;
|
||||||
padding: 0 var(--space-3);
|
padding: 0 var(--space-2);
|
||||||
border: 1px solid transparent;
|
border: 0;
|
||||||
border-radius: var(--radius-xs);
|
border-radius: var(--radius-xs);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
@@ -95,22 +95,6 @@
|
|||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
.budget-tab[data-tab="budget"] {
|
|
||||||
color: var(--module-budget);
|
|
||||||
}
|
|
||||||
|
|
||||||
.budget-tab[data-tab="loans"] {
|
|
||||||
color: var(--color-info);
|
|
||||||
}
|
|
||||||
|
|
||||||
.budget-tab[data-tab="budget"]:not(.budget-tab--active) {
|
|
||||||
background: color-mix(in srgb, var(--module-budget) 10%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.budget-tab[data-tab="loans"]:not(.budget-tab--active) {
|
|
||||||
background: color-mix(in srgb, var(--color-info) 10%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.budget-tab--active {
|
.budget-tab--active {
|
||||||
color: var(--color-text-on-accent);
|
color: var(--color-text-on-accent);
|
||||||
}
|
}
|
||||||
@@ -313,6 +297,13 @@
|
|||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.budget-loans__filter i {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 4px;
|
||||||
|
vertical-align: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
.budget-loans__filter--active {
|
.budget-loans__filter--active {
|
||||||
background: var(--color-info);
|
background: var(--color-info);
|
||||||
color: var(--color-text-on-accent);
|
color: var(--color-text-on-accent);
|
||||||
@@ -420,6 +411,12 @@
|
|||||||
border-color: var(--module-accent);
|
border-color: var(--module-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.budget-loan-card__filter--active {
|
||||||
|
color: var(--color-text-on-accent);
|
||||||
|
border-color: var(--color-info);
|
||||||
|
background: var(--color-info);
|
||||||
|
}
|
||||||
|
|
||||||
.budget-loan-card__filter i {
|
.budget-loan-card__filter i {
|
||||||
width: 15px;
|
width: 15px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
@@ -737,6 +734,13 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.budget-entry__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-1);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.budget-entry__delete::before {
|
.budget-entry__delete::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -747,6 +751,10 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.budget-entry--loan .budget-entry__delete {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.budget-entry__delete:hover {
|
.budget-entry__delete:hover {
|
||||||
color: var(--color-danger);
|
color: var(--color-danger);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,9 +240,15 @@ function cents(value) {
|
|||||||
|
|
||||||
function loanSummaryRow(loan) {
|
function loanSummaryRow(loan) {
|
||||||
const payments = db.get().prepare(`
|
const payments = db.get().prepare(`
|
||||||
SELECT p.*, u.display_name AS creator_name
|
SELECT p.*, u.display_name AS creator_name,
|
||||||
|
b.title AS entry_title,
|
||||||
|
b.category AS entry_category,
|
||||||
|
b.subcategory AS entry_subcategory,
|
||||||
|
b.is_recurring AS entry_is_recurring,
|
||||||
|
b.recurrence_parent_id AS entry_recurrence_parent_id
|
||||||
FROM budget_loan_payments p
|
FROM budget_loan_payments p
|
||||||
LEFT JOIN users u ON u.id = p.created_by
|
LEFT JOIN users u ON u.id = p.created_by
|
||||||
|
LEFT JOIN budget_entries b ON b.id = p.budget_entry_id
|
||||||
WHERE p.loan_id = ?
|
WHERE p.loan_id = ?
|
||||||
ORDER BY p.installment_number ASC
|
ORDER BY p.installment_number ASC
|
||||||
`).all(loan.id);
|
`).all(loan.id);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const VALID_CURRENCIES = ['AED', 'AUD', 'BRL', 'CAD', 'CHF', 'CNY', 'CZK', 'DKK'
|
|||||||
const DEFAULT_CURRENCY = 'EUR';
|
const DEFAULT_CURRENCY = 'EUR';
|
||||||
const DEFAULT_APP_NAME = 'Oikos';
|
const DEFAULT_APP_NAME = 'Oikos';
|
||||||
|
|
||||||
const VALID_DATE_FORMATS = ['mdy', 'dmy', 'ymd'];
|
const VALID_DATE_FORMATS = ['mdy', 'dmy', 'ymd', 'mdy_dot', 'dmy_dot', 'ymd_dot', 'ymd_slash'];
|
||||||
const DEFAULT_DATE_FORMAT = 'mdy';
|
const DEFAULT_DATE_FORMAT = 'mdy';
|
||||||
const VALID_TIME_FORMATS = ['24h', '12h'];
|
const VALID_TIME_FORMATS = ['24h', '12h'];
|
||||||
const DEFAULT_TIME_FORMAT = '24h';
|
const DEFAULT_TIME_FORMAT = '24h';
|
||||||
|
|||||||
Reference in New Issue
Block a user