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:
@@ -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}`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user