mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
7bd9e08016
* feat: add vacay school holiday overlays * fix(vacay): remove SOH control character corruption in locale files 13 of 22 vacay locale files contained a 0x01 (SOH) control character that corrupted the closing }; token. This broke the shared package build (tsdown/rolldown PARSE_ERROR: Invalid Character) and cascaded to all client tests failing to resolve @trek/shared. Affected locales: ar, br, ca, cs, es, fr, gr, hu, id, it, ja, ko, nl, pl, ru, sv, tr, uk, vi, zh, zh-TW * fix(vacay): school-holiday migration order, i18n dupes, settings layout - Move the school-holiday columns migration to the end of the migrations array. It sat before the #1435/#1281 migrations, which every existing DB had already run, so the runner skipped it and school_holidays_enabled / vacay_holiday_calendars.type were never created — PUT /addons/vacay/plan then 500'd with "no such column". - Remove duplicate i18n keys in 13 locale vacay files (TS1117) that failed the shared + client typechecks. - Move the school-holidays block into the right column, directly under public holidays, instead of full-width below everything. - Add the school-holidays section to the mobile Vacay settings sheet. * feat(vacay): modern school-holiday day styling + combined hover tooltip - Replace the stacked 2px underlines with a single rounded accent band and a soft background wash on plain school-break days (desktop + mobile — mobile did not render school holidays at all before). - Fold school holidays into the custom day tooltip under a divider instead of a separate native title, and show the tooltip on every set day (whole vacation days too), not only half days / shared calendars. --------- Co-authored-by: Otto <uif86164@automotive-wan.com> Co-authored-by: Maurice <mauriceboe@icloud.com>
84 lines
3.3 KiB
TypeScript
84 lines
3.3 KiB
TypeScript
import apiClient from '../../api/client'
|
|
import iso31662 from 'iso-3166-2'
|
|
import { SCHOOL_HOLIDAY_COUNTRY_CONFIG } from '../../vacay/schoolHolidayCountries'
|
|
|
|
// Loads the subdivision (state/region) options for a holiday-calendar country.
|
|
//
|
|
// The subdivision list is sourced from ISO 3166-2 (proper names, complete per country)
|
|
// rather than inferred from which subdivisions happen to have a holiday this year — the
|
|
// latter silently dropped states with no state-specific holiday (e.g. US-WA, issue #1456).
|
|
// We only surface a list for countries that are actually region-partitioned, so countries
|
|
// with only nationwide holidays keep showing no region picker (and allow a country-level
|
|
// calendar, matching the server's applyHolidayCalendars behaviour).
|
|
export async function fetchRegionOptions(country: string): Promise<{ value: string; label: string }[]> {
|
|
try {
|
|
const year = new Date().getFullYear()
|
|
const r = await apiClient.get(`/addons/vacay/holidays/${year}/${country}`)
|
|
const hasRegions = r.data.some(h => h.counties && h.counties.length > 0)
|
|
if (!hasRegions) return []
|
|
|
|
const opts = new Map<string, string>() // ISO code -> display name
|
|
const sub = iso31662.country(country)?.sub || {}
|
|
for (const [code, info] of Object.entries(sub)) opts.set(code, info.name)
|
|
|
|
// Fall back to any nager county code ISO doesn't know about, so nothing regresses.
|
|
r.data.forEach(h => h.counties?.forEach(c => {
|
|
if (!opts.has(c)) opts.set(c, iso31662.subdivision(c)?.name || c.split('-')[1] || c)
|
|
}))
|
|
|
|
return [...opts]
|
|
.map(([value, label]) => ({ value, label }))
|
|
.sort((a, b) => a.label.localeCompare(b.label))
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
type LocalizedName = { language: string; text: string }
|
|
type OpenHolidayOption = {
|
|
code: string
|
|
shortName?: string
|
|
name?: LocalizedName[]
|
|
children?: OpenHolidayOption[] | null
|
|
}
|
|
|
|
function localizedName(names: LocalizedName[] | undefined, fallback: string): string {
|
|
return names?.find(n => n.language?.toUpperCase() === 'DE')?.text
|
|
|| names?.find(n => n.language?.toUpperCase() === 'EN')?.text
|
|
|| names?.[0]?.text
|
|
|| fallback
|
|
}
|
|
|
|
function flattenOptions(items: OpenHolidayOption[] | undefined, prefix = ''): { value: string; label: string }[] {
|
|
const result: { value: string; label: string }[] = []
|
|
for (const item of items ?? []) {
|
|
const label = localizedName(item.name, item.shortName || item.code)
|
|
result.push({ value: item.code, label: prefix ? `${prefix} / ${label}` : label })
|
|
result.push(...flattenOptions(item.children ?? undefined, prefix ? `${prefix} / ${label}` : label))
|
|
}
|
|
return result
|
|
}
|
|
|
|
export async function fetchSchoolHolidayRegionOptions(country: string): Promise<{ value: string; label: string }[]> {
|
|
const config = SCHOOL_HOLIDAY_COUNTRY_CONFIG[country]
|
|
if (!config || config.strategy === 'country') return []
|
|
|
|
try {
|
|
const r = await apiClient.get(`/addons/vacay/school-holidays/regions/${country}`)
|
|
if (config.strategy === 'groups') {
|
|
return flattenOptions(r.data.groups)
|
|
.map(opt => ({
|
|
value: `${country}|group:${opt.value}`,
|
|
label: opt.label,
|
|
}))
|
|
.sort((a, b) => a.label.localeCompare(b.label))
|
|
}
|
|
|
|
return flattenOptions(r.data.subdivisions)
|
|
.map(opt => ({ value: opt.value, label: opt.label }))
|
|
.sort((a, b) => a.label.localeCompare(b.label))
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|