Skip to content

Commit

Permalink
feat: use loading state for dialog from altinn-components (#1884)
Browse files Browse the repository at this point in the history
  • Loading branch information
seanes authored Mar 4, 2025
1 parent b11ab42 commit 0ed7832
Show file tree
Hide file tree
Showing 22 changed files with 116 additions and 524 deletions.
1 change: 0 additions & 1 deletion packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-i18next": "^15.0.1",
"react-loading-skeleton": "^3.5.0",
"react-router-dom": "^7.2.0",
"sse.js": "^2.5.0",
"use-debounce": "^10.0.1"
Expand Down
4 changes: 2 additions & 2 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Navigate, Route, Routes } from 'react-router-dom';
import { ProtectedPageLayout } from './components/PageLayout/PageLayout.tsx';
import { DialogDetailsPage } from './pages/DialogDetailsPage';
import { Inbox } from './pages/Inbox';
import { InboxItemPage } from './pages/InboxItemPage';
import { Logout } from './pages/LogoutPage';
import { SavedSearchesPage } from './pages/SavedSearches';
import { PageRoutes } from './pages/routes.ts';
Expand All @@ -18,7 +18,7 @@ function App() {
<Route path={PageRoutes.sent} element={<Inbox key="sent" viewType={'sent'} />} />
<Route path={PageRoutes.archive} element={<Inbox key="archive" viewType={'archive'} />} />
<Route path={PageRoutes.bin} element={<Inbox key="bin" viewType={'bin'} />} />
<Route path={PageRoutes.inboxItem} element={<InboxItemPage />} />
<Route path={PageRoutes.inboxItem} element={<DialogDetailsPage />} />
<Route path={PageRoutes.savedSearches} element={<SavedSearchesPage />} />
<Route path="*" element={<Navigate to="/" />} />
</Route>
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/api/useDialogById.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from 'bff-types-generated';
import { AttachmentUrlConsumer } from 'bff-types-generated';
import { t } from 'i18next';
import type { DialogActionProps } from '../components/InboxItem/InboxItemDetail.tsx';
import type { DialogActionProps } from '../components/DialogDetails/DialogDetails.tsx';
import { QUERY_KEYS } from '../constants/queryKeys.ts';
import { type ValueType, getPreferredPropertyByLocale } from '../i18n/property.ts';
import { useOrganizations } from '../pages/Inbox/useOrganizations.ts';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
type DialogButtonPriority,
DialogHeader,
} from '@altinn/altinn-components';
import { DialogStatus } from 'bff-types-generated';
import { type ReactElement, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { DialogActivity, DialogByIdDetails, DialogTransmission } from '../../api/useDialogById.tsx';
Expand All @@ -18,10 +19,11 @@ import { Activity } from '../Activity';
import { AdditionalInfoContent } from '../AdditonalInfoContent';
import { MainContentReference } from '../MainContentReference';
import { Transmission } from '../Transmission';
import styles from './inboxItemDetail.module.css';
import styles from './dialogDetails.module.css';

interface InboxItemDetailProps {
interface DialogDetailsProps {
dialog: DialogByIdDetails | undefined | null;
isLoading?: boolean;
}

/**
Expand Down Expand Up @@ -94,83 +96,94 @@ const handleDialogActionClick = async (
}
};

export const InboxItemDetail = ({ dialog }: InboxItemDetailProps): ReactElement => {
export const DialogDetails = ({ dialog, isLoading }: DialogDetailsProps): ReactElement => {
const { t } = useTranslation();
const [actionIdLoading, setActionIdLoading] = useState<string>('');
const format = useFormat();

if (!dialog) {
if (isLoading) {
return (
<div className={styles.errorFallBack}>
<section className={styles.inboxItemDetail}>
<header className={styles.header} data-id="dialog-header">
<h1 className={styles.title}>{t('error.dialog.not_found')}</h1>
</header>
<p className={styles.summary}>{t('dialog.error_message')}</p>
</section>
</div>
<Article padding={6} spacing={6}>
<DialogHeader
loading
dueAt={new Date().toISOString()}
dueAtLabel={format(new Date(), 'do MMMM yyyy HH.mm').toString()}
status={getDialogStatus(DialogStatus.New, t)}
title={'???'}
/>
<DialogBody
sender={{ name: 'XXX' }}
recipient={{ name: 'YYY' }}
updatedAt={new Date().toISOString()}
updatedAtLabel={format(new Date(), 'do MMMM yyyy HH.mm').toString()}
loading
/>
</Article>
);
}

const {
dueAt,
title,
dialogToken,
summary,
sender,
receiver,
guiActions,
additionalInfo,
attachments,
mainContentReference,
activities,
transmissions,
updatedAt,
status,
seenByLabel,
isSeenByEndUser,
seenByOthersCount,
} = dialog;
if (!dialog) {
return (
<Article padding={6} spacing={6}>
<header className={styles.header} data-id="dialog-header">
<h1 className={styles.title}>{t('error.dialog.not_found')}</h1>
</header>
<p className={styles.summary}>{t('dialog.error_message')}</p>
</Article>
);
}

const attachmentCount = attachments.reduce((count, { urls }) => count + urls.length, 0);
const attachmentItems: AttachmentLinkProps[] = attachments.flatMap((attachment) =>
const attachmentCount = dialog.attachments.reduce((count, { urls }) => count + urls.length, 0);
const attachmentItems: AttachmentLinkProps[] = dialog.attachments.flatMap((attachment) =>
attachment.urls.map((url) => ({
label: getPreferredPropertyByLocale(attachment.displayName)?.value || url.url,
href: url.url,
})),
);
const clockPrefix = t('word.clock_prefix');
const formatString = clockPrefix ? `do MMMM yyyy '${clockPrefix}' HH.mm` : `do MMMM yyyy HH.mm`;
const dueAtLabel = dueAt ? format(dueAt, formatString) : '';

const [isLoading, setIsLoading] = useState<string>('');
const dueAtLabel = dialog.dueAt ? format(dialog.dueAt, formatString) : '';

const dialogActions: DialogActionButtonProps[] = guiActions.map((action) => ({
const dialogActions: DialogActionButtonProps[] = dialog.guiActions.map((action) => ({
id: action.id,
label: action.title,
disabled: !!isLoading || action.disabled,
priority: action.priority.toLocaleLowerCase() as DialogButtonPriority,
url: action.url,
httpMethod: action.httpMethod,
loading: isLoading === action.id,
loading: actionIdLoading === action.id,
onClick: () => {
setIsLoading(action.id);
handleDialogActionClick(action, dialogToken, () => setIsLoading(''));
setActionIdLoading(action.id);
void handleDialogActionClick(action, dialog.dialogToken, () => setActionIdLoading(''));
},
}));

return (
<Article padding={6} spacing={6}>
<DialogHeader dueAt={dueAt} dueAtLabel={dueAtLabel} status={getDialogStatus(status, t)} title={title} />
<DialogHeader
dueAt={dialog.dueAt}
dueAtLabel={dueAtLabel}
status={getDialogStatus(dialog.status, t)}
title={dialog.title}
/>
<DialogBody
sender={sender}
recipient={receiver}
updatedAt={updatedAt}
updatedAtLabel={format(updatedAt, formatString)}
sender={dialog.sender}
recipient={dialog.receiver}
updatedAt={dialog.updatedAt}
updatedAtLabel={format(dialog.updatedAt, formatString)}
recipientLabel={t('word.to')}
seenBy={seenByLabel ? { seenByEndUser: isSeenByEndUser, seenByOthersCount, label: seenByLabel } : undefined}
seenBy={
dialog.seenByLabel
? {
seenByEndUser: dialog.isSeenByEndUser,
seenByOthersCount: dialog.seenByOthersCount,
label: dialog.seenByLabel,
}
: undefined
}
>
<p>{summary}</p>
<MainContentReference content={mainContentReference} dialogToken={dialogToken} />
<p>{dialog.summary}</p>
<MainContentReference content={dialog.mainContentReference} dialogToken={dialog.dialogToken} />
{attachmentCount > 0 && (
<DialogAttachments
title={t('inbox.heading.attachments', { count: attachmentCount })}
Expand All @@ -179,19 +192,19 @@ export const InboxItemDetail = ({ dialog }: InboxItemDetailProps): ReactElement
)}
<DialogActions items={dialogActions} />
</DialogBody>
<AdditionalInfoContent mediaType={additionalInfo?.mediaType} value={additionalInfo?.value} />
{activities.length > 0 && (
<AdditionalInfoContent mediaType={dialog.additionalInfo?.mediaType} value={dialog.additionalInfo?.value} />
{dialog.activities.length > 0 && (
<section data-id="dialog-activity-history" className={styles.activities}>
<h3 className={styles.activitiesTitle}>{t('word.activities')}</h3>
{activities.map((activity: DialogActivity) => (
<Activity key={activity.id} activity={activity} serviceOwner={sender} />
{dialog.activities.map((activity: DialogActivity) => (
<Activity key={activity.id} activity={activity} serviceOwner={dialog.sender} />
))}
</section>
)}
{transmissions.length > 0 && (
{dialog.transmissions.length > 0 && (
<section data-id="dialog-transmissions" className={styles.transmissions}>
<h3 className={styles.transmissionsTitle}>{t('word.transmissions')}</h3>
{transmissions.map((transmission: DialogTransmission) => (
{dialog.transmissions.map((transmission: DialogTransmission) => (
<Transmission key={transmission.id} transmission={transmission} />
))}
</section>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.summarySection {
display: flex;
flex-direction: column;
row-gap: 0.25rem;
}

.header {
display: flex;
font-size: 1.5rem;
align-items: center;
}

.title {
color: #000;
font-weight: 500;
font-size: 1.5rem;
line-height: 1.25;
margin-top: 0;
}

.activities,
.transmissions {
display: flex;
flex-direction: column;
row-gap: 1.5rem;
border-top: 1px solid rgba(0, 0, 0, 0.2);
padding: 1.5rem 0;
}

.activitiesTitle,
.transmissionsTitle {
font-size: 1.25rem;
font-weight: 500;
margin: 0;
}
1 change: 1 addition & 0 deletions packages/frontend/src/components/DialogDetails/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './DialogDetails.tsx';
24 changes: 0 additions & 24 deletions packages/frontend/src/components/InboxItem/InboxItems.tsx

This file was deleted.

41 changes: 0 additions & 41 deletions packages/frontend/src/components/InboxItem/InboxItemsHeader.tsx

This file was deleted.

Loading

0 comments on commit 0ed7832

Please sign in to comment.