Filter chats by Unread
This commit is contained in:
parent
45e9c07125
commit
a56e7d0ade
27 changed files with 883 additions and 438 deletions
|
@ -66,6 +66,8 @@ import type {
|
|||
} from '../state/ducks/calling';
|
||||
import { DAY, MINUTE, SECOND } from '../util/durations';
|
||||
import type { StartCallData } from './ConfirmLeaveCallModal';
|
||||
import { Button, ButtonVariant } from './Button';
|
||||
import type { ICUJSXMessageParamsByKeyType } from '../types/Util';
|
||||
|
||||
function Timestamp({
|
||||
i18n,
|
||||
|
@ -153,6 +155,7 @@ type CallsListProps = Readonly<{
|
|||
togglePip: () => void;
|
||||
}>;
|
||||
|
||||
const FILTER_HEADER_ROW_HEIGHT = 50;
|
||||
const CALL_LIST_ITEM_ROW_HEIGHT = 62;
|
||||
const INACTIVE_CALL_LINKS_TO_PEEK = 10;
|
||||
const INACTIVE_CALL_LINK_AGE_THRESHOLD = 10 * DAY;
|
||||
|
@ -167,7 +170,11 @@ function isSameOptions(
|
|||
return a.query === b.query && a.status === b.status;
|
||||
}
|
||||
|
||||
type SpecialRows = 'CreateCallLink' | 'EmptyState';
|
||||
type SpecialRows =
|
||||
| 'CreateCallLink'
|
||||
| 'EmptyState'
|
||||
| 'FilterHeader'
|
||||
| 'ClearFilterButton';
|
||||
type Row = CallHistoryGroup | SpecialRows;
|
||||
|
||||
export function CallsList({
|
||||
|
@ -206,25 +213,34 @@ export function CallsList({
|
|||
const searchStateStatus =
|
||||
searchState.options?.status ?? CallHistoryFilterStatus.All;
|
||||
const hasSearchStateQuery = searchStateQuery !== '';
|
||||
const searchFiltering =
|
||||
hasSearchStateQuery || searchStateStatus !== CallHistoryFilterStatus.All;
|
||||
const hasMissedCallFilter =
|
||||
searchStateStatus === CallHistoryFilterStatus.Missed;
|
||||
const searchFiltering = hasSearchStateQuery || hasMissedCallFilter;
|
||||
const searchPending = searchState.state === 'pending';
|
||||
const isEmpty = !searchState.results?.items?.length;
|
||||
|
||||
const rows = useMemo(() => {
|
||||
let results: ReadonlyArray<Row> = searchState.results?.items ?? [];
|
||||
if (results.length === 0 && hasSearchStateQuery) {
|
||||
results = ['EmptyState'];
|
||||
const rows = useMemo<ReadonlyArray<Row>>(() => {
|
||||
const results: ReadonlyArray<Row> = searchState.results?.items ?? [];
|
||||
|
||||
if (results.length === 0 && searchFiltering) {
|
||||
return hasMissedCallFilter
|
||||
? ['FilterHeader', 'EmptyState', 'ClearFilterButton']
|
||||
: ['EmptyState'];
|
||||
}
|
||||
|
||||
if (!searchFiltering && canCreateCallLinks) {
|
||||
results = ['CreateCallLink', ...results];
|
||||
return ['CreateCallLink', ...results];
|
||||
}
|
||||
|
||||
if (hasMissedCallFilter) {
|
||||
return ['FilterHeader', ...results, 'ClearFilterButton'];
|
||||
}
|
||||
return results;
|
||||
}, [
|
||||
searchState.results?.items,
|
||||
hasSearchStateQuery,
|
||||
searchFiltering,
|
||||
canCreateCallLinks,
|
||||
hasMissedCallFilter,
|
||||
]);
|
||||
|
||||
const rowCount = rows.length;
|
||||
|
@ -675,10 +691,8 @@ export function CallsList({
|
|||
({ index }: Index) => {
|
||||
const item = rows.at(index) ?? null;
|
||||
|
||||
if (item === 'EmptyState') {
|
||||
// arbitary large number so the empty state can be as big as it wants,
|
||||
// scrolling should always be locked when the list is empty
|
||||
return 9999;
|
||||
if (item === 'FilterHeader') {
|
||||
return FILTER_HEADER_ROW_HEIGHT;
|
||||
}
|
||||
|
||||
return CALL_LIST_ITEM_ROW_HEIGHT;
|
||||
|
@ -710,11 +724,23 @@ export function CallsList({
|
|||
}
|
||||
|
||||
if (item === 'EmptyState') {
|
||||
let i18nId: keyof ICUJSXMessageParamsByKeyType;
|
||||
|
||||
if (hasSearchStateQuery && hasMissedCallFilter) {
|
||||
i18nId = 'icu:CallsList__EmptyState--hasQueryAndMissedCalls';
|
||||
} else if (hasSearchStateQuery) {
|
||||
i18nId = 'icu:CallsList__EmptyState--hasQuery';
|
||||
} else if (hasMissedCallFilter) {
|
||||
i18nId = 'icu:CallsList__EmptyState--missedCalls';
|
||||
} else {
|
||||
// This should never happen
|
||||
i18nId = 'icu:CallsList__EmptyState--hasQuery';
|
||||
}
|
||||
return (
|
||||
<div key={key} className="CallsList__EmptyState" style={style}>
|
||||
<I18n
|
||||
i18n={i18n}
|
||||
id="icu:CallsList__EmptyState--hasQuery"
|
||||
id={i18nId}
|
||||
components={{
|
||||
query: <UserText text={searchStateQuery} />,
|
||||
}}
|
||||
|
@ -723,6 +749,32 @@ export function CallsList({
|
|||
);
|
||||
}
|
||||
|
||||
if (item === 'FilterHeader') {
|
||||
return (
|
||||
<div key={key} style={style} className="CallsList__FilterHeader">
|
||||
{i18n('icu:CallsList__FilteredByMissedHeader')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (item === 'ClearFilterButton') {
|
||||
return (
|
||||
<div key={key} style={style} className="ClearFilterButton">
|
||||
<Button
|
||||
variant={ButtonVariant.SecondaryAffirmative}
|
||||
className={classNames('ClearFilterButton__inner', {
|
||||
// The clear filter button should be closer to the emty state
|
||||
// text than to the search results.
|
||||
'ClearFilterButton__inner-vertical-center': !isEmpty,
|
||||
})}
|
||||
onClick={() => setStatusInput(CallHistoryFilterStatus.All)}
|
||||
>
|
||||
{i18n('icu:clearFilterButton')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const conversation = getConversationForItem(item);
|
||||
const activeCallConversationId = activeCall?.conversationId;
|
||||
|
||||
|
@ -918,6 +970,8 @@ export function CallsList({
|
|||
getIsAnybodyInCall,
|
||||
getIsCallActive,
|
||||
getIsInCall,
|
||||
hasMissedCallFilter,
|
||||
hasSearchStateQuery,
|
||||
selectedCallHistoryGroup,
|
||||
onChangeCallsTabSelectedView,
|
||||
onCreateCallLink,
|
||||
|
@ -927,6 +981,7 @@ export function CallsList({
|
|||
toggleConfirmLeaveCallModal,
|
||||
togglePip,
|
||||
i18n,
|
||||
isEmpty,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -957,20 +1012,14 @@ export function CallsList({
|
|||
subtitle={i18n('icu:CallsList__EmptyState--noQuery__subtitle')}
|
||||
/>
|
||||
)}
|
||||
{isEmpty &&
|
||||
statusInput === CallHistoryFilterStatus.Missed &&
|
||||
!hasSearchStateQuery && (
|
||||
<NavSidebarEmpty
|
||||
title={i18n('icu:CallsList__EmptyState--noQuery--missed__title')}
|
||||
subtitle={i18n(
|
||||
'icu:CallsList__EmptyState--noQuery--missed__subtitle'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<NavSidebarSearchHeader>
|
||||
<SearchInput
|
||||
i18n={i18n}
|
||||
placeholder={i18n('icu:CallsList__SearchInputPlaceholder')}
|
||||
placeholder={
|
||||
searchFiltering
|
||||
? i18n('icu:CallsList__SearchInputPlaceholder--missed-calls')
|
||||
: i18n('icu:CallsList__SearchInputPlaceholder')
|
||||
}
|
||||
onChange={handleSearchInputChange}
|
||||
onClear={handleSearchInputClear}
|
||||
value={queryInput}
|
||||
|
@ -983,11 +1032,10 @@ export function CallsList({
|
|||
>
|
||||
<button
|
||||
className={classNames('CallsList__ToggleFilterByMissed', {
|
||||
'CallsList__ToggleFilterByMissed--pressed':
|
||||
statusInput === CallHistoryFilterStatus.Missed,
|
||||
'CallsList__ToggleFilterByMissed--pressed': hasMissedCallFilter,
|
||||
})}
|
||||
type="button"
|
||||
aria-pressed={statusInput === CallHistoryFilterStatus.Missed}
|
||||
aria-pressed={hasMissedCallFilter}
|
||||
aria-roledescription={i18n(
|
||||
'icu:CallsList__ToggleFilterByMissed__RoleDescription'
|
||||
)}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue