diff --git a/public/i18n.js b/public/i18n.js index b45a0fe..fa1c1d6 100644 --- a/public/i18n.js +++ b/public/i18n.js @@ -87,9 +87,11 @@ function isDateOnlyString(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() { 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() { @@ -112,8 +114,12 @@ function formatDateParts(date, useUtc = false) { const month = String((useUtc ? d.getUTCMonth() : d.getMonth()) + 1).padStart(2, '0'); const day = String(useUtc ? d.getUTCDate() : d.getDate()).padStart(2, '0'); 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_dot': return `${year}.${month}.${day}`; + case 'ymd_slash': return `${year}/${month}/${day}`; default: return `${month}/${day}/${year}`; } } @@ -139,8 +145,12 @@ export function formatDate(date) { export function dateInputPlaceholder() { 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_dot': return 'YYYY.MM.DD'; + case 'ymd_slash': return 'YYYY/MM/DD'; default: return 'MM/DD/YYYY'; } } @@ -157,11 +167,18 @@ export function parseDateInput(value) { const isoMatch = raw.match(/^(\d{4})-(\d{2})-(\d{2})$/); 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})$/); if (!slashMatch) return ''; const [, first, second, year] = slashMatch; - const [month, day] = getDateFormatPreference() === 'dmy' + const [month, day] = getDateFormatPreference().startsWith('dmy') ? [second, first] : [first, second]; diff --git a/public/locales/ar.json b/public/locales/ar.json index c319ce2..755fa39 100644 --- a/public/locales/ar.json +++ b/public/locales/ar.json @@ -573,6 +573,7 @@ "editLoan": "تعديل القرض", "deleteLoan": "حذف القرض", "deleteLoanConfirm": "هل تريد حذف القرض \"{{title}}\"؟ ستتم إزالة الدفعات المسجلة في الميزانية أيضًا.", + "deleteLoanPaymentConfirm": "هل تريد حذف دفعة القرض هذه؟", "loanRemainingAmount": "المتبقي", "loanRemainingInstallments": "الأقساط المتبقية", "loanPaidAmount": "المدفوع", @@ -597,6 +598,7 @@ "loanSavedToast": "تم حفظ القرض", "loanDeletedToast": "تم حذف القرض", "loanPaymentAddedToast": "تم تسجيل الدفع", + "loanPaymentTitle": "سداد القرض: {{borrower}}", "typeLoan": "قرض", "tabsLabel": "أقسام الميزانية", "budgetTab": "الميزانية", diff --git a/public/locales/de.json b/public/locales/de.json index 11793da..790936b 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -598,6 +598,7 @@ "editLoan": "Darlehen bearbeiten", "deleteLoan": "Darlehen löschen", "deleteLoanConfirm": "Darlehen \"{{title}}\" löschen? Bereits im Budget verbuchte Zahlungen werden ebenfalls entfernt.", + "deleteLoanPaymentConfirm": "Diese Darlehenszahlung löschen?", "loanRemainingAmount": "Offen", "loanRemainingInstallments": "Raten offen", "loanPaidAmount": "Bezahlt", @@ -622,6 +623,7 @@ "loanSavedToast": "Darlehen gespeichert", "loanDeletedToast": "Darlehen gelöscht", "loanPaymentAddedToast": "Zahlung erfasst", + "loanPaymentTitle": "Darlehensrückzahlung: {{borrower}}", "typeLoan": "Darlehen", "tabsLabel": "Budgetbereiche", "budgetTab": "Budget", diff --git a/public/locales/el.json b/public/locales/el.json index f570d37..b2f2ff5 100644 --- a/public/locales/el.json +++ b/public/locales/el.json @@ -573,6 +573,7 @@ "editLoan": "Επεξεργασία δανείου", "deleteLoan": "Διαγραφή δανείου", "deleteLoanConfirm": "Να διαγραφεί το δάνειο «{{title}}»; Οι πληρωμές που έχουν ήδη περαστεί στον προϋπολογισμό θα αφαιρεθούν επίσης.", + "deleteLoanPaymentConfirm": "Διαγραφή αυτής της πληρωμής δανείου;", "loanRemainingAmount": "Υπόλοιπο", "loanRemainingInstallments": "Δόσεις που απομένουν", "loanPaidAmount": "Πληρωμένο", @@ -597,6 +598,7 @@ "loanSavedToast": "Το δάνειο αποθηκεύτηκε", "loanDeletedToast": "Το δάνειο διαγράφηκε", "loanPaymentAddedToast": "Η πληρωμή καταγράφηκε", + "loanPaymentTitle": "Πληρωμή δανείου: {{borrower}}", "typeLoan": "Δάνειο", "tabsLabel": "Ενότητες προϋπολογισμού", "budgetTab": "Προϋπολογισμός", diff --git a/public/locales/en.json b/public/locales/en.json index d11437c..d11161d 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -573,6 +573,7 @@ "editLoan": "Edit loan", "deleteLoan": "Delete loan", "deleteLoanConfirm": "Delete loan \"{{title}}\"? Payments already posted to the budget will also be removed.", + "deleteLoanPaymentConfirm": "Delete this loan payment?", "loanRemainingAmount": "Remaining", "loanRemainingInstallments": "Installments left", "loanPaidAmount": "Paid", @@ -597,6 +598,7 @@ "loanSavedToast": "Loan saved", "loanDeletedToast": "Loan deleted", "loanPaymentAddedToast": "Payment recorded", + "loanPaymentTitle": "Loan repayment: {{borrower}}", "typeLoan": "Loan", "tabsLabel": "Budget sections", "budgetTab": "Budget", diff --git a/public/locales/es.json b/public/locales/es.json index 26e044e..a797df3 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -573,6 +573,7 @@ "editLoan": "Editar préstamo", "deleteLoan": "Eliminar préstamo", "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", "loanRemainingInstallments": "Cuotas restantes", "loanPaidAmount": "Pagado", @@ -597,6 +598,7 @@ "loanSavedToast": "Préstamo guardado", "loanDeletedToast": "Préstamo eliminado", "loanPaymentAddedToast": "Pago registrado", + "loanPaymentTitle": "Pago del préstamo: {{borrower}}", "typeLoan": "Préstamo", "tabsLabel": "Secciones del presupuesto", "budgetTab": "Presupuesto", diff --git a/public/locales/fr.json b/public/locales/fr.json index e1e3438..c29e954 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -573,6 +573,7 @@ "editLoan": "Modifier 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.", + "deleteLoanPaymentConfirm": "Supprimer ce paiement de prêt ?", "loanRemainingAmount": "Restant", "loanRemainingInstallments": "Échéances restantes", "loanPaidAmount": "Payé", @@ -597,6 +598,7 @@ "loanSavedToast": "Prêt enregistré", "loanDeletedToast": "Prêt supprimé", "loanPaymentAddedToast": "Paiement enregistré", + "loanPaymentTitle": "Remboursement du prêt : {{borrower}}", "typeLoan": "Prêt", "tabsLabel": "Sections du budget", "budgetTab": "Budget", diff --git a/public/locales/hi.json b/public/locales/hi.json index 91df594..81b6856 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -573,6 +573,7 @@ "editLoan": "उधार संपादित करें", "deleteLoan": "उधार हटाएं", "deleteLoanConfirm": "उधार \"{{title}}\" हटाएं? बजट में दर्ज भुगतान भी हटा दिए जाएंगे।", + "deleteLoanPaymentConfirm": "यह ऋण भुगतान हटाएँ?", "loanRemainingAmount": "बाकी", "loanRemainingInstallments": "बाकी किस्तें", "loanPaidAmount": "भुगतान किया", @@ -597,6 +598,7 @@ "loanSavedToast": "उधार सहेजा गया", "loanDeletedToast": "उधार हटाया गया", "loanPaymentAddedToast": "भुगतान दर्ज किया गया", + "loanPaymentTitle": "ऋण भुगतान: {{borrower}}", "typeLoan": "उधार", "tabsLabel": "बजट अनुभाग", "budgetTab": "बजट", diff --git a/public/locales/it.json b/public/locales/it.json index cc01c3a..c21649a 100644 --- a/public/locales/it.json +++ b/public/locales/it.json @@ -573,6 +573,7 @@ "editLoan": "Modifica prestito", "deleteLoan": "Elimina prestito", "deleteLoanConfirm": "Eliminare il prestito \"{{title}}\"? Verranno rimossi anche i pagamenti già registrati nel bilancio.", + "deleteLoanPaymentConfirm": "Eliminare questo pagamento del prestito?", "loanRemainingAmount": "Rimanente", "loanRemainingInstallments": "Rate rimanenti", "loanPaidAmount": "Pagato", @@ -597,6 +598,7 @@ "loanSavedToast": "Prestito salvato", "loanDeletedToast": "Prestito eliminato", "loanPaymentAddedToast": "Pagamento registrato", + "loanPaymentTitle": "Rimborso del prestito: {{borrower}}", "typeLoan": "Prestito", "tabsLabel": "Sezioni del bilancio", "budgetTab": "Bilancio", diff --git a/public/locales/ja.json b/public/locales/ja.json index bcc9bc6..af4b07f 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -573,6 +573,7 @@ "editLoan": "貸付を編集", "deleteLoan": "貸付を削除", "deleteLoanConfirm": "貸付「{{title}}」を削除しますか?予算に記録済みの返済も削除されます。", + "deleteLoanPaymentConfirm": "このローン支払いを削除しますか?", "loanRemainingAmount": "残額", "loanRemainingInstallments": "残り回数", "loanPaidAmount": "返済済み", @@ -597,6 +598,7 @@ "loanSavedToast": "貸付を保存しました", "loanDeletedToast": "貸付を削除しました", "loanPaymentAddedToast": "返済を記録しました", + "loanPaymentTitle": "ローン返済: {{borrower}}", "typeLoan": "貸付", "tabsLabel": "予算セクション", "budgetTab": "予算", diff --git a/public/locales/pt.json b/public/locales/pt.json index 783ca78..454feee 100644 --- a/public/locales/pt.json +++ b/public/locales/pt.json @@ -573,6 +573,7 @@ "editLoan": "Editar empréstimo", "deleteLoan": "Excluir empréstimo", "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", "loanRemainingInstallments": "Parcelas restantes", "loanPaidAmount": "Pago", @@ -597,6 +598,7 @@ "loanSavedToast": "Empréstimo salvo", "loanDeletedToast": "Empréstimo excluído", "loanPaymentAddedToast": "Pagamento registrado", + "loanPaymentTitle": "Pagamento do empréstimo: {{borrower}}", "typeLoan": "Empréstimo", "tabsLabel": "Seções do orçamento", "budgetTab": "Orçamento", diff --git a/public/locales/ru.json b/public/locales/ru.json index 7dac1a0..1cab256 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -573,6 +573,7 @@ "editLoan": "Изменить займ", "deleteLoan": "Удалить займ", "deleteLoanConfirm": "Удалить займ «{{title}}»? Платежи, уже добавленные в бюджет, тоже будут удалены.", + "deleteLoanPaymentConfirm": "Удалить этот платеж по займу?", "loanRemainingAmount": "Осталось", "loanRemainingInstallments": "Осталось платежей", "loanPaidAmount": "Оплачено", @@ -597,6 +598,7 @@ "loanSavedToast": "Займ сохранён", "loanDeletedToast": "Займ удалён", "loanPaymentAddedToast": "Платёж записан", + "loanPaymentTitle": "Платеж по займу: {{borrower}}", "typeLoan": "Займ", "tabsLabel": "Разделы бюджета", "budgetTab": "Бюджет", diff --git a/public/locales/sv.json b/public/locales/sv.json index d3c9234..f611d3c 100644 --- a/public/locales/sv.json +++ b/public/locales/sv.json @@ -573,6 +573,7 @@ "editLoan": "Redigera lån", "deleteLoan": "Ta bort lån", "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", "loanRemainingInstallments": "Delbetalningar kvar", "loanPaidAmount": "Betalt", @@ -597,6 +598,7 @@ "loanSavedToast": "Lån sparat", "loanDeletedToast": "Lån borttaget", "loanPaymentAddedToast": "Betalning registrerad", + "loanPaymentTitle": "Låneåterbetalning: {{borrower}}", "typeLoan": "Lån", "tabsLabel": "Budgetsektioner", "budgetTab": "Budget", diff --git a/public/locales/tr.json b/public/locales/tr.json index 6eba08c..2e97e6a 100644 --- a/public/locales/tr.json +++ b/public/locales/tr.json @@ -573,6 +573,7 @@ "editLoan": "Borcu düzenle", "deleteLoan": "Borcu sil", "deleteLoanConfirm": "\"{{title}}\" borcu silinsin mi? Bütçeye işlenmiş ödemeler de kaldırılır.", + "deleteLoanPaymentConfirm": "Bu kredi ödemesi silinsin mi?", "loanRemainingAmount": "Kalan", "loanRemainingInstallments": "Kalan taksit", "loanPaidAmount": "Ödenen", @@ -597,6 +598,7 @@ "loanSavedToast": "Borç kaydedildi", "loanDeletedToast": "Borç silindi", "loanPaymentAddedToast": "Ödeme kaydedildi", + "loanPaymentTitle": "Kredi geri ödemesi: {{borrower}}", "typeLoan": "Borç", "tabsLabel": "Bütçe bölümleri", "budgetTab": "Bütçe", diff --git a/public/locales/uk.json b/public/locales/uk.json index f41aeae..e295193 100644 --- a/public/locales/uk.json +++ b/public/locales/uk.json @@ -573,6 +573,7 @@ "editLoan": "Редагувати позику", "deleteLoan": "Видалити позику", "deleteLoanConfirm": "Видалити позику «{{title}}»? Платежі, вже додані до бюджету, також буде видалено.", + "deleteLoanPaymentConfirm": "Видалити цей платіж за позикою?", "loanRemainingAmount": "Залишилось", "loanRemainingInstallments": "Залишилось платежів", "loanPaidAmount": "Сплачено", @@ -597,6 +598,7 @@ "loanSavedToast": "Позику збережено", "loanDeletedToast": "Позику видалено", "loanPaymentAddedToast": "Платіж записано", + "loanPaymentTitle": "Платіж за позикою: {{borrower}}", "typeLoan": "Позика", "tabsLabel": "Розділи бюджету", "budgetTab": "Бюджет", diff --git a/public/locales/zh.json b/public/locales/zh.json index 285a9d4..6388d87 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -573,6 +573,7 @@ "editLoan": "编辑借款", "deleteLoan": "删除借款", "deleteLoanConfirm": "删除借款“{{title}}”?已记入预算的还款也会被删除。", + "deleteLoanPaymentConfirm": "删除这笔借款还款?", "loanRemainingAmount": "剩余金额", "loanRemainingInstallments": "剩余期数", "loanPaidAmount": "已还金额", @@ -597,6 +598,7 @@ "loanSavedToast": "借款已保存", "loanDeletedToast": "借款已删除", "loanPaymentAddedToast": "还款已记录", + "loanPaymentTitle": "借款还款:{{borrower}}", "typeLoan": "借款", "tabsLabel": "预算分区", "budgetTab": "预算", diff --git a/public/pages/budget.js b/public/pages/budget.js index 5259966..24a56bf 100644 --- a/public/pages/budget.js +++ b/public/pages/budget.js @@ -6,7 +6,7 @@ */ 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 { t, formatDate, getLocale, dateInputPlaceholder, formatDateInput, parseDateInput, isDateInputValid } from '/i18n.js'; import { esc } from '/utils/html.js'; @@ -164,11 +164,8 @@ function setHtml(element, html) { async function loadMonth(month) { const prevMonth = addMonths(month, -1); try { - const entriesPath = state.loanFilterId - ? `/budget?loan_id=${encodeURIComponent(state.loanFilterId)}` - : `/budget?month=${month}`; 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=${prevMonth}`), api.get('/budget/loans'), @@ -357,16 +354,10 @@ function renderBody() {