From 6c7229542497fe5260ea71b3f9159d3e689269ae Mon Sep 17 00:00:00 2001 From: jubnl Date: Fri, 3 Apr 2026 19:49:58 +0200 Subject: [PATCH] fix(vacay): fix entitlement counter, year deletion, and year creation bugs - toggleCompanyHoliday now calls loadStats() so the entitlement sidebar updates immediately when a vacation day is converted to a company holiday - deleteYear now deletes vacay_user_years rows for the removed year, preventing stale entitlement data from persisting and re-appearing when the year is re-created - deleteYear recalculates carry-over for year+1 when year N is deleted, using the new actual previous year as the source - removeYear store action now calls loadStats() so the sidebar reflects the recalculated carry-over without requiring a page refresh - Add prev-year button (+[<] 2026 [>]+) so users can add years going backwards after deleting a past year; add vacay.addPrevYear i18n key to all 13 supported languages Closes #371 --- client/src/i18n/translations/ar.ts | 3 ++- client/src/i18n/translations/br.ts | 3 ++- client/src/i18n/translations/cs.ts | 3 ++- client/src/i18n/translations/de.ts | 3 ++- client/src/i18n/translations/en.ts | 3 ++- client/src/i18n/translations/es.ts | 3 ++- client/src/i18n/translations/fr.ts | 3 ++- client/src/i18n/translations/hu.ts | 3 ++- client/src/i18n/translations/it.ts | 3 ++- client/src/i18n/translations/nl.ts | 3 ++- client/src/i18n/translations/pl.ts | 3 ++- client/src/i18n/translations/ru.ts | 3 ++- client/src/i18n/translations/zh.ts | 3 ++- client/src/pages/VacayPage.tsx | 34 +++++++++++++++++++---------- client/src/store/vacayStore.ts | 2 ++ server/src/services/vacayService.ts | 24 ++++++++++++++++++++ 16 files changed, 75 insertions(+), 24 deletions(-) diff --git a/client/src/i18n/translations/ar.ts b/client/src/i18n/translations/ar.ts index 4ba26be..a2523e7 100644 --- a/client/src/i18n/translations/ar.ts +++ b/client/src/i18n/translations/ar.ts @@ -598,7 +598,8 @@ const ar: Record = { 'vacay.subtitle': 'خطط وأدر أيام الإجازة', 'vacay.settings': 'الإعدادات', 'vacay.year': 'السنة', - 'vacay.addYear': 'إضافة سنة', + 'vacay.addYear': 'إضافة السنة التالية', + 'vacay.addPrevYear': 'إضافة السنة السابقة', 'vacay.removeYear': 'إزالة السنة', 'vacay.removeYearConfirm': 'إزالة {year}؟', 'vacay.removeYearHint': 'سيتم حذف كل إدخالات الإجازات والعطل الخاصة بهذه السنة نهائيًا.', diff --git a/client/src/i18n/translations/br.ts b/client/src/i18n/translations/br.ts index c51907d..4c2eb59 100644 --- a/client/src/i18n/translations/br.ts +++ b/client/src/i18n/translations/br.ts @@ -579,7 +579,8 @@ const br: Record = { 'vacay.subtitle': 'Planeje e gerencie dias de férias', 'vacay.settings': 'Configurações', 'vacay.year': 'Ano', - 'vacay.addYear': 'Adicionar ano', + 'vacay.addYear': 'Adicionar próximo ano', + 'vacay.addPrevYear': 'Adicionar ano anterior', 'vacay.removeYear': 'Remover ano', 'vacay.removeYearConfirm': 'Remover {year}?', 'vacay.removeYearHint': 'Todas as entradas de férias e feriados da empresa deste ano serão excluídas permanentemente.', diff --git a/client/src/i18n/translations/cs.ts b/client/src/i18n/translations/cs.ts index 1bf8e1d..669d496 100644 --- a/client/src/i18n/translations/cs.ts +++ b/client/src/i18n/translations/cs.ts @@ -597,7 +597,8 @@ const cs: Record = { 'vacay.subtitle': 'Plánování a správa dovolené', 'vacay.settings': 'Nastavení', 'vacay.year': 'Rok', - 'vacay.addYear': 'Přidat rok', + 'vacay.addYear': 'Přidat následující rok', + 'vacay.addPrevYear': 'Přidat předchozí rok', 'vacay.removeYear': 'Odebrat rok', 'vacay.removeYearConfirm': 'Odebrat rok {year}?', 'vacay.removeYearHint': 'Všechny záznamy o dovolené a firemní svátky pro tento rok budou trvale smazány.', diff --git a/client/src/i18n/translations/de.ts b/client/src/i18n/translations/de.ts index 7ebb8c0..a2980ba 100644 --- a/client/src/i18n/translations/de.ts +++ b/client/src/i18n/translations/de.ts @@ -595,7 +595,8 @@ const de: Record = { 'vacay.subtitle': 'Urlaubstage planen und verwalten', 'vacay.settings': 'Einstellungen', 'vacay.year': 'Jahr', - 'vacay.addYear': 'Jahr hinzufügen', + 'vacay.addYear': 'Nächstes Jahr hinzufügen', + 'vacay.addPrevYear': 'Vorheriges Jahr hinzufügen', 'vacay.removeYear': 'Jahr entfernen', 'vacay.removeYearConfirm': '{year} entfernen?', 'vacay.removeYearHint': 'Alle Urlaubseinträge und Betriebsferien für dieses Jahr werden unwiderruflich gelöscht.', diff --git a/client/src/i18n/translations/en.ts b/client/src/i18n/translations/en.ts index 9f1c3f6..c2107e6 100644 --- a/client/src/i18n/translations/en.ts +++ b/client/src/i18n/translations/en.ts @@ -592,7 +592,8 @@ const en: Record = { 'vacay.subtitle': 'Plan and manage vacation days', 'vacay.settings': 'Settings', 'vacay.year': 'Year', - 'vacay.addYear': 'Add year', + 'vacay.addYear': 'Add next year', + 'vacay.addPrevYear': 'Add previous year', 'vacay.removeYear': 'Remove year', 'vacay.removeYearConfirm': 'Remove {year}?', 'vacay.removeYearHint': 'All vacation entries and company holidays for this year will be permanently deleted.', diff --git a/client/src/i18n/translations/es.ts b/client/src/i18n/translations/es.ts index bafee34..b1142fd 100644 --- a/client/src/i18n/translations/es.ts +++ b/client/src/i18n/translations/es.ts @@ -572,7 +572,8 @@ const es: Record = { 'vacay.subtitle': 'Planifica y gestiona días de vacaciones', 'vacay.settings': 'Ajustes', 'vacay.year': 'Año', - 'vacay.addYear': 'Añadir año', + 'vacay.addYear': 'Añadir año siguiente', + 'vacay.addPrevYear': 'Añadir año anterior', 'vacay.removeYear': 'Eliminar año', 'vacay.removeYearConfirm': '¿Eliminar {year}?', 'vacay.removeYearHint': 'Todas las vacaciones y festivos de empresa de este año se borrarán permanentemente.', diff --git a/client/src/i18n/translations/fr.ts b/client/src/i18n/translations/fr.ts index 4727ff7..c37207d 100644 --- a/client/src/i18n/translations/fr.ts +++ b/client/src/i18n/translations/fr.ts @@ -594,7 +594,8 @@ const fr: Record = { 'vacay.subtitle': 'Planifiez et gérez vos jours de congés', 'vacay.settings': 'Paramètres', 'vacay.year': 'Année', - 'vacay.addYear': 'Ajouter une année', + 'vacay.addYear': 'Ajouter l\'année suivante', + 'vacay.addPrevYear': 'Ajouter l\'année précédente', 'vacay.removeYear': 'Supprimer l\'année', 'vacay.removeYearConfirm': 'Supprimer {year} ?', 'vacay.removeYearHint': 'Toutes les entrées de vacances et jours fériés d\'entreprise de cette année seront définitivement supprimés.', diff --git a/client/src/i18n/translations/hu.ts b/client/src/i18n/translations/hu.ts index 91211d8..05c72e1 100644 --- a/client/src/i18n/translations/hu.ts +++ b/client/src/i18n/translations/hu.ts @@ -595,7 +595,8 @@ const hu: Record = { 'vacay.subtitle': 'Szabadságnapok tervezése és kezelése', 'vacay.settings': 'Beállítások', 'vacay.year': 'Év', - 'vacay.addYear': 'Év hozzáadása', + 'vacay.addYear': 'Következő év hozzáadása', + 'vacay.addPrevYear': 'Előző év hozzáadása', 'vacay.removeYear': 'Év eltávolítása', 'vacay.removeYearConfirm': '{year} eltávolítása?', 'vacay.removeYearHint': 'Az adott év összes szabadság-bejegyzése és céges szabadnapja véglegesen törlődik.', diff --git a/client/src/i18n/translations/it.ts b/client/src/i18n/translations/it.ts index bebaf16..e1d76e2 100644 --- a/client/src/i18n/translations/it.ts +++ b/client/src/i18n/translations/it.ts @@ -595,7 +595,8 @@ const it: Record = { 'vacay.subtitle': 'Pianifica e gestisci i giorni di ferie', 'vacay.settings': 'Impostazioni', 'vacay.year': 'Anno', - 'vacay.addYear': 'Aggiungi anno', + 'vacay.addYear': 'Aggiungi anno successivo', + 'vacay.addPrevYear': 'Aggiungi anno precedente', 'vacay.removeYear': 'Rimuovi anno', 'vacay.removeYearConfirm': 'Rimuovere {year}?', 'vacay.removeYearHint': 'Tutte le voci delle ferie e le ferie aziendali di questo anno verranno eliminate in modo permanente.', diff --git a/client/src/i18n/translations/nl.ts b/client/src/i18n/translations/nl.ts index 814be9b..c40f87a 100644 --- a/client/src/i18n/translations/nl.ts +++ b/client/src/i18n/translations/nl.ts @@ -594,7 +594,8 @@ const nl: Record = { 'vacay.subtitle': 'Plan en beheer vakantiedagen', 'vacay.settings': 'Instellingen', 'vacay.year': 'Jaar', - 'vacay.addYear': 'Jaar toevoegen', + 'vacay.addYear': 'Volgend jaar toevoegen', + 'vacay.addPrevYear': 'Vorig jaar toevoegen', 'vacay.removeYear': 'Jaar verwijderen', 'vacay.removeYearConfirm': '{year} verwijderen?', 'vacay.removeYearHint': 'Alle vakantie-invoeren en bedrijfsvakanties voor dit jaar worden permanent verwijderd.', diff --git a/client/src/i18n/translations/pl.ts b/client/src/i18n/translations/pl.ts index 36d9e9f..ecd3b8a 100644 --- a/client/src/i18n/translations/pl.ts +++ b/client/src/i18n/translations/pl.ts @@ -558,7 +558,8 @@ const pl: Record = { 'vacay.subtitle': 'Planuj i zarządzaj dniami urlopu', 'vacay.settings': 'Ustawienia', 'vacay.year': 'Rok', - 'vacay.addYear': 'Dodaj rok', + 'vacay.addYear': 'Dodaj następny rok', + 'vacay.addPrevYear': 'Dodaj poprzedni rok', 'vacay.removeYear': 'Usuń rok', 'vacay.removeYearConfirm': 'Usunąć {year}?', 'vacay.removeYearHint': 'Wszystkie wpisy dotyczące urlopów oraz dni wolnych w tym roku zostaną trwale usunięte.', diff --git a/client/src/i18n/translations/ru.ts b/client/src/i18n/translations/ru.ts index 997e25b..518c5e9 100644 --- a/client/src/i18n/translations/ru.ts +++ b/client/src/i18n/translations/ru.ts @@ -594,7 +594,8 @@ const ru: Record = { 'vacay.subtitle': 'Планируйте и управляйте днями отпуска', 'vacay.settings': 'Настройки', 'vacay.year': 'Год', - 'vacay.addYear': 'Добавить год', + 'vacay.addYear': 'Добавить следующий год', + 'vacay.addPrevYear': 'Добавить предыдущий год', 'vacay.removeYear': 'Удалить год', 'vacay.removeYearConfirm': 'Удалить {year}?', 'vacay.removeYearHint': 'Все записи об отпуске и корпоративные выходные за этот год будут безвозвратно удалены.', diff --git a/client/src/i18n/translations/zh.ts b/client/src/i18n/translations/zh.ts index 5fd14e4..5a90e5b 100644 --- a/client/src/i18n/translations/zh.ts +++ b/client/src/i18n/translations/zh.ts @@ -594,7 +594,8 @@ const zh: Record = { 'vacay.subtitle': '规划和管理假期', 'vacay.settings': '设置', 'vacay.year': '年份', - 'vacay.addYear': '添加年份', + 'vacay.addYear': '添加下一年', + 'vacay.addPrevYear': '添加上一年', 'vacay.removeYear': '移除年份', 'vacay.removeYearConfirm': '移除 {year}?', 'vacay.removeYearHint': '该年度所有假期记录和公司假日将被永久删除。', diff --git a/client/src/pages/VacayPage.tsx b/client/src/pages/VacayPage.tsx index 68efd34..b3a524e 100644 --- a/client/src/pages/VacayPage.tsx +++ b/client/src/pages/VacayPage.tsx @@ -41,11 +41,16 @@ export default function VacayPage(): React.ReactElement { if (selectedYear) { loadEntries(selectedYear); loadStats(selectedYear); loadHolidays(selectedYear) } }, [selectedYear]) - const handleAddYear = () => { + const handleAddNextYear = () => { const nextYear = years.length > 0 ? Math.max(...years) + 1 : new Date().getFullYear() addYear(nextYear) } + const handleAddPrevYear = () => { + const prevYear = years.length > 0 ? Math.min(...years) - 1 : new Date().getFullYear() + addYear(prevYear) + } + if (loading) { return (
@@ -62,20 +67,27 @@ export default function VacayPage(): React.ReactElement { <> {/* Year Selector */}
-
+
{t('vacay.year')} -
- +
+ + +
{selectedYear} - +
+ + +
{years.map(y => ( diff --git a/client/src/store/vacayStore.ts b/client/src/store/vacayStore.ts index 73fb0c6..8e87664 100644 --- a/client/src/store/vacayStore.ts +++ b/client/src/store/vacayStore.ts @@ -229,6 +229,7 @@ export const useVacayStore = create((set, get) => ({ : new Date().getFullYear() } set(updates) + await get().loadStats() }, loadEntries: async (year?: number) => { @@ -246,6 +247,7 @@ export const useVacayStore = create((set, get) => ({ toggleCompanyHoliday: async (date: string) => { await api.toggleCompanyHoliday(date) await get().loadEntries() + await get().loadStats() }, loadStats: async (year?: number) => { diff --git a/server/src/services/vacayService.ts b/server/src/services/vacayService.ts index ba5aa43..c1607dc 100644 --- a/server/src/services/vacayService.ts +++ b/server/src/services/vacayService.ts @@ -496,6 +496,30 @@ export function deleteYear(planId: number, year: number, socketId: string | unde db.prepare('DELETE FROM vacay_years WHERE plan_id = ? AND year = ?').run(planId, year); db.prepare("DELETE FROM vacay_entries WHERE plan_id = ? AND date LIKE ?").run(planId, `${year}-%`); db.prepare("DELETE FROM vacay_company_holidays WHERE plan_id = ? AND date LIKE ?").run(planId, `${year}-%`); + db.prepare('DELETE FROM vacay_user_years WHERE plan_id = ? AND year = ?').run(planId, year); + + // Recalculate carry-over for year+1 if it exists, since its previous year has changed + const nextYearExists = db.prepare('SELECT id FROM vacay_years WHERE plan_id = ? AND year = ?').get(planId, year + 1); + if (nextYearExists) { + const plan = db.prepare('SELECT * FROM vacay_plans WHERE id = ?').get(planId) as VacayPlan | undefined; + const carryOverEnabled = plan ? !!plan.carry_over_enabled : true; + const users = getPlanUsers(planId); + const prevYear = db.prepare('SELECT year FROM vacay_years WHERE plan_id = ? AND year < ? ORDER BY year DESC LIMIT 1').get(planId, year + 1) as { year: number } | undefined; + + for (const u of users) { + let carry = 0; + if (carryOverEnabled && prevYear) { + const prevConfig = db.prepare('SELECT * FROM vacay_user_years WHERE user_id = ? AND plan_id = ? AND year = ?').get(u.id, planId, prevYear.year) as VacayUserYear | undefined; + if (prevConfig) { + const used = (db.prepare("SELECT COUNT(*) as count FROM vacay_entries WHERE user_id = ? AND plan_id = ? AND date LIKE ?").get(u.id, planId, `${prevYear.year}-%`) as { count: number }).count; + const total = prevConfig.vacation_days + prevConfig.carried_over; + carry = Math.max(0, total - used); + } + } + db.prepare('UPDATE vacay_user_years SET carried_over = ? WHERE user_id = ? AND plan_id = ? AND year = ?').run(carry, u.id, planId, year + 1); + } + } + notifyPlanUsers(planId, socketId, 'vacay:settings'); return listYears(planId); }