mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat(web): indicate session activity in date picker (#1103)
* feat(web): indicate session activity in date picker * fix(web): expose session activity to assistive tech
This commit is contained in:
@@ -143,8 +143,13 @@ describe('SessionList time filter', () => {
|
||||
expect(screen.getByRole('button', { name: /Old session/ })).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Filter sessions by last activity' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: new Date(2026, 6, 17).toLocaleDateString() }))
|
||||
fireEvent.click(screen.getByRole('button', { name: new Date(2026, 6, 18).toLocaleDateString() }))
|
||||
const emptyDate = screen.getByRole('button', { name: new Date(2026, 6, 17).toLocaleDateString() })
|
||||
const activeDate = screen.getByRole('button', { name: `${new Date(2026, 6, 18).toLocaleDateString()}, has session activity` })
|
||||
expect(emptyDate).toHaveClass('text-[var(--app-hint)]')
|
||||
expect(activeDate).toHaveClass('text-[var(--app-fg)]')
|
||||
expect(activeDate).toHaveAttribute('title', `${new Date(2026, 6, 18).toLocaleDateString()}, has session activity`)
|
||||
fireEvent.click(emptyDate)
|
||||
fireEvent.click(activeDate)
|
||||
|
||||
expect(screen.getByRole('button', { name: /Recent session/ })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('button', { name: /Old session/ })).toBeNull()
|
||||
@@ -174,7 +179,7 @@ describe('SessionList time filter', () => {
|
||||
fireEvent.click(filterButton)
|
||||
fireEvent.click(screen.getByRole('button', { name: new Date(2026, 6, 1).toLocaleDateString() }))
|
||||
expect(screen.getByText('Select end date')).toBeInTheDocument()
|
||||
fireEvent.click(screen.getByRole('button', { name: new Date(2026, 6, 18).toLocaleDateString() }))
|
||||
fireEvent.click(screen.getByRole('button', { name: `${new Date(2026, 6, 18).toLocaleDateString()}, has session activity` }))
|
||||
|
||||
expect(filterButton).toHaveAttribute('aria-expanded', 'false')
|
||||
expect(filterButton).toHaveAttribute('title', '2026-07-01 – 2026-07-18')
|
||||
|
||||
@@ -573,6 +573,7 @@ function formatDateValue(date: Date): string {
|
||||
function SessionDateRangePicker(props: {
|
||||
start: string
|
||||
end: string
|
||||
sessionActivityDates: ReadonlySet<string>
|
||||
onChange: (start: string, end: string) => void
|
||||
onClose: () => void
|
||||
}) {
|
||||
@@ -627,17 +628,24 @@ function SessionDateRangePicker(props: {
|
||||
const value = formatDateValue(date)
|
||||
const isEndpoint = value === props.start || value === props.end
|
||||
const isInRange = Boolean(props.start && props.end && value > props.start && value < props.end)
|
||||
const hasSessionActivity = props.sessionActivityDates.has(value)
|
||||
const dateLabel = date.toLocaleDateString()
|
||||
const activityLabel = hasSessionActivity
|
||||
? t('sessions.timeFilter.dayWithActivity', { date: dateLabel })
|
||||
: dateLabel
|
||||
return (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => selectDate(value)}
|
||||
aria-label={date.toLocaleDateString()}
|
||||
aria-label={activityLabel}
|
||||
title={hasSessionActivity ? activityLabel : undefined}
|
||||
className={cn(
|
||||
'h-8 rounded-lg text-xs transition-colors',
|
||||
isEndpoint && 'bg-[var(--app-link)] text-white',
|
||||
isInRange && 'bg-[var(--app-link)]/15 text-[var(--app-link)]',
|
||||
!isEndpoint && !isInRange && 'hover:bg-[var(--app-subtle-bg)]'
|
||||
!isEndpoint && !isInRange && hasSessionActivity && 'text-[var(--app-fg)] hover:bg-[var(--app-subtle-bg)]',
|
||||
!isEndpoint && !isInRange && !hasSessionActivity && 'text-[var(--app-hint)] hover:bg-[var(--app-subtle-bg)]'
|
||||
)}
|
||||
>
|
||||
{index + 1}
|
||||
@@ -668,6 +676,7 @@ function SessionListSearch(props: {
|
||||
onChange: (value: string) => void
|
||||
customStart: string
|
||||
customEnd: string
|
||||
sessionActivityDates: ReadonlySet<string>
|
||||
onDateRangeChange: (start: string, end: string) => void
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
@@ -719,6 +728,7 @@ function SessionListSearch(props: {
|
||||
<SessionDateRangePicker
|
||||
start={props.customStart}
|
||||
end={props.customEnd}
|
||||
sessionActivityDates={props.sessionActivityDates}
|
||||
onChange={props.onDateRangeChange}
|
||||
onClose={() => setDatePickerOpen(false)}
|
||||
/>
|
||||
@@ -1051,6 +1061,10 @@ export function SessionList(props: {
|
||||
},
|
||||
[props.sessions, selectedSessionId, showActiveSessionsOnly]
|
||||
)
|
||||
const sessionActivityDates = useMemo(
|
||||
() => new Set(allSessions.map(session => formatDateValue(new Date(session.updatedAt)))),
|
||||
[allSessions]
|
||||
)
|
||||
const visibleSessions = useMemo(
|
||||
() => isFiltering
|
||||
? allSessions.filter(session => (
|
||||
@@ -1246,6 +1260,7 @@ export function SessionList(props: {
|
||||
onChange={setSearchQuery}
|
||||
customStart={customStart}
|
||||
customEnd={customEnd}
|
||||
sessionActivityDates={sessionActivityDates}
|
||||
onDateRangeChange={(start, end) => {
|
||||
setCustomStart(start)
|
||||
setCustomEnd(end)
|
||||
|
||||
@@ -60,6 +60,7 @@ export default {
|
||||
'sessions.timeFilter.close': 'Close date picker',
|
||||
'sessions.timeFilter.previousMonth': 'Previous month',
|
||||
'sessions.timeFilter.nextMonth': 'Next month',
|
||||
'sessions.timeFilter.dayWithActivity': '{date}, has session activity',
|
||||
'sessions.group.showMore': 'Show {n} more',
|
||||
'sessions.group.showLess': 'Show less',
|
||||
'sessions.group.new': 'New session in this directory',
|
||||
|
||||
@@ -60,6 +60,7 @@ export default {
|
||||
'sessions.timeFilter.close': '关闭日期选择器',
|
||||
'sessions.timeFilter.previousMonth': '上个月',
|
||||
'sessions.timeFilter.nextMonth': '下个月',
|
||||
'sessions.timeFilter.dayWithActivity': '{date},有会话活动',
|
||||
'sessions.group.showMore': '再显示 {n} 个',
|
||||
'sessions.group.showLess': '收起',
|
||||
'sessions.group.new': '在此目录新建会话',
|
||||
|
||||
Reference in New Issue
Block a user