mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 19:56:18 -04:00
169 lines
4.9 KiB
TypeScript
169 lines
4.9 KiB
TypeScript
|
|
import type { Meeting, Session } from '../types'
|
||
|
|
|
||
|
|
const DEFAULT_SESSION_DURATION_MS = 3 * 60 * 60 * 1000
|
||
|
|
|
||
|
|
export function parseScheduleTime(value: string): Date | null {
|
||
|
|
if (!value) return null
|
||
|
|
const parsed = Date.parse(value)
|
||
|
|
if (!Number.isNaN(parsed)) return new Date(parsed)
|
||
|
|
const dateOnly = Date.parse(value.slice(0, 10))
|
||
|
|
return Number.isNaN(dateOnly) ? null : new Date(dateOnly)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function meetingStartTime(meeting: Meeting): Date | null {
|
||
|
|
return parseScheduleTime(meeting.date_start)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function meetingEndTime(meeting: Meeting): Date | null {
|
||
|
|
const end = parseScheduleTime(meeting.date_end)
|
||
|
|
if (end) return end
|
||
|
|
const start = meetingStartTime(meeting)
|
||
|
|
return start ? new Date(start.getTime() + 72 * 60 * 60 * 1000) : null
|
||
|
|
}
|
||
|
|
|
||
|
|
export function sessionStartTime(session: Session): Date | null {
|
||
|
|
return parseScheduleTime(session.date_start)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function sessionEndTime(session: Session): Date | null {
|
||
|
|
const end = parseScheduleTime(session.date_end)
|
||
|
|
if (end) return end
|
||
|
|
const start = sessionStartTime(session)
|
||
|
|
return start ? new Date(start.getTime() + DEFAULT_SESSION_DURATION_MS) : null
|
||
|
|
}
|
||
|
|
|
||
|
|
export function sortSessionsByStart(sessions: Session[]): Session[] {
|
||
|
|
return [...sessions].sort((a, b) => {
|
||
|
|
const left = sessionStartTime(a)?.getTime() ?? 0
|
||
|
|
const right = sessionStartTime(b)?.getTime() ?? 0
|
||
|
|
if (left !== right) return left - right
|
||
|
|
return a.date_start.localeCompare(b.date_start)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
export function currentMeeting(meetings: Meeting[], now: Date): Meeting | null {
|
||
|
|
let selected: Meeting | null = null
|
||
|
|
let latest = 0
|
||
|
|
|
||
|
|
for (const meeting of meetings) {
|
||
|
|
const start = meetingStartTime(meeting)
|
||
|
|
if (!start || start > now) continue
|
||
|
|
const end = meetingEndTime(meeting)
|
||
|
|
if (!end || now > new Date(end.getTime() + 24 * 60 * 60 * 1000)) continue
|
||
|
|
const startMs = start.getTime()
|
||
|
|
if (!selected || startMs > latest) {
|
||
|
|
selected = meeting
|
||
|
|
latest = startMs
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return selected
|
||
|
|
}
|
||
|
|
|
||
|
|
export function nextUpcomingMeeting(meetings: Meeting[], now: Date): Meeting | null {
|
||
|
|
for (const meeting of meetings) {
|
||
|
|
const start = meetingStartTime(meeting)
|
||
|
|
if (start && start > now) return meeting
|
||
|
|
}
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
export function mostRecentPastMeeting(meetings: Meeting[], now: Date): Meeting | null {
|
||
|
|
let selected: Meeting | null = null
|
||
|
|
let latest = 0
|
||
|
|
|
||
|
|
for (const meeting of meetings) {
|
||
|
|
const start = meetingStartTime(meeting)
|
||
|
|
if (!start || start > now) continue
|
||
|
|
const startMs = start.getTime()
|
||
|
|
if (!selected || startMs > latest) {
|
||
|
|
selected = meeting
|
||
|
|
latest = startMs
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return selected
|
||
|
|
}
|
||
|
|
|
||
|
|
export function pickFocusMeeting(meetings: Meeting[], now: Date): Meeting | null {
|
||
|
|
return (
|
||
|
|
currentMeeting(meetings, now) ??
|
||
|
|
nextUpcomingMeeting(meetings, now) ??
|
||
|
|
mostRecentPastMeeting(meetings, now) ??
|
||
|
|
meetings[0] ??
|
||
|
|
null
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
export function meetingHasStarted(meeting: Meeting, now: Date): boolean {
|
||
|
|
const start = meetingStartTime(meeting)
|
||
|
|
return start != null && now >= start
|
||
|
|
}
|
||
|
|
|
||
|
|
export function currentAndNextSession(
|
||
|
|
sessions: Session[],
|
||
|
|
now: Date,
|
||
|
|
): { current: Session | null; next: Session | null } {
|
||
|
|
const sorted = sortSessionsByStart(sessions)
|
||
|
|
|
||
|
|
for (const session of sorted) {
|
||
|
|
const start = sessionStartTime(session)
|
||
|
|
const end = sessionEndTime(session)
|
||
|
|
if (!start || !end) continue
|
||
|
|
|
||
|
|
if (now >= start && now < end) {
|
||
|
|
return { current: session, next: null }
|
||
|
|
}
|
||
|
|
if (now < start) {
|
||
|
|
return { current: null, next: session }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { current: null, next: null }
|
||
|
|
}
|
||
|
|
|
||
|
|
export function formatCountdown(target: Date, now: Date): string {
|
||
|
|
const diffMs = Math.max(0, target.getTime() - now.getTime())
|
||
|
|
const totalSeconds = Math.floor(diffMs / 1000)
|
||
|
|
const days = Math.floor(totalSeconds / 86400)
|
||
|
|
const hours = Math.floor((totalSeconds % 86400) / 3600)
|
||
|
|
const mins = Math.floor((totalSeconds % 3600) / 60)
|
||
|
|
const secs = totalSeconds % 60
|
||
|
|
return `${days}d ${String(hours).padStart(2, '0')}h ${String(mins).padStart(2, '0')}m ${String(secs).padStart(2, '0')}s`
|
||
|
|
}
|
||
|
|
|
||
|
|
export function formatSessionScheduleTime(value: string): string {
|
||
|
|
const date = parseScheduleTime(value)
|
||
|
|
if (!date) return '—'
|
||
|
|
return date.toLocaleString('en-GB', {
|
||
|
|
weekday: 'short',
|
||
|
|
day: '2-digit',
|
||
|
|
month: 'short',
|
||
|
|
hour: '2-digit',
|
||
|
|
minute: '2-digit',
|
||
|
|
hour12: false,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
export type FocusMeetingKind = 'current' | 'next' | 'recent' | 'fallback'
|
||
|
|
|
||
|
|
export function focusMeetingKind(meeting: Meeting, now: Date): FocusMeetingKind {
|
||
|
|
if (currentMeeting([meeting], now)) return 'current'
|
||
|
|
if (nextUpcomingMeeting([meeting], now)) return 'next'
|
||
|
|
if (mostRecentPastMeeting([meeting], now)) return 'recent'
|
||
|
|
return 'fallback'
|
||
|
|
}
|
||
|
|
|
||
|
|
export function focusMeetingLabel(kind: FocusMeetingKind): string {
|
||
|
|
switch (kind) {
|
||
|
|
case 'current':
|
||
|
|
return 'Current Weekend'
|
||
|
|
case 'next':
|
||
|
|
return 'Next Weekend'
|
||
|
|
case 'recent':
|
||
|
|
return 'Recent Local Weekend'
|
||
|
|
default:
|
||
|
|
return 'Weekend'
|
||
|
|
}
|
||
|
|
}
|