mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 11:33:12 +00:00
176 lines
4.8 KiB
TypeScript
176 lines
4.8 KiB
TypeScript
export const MONTH_NAMES = [
|
||
"January",
|
||
"February",
|
||
"March",
|
||
"April",
|
||
"May",
|
||
"June",
|
||
"July",
|
||
"August",
|
||
"September",
|
||
"October",
|
||
"November",
|
||
"December",
|
||
];
|
||
|
||
export function pad2(n: number): string {
|
||
return String(n).padStart(2, "0");
|
||
}
|
||
|
||
export function todayIso(): string {
|
||
const d = new Date();
|
||
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
||
}
|
||
|
||
export function fmtDate(iso: string): string {
|
||
if (!/^\d{4}-\d{2}-\d{2}$/.test(iso)) {
|
||
return "";
|
||
}
|
||
const [y, m, d] = iso.split("-").map(Number);
|
||
return `${MONTH_NAMES[m - 1].slice(0, 3)} ${d}, ${y}`;
|
||
}
|
||
|
||
export function addDaysIso(iso: string, days: number): string {
|
||
const d = new Date(`${iso}T12:00:00`);
|
||
d.setDate(d.getDate() + days);
|
||
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
||
}
|
||
|
||
export function mondayOf(iso: string): string {
|
||
const d = new Date(`${iso}T12:00:00`);
|
||
const dow = d.getDay();
|
||
d.setDate(d.getDate() + (dow === 0 ? -6 : 1 - dow));
|
||
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
||
}
|
||
|
||
export function weeksOfMonth(y: number, m: number): string[] {
|
||
const firstIso = `${y}-${pad2(m)}-01`;
|
||
const lastIso = `${y}-${pad2(m)}-${pad2(new Date(y, m, 0).getDate())}`;
|
||
const out: string[] = [];
|
||
for (let mon = mondayOf(firstIso); mon <= lastIso; mon = addDaysIso(mon, 7)) {
|
||
if (addDaysIso(mon, 4) >= firstIso) {
|
||
out.push(mon);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
export function weekRangeLabel(mondayIso: string): string {
|
||
if (!/^\d{4}-\d{2}-\d{2}$/.test(mondayIso)) {
|
||
return "";
|
||
}
|
||
const fri = addDaysIso(mondayIso, 4);
|
||
const [, sm, sd] = mondayIso.split("-").map(Number);
|
||
const [, em, ed] = fri.split("-").map(Number);
|
||
return `${MONTH_NAMES[sm - 1].slice(0, 3)} ${sd} – ${MONTH_NAMES[em - 1].slice(0, 3)} ${ed}`;
|
||
}
|
||
|
||
export function fmtWeekRange(startIso: string): string {
|
||
if (!/^\d{4}-\d{2}-\d{2}$/.test(startIso)) {
|
||
return fmtDate(startIso);
|
||
}
|
||
return weekRangeLabel(startIso);
|
||
}
|
||
|
||
/** Short date for ranges: "Jun 8". */
|
||
export function fmtShortDate(iso: string): string {
|
||
if (!iso || !/^\d{4}-\d{2}-\d{2}$/.test(iso)) {
|
||
return "—";
|
||
}
|
||
const [, m, d] = iso.split("-").map(Number);
|
||
return `${MONTH_NAMES[m - 1].slice(0, 3)} ${d}`;
|
||
}
|
||
|
||
/** "Jun 8 – Jun 12" for a Schedule On range. */
|
||
export function fmtDateRange(start: string, end: string): string {
|
||
return `${fmtShortDate(start)} – ${fmtShortDate(end)}`;
|
||
}
|
||
|
||
/** Inclusive day count between two ISO dates (order-independent). */
|
||
export function rangeDayCount(a: string, b: string): number {
|
||
const d1 = new Date(`${a}T12:00:00`).getTime();
|
||
const d2 = new Date(`${b}T12:00:00`).getTime();
|
||
return Math.abs(Math.round((d2 - d1) / 86400000)) + 1;
|
||
}
|
||
|
||
export function fmt12(t: string): string {
|
||
const [h, m] = t.split(":").map(Number);
|
||
const ampm = h < 12 ? "AM" : "PM";
|
||
const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
|
||
return `${h12}:${String(m).padStart(2, "0")} ${ampm}`;
|
||
}
|
||
|
||
export function calcDuration(s: string, e: string): string {
|
||
const [sh, sm] = s.split(":").map(Number);
|
||
const [eh, em] = e.split(":").map(Number);
|
||
const mins = eh * 60 + em - (sh * 60 + sm);
|
||
if (mins <= 0) {
|
||
return "";
|
||
}
|
||
const h = Math.floor(mins / 60);
|
||
const m = mins % 60;
|
||
if (h && m) {
|
||
return `${h}h ${m}m`;
|
||
}
|
||
if (h) {
|
||
return `${h}h`;
|
||
}
|
||
return `${m}m`;
|
||
}
|
||
|
||
export function formatApptRange(start: string, end: string): string {
|
||
if (!start) {
|
||
return "—";
|
||
}
|
||
return end ? `${fmt12(start)} – ${fmt12(end)}` : fmt12(start);
|
||
}
|
||
|
||
export const TIME_SLOTS: string[] = (() => {
|
||
const out: string[] = [];
|
||
for (let m = 5 * 60; m <= 22 * 60; m += 30) {
|
||
const h = Math.floor(m / 60);
|
||
const mm = m % 60;
|
||
out.push(`${String(h).padStart(2, "0")}:${String(mm).padStart(2, "0")}`);
|
||
}
|
||
return out;
|
||
})();
|
||
|
||
export function parseApptTime(appt: string): { start: string; end: string } {
|
||
const m = appt.match(/(\d{1,2}):(\d{2})\s*[–-]\s*(\d{1,2}):(\d{2})/);
|
||
if (m) {
|
||
return {
|
||
start: `${m[1].padStart(2, "0")}:${m[2]}`,
|
||
end: `${m[3].padStart(2, "0")}:${m[4]}`,
|
||
};
|
||
}
|
||
const single = appt.match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i);
|
||
if (single) {
|
||
let h = Number(single[1]) % 12;
|
||
if (/pm/i.test(single[3])) {
|
||
h += 12;
|
||
}
|
||
return { start: `${pad2(h)}:${single[2]}`, end: "" };
|
||
}
|
||
const startOnly = appt.trim().match(/^(\d{1,2}):(\d{2})$/);
|
||
if (startOnly) {
|
||
return {
|
||
start: `${startOnly[1].padStart(2, "0")}:${startOnly[2]}`,
|
||
end: "",
|
||
};
|
||
}
|
||
return { start: "", end: "" };
|
||
}
|
||
|
||
export function formatApptTimeValue(start: string, end: string): string {
|
||
if (!start) {
|
||
return "";
|
||
}
|
||
return end ? `${start} – ${end}` : start;
|
||
}
|
||
|
||
export function fmtWeekOf(mondayIso: string): string {
|
||
if (!mondayIso) {
|
||
return "";
|
||
}
|
||
return `Week of ${weekRangeLabel(mondayIso)}`;
|
||
}
|