feat(attendance): richer /attendance (here-now, search, day filter, grouping)
Per request. /attendance is now a client view: a 'Here now' section with host badges + inline self check-in / check-in-as-host / check-out with a note; and a history with name search, a day picker, grouped-by- day headers, per-visit duration, status, notes, and a live visits/members summary for the current filter. New pure tested helpers dayKey (tz-stable UTC bucket) + visitDurationMinutes (+5 tests, suite 459).
This commit is contained in:
@@ -3,6 +3,8 @@ import {
|
||||
presenceStatus,
|
||||
hostEligibility,
|
||||
summarizePresence,
|
||||
dayKey,
|
||||
visitDurationMinutes,
|
||||
PRESENCE_MAX_OPEN_HOURS,
|
||||
} from '@/lib/presence-logic'
|
||||
|
||||
@@ -63,3 +65,27 @@ describe('summarizePresence', () => {
|
||||
expect(summarizePresence([], NOW)).toEqual({ present: 0, hosts: 0 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('dayKey', () => {
|
||||
it('returns the UTC date portion, stable regardless of runtime tz', () => {
|
||||
expect(dayKey('2026-05-17T23:30:00Z')).toBe('2026-05-17')
|
||||
expect(dayKey('2026-01-02T00:00:00+00:00')).toBe('2026-01-02')
|
||||
})
|
||||
it('returns empty for unparseable input', () => {
|
||||
expect(dayKey('not-a-date')).toBe('')
|
||||
expect(dayKey('')).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('visitDurationMinutes', () => {
|
||||
it('uses checkout when present', () => {
|
||||
expect(visitDurationMinutes('2026-05-17T10:00:00Z', '2026-05-17T11:30:00Z')).toBe(90)
|
||||
})
|
||||
it('uses now when still open', () => {
|
||||
expect(visitDurationMinutes(hoursAgo(2), null, NOW)).toBe(120)
|
||||
})
|
||||
it('never negative; unparseable -> 0', () => {
|
||||
expect(visitDurationMinutes('2026-05-17T12:00:00Z', '2026-05-17T11:00:00Z')).toBe(0)
|
||||
expect(visitDurationMinutes('nope', null, NOW)).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user