mark as read and unread woth correct position

UI_CHANGES
ahmed.mujtaba 2026-08-19 19:30:02 +05:00
parent 63a0fbf857
commit 8e95adb320
1 changed files with 35 additions and 11 deletions

View File

@ -25,8 +25,8 @@ import { qk } from '../lib/queryKeys'
import { friendlyAuthError } from '../lib/errors'
import * as inboxApi from '../api/inbox'
import {
atsRecommendationClass, avatarColor, fmtDate, fmtShort, initials as initialsOf,
inboxSources, relTime, sourceMeta,
atsRecommendationClass, avatarColor, fmtDate, initials as initialsOf,
inboxSources, sourceMeta,
} from '../data/seed'
const TABS = ['All Applications', 'Unread', 'Processed', 'Rejected', 'Duplicates', 'Email']
@ -57,9 +57,6 @@ const TAB_FILTERS = {
*/
const SERVER_SCOPED_TABS = new Set(['All Applications', 'Unread'])
/** The prototype computed "time ago" against a fixed 2026-07-09T20:00. */
const NOW = new Date('2026-07-09T20:00')
/**
* message_received_time / message_sent_time are plain string columns
* (backend/inbox/models.py:54-56), not timestamps. An unparseable value yields
@ -72,6 +69,33 @@ function parseDate(value) {
return Number.isNaN(d.getTime()) ? null : d
}
function startOfDay(d) {
return new Date(d.getFullYear(), d.getMonth(), d.getDate())
}
/**
* Outlook-style list timestamps in the client's local timezone.
* Within 7 days: weekday + AM/PM time. Older: dd/mm/yyyy + AM/PM time.
*/
function outlookListTime(value) {
if (!value) return '—'
const d = value instanceof Date ? value : new Date(value)
if (Number.isNaN(d.getTime())) return '—'
const time = d.toLocaleTimeString(undefined, {
hour: 'numeric',
minute: '2-digit',
hour12: true,
})
const daysAgo = Math.round((startOfDay(new Date()) - startOfDay(d)) / 86400000)
if (daysAgo < 7) {
const weekday = d.toLocaleDateString(undefined, { weekday: 'short' })
return `${weekday} ${time}`
}
const dd = String(d.getDate()).padStart(2, '0')
const mm = String(d.getMonth() + 1).padStart(2, '0')
return `${dd}/${mm}/${d.getFullYear()} ${time}`
}
/**
* `source` arrives as the raw To address, because that is where the board tag
* lands careers-rozee@, employee-referral@, mustakbil@ and so on. Strip
@ -487,7 +511,7 @@ function BulkReadBar({ rows, selection, onSetRead, onSetAllRead, busy, canEdit,
onClick={() => flashRead('selected', () => onSetRead(true))}
>
{ticked === 'selected' && <Icon name="check" />}
Read
Mark as Read
</button>
<button
className="btn btn-secondary btn-sm"
@ -495,7 +519,7 @@ function BulkReadBar({ rows, selection, onSetRead, onSetAllRead, busy, canEdit,
title={readCount === 0 ? nothingTo('unread') : tip || 'Mark selected unread'}
onClick={() => onSetRead(false)}
>
Unread
Mark as Unread
</button>
<button className="btn btn-ghost btn-sm" onClick={clear}>Clear</button>
</>
@ -508,7 +532,7 @@ function BulkReadBar({ rows, selection, onSetRead, onSetAllRead, busy, canEdit,
onClick={() => flashRead('all', () => onSetAllRead(true))}
>
{ticked === 'all' && <Icon name="check" />}
Read
Mark all Read
</button>
<button
className="btn btn-secondary btn-sm"
@ -516,7 +540,7 @@ function BulkReadBar({ rows, selection, onSetRead, onSetAllRead, busy, canEdit,
title={readCount === 0 ? nothingTo('unread') : scopeTip}
onClick={() => onSetAllRead(false)}
>
Unread
Mark all Unread
</button>
</>
)}
@ -816,7 +840,7 @@ export default function Inbox() {
</div>
<div style={{ textAlign: 'right', flexShrink: 0 }}>
<div className="ii-time">
{i.received ? relTime(Math.round((NOW - i.received) / 60000)) : '—'}
{outlookListTime(i.received)}
</div>
{/* No ATS score exists server-side the agent returns a
verdict, not a number. The chip stays off rather than
@ -1473,7 +1497,7 @@ function EmailTab({ query, toast }) {
{isImported(e) && <Badge className="b-green">Imported</Badge>}
</div>
</div>
<div className="ii-time">{e.when ? fmtShort(e.when) : '—'}</div>
<div className="ii-time">{outlookListTime(e.when)}</div>
</div>
))}
</div>