import type { Priority1DisplayLine } from "@/modules/priority1/quote-storage"; import { priority1CustomerDisplayUsd } from "@/lib/priority1/display-price"; export type Priority1CalendarCell = { date: Date; inMonth: boolean; priceUsd: number | null; line: Priority1DisplayLine | null; }; const WEEKDAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"] as const; function parseCalendarAnchor(raw: string): Date { const trimmed = raw.trim(); const iso = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); if (iso) { return new Date(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]), 12); } const slash = trimmed.match(/^(\d{2})\/(\d{2})\/(\d{4})$/); if (slash) { return new Date(Number(slash[3]), Number(slash[1]) - 1, Number(slash[2]), 12); } return new Date(`${trimmed}T12:00:00`); } function resolveLineDay( line: Priority1DisplayLine, fallbackDay: number, year: number, month: number, ): number | null { if (line.deliveryDate) { const dayTag = line.deliveryDate.match(/^DAY:(\d{1,2})$/); if (dayTag) { const day = Number(dayTag[1]); return day >= 1 && day <= 31 ? day : null; } const iso = line.deliveryDate.match(/^(\d{4})-(\d{2})-(\d{2})/); if (iso) { const date = new Date( Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]), 12, ); if (date.getFullYear() === year && date.getMonth() === month) { return date.getDate(); } } const slash = line.deliveryDate.match(/^(\d{2})\/(\d{2})\/(\d{4})$/); if (slash) { const date = new Date( Number(slash[3]), Number(slash[1]) - 1, Number(slash[2]), 12, ); if (date.getFullYear() === year && date.getMonth() === month) { return date.getDate(); } } } return fallbackDay; } export function weekdayLabels(): readonly string[] { return WEEKDAYS; } export function weekdayLabelsZh(): readonly string[] { return ["周日", "周一", "周二", "周三", "周四", "周五", "周六"] as const; } /** 将 FTL 日历报价按提货日起连续填入当月日历格 */ export function buildPriority1CalendarGrid( pickupDateIso: string, lines: Priority1DisplayLine[], ): { monthLabel: string; cells: Priority1CalendarCell[] } { const anchor = parseCalendarAnchor(pickupDateIso); if (Number.isNaN(anchor.getTime())) { return { monthLabel: "", cells: [] }; } const year = anchor.getFullYear(); const month = anchor.getMonth(); const monthLabel = anchor.toLocaleString("en-US", { month: "short" }); const firstOfMonth = new Date(year, month, 1); const startPad = firstOfMonth.getDay(); const daysInMonth = new Date(year, month + 1, 0).getDate(); const sorted = [...lines].sort((a, b) => a.rank - b.rank); const priceByDay = new Map(); let cursor = anchor.getDate(); for (const line of sorted) { while (cursor > daysInMonth) break; const day = resolveLineDay(line, cursor, year, month); if (day != null && day >= 1 && day <= daysInMonth) { priceByDay.set(day, line); cursor = day + 1; continue; } priceByDay.set(cursor, line); cursor += 1; } const cells: Priority1CalendarCell[] = []; for (let i = 0; i < startPad; i += 1) { const d = new Date(year, month, -(startPad - 1 - i)); cells.push({ date: d, inMonth: false, priceUsd: null, line: null }); } for (let day = 1; day <= daysInMonth; day += 1) { const date = new Date(year, month, day); const line = priceByDay.get(day) ?? null; cells.push({ date, inMonth: true, priceUsd: line != null ? priority1CustomerDisplayUsd(line) : null, line, }); } while (cells.length % 7 !== 0) { const last = cells[cells.length - 1]!.date; const next = new Date(last); next.setDate(next.getDate() + 1); cells.push({ date: next, inMonth: false, priceUsd: null, line: null }); } return { monthLabel, cells }; }