diff --git a/packages/frontend/src/pages/Inbox/Inbox.tsx b/packages/frontend/src/pages/Inbox/Inbox.tsx index c14698ad..f7d29fbd 100644 --- a/packages/frontend/src/pages/Inbox/Inbox.tsx +++ b/packages/frontend/src/pages/Inbox/Inbox.tsx @@ -112,7 +112,11 @@ export const Inbox = ({ viewType }: InboxProps) => {
{dataSourceSuccess && !filteredItems.length &&

{t(`inbox.heading.title.${viewType}`, { count: 0 })}

} - b[0].localeCompare(a[0])} /> + (groups[bKey]?.orderIndex ?? 0) - (groups[aKey]?.orderIndex ?? 0)} + />
); diff --git a/packages/frontend/src/pages/Inbox/useGroupedDialogs.spec.tsx b/packages/frontend/src/pages/Inbox/useGroupedDialogs.spec.tsx new file mode 100644 index 00000000..f6ba8826 --- /dev/null +++ b/packages/frontend/src/pages/Inbox/useGroupedDialogs.spec.tsx @@ -0,0 +1,250 @@ +import { renderHook } from '@testing-library/react'; +import type { DialogStatus, SystemLabel } from 'bff-types-generated'; +import { useTranslation } from 'react-i18next'; +import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest'; +import type { InboxViewType } from '../../api/useDialogs.tsx'; +import { useFormat } from '../../i18n/useDateFnsLocale.tsx'; +import useGroupedDialogs from './useGroupedDialogs.tsx'; + +vi.mock('react-i18next', () => ({ + useTranslation: vi.fn(), +})); + +vi.mock('../../i18n/useDateFnsLocale.tsx', () => ({ + useFormat: vi.fn(), +})); + +const mockData = [ + { + id: '01953842-438a-7232-b9c0-d90bd89e1072', + party: 'urn:altinn:person:identifier-no:14886498226', + title: 'Dialog laget med Scenario Builder 2025-2-24 14:52', + summary: 'Denne teksten representerer et sammendrag av dialogen, som er opprettet av serviceOwner i dialogporten.', + sender: { + name: 'Digitaliseringsdirektoratet', + isCompany: true, + imageURL: '', + }, + receiver: { + name: 'Hjelpelinje Ordinær', + isCompany: false, + }, + guiAttachmentCount: 0, + createdAt: '2025-02-24T14:00:21.642Z', + updatedAt: '2025-02-24T14:02:58.540Z', + status: 'NEW' as DialogStatus, + isSeenByEndUser: true, + label: 'DEFAULT' as SystemLabel, + org: 'digdir', + seenByLabel: 'Sett av deg', + seenByOthersCount: 0, + viewType: 'inbox' as InboxViewType, + }, + { + id: '0195383e-0d9a-73b1-b96b-73c49e31097d', + party: 'urn:altinn:person:identifier-no:14886498226', + title: 'Dialog laget med Scenario Builder 2025-2-24 14:52', + summary: 'Denne teksten representerer et sammendrag av dialogen, som er opprettet av serviceOwner i dialogporten.', + sender: { + name: 'Digitaliseringsdirektoratet', + isCompany: true, + imageURL: '', + }, + receiver: { + name: 'Hjelpelinje Ordinær', + isCompany: false, + }, + guiAttachmentCount: 1, + createdAt: '2025-02-24T13:55:45.689Z', + updatedAt: '2025-02-24T13:55:45.689Z', + status: 'NEW' as DialogStatus, + isSeenByEndUser: true, + label: 'DEFAULT' as SystemLabel, + org: 'digdir', + seenByLabel: 'Sett av deg', + seenByOthersCount: 0, + viewType: 'inbox' as InboxViewType, + }, + { + id: '0195383b-6068-726a-b632-1ff3cc836e9a', + party: 'urn:altinn:person:identifier-no:14886498226', + title: 'Dialog laget med Scenario Builder 2025-2-24 14:52', + summary: 'Denne teksten representerer et sammendrag av dialogen, som er opprettet av serviceOwner i dialogporten.', + sender: { + name: 'Digitaliseringsdirektoratet', + isCompany: true, + imageURL: '', + }, + receiver: { + name: 'Hjelpelinje Ordinær', + isCompany: false, + }, + guiAttachmentCount: 1, + createdAt: '2025-02-24T13:52:50.280Z', + updatedAt: '2025-02-24T13:52:50.280Z', + status: 'NEW' as DialogStatus, + isSeenByEndUser: true, + label: 'DEFAULT' as SystemLabel, + org: 'digdir', + seenByLabel: 'Sett av deg', + seenByOthersCount: 0, + viewType: 'inbox' as InboxViewType, + }, + { + id: '01937766-ff24-7305-9361-d14743d2db30', + party: 'urn:altinn:person:identifier-no:14886498226', + title: 'Test melding8', + summary: 'Et sammendrag her. Maks 200 tegn, ingen HTML-støtte. Påkrevd. Vises i liste.', + sender: { + name: 'digitaliseringsdirektoratet', + isCompany: true, + imageURL: '', + }, + receiver: { + name: 'Hjelpelinje Ordinær', + isCompany: false, + }, + guiAttachmentCount: 1, + createdAt: '2024-11-29T10:10:58.980Z', + updatedAt: '2024-11-29T10:10:58.980Z', + status: 'NEW' as DialogStatus, + isSeenByEndUser: true, + label: 'DEFAULT' as SystemLabel, + org: 'digitaliseringsdirektoratet', + seenByLabel: 'Sett av deg', + seenByOthersCount: 0, + viewType: 'inbox' as InboxViewType, + }, + { + id: '01936e30-ac82-70f3-96ba-eb3ccd31586a', + party: 'urn:altinn:person:identifier-no:14886498226', + title: 'Test melding3', + summary: 'Et sammendrag her. Maks 200 tegn, ingen HTML-støtte. Påkrevd. Vises i liste.', + sender: { + name: 'digitaliseringsdirektoratet', + isCompany: true, + imageURL: '', + }, + receiver: { + name: 'Hjelpelinje Ordinær', + isCompany: false, + }, + guiAttachmentCount: 1, + createdAt: '2024-11-27T15:15:03.934Z', + updatedAt: '2024-11-27T15:15:03.934Z', + status: 'NEW' as DialogStatus, + isSeenByEndUser: true, + label: 'DEFAULT' as SystemLabel, + org: 'digitaliseringsdirektoratet', + seenByLabel: 'Sett av deg', + seenByOthersCount: 0, + viewType: 'inbox' as InboxViewType, + }, + { + id: 'f67d9101-5149-6377-95db-6146e6b2e196', + party: 'urn:altinn:person:identifier-no:14886498226', + title: 'Tjeneste for rapportering av et eller annet fra SKE', + summary: 'Et sammendrag her. Maks 200 tegn, ingen riktekst-støtte. Påkrevd. Vises i liste.', + sender: { + name: 'Skatteetaten', + isCompany: true, + imageURL: 'https://altinncdn.no/orgs/skd/skd.png', + }, + receiver: { + name: 'Hjelpelinje Ordinær', + isCompany: false, + }, + guiAttachmentCount: 2, + createdAt: '2024-08-23T06:39:38.321Z', + updatedAt: '2024-08-23T06:39:38.321Z', + status: 'NEW' as DialogStatus, + isSeenByEndUser: true, + label: 'DEFAULT' as SystemLabel, + org: 'skd', + seenByLabel: 'Sett av deg', + seenByOthersCount: 0, + viewType: 'inbox' as InboxViewType, + }, + { + id: 'f57d9101-e17a-8b76-94ba-c6d63f56b96f', + party: 'urn:altinn:person:identifier-no:14886498226', + title: 'Tjeneste for rapportering av et eller annet fra SKE', + summary: 'Et sammendrag her. Maks 200 tegn, ingen riktekst-støtte. Påkrevd. Vises i liste.', + sender: { + name: 'Skatteetaten', + isCompany: true, + imageURL: 'https://altinncdn.no/orgs/skd/skd.png', + }, + receiver: { + name: 'Hjelpelinje Ordinær', + isCompany: false, + }, + guiAttachmentCount: 2, + createdAt: '2024-08-23T06:38:45.473Z', + updatedAt: '2024-08-23T06:38:45.473Z', + status: 'NEW' as DialogStatus, + isSeenByEndUser: true, + label: 'DEFAULT' as SystemLabel, + org: 'skd', + seenByLabel: 'Sett av deg', + seenByOthersCount: 0, + viewType: 'inbox' as InboxViewType, + }, +]; + +describe('useGroupedDialogs', () => { + const t = vi.fn((key) => key); + const format = vi.fn((date) => date.toString()); + + beforeEach(() => { + (useTranslation as Mock).mockReturnValue({ t }); + (useFormat as Mock).mockReturnValue(format); + }); + + it('should return loading items when isLoading is true', () => { + const { result } = renderHook(() => + useGroupedDialogs({ + items: [], + displaySearchResults: false, + viewType: 'inbox', + isLoading: true, + }), + ); + + expect(result.current.mappedGroupedDialogs).toHaveLength(5); + expect(result.current.groups).toEqual({ loading: { title: 'word.loading' } }); + }); + + it('should return grouped dialogs when isLoading is false', () => { + const { result } = renderHook(() => + useGroupedDialogs({ + items: mockData, + displaySearchResults: true, + viewType: 'inbox', + isLoading: false, + }), + ); + + expect(result.current.mappedGroupedDialogs).toHaveLength(7); + expect(result.current.groups).toEqual({ + inbox: { title: 'inbox.heading.search_results.inbox', orderIndex: null }, + }); + }); + + it('should generat groups orderIndex correctly', () => { + const { result } = renderHook(() => + useGroupedDialogs({ + items: mockData, + displaySearchResults: false, + viewType: 'inbox', + isLoading: false, + }), + ); + + const groups = result.current.groups; + const uniqueOrderIndexes = [...new Set(Object.values(groups).map((item) => item.orderIndex?.toString()))]; + + expect(uniqueOrderIndexes).toHaveLength(2); + expect(uniqueOrderIndexes.toString()).toBe('2025,2024'); + }); +}); diff --git a/packages/frontend/src/pages/Inbox/useGroupedDialogs.tsx b/packages/frontend/src/pages/Inbox/useGroupedDialogs.tsx index 3feaa496..8dfef06b 100644 --- a/packages/frontend/src/pages/Inbox/useGroupedDialogs.tsx +++ b/packages/frontend/src/pages/Inbox/useGroupedDialogs.tsx @@ -16,16 +16,21 @@ interface GroupedItem { id: string | number; label: string; items: InboxItemInput[]; + orderIndex: number | null; +} + +interface DialogListGroupPropsSort extends DialogListGroupProps { + orderIndex?: number | null; } interface UseGroupedDialogsOutput { mappedGroupedDialogs: DialogListItemProps[]; - groups?: Record; + groups: Record; } interface UseGroupedDialogsProps { items: InboxItemInput[]; - filters: FilterState; + filters?: FilterState; displaySearchResults: boolean; viewType: InboxViewType; isLoading: boolean; @@ -132,15 +137,25 @@ const useGroupedDialogs = ({ }; } + const getOrderIndex = (createdAt: Date, isDateKey: boolean) => { + if (!isDateKey) return null; + + if (allWithinSameYear) { + return createdAt.getMonth(); + } + return createdAt.getFullYear(); + }; + const groupedItems = items.reduce((acc, item, _, list) => { const createdAt = new Date(item.createdAt); - const groupKey = displaySearchResults ? item.viewType : allWithinSameYear ? format(createdAt, 'LLLL') : format(createdAt, 'yyyy'); + const isDateKey = !displaySearchResults; + const label = displaySearchResults ? t(`inbox.heading.search_results.${groupKey}`, { count: list.filter((i) => i.viewType === groupKey).length, @@ -148,16 +163,21 @@ const useGroupedDialogs = ({ : groupKey; const existingGroup = acc.find((group) => group.id === groupKey); + if (existingGroup) { existingGroup.items.push(item); } else { - acc.push({ id: groupKey, label, items: [item] }); + const orderIndex = getOrderIndex(createdAt, isDateKey); + + acc.push({ id: groupKey, label, items: [item], orderIndex }); } return acc; }, []); - const groups = Object.fromEntries(groupedItems.map(({ id, label }) => [id, { title: label }])); + const groups = Object.fromEntries( + groupedItems.map(({ id, label, orderIndex }) => [id, { title: label, orderIndex }]), + ); const mappedGroupedDialogs = groupedItems.flatMap(({ id, items }) => items.map((item) => formatDialogItem(item, id.toString())),