You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
134 lines
3.9 KiB
134 lines
3.9 KiB
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<number, Priority1DisplayLine>();
|
|
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 };
|
|
}
|