Skip to content

Commit

Permalink
chore: replace inboxItems with dialogList
Browse files Browse the repository at this point in the history
  • Loading branch information
mbacherycz committed Feb 25, 2025
1 parent 8108109 commit 1f1eb76
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 126 deletions.
45 changes: 32 additions & 13 deletions packages/frontend/src/api/useDialogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type SearchDialogFieldsFragment,
SystemLabel,
} from 'bff-types-generated';
import { t } from 'i18next';
import type { InboxItemInput, InboxItemMetaField, InboxItemMetaFieldType } from '../components';
import { QUERY_KEYS } from '../constants/queryKeys.ts';
import { i18n } from '../i18n/config.ts';
Expand Down Expand Up @@ -49,6 +50,10 @@ export function mapDialogToToInboxItem(
const serviceOwner = getOrganization(organizations || [], item.org, 'nb');
const isSeenByEndUser =
item.seenSinceLastUpdate.find((seenLogEntry) => seenLogEntry.isCurrentEndUser) !== undefined;

const { isSeenByYou, seenByOthersCount } = getSeenByLabel(item.seenSinceLastUpdate);

const seenByLabel = `${t('word.seenBy')} ${isSeenByYou ? t('word.you') : ''} ${seenByOthersCount > 0 ? ' + ' + seenByOthersCount : ''}`;
return {
id: item.id,
party: item.party,
Expand All @@ -63,13 +68,16 @@ export function mapDialogToToInboxItem(
name: actualReceiverParty?.name ?? dialogReceiverSubParty?.name ?? '',
isCompany: actualReceiverParty?.partyType === 'Organization',
},
metaFields: getMetaFields(item, isSeenByEndUser),
guiAttachmentCount: item.guiAttachmentCount ?? 0,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
status: item.status ?? 'UnknownStatus',
isSeenByEndUser,
label: item.systemLabel,
org: item.org,
seenByLabel: seenByLabel,
seenByOthersCount: seenByOthersCount,
viewType: getViewType(item),
};
});
}
Expand Down Expand Up @@ -98,6 +106,17 @@ export function mapAutocompleteDialogsDtoToInboxItem(
});
}

interface SeenByItem {
isCurrentEndUser: boolean;
}

export const getSeenByLabel = (seenBy: SeenByItem[]): { isSeenByYou: boolean; seenByOthersCount: number } => {
const isSeenByYou = seenBy?.some((item) => item.isCurrentEndUser === true);
const seenByOthersCount = seenBy?.filter((item) => item.isCurrentEndUser === false).length;

return { isSeenByYou, seenByOthersCount };
};

export const searchDialogs = (
partyURIs: string[],
search: string | undefined,
Expand Down Expand Up @@ -131,24 +150,25 @@ export const flattenParties = (partiesToUse: PartyFieldsFragment[]) => {
return [...partyURIs, ...subPartyURIs] as string[];
};

export const isBinDialog = (dialog: InboxItemInput): boolean => dialog.label === SystemLabel.Bin;
export const isBinDialog = (dialog: SearchDialogFieldsFragment): boolean => dialog.systemLabel === SystemLabel.Bin;

export const isArchivedDialog = (dialog: InboxItemInput): boolean => dialog.label === SystemLabel.Archive;
export const isArchivedDialog = (dialog: SearchDialogFieldsFragment): boolean =>
dialog.systemLabel === SystemLabel.Archive;

export const isInboxDialog = (dialog: InboxItemInput): boolean =>
export const isInboxDialog = (dialog: SearchDialogFieldsFragment): boolean =>
!isBinDialog(dialog) &&
!isArchivedDialog(dialog) &&
[DialogStatus.New, DialogStatus.InProgress, DialogStatus.RequiresAttention, DialogStatus.Completed].includes(
dialog.status,
);

export const isDraftDialog = (dialog: InboxItemInput): boolean =>
export const isDraftDialog = (dialog: SearchDialogFieldsFragment): boolean =>
!isBinDialog(dialog) && !isArchivedDialog(dialog) && dialog.status === DialogStatus.Draft;

export const isSentDialog = (dialog: InboxItemInput): boolean =>
export const isSentDialog = (dialog: SearchDialogFieldsFragment): boolean =>
!isBinDialog(dialog) && !isArchivedDialog(dialog) && dialog.status === DialogStatus.Sent;

export const getViewType = (dialog: InboxItemInput): InboxViewType => {
export const getViewType = (dialog: SearchDialogFieldsFragment): InboxViewType => {
if (isDraftDialog(dialog)) {
return 'drafts';
}
Expand All @@ -167,7 +187,6 @@ export const getViewType = (dialog: InboxItemInput): InboxViewType => {
export const useDialogs = (parties: PartyFieldsFragment[]): UseDialogsOutput => {
const { organizations } = useOrganizations();
const { selectedParties } = useParties();

const partiesToUse = parties ? parties : selectedParties;
const mergedPartiesWithSubParties = flattenParties(partiesToUse);

Expand All @@ -186,11 +205,11 @@ export const useDialogs = (parties: PartyFieldsFragment[]): UseDialogsOutput =>
isSuccess,
dialogs,
dialogsByView: {
inbox: dialogs.filter(isInboxDialog),
drafts: dialogs.filter(isDraftDialog),
sent: dialogs.filter(isSentDialog),
archive: dialogs.filter(isArchivedDialog),
bin: dialogs.filter(isBinDialog),
inbox: dialogs.filter((dialog) => dialog.viewType === 'inbox'),
drafts: dialogs.filter((dialog) => dialog.viewType === 'drafts'),
sent: dialogs.filter((dialog) => dialog.viewType === 'sent'),
archive: dialogs.filter((dialog) => dialog.viewType === 'archive'),
bin: dialogs.filter((dialog) => dialog.viewType === 'bin'),
},
dialogCountInconclusive: data?.searchDialogs?.hasNextPage === true || data?.searchDialogs?.items === null,
};
Expand Down
5 changes: 4 additions & 1 deletion packages/frontend/src/components/InboxItem/InboxItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ export interface InboxItemInput {
summary: string;
sender: Participant;
receiver: Participant;
metaFields: InboxItemMetaField[];
createdAt: string;
updatedAt: string;
status: DialogStatus;
isSeenByEndUser: boolean;
label: SystemLabel;
org?: string;
guiAttachmentCount: number;
seenByOthersCount: number;
seenByLabel: string;
viewType: InboxViewType;
}

export const OptionalLinkContent = ({
Expand Down
15 changes: 12 additions & 3 deletions packages/frontend/src/components/PageLayout/Accounts/badge.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@ describe('getCountBadge', () => {
name: '',
isCompany: true,
},
metaFields: [],
createdAt: '',
updatedAt: '',
status: DialogStatus.Completed,
isSeenByEndUser: false,
label: SystemLabel.Default,
guiAttachmentCount: 0,
seenByOthersCount: 0,
seenByLabel: '',
viewType: 'inbox',
},
{
party: 'subParty1',
Expand All @@ -40,12 +43,15 @@ describe('getCountBadge', () => {
name: '',
isCompany: true,
},
metaFields: [],
createdAt: '',
updatedAt: '',
status: DialogStatus.Completed,
isSeenByEndUser: false,
label: SystemLabel.Default,
guiAttachmentCount: 0,
seenByOthersCount: 0,
seenByLabel: '',
viewType: 'inbox',
},
{
party: 'party2',
Expand All @@ -61,12 +67,15 @@ describe('getCountBadge', () => {
name: '',
isCompany: true,
},
metaFields: [],
createdAt: '',
updatedAt: '',
status: DialogStatus.Completed,
isSeenByEndUser: false,
label: SystemLabel.Default,
guiAttachmentCount: 0,
seenByOthersCount: 0,
seenByLabel: '',
viewType: 'inbox',
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ describe('generateSendersAutocompleteBySearchString', () => {
isSeenByEndUser: false,
label: 'DEFAULT',
org: 'skd',
guiAttachmentCount: 1,
seenByOthersCount: 0,
seenByLabel: 'Sett av deg',
viewType: 'DEFAULT',
},
{
id: '019241f7-812c-71c8-8e68-94a0b771fa10',
Expand Down Expand Up @@ -72,11 +76,14 @@ describe('generateSendersAutocompleteBySearchString', () => {
},
],
createdAt: '2023-05-17T09:30:00.000Z',
updatedAt: '2023-05-17T09:30:00.000Z',
org: 'ssb',
guiAttachmentCount: 1,
seenByOthersCount: 1,
seenByLabel: 'Sett av deg',
viewType: 'DEFAULT',
status: 'REQUIRES_ATTENTION',
isSeenByEndUser: true,
label: 'DEFAULT',
org: 'ssb',
},
];

Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/i18n/resources/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dialog.toolbar.toast.move_to_bin_success": "Moved to bin",
"dialog.toolbar.toast.move_to_inbox_failed": "Failed to move to inbox",
"dialog.toolbar.toast.move_to_inbox_success": "Moved to inbox",
"dialog.imageAltURL": "Company logo for {companyName}",
"dialogs.attachment_count": "{count, plural, one {# attachment} other {# attachments}}",
"editSavedSearch.give_search_name": "Name the search",
"editSavedSearch.save_and_close": "Save and close",
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/i18n/resources/nb.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dialog.toolbar.toast.move_to_bin_success": "Flyttet til papirkurv",
"dialog.toolbar.toast.move_to_inbox_failed": "Flytting til innboks feilet",
"dialog.toolbar.toast.move_to_inbox_success": "Flyttet til innboks",
"dialog.imageAltURL": "Selskapets logo for {companyName}",
"dialogs.attachment_count": "{count, plural, one {# vedlegg} other {# vedlegg}}",
"editSavedSearch.give_search_name": "Gi søket et navn",
"editSavedSearch.save_and_close": "Lagre og avslutt",
Expand Down
68 changes: 13 additions & 55 deletions packages/frontend/src/pages/Inbox/Inbox.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import { Toolbar } from '@altinn/altinn-components';
import { DialogList, Section, Toolbar } from '@altinn/altinn-components';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation, useSearchParams } from 'react-router-dom';
import { useSearchParams } from 'react-router-dom';
import { type InboxViewType, useDialogs } from '../../api/useDialogs.tsx';
import { useParties } from '../../api/useParties.ts';
import { InboxItem, InboxItems, useSelectedDialogs } from '../../components';
import { InboxItemsHeader } from '../../components/InboxItem/InboxItemsHeader.tsx';
import { useAccounts } from '../../components/PageLayout/Accounts/useAccounts.tsx';
import { useSearchDialogs, useSearchString } from '../../components/PageLayout/Search/';
import { SaveSearchButton } from '../../components/SavedSearchButton/SaveSearchButton.tsx';
import { PageRoutes } from '../routes.ts';
import { InboxSkeleton } from './InboxSkeleton.tsx';
import { filterDialogs } from './filters.ts';
import styles from './inbox.module.css';
import { useFilters } from './useFilters.tsx';
Expand All @@ -21,9 +18,7 @@ interface InboxProps {
}

export const Inbox = ({ viewType }: InboxProps) => {
const location = useLocation();
const { t } = useTranslation();
const { selectedItems, setSelectedItems } = useSelectedDialogs();
const { selectedParties, allOrganizationsSelected, parties, partiesEmptyList } = useParties();
const [searchParams] = useSearchParams();
const searchBarParam = new URLSearchParams(searchParams);
Expand All @@ -43,6 +38,7 @@ export const Inbox = ({ viewType }: InboxProps) => {
parties: selectedParties,
searchValue: enteredSearchValue,
});

const { accounts, selectedAccount, accountSearch, accountGroups, onSelectAccount } = useAccounts({
parties,
selectedParties,
Expand All @@ -55,20 +51,17 @@ export const Inbox = ({ viewType }: InboxProps) => {
const dataSource = displaySearchResults ? searchResults : dialogsForView;
const { filterState, filters, onFiltersChange, getFilterLabel } = useFilters({ dialogs: dataSource });
const filteredItems = useMemo(() => filterDialogs(dataSource, filterState), [dataSource, filterState]);
const dialogsGroupedByCategory = useGroupedDialogs({

const isLoading = !isSuccessDialogs || isFetchingSearchResults || isLoadingDialogs;

const { mappedGroupedDialogs, groups } = useGroupedDialogs({
items: filteredItems,
displaySearchResults,
filters: filterState,
viewType,
isLoading,
});

const handleCheckedChange = (checkboxValue: string, checked: boolean) => {
setSelectedItems((prev: Record<string, boolean>) => ({
...prev,
[checkboxValue]: checked,
}));
};

if (partiesEmptyList) {
return (
<div className={styles.noParties}>
Expand All @@ -77,10 +70,6 @@ export const Inbox = ({ viewType }: InboxProps) => {
);
}

if (!isSuccessDialogs || isFetchingSearchResults || isLoadingDialogs) {
return <InboxSkeleton numberOfItems={5} />;
}

const savedSearchDisabled =
!Object.keys(filterState)?.length &&
Object.values(filterState).every((item) => item?.values?.length === 0) &&
Expand Down Expand Up @@ -112,42 +101,11 @@ export const Inbox = ({ viewType }: InboxProps) => {
</>
) : null}
</section>
<section>
{dialogsGroupedByCategory.map(({ id, label, items }) => {
const hideSelectAll = items.every((item) => selectedItems[item.id]);
return (
<InboxItems key={id}>
<InboxItemsHeader
hideSelectAll={hideSelectAll}
onSelectAll={() => {
const newItems = Object.fromEntries(items.map((item) => [item.id, true]));
setSelectedItems({
...selectedItems,
...newItems,
});
}}
title={label}
/>
{items.map((item) => (
<InboxItem
key={item.id}
checkboxValue={item.id}
title={item.title}
summary={item.summary}
sender={item.sender}
receiver={item.receiver}
isUnread={!item.isSeenByEndUser}
isChecked={selectedItems[item.id]}
onCheckedChange={(checked) => handleCheckedChange(item.id, checked)}
metaFields={item.metaFields}
viewType={viewType}
linkTo={`/inbox/${item.id}/${location.search}`}
/>
))}
</InboxItems>
);
})}
</section>
<Section spacing={3} margin="section">
{!filteredItems.length && <h1>{t(`inbox.heading.title.${viewType}`, { count: 0 })}</h1>}

<DialogList items={mappedGroupedDialogs} groups={groups} />
</Section>
</>
);
};
Loading

0 comments on commit 1f1eb76

Please sign in to comment.