Share one date formatter, and stop describing files that are gone

Two components each carried the same four lines turning a date into an ISO
string from local parts. They now share one helper, deliberately separate
from the one that formats in UTC: substituting one for the other shifts the
day by one anywhere east of Greenwich, Sweden included, so a test pins the
difference rather than a comment asking nicely.

Creating a period ran its own copy of the sync-and-report sequence purely so
it could name a wish the lookup could not yet see; it now passes that wish to
the ordinary path instead.

Two comments still explained their relationship to components deleted some
time ago, which would send the next reader looking for files that are not
there, and some styling outlived the markup it described.
This commit is contained in:
2026-09-18 01:12:58 +02:00
parent 9ac67933d4
commit f2b3920cbc
8 changed files with 112 additions and 99 deletions
+43 -55
View File
@@ -30,7 +30,7 @@ import { PeriodPopover, type PeriodPopoverSavePayload } from '@/components/calen
import { PresetRail, type SeasonPreset } from '@/components/calendar/PresetRail'
import { getColorForType } from '@/utils/leaveTypes'
import { resolveWishName, resolveWishNameFromData } from '@/utils/wishName'
import { datesInRange, formatDateForDisplay } from '@/utils/dates'
import { datesInRange, formatDateForDisplay, formatDateLocalISO } from '@/utils/dates'
import { renderRejection, type Translate } from '@/utils/rejectionMessages'
import { useTranslation } from '@/i18n'
import './calendar.css'
@@ -285,7 +285,17 @@ export function Calendar(): ReactNode {
const runSync = useCallback(
async (
workspaceId: string,
options?: { silent?: boolean; undo?: CalendarToast['undo']; watchWishId?: string },
options?: {
silent?: boolean
undo?: CalendarToast['undo']
watchWishId?: string
// Used right after creating a wish, where `wishesHook.data` is
// whatever it was as of this closure's own render (captured before
// the create's background refetch lands) and so never actually
// contains a wish created moments ago. Passing the just-created
// wish object directly sidesteps that — see `buildSyncToastMessage`.
watchWish?: LeaveWish
},
): Promise<PlanSyncResponse | undefined> => {
try {
const result = await syncHook.sync(workspaceId)
@@ -297,6 +307,7 @@ export function Calendar(): ReactNode {
membersHook.data,
options?.watchWishId,
t,
options?.watchWish,
)
if (message) showToast(message, options?.undo ?? null)
}
@@ -319,46 +330,6 @@ export function Calendar(): ReactNode {
void runSync(workspace.id, { silent: true })
}, [workspace?.id, runSync])
// Runs the sync that persists a just-created wish, then reports it and
// wires up undo — split out from a plain `runSync(..., { undo })` call
// because undo here needs to pass the just-created wish's own fields
// (`created`) to `buildSyncToastMessage` rather than looking `wishId` up
// in `wishesHook.data`, which is whatever it was as of this closure's own
// render (captured before the create's background refetch lands) and so
// never actually contains a wish created moments ago.
//
// Undo itself is just "delete the wish, re-sync": the backend deletes
// every `planned_leave_day` row the wish produced as part of deleting the
// wish (`LeaveWishService.delete()`), so there is nothing left here for
// the frontend to individually track or clean up.
const runSyncAfterWishCreate = useCallback(
async (workspaceId: string, created: LeaveWish): Promise<PlanSyncResponse | undefined> => {
const wishId = created.id
const result = await runSync(workspaceId, { silent: true })
if (!result) return result
const message = buildSyncToastMessage(
result,
wishesHook.data,
membersHook.data,
wishId,
t,
created,
)
if (message) {
showToast(message, {
label: t('calendar.undoButton'),
run: async () => {
await wishesHook.remove(wishId)
await runSync(workspaceId, { silent: true })
},
})
}
return result
},
[runSync, wishesHook, membersHook.data, t, showToast],
)
const handleCreatePeriodFromDrag = useCallback(
async (dates: string[]) => {
setSelectedDates(new Set())
@@ -393,7 +364,21 @@ export function Calendar(): ReactNode {
parentPersonalDataUuid: personalDataUuid,
windowMode: 'fixed',
})
await runSyncAfterWishCreate(workspace.id, created)
// Undo is just "delete the wish, re-sync": the backend deletes every
// `planned_leave_day` row the wish produced as part of deleting the
// wish (`LeaveWishService.delete()`), so there is nothing left here
// for the frontend to individually track or clean up.
await runSync(workspace.id, {
watchWishId: created.id,
watchWish: created,
undo: {
label: t('calendar.undoButton'),
run: async () => {
await wishesHook.remove(created.id)
await runSync(workspace.id, { silent: true })
},
},
})
} catch (err) {
showToast(err instanceof Error ? err.message : t('calendar.periodCreateError'), null)
} finally {
@@ -406,7 +391,7 @@ export function Calendar(): ReactNode {
personalDataUuid,
wishesHook,
t,
runSyncAfterWishCreate,
runSync,
showToast,
],
)
@@ -461,11 +446,11 @@ export function Calendar(): ReactNode {
for (let i = 0; i < startOffset; i++) {
const prevDate = new Date(year, month, -startOffset + i + 1)
currentWeek.push(formatDate(prevDate))
currentWeek.push(formatDateLocalISO(prevDate))
}
for (let day = 1; day <= daysInMonth; day++) {
currentWeek.push(formatDate(new Date(year, month, day)))
currentWeek.push(formatDateLocalISO(new Date(year, month, day)))
if (currentWeek.length === 7) {
weeks.push(currentWeek)
currentWeek = []
@@ -474,7 +459,7 @@ export function Calendar(): ReactNode {
const remaining = 7 - currentWeek.length
for (let i = 0; i < remaining; i++) {
currentWeek.push(formatDate(new Date(year, month + 1, i + 1)))
currentWeek.push(formatDateLocalISO(new Date(year, month + 1, i + 1)))
}
weeks.push(currentWeek)
@@ -764,7 +749,17 @@ export function Calendar(): ReactNode {
windowMode: 'flexible',
})
const result = await runSyncAfterWishCreate(workspace.id, created)
const result = await runSync(workspace.id, {
watchWishId: created.id,
watchWish: created,
undo: {
label: t('calendar.undoButton'),
run: async () => {
await wishesHook.remove(created.id)
await runSync(workspace.id, { silent: true })
},
},
})
if (result) {
const placedDates = result.changes
@@ -1018,10 +1013,3 @@ export function Calendar(): ReactNode {
</Layout>
)
}
function formatDate(date: Date): string {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}