Skip to content

Commit

Permalink
MNES-1063: Refactoring of AASListView component (#17)
Browse files Browse the repository at this point in the history
Main refactored elements:

- AASListView component was split into smaller components
- AasListComparisonHeader - Header on the top of the page to visualize
selected AAS from the list
      - SelectProductType - Dropdown menu to select filter product class
      - AasList - Aas List 
      - AasListTableRow - Entry row for one AAS in the table
- ImageWithFallback - component ensures that a fallback icon (ShellIcon)
is displayed if the image specified by src fails to load.
      
Small refactoring to fix build.
  • Loading branch information
pawel-baran-se committed Jul 4, 2024
1 parent 73966fd commit 6d453b2
Show file tree
Hide file tree
Showing 21 changed files with 632 additions and 497 deletions.
2 changes: 0 additions & 2 deletions src/app/[locale]/asset/_components/RedirectToViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ import { NotFoundError } from 'lib/errors/NotFoundError';
import { useState } from 'react';
import { CenteredLoadingSpinner } from 'components/basics/CenteredLoadingSpinner';
import { useSearchParams, useRouter } from 'next/navigation';
import { useEnv } from 'app/env/provider';

export const RedirectToViewer = () => {
const { discoveryServiceClient } = useApis();
const navigate = useRouter();
const searchParams = useSearchParams();
const assetIdParam = searchParams.get('assetId')?.toString();
const notificationSpawner = useNotificationSpawner();
const env = useEnv();
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);

Expand Down
474 changes: 0 additions & 474 deletions src/app/[locale]/list/_components/AASListView.tsx

This file was deleted.

94 changes: 94 additions & 0 deletions src/app/[locale]/list/_components/AasList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Tooltip,
Typography,
useTheme,
} from '@mui/material';
import { FormattedMessage } from 'react-intl';
import { messages } from 'lib/i18n/localization';
import CompareArrowsIcon from '@mui/icons-material/CompareArrows';
import { AasListEntry } from 'lib/api/generated-api/clients.g';
import { AasListTableRow } from 'app/[locale]/list/_components/AasListTableRow';

type AasListProps = {
shells: AasListEntry[];
tableHeaders: { label: string }[];
comparisonFeatureFlag?: boolean;
selectedAasList: string[] | undefined;
updateSelectedAasList: (isChecked: boolean, aasId: string | undefined) => void;
};

export default function AasList(props: AasListProps) {
const { shells, tableHeaders, selectedAasList, updateSelectedAasList, comparisonFeatureFlag } = props;
const theme = useTheme();
const MAX_SELECTED_ITEMS = 3;

/**
* Decides if the current checkbox should be disabled or not.
*/
const checkBoxDisabled = (aasId: string | undefined) => {
if (!aasId) return false;
return selectedAasList && selectedAasList.length >= MAX_SELECTED_ITEMS && !selectedAasList.includes(aasId);
};

return (
<TableContainer>
<Table>
<TableHead>
<TableRow
sx={{
color: 'primary',
lineHeight: '150%',
letterSpacing: '0.16px',
fontSize: '16px',
}}
>
{comparisonFeatureFlag && (
<TableCell align="center" width="50px">
<Tooltip
title={<FormattedMessage {...messages.mnestix.aasList.compareTooltip} />}
arrow
>
<CompareArrowsIcon
sx={{ width: '35px', height: '35px', verticalAlign: 'middle' }}
/>
</Tooltip>
</TableCell>
)}
{!!tableHeaders &&
tableHeaders.map((header: { label: string }, index) => (
<TableCell key={index}>
<Typography fontWeight="bold">{header.label}</Typography>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{shells?.map((aasListEntry) => (
<TableRow
key={aasListEntry.aasId}
sx={{
'&:last-child td, &:last-child th': { border: 0 },
backgroundColor: theme.palette?.common?.white,
}}
data-testid={`list-row-${aasListEntry.aasId}`}
>
<AasListTableRow
aasListEntry={aasListEntry}
comparisonFeatureFlag={comparisonFeatureFlag}
checkBoxDisabled={checkBoxDisabled}
selectedAasList={selectedAasList}
updateSelectedAasList={updateSelectedAasList}
/>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
50 changes: 50 additions & 0 deletions src/app/[locale]/list/_components/AasListComparisonHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Box, Button, IconButton, Typography } from '@mui/material';
import { FormattedMessage } from 'react-intl';
import { messages } from 'lib/i18n/localization';
import CloseIcon from '@mui/icons-material/Close';
import { useRouter } from 'next/navigation';
import { tooltipText } from 'lib/util/ToolTipText';

type CompareAasListBarType = {
selectedAasList: string[] | undefined;
updateSelectedAasList: (isChecked: boolean, aasId: string | undefined) => void;
};

export const AasListComparisonHeader = (props: CompareAasListBarType) => {
const { selectedAasList, updateSelectedAasList } = props;

const navigate = useRouter();
const navigateToCompare = () => {
const encodedAasList = selectedAasList?.map((aasId) => {
return encodeURIComponent(aasId);
});
const searchString = encodedAasList?.join('&aasId=');
navigate.push(`/compare?aasId=${searchString}`);
};

return (
<>
<Typography marginBottom={3}>
<FormattedMessage {...messages.mnestix.aasList.subtitle} />
</Typography>
<Box display="flex" gap={2} alignItems="center">
{selectedAasList?.map((selectedAas) => (
<Box display="flex" flexDirection="row" alignItems="center" key={selectedAas}>
<Typography data-testid={`selected-${selectedAas}`}>{tooltipText(selectedAas, 15)}</Typography>
<IconButton onClick={() => updateSelectedAasList(false, selectedAas)}>
<CloseIcon />
</IconButton>
</Box>
))}
<Button
variant="contained"
onClick={navigateToCompare}
disabled={!selectedAasList || selectedAasList.length < 1}
data-testid="compare-button"
>
<FormattedMessage {...messages.mnestix.aasList.goToCompare} />
</Button>
</Box>
</>
);
};
12 changes: 12 additions & 0 deletions src/app/[locale]/list/_components/AasListHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getTranslations } from 'next-intl/server';
import { Typography } from '@mui/material';

export default async function AasListHeader() {
const t = await getTranslations('aas-list');

return (
<Typography variant="h2" textAlign="left" marginBottom={2}>
{t('header')}
</Typography>
);
}
133 changes: 133 additions & 0 deletions src/app/[locale]/list/_components/AasListTableRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { Box, Checkbox, Chip, Paper, TableCell, Typography } from '@mui/material';
import { ShellIcon } from 'components/custom-icons/ShellIcon';
import { FormattedMessage, useIntl } from 'react-intl';
import { messages } from 'lib/i18n/localization';
import { getProductClassId } from 'lib/util/ProductClassResolverUtil';
import LabelOffIcon from '@mui/icons-material/LabelOff';
import { AasListEntry } from 'lib/api/generated-api/clients.g';
import { encodeBase64 } from 'lib/util/Base64Util';
import { useRouter } from 'next/navigation';
import { useAasState } from 'components/contexts/CurrentAasContext';
import { useNotificationSpawner } from 'lib/hooks/UseNotificationSpawner';
import { ImageWithFallback } from './StyledImageWithFallBack';
import { ProductClassChip } from 'app/[locale]/list/_components/ProductClassChip';
import { tooltipText } from 'lib/util/ToolTipText';

type AasTableRow = {
aasListEntry: AasListEntry;
comparisonFeatureFlag: boolean | undefined;
checkBoxDisabled: (aasId: string | undefined) => boolean | undefined;
selectedAasList: string[] | undefined;
updateSelectedAasList: (isChecked: boolean, aasId: string | undefined) => void;
};

const tableBodyText = {
lineHeight: '150%',
fontSize: '16px',
color: 'text.primary',
};
export const AasListTableRow = (props: AasTableRow) => {
const { aasListEntry, comparisonFeatureFlag, checkBoxDisabled, selectedAasList, updateSelectedAasList } = props;
const navigate = useRouter();
const intl = useIntl();
const [, setAas] = useAasState();
const notificationSpawner = useNotificationSpawner();
const navigateToAas = (listEntry: AasListEntry) => {
setAas(null);
if (listEntry.aasId) navigate.push(`/viewer/${encodeBase64(listEntry.aasId)}`);
};

const translateListText = (property: { [key: string]: string } | undefined) => {
if (!property) return '';
return property[intl.locale] ?? Object.values(property)[0] ?? '';
};

const showMaxElementsNotification = () => {
notificationSpawner.spawn({
message: (
<Typography variant="body2" sx={{ opacity: 0.7 }}>
<FormattedMessage {...messages.mnestix.aasList.maxElementsWarning} />
</Typography>
),
severity: 'warning',
});
};

return (
<>
{comparisonFeatureFlag && (
<TableCell align="center" sx={tableBodyText}>
<Box
component="span"
onClick={() => {
if (checkBoxDisabled(aasListEntry.aasId)) showMaxElementsNotification();
}}
>
<Checkbox
checked={!!(selectedAasList && selectedAasList.some((el) => el == aasListEntry.aasId))}
disabled={checkBoxDisabled(aasListEntry.aasId)}
onChange={(evt) => updateSelectedAasList(evt.target.checked, aasListEntry.aasId)}
data-testid="list-checkbox"
/>
</Box>
</TableCell>
)}
<TableCell component="th" scope="row" sx={tableBodyText}>
<Paper
onClick={() => navigateToAas(aasListEntry)}
sx={{
width: '88px',
height: '88px',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
'&:hover': {
boxShadow: 6,
cursor: 'pointer',
},
}}
data-testid="list-thumbnail"
>
{aasListEntry.thumbnailUrl ? (
<ImageWithFallback
src={aasListEntry.thumbnailUrl}
alt={'Thumbnail image for: ' + aasListEntry.assetId}
/>
) : (
<ShellIcon fontSize="large" color="primary" />
)}
</Paper>
</TableCell>
<TableCell align="left" sx={tableBodyText}>
{translateListText(aasListEntry.manufacturerName)}
</TableCell>
<TableCell align="left" sx={tableBodyText}>
{tooltipText(translateListText(aasListEntry.manufacturerProductDesignation), 80)}
</TableCell>
<TableCell align="left" sx={tableBodyText}>
<Typography fontWeight="bold" sx={{ letterSpacing: '0.16px' }}>
<FormattedMessage {...messages.mnestix.aasList.assetIdHeading} />
</Typography>
{tooltipText(aasListEntry.assetId, 80)} <br />
<Typography fontWeight="bold" sx={{ letterSpacing: '0.16px' }}>
<FormattedMessage {...messages.mnestix.aasList.aasIdHeading} />
</Typography>
{tooltipText(aasListEntry.aasId, 80)}
</TableCell>
<TableCell align="left">
{aasListEntry.productGroup ? (
<ProductClassChip productClassId={getProductClassId(aasListEntry.productGroup)} maxChars={25} />
) : (
<Chip
sx={{ paddingX: '16px', paddingY: '6px' }}
color={'primary'}
label={<FormattedMessage {...messages.mnestix.aasList.notAvailable} />}
variant="outlined"
icon={<LabelOffIcon color={'primary'} />}
data-testid="product-class-chip"
/>
)}
</TableCell>
</>
);
};
Loading

0 comments on commit 6d453b2

Please sign in to comment.