126 lines
3.9 KiB
JavaScript
126 lines
3.9 KiB
JavaScript
/** Display formatting for identifiers stored snake_case in the DB. */
|
|
|
|
/** 'system_administrator' → 'System Administrator', 'INTERVIEW' → 'Interview'. */
|
|
export function formatRole(name) {
|
|
const s = String(name || '').trim()
|
|
if (!s) return s
|
|
return s
|
|
.split(/[_\s]+/)
|
|
.map((w) => (w ? w[0].toUpperCase() + w.slice(1).toLowerCase() : w))
|
|
.join(' ')
|
|
}
|
|
|
|
/* Platform date/time display. One spelling everywhere:
|
|
date → 3rd Sept 2026
|
|
time → 7:30pm (12-hour, lowercase, no space)
|
|
both → 3rd Sept 2026 - 7:30pm
|
|
|
|
API timestamps are year-month-day, then optional time, e.g.
|
|
2026-08-28 03:56:19.685066-07
|
|
2026-08-28T03:56:19.685066-07:00
|
|
2026-08-28
|
|
Display uses those numbers as written: 2026 = year, 08 = month (Aug),
|
|
28 = day, 03:56 = 3:56am. The timezone suffix is not applied. */
|
|
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sept', 'Oct', 'Nov', 'Dec']
|
|
const WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
|
const WEEKDAYS_SHORT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
|
|
|
const API_TS = /^(\d{4})-(\d{2})-(\d{2})(?:[T\s](\d{1,2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?)?/
|
|
|
|
/** Parse an API timestamp into a Date whose local fields match the payload. */
|
|
export function toDate(value) {
|
|
if (value == null || value === '') return null
|
|
if (value instanceof Date) {
|
|
return Number.isNaN(value.getTime()) ? null : value
|
|
}
|
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
const d = new Date(value)
|
|
return Number.isNaN(d.getTime()) ? null : d
|
|
}
|
|
const s = String(value).trim()
|
|
const m = s.match(API_TS)
|
|
if (!m) return null
|
|
const year = Number(m[1])
|
|
const month = Number(m[2])
|
|
const day = Number(m[3])
|
|
const hour = m[4] != null ? Number(m[4]) : 0
|
|
const minute = m[5] != null ? Number(m[5]) : 0
|
|
const second = m[6] != null ? Number(m[6]) : 0
|
|
if (month < 1 || month > 12 || day < 1 || day > 31) return null
|
|
const d = new Date(year, month - 1, day, hour, minute, second)
|
|
if (d.getFullYear() !== year || d.getMonth() !== month - 1 || d.getDate() !== day) return null
|
|
return d
|
|
}
|
|
|
|
function ordinal(n) {
|
|
const v = n % 100
|
|
if (v >= 11 && v <= 13) return `${n}th`
|
|
switch (n % 10) {
|
|
case 1: return `${n}st`
|
|
case 2: return `${n}nd`
|
|
case 3: return `${n}rd`
|
|
default: return `${n}th`
|
|
}
|
|
}
|
|
|
|
/** 3rd Sept 2026 */
|
|
export function fmtDate(value) {
|
|
const d = toDate(value)
|
|
if (!d) return ''
|
|
return `${ordinal(d.getDate())} ${MONTHS[d.getMonth()]} ${d.getFullYear()}`
|
|
}
|
|
|
|
/** Same as fmtDate — the platform uses one date spelling. */
|
|
export function fmtShort(value) {
|
|
return fmtDate(value)
|
|
}
|
|
|
|
/** 7:30pm */
|
|
export function fmtTime(value) {
|
|
const d = toDate(value)
|
|
if (!d) return ''
|
|
let hours = d.getHours()
|
|
const minutes = d.getMinutes()
|
|
const suffix = hours >= 12 ? 'pm' : 'am'
|
|
hours = hours % 12
|
|
if (hours === 0) hours = 12
|
|
return `${hours}:${String(minutes).padStart(2, '0')}${suffix}`
|
|
}
|
|
|
|
/** 3rd Sept 2026 - 7:30pm */
|
|
export function fmtDateTime(value, sep = ' - ') {
|
|
const d = toDate(value)
|
|
if (!d) return ''
|
|
return `${fmtDate(d)}${sep}${fmtTime(d)}`
|
|
}
|
|
|
|
/** Sept 2026 — calendar month headers, not a day. */
|
|
export function fmtMonthYear(value) {
|
|
const d = toDate(value)
|
|
if (!d) return ''
|
|
return `${MONTHS[d.getMonth()]} ${d.getFullYear()}`
|
|
}
|
|
|
|
/** Thursday, 3rd Sept 2026 — dashboard "today" line. */
|
|
export function fmtWeekdayDate(value) {
|
|
const d = toDate(value)
|
|
if (!d) return ''
|
|
return `${WEEKDAYS[d.getDay()]}, ${fmtDate(d)}`
|
|
}
|
|
|
|
export function fmtWeekdayShort(value) {
|
|
const d = toDate(value)
|
|
if (!d) return ''
|
|
return WEEKDAYS_SHORT[d.getDay()]
|
|
}
|
|
|
|
/** yyyy-mm-dd for <input type="date">, from the payload's calendar day. */
|
|
export function toDateInput(value) {
|
|
const d = toDate(value)
|
|
if (!d) return ''
|
|
const y = d.getFullYear()
|
|
const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
const day = String(d.getDate()).padStart(2, '0')
|
|
return `${y}-${m}-${day}`
|
|
}
|