mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
Comp/Flex days (#1074) — complete. A second leave type that does not deduct from the vacation entitlement (flextime / overtime offset). New kind column on vacay_entries, excluded from usedDays via a CASE sum; toggleEntry carries kind through service/nest/store with change-detection; desktop and mobile get a comp toolbar toggle next to the half-day one (orthogonal, so a half comp day is free); comp segments render as a diagonal hatch of the person colour, per-segment for split days (Version B). comp_used is surfaced separately in getStats. Translated into all 23 locales. Configurable vacation year (#737) — backend only. Per-user vacay_user_settings (calendar / fiscal / anniversary), resolveYearWindow, and usedDays / compUsedDays / getStats / carry-over now computed over the leave-year [start,end) window. 'calendar' resolves byte-identically to the old date-prefix behaviour, so nothing changes for existing users and the feature is inert until the settings UI lands. Frontend (settings selector + calendar window rollover), the settings endpoint, window-aware getEntries and holiday loading are still to do — see the PR description.
This commit is contained in:
@@ -75,7 +75,7 @@ interface VacayApi {
|
||||
addYear: (year: number) => Promise<VacayYearsResponse>
|
||||
removeYear: (year: number) => Promise<VacayYearsResponse>
|
||||
getEntries: (year: number) => Promise<VacayEntriesResponse>
|
||||
toggleEntry: (date: string, targetUserId?: number, fraction?: 0.5 | 1) => Promise<unknown>
|
||||
toggleEntry: (date: string, targetUserId?: number, fraction?: 0.5 | 1, kind?: 'vacation' | 'comp') => Promise<unknown>
|
||||
toggleCompanyHoliday: (date: string) => Promise<unknown>
|
||||
getStats: (year: number) => Promise<VacayStatsResponse>
|
||||
updateStats: (year: number, days: number, targetUserId?: number) => Promise<unknown>
|
||||
@@ -107,7 +107,7 @@ const api: VacayApi = {
|
||||
addYear: (year) => ax.post('/addons/vacay/years', { year } satisfies VacayAddYearRequest).then((r: AxiosResponse) => r.data),
|
||||
removeYear: (year) => ax.delete(`/addons/vacay/years/${year}`).then((r: AxiosResponse) => r.data),
|
||||
getEntries: (year) => ax.get(`/addons/vacay/entries/${year}`).then((r: AxiosResponse) => r.data),
|
||||
toggleEntry: (date, targetUserId, fraction) => ax.post('/addons/vacay/entries/toggle', { date, target_user_id: targetUserId, fraction } satisfies VacayToggleEntryRequest).then((r: AxiosResponse) => r.data),
|
||||
toggleEntry: (date, targetUserId, fraction, kind) => ax.post('/addons/vacay/entries/toggle', { date, target_user_id: targetUserId, fraction, kind } satisfies VacayToggleEntryRequest).then((r: AxiosResponse) => r.data),
|
||||
toggleCompanyHoliday: (date) => ax.post('/addons/vacay/entries/company-holiday', { date } satisfies VacayCompanyHolidayRequest).then((r: AxiosResponse) => r.data),
|
||||
getStats: (year) => ax.get(`/addons/vacay/stats/${year}`).then((r: AxiosResponse) => r.data),
|
||||
updateStats: (year, days, targetUserId) => ax.put(`/addons/vacay/stats/${year}`, { vacation_days: days, target_user_id: targetUserId } satisfies VacayUpdateStatsRequest).then((r: AxiosResponse) => r.data),
|
||||
@@ -199,7 +199,7 @@ interface VacayState {
|
||||
addYear: (year: number) => Promise<void>
|
||||
removeYear: (year: number) => Promise<void>
|
||||
loadEntries: (year?: number) => Promise<void>
|
||||
toggleEntry: (date: string, targetUserId?: number, fraction?: 0.5 | 1) => Promise<void>
|
||||
toggleEntry: (date: string, targetUserId?: number, fraction?: 0.5 | 1, kind?: 'vacation' | 'comp') => Promise<void>
|
||||
toggleCompanyHoliday: (date: string) => Promise<void>
|
||||
loadStats: (year?: number) => Promise<void>
|
||||
updateVacationDays: (year: number, days: number, targetUserId?: number) => Promise<void>
|
||||
@@ -320,26 +320,27 @@ export const useVacayStore = create<VacayState>((set, get) => ({
|
||||
set({ entries: data.entries, companyHolidays: data.companyHolidays })
|
||||
},
|
||||
|
||||
toggleEntry: async (date: string, targetUserId?: number, fraction: 0.5 | 1 = 1) => {
|
||||
toggleEntry: async (date: string, targetUserId?: number, fraction: 0.5 | 1 = 1, kind: 'vacation' | 'comp' = 'vacation') => {
|
||||
// Optimistic: mirror the server's toggle locally so the cell reacts instantly,
|
||||
// then reconcile (stats are server-computed). Same fraction clears the day, a
|
||||
// different one converts it (full ↔ half); an empty day gets the new entry.
|
||||
// then reconcile (stats are server-computed). Same type AND fraction clears the
|
||||
// day, a different type or fraction converts it (#552/#1074); an empty day gets
|
||||
// the new entry.
|
||||
const userId = targetUserId ?? useAuthStore.getState().user?.id
|
||||
const prevEntries = get().entries
|
||||
const prevStats = get().stats
|
||||
if (userId != null) {
|
||||
const existing = prevEntries.find(e => e.date === date && e.user_id === userId)
|
||||
if (existing && (existing.fraction ?? 1) === fraction) {
|
||||
if (existing && (existing.fraction ?? 1) === fraction && (existing.kind ?? 'vacation') === kind) {
|
||||
set({ entries: prevEntries.filter(e => !(e.date === date && e.user_id === userId)) })
|
||||
} else if (existing) {
|
||||
set({ entries: prevEntries.map(e => (e.date === date && e.user_id === userId ? { ...e, fraction } : e)) })
|
||||
set({ entries: prevEntries.map(e => (e.date === date && e.user_id === userId ? { ...e, fraction, kind } : e)) })
|
||||
} else {
|
||||
const u = get().users.find(x => x.id === userId)
|
||||
set({ entries: [...prevEntries, { date, user_id: userId, fraction, person_color: u?.color || undefined, person_name: u?.username }] })
|
||||
set({ entries: [...prevEntries, { date, user_id: userId, fraction, kind, person_color: u?.color || undefined, person_name: u?.username }] })
|
||||
}
|
||||
}
|
||||
try {
|
||||
await api.toggleEntry(date, targetUserId, fraction)
|
||||
await api.toggleEntry(date, targetUserId, fraction, kind)
|
||||
await get().loadEntries()
|
||||
await get().loadStats()
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user