Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new project landing page #568

Merged
merged 16 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/pr-568-1721565010.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

---
"fusion-project-portal": major
---
Project Landing Page Update: Design and User Experience Enhancements
- Enhanced the project landing page by integrating a full-width header containing essential project information, aligning it with the Fusion landing page.
- Introduced a new Overview tab with a new layout.
- Users can now view their project allocation, with a direct link to the project organization application in Fusion.
- Displayed the project director prominently on the project landing page.
- Implemented a project phase indicator on the Overview tab, showcasing DG dates and the current DG phase of the project.
- Aligned the design of pinned apps with Fusion while maintaining content stored in local storage.
- Implemented cleanup functionality for removing deleted apps within pinned apps.
- Redesigned the contract list to feature cards instead of a table, categorizing contracts into active and closed groups based on closing date.
- Introduced a Project Portal Info section with quick facts.
- Implemented a Construction and Commissioning tab featuring milestones and CC-Application KPIs, accessible behind a feature flag.
- Developed a new menu design, also accessible behind a feature flag, in alignment with the all-apps list.
- Added functionality for feature flagging with local storage implementation utilizing the new feature flag module in Fusion.
- Introduced a "My Features" tab under the user's account to enable feature flags.
- Global app search and navigation behind feature flag.
6 changes: 3 additions & 3 deletions client/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"root": true,

"plugins": ["eslint-plugin-tsdoc"],
"extends": ["plugin:@typescript-eslint/recommended" ],

"parser": "@typescript-eslint/parser",
"ignorePatterns": [
"*.md", "*.svg", "*.d.ts", "*.ico", "*.html", "*.js", "**/coverage/**"
"*.md", "*.svg", "*.d.ts", "*.ico", "*.html", "*.js", "**/coverage/**", "*.jpg"
],
"rules": {
"tsdoc/syntax": "warn",
Expand Down Expand Up @@ -76,6 +76,6 @@
"no-restricted-globals": "off"
},
"overrides": [

]
}
32 changes: 17 additions & 15 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,22 +63,23 @@
"@equinor/eds-core-react": "^0.33.1",
"@equinor/eds-icons": "^0.17.0",
"@equinor/eds-tokens": "^0.9.0",
"@equinor/fusion-framework-app": "^7.1.15",
"@equinor/fusion-framework-app": "^8.1.1",
"@equinor/fusion-framework-module-ag-grid": "^31.0.1",
"@equinor/fusion-framework-module-app": "^5.2.12",
"@equinor/fusion-framework-module-context": "^4.0.19",
"@equinor/fusion-framework-module-http": "^5.1.5",
"@equinor/fusion-framework-module-navigation": "^3.1.3",
"@equinor/fusion-framework-module-services": "^3.2.3",
"@equinor/fusion-framework-module-signalr": "^2.0.17",
"@equinor/fusion-framework-react": "^5.3.9",
"@equinor/fusion-framework-react-app": "^4.2.1",
"@equinor/fusion-framework-react-components-bookmark": "0.3.4",
"@equinor/fusion-framework-react-components-people-provider": "^1.1.11",
"@equinor/fusion-framework-react-module-signalr": "^2.0.16",
"@equinor/fusion-observable": "^8.1.4",
"@equinor/fusion-framework-module-app": "^5.2.13",
"@equinor/fusion-framework-module-context": "^4.0.21",
"@equinor/fusion-framework-module-feature-flag": "^1.0.2",
"@equinor/fusion-framework-module-http": "^5.1.6",
"@equinor/fusion-framework-module-navigation": "^3.1.4",
"@equinor/fusion-framework-module-services": "^3.2.4",
"@equinor/fusion-framework-module-signalr": "^2.0.19",
"@equinor/fusion-framework-react": "^6.0.2",
"@equinor/fusion-framework-react-app": "^4.3.3",
"@equinor/fusion-framework-react-components-bookmark": "0.3.7",
"@equinor/fusion-framework-react-components-people-provider": "^1.1.15",
"@equinor/fusion-framework-react-module-signalr": "^2.0.18",
"@equinor/fusion-observable": "^8.1.5",
"@equinor/fusion-react-context-selector": "^0.6.0",
"@equinor/fusion-react-person": "^0.6.5",
"@equinor/fusion-react-person": "^0.7.0",
"@equinor/fusion-react-side-sheet": "1.3.0",
"@equinor/fusion-react-skeleton": "^0.3.0",
"@equinor/fusion-react-styles": "^0.6.1",
Expand All @@ -93,6 +94,7 @@
"re-resizable": "^6.9.9",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.48.2",
"react-query": "^3.39.2",
"react-router-dom": "^6.16.0",
Expand All @@ -104,4 +106,4 @@
"workspaces": [
"packages/**"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const defaultIcon =
'<svg width="48" height="48" viewBox="0 0 48 48" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> <path fill-rule="evenodd" clip-rule="evenodd" d="M15.068 15.136C17.98 15.136 20.122 12.98 20.122 10.068C20.122 7.156 17.98 5 15.068 5C12.156 5 10 7.156 10 10.068C10 12.98 12.156 15.136 15.068 15.136ZM15.068 13.456C13.136 13.456 11.834 12.014 11.834 10.068C11.834 8.108 13.136 6.68 15.068 6.68C16.986 6.68 18.288 8.108 18.288 10.068C18.288 12.014 16.986 13.456 15.068 13.456Z"/> <path d="M23.58 14.968C23.748 14.968 23.846 14.87 23.846 14.716V8.29L28.732 14.814C28.816 14.912 28.914 14.968 29.04 14.968H30.062C30.23 14.968 30.328 14.87 30.328 14.716V5.308C30.328 5.126 30.202 5.042 30.02 5.112L28.76 5.574C28.62 5.63 28.55 5.728 28.55 5.868V11.678L23.79 5.322C23.72 5.224 23.608 5.168 23.482 5.168H22.334C22.166 5.168 22.068 5.266 22.068 5.42V14.716C22.068 14.87 22.166 14.968 22.334 14.968H23.58Z"/> <path d="M38.9988 6.568C38.9988 6.722 38.9008 6.82 38.7328 6.82H34.3228V8.976H37.6268C37.7948 8.976 37.8928 9.074 37.8928 9.228V10.376C37.8928 10.53 37.7948 10.628 37.6268 10.628H34.3228V13.316H38.7328C38.9008 13.316 38.9988 13.414 38.9988 13.568V14.716C38.9988 14.87 38.9008 14.968 38.7328 14.968H32.7828C32.6148 14.968 32.5168 14.87 32.5168 14.716V5.42C32.5168 5.266 32.6148 5.168 32.7828 5.168H38.7328C38.9008 5.168 38.9988 5.266 38.9988 5.42V6.568Z"/> <path d="M16.1663 30.6839L16.1661 24.2382C16.1665 23.9648 16.4624 23.7939 16.6995 23.9304L22.2846 27.1479C22.4503 27.2433 22.5521 27.4198 22.5519 27.6109V34.0565C22.5518 34.33 22.2496 34.501 22.0127 34.3645L16.4274 31.1468C16.2619 31.0515 16.1661 30.875 16.1663 30.6839Z"/> <path d="M25.3281 23.8376L33.3691 19.205C33.7104 19.0086 34.1364 19.2546 34.1368 19.6485L34.1366 28.9284C34.137 29.2033 33.9992 29.4573 33.761 29.5947L25.7199 34.2269C25.3788 34.4235 24.9439 34.1775 24.9435 33.7836V24.5038C24.9431 24.2288 25.09 23.9748 25.3281 23.8376Z"/> <path d="M20.104 40.2295L22.3387 38.9419C22.4334 38.8875 22.5519 38.956 22.5519 39.0653V41.6444C22.5521 41.7206 22.5137 41.7914 22.4474 41.8294L20.2128 43.1168C20.1181 43.1714 19.9969 43.1031 19.9969 42.9934L19.997 40.4147C19.997 40.3383 20.0379 40.2677 20.104 40.2295Z"/> <path d="M17.6898 34.6259L21.0363 36.5625C21.1783 36.6446 21.1783 36.8495 21.0363 36.9318L17.6898 38.868C17.5906 38.9256 17.4683 38.9256 17.3691 38.868L14.0225 36.9318C13.8805 36.8495 13.8805 36.6446 14.0225 36.5625L17.3691 34.6259C17.4683 34.5687 17.5906 34.5687 17.6898 34.6259Z"/> <path d="M27.9109 37.7718L26.4239 36.9116C26.2977 36.8383 26.2977 36.6561 26.4239 36.5831L27.9109 35.7228C27.9989 35.6718 28.1075 35.6718 28.1957 35.7228L29.6825 36.5831C29.8087 36.6561 29.8087 36.8383 29.6825 36.9116L28.1957 37.7718C28.1075 37.8229 27.9989 37.8229 27.9109 37.7718Z"/> <path d="M26.7163 39.8395L25.2279 38.9821C25.1015 38.9092 24.9435 39.0008 24.9435 39.1463L24.9437 40.8641C24.9437 40.9656 24.9961 41.0601 25.0843 41.111L26.5727 41.9684C26.6991 42.0411 26.8569 41.9499 26.8571 41.8042L26.8589 40.0864C26.8589 39.9846 26.8045 39.8904 26.7163 39.8395Z"/> </svg>';

export default defaultIcon;
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import styled, { css } from 'styled-components';
import { Skeleton } from '@equinor/fusion-react-skeleton';
import { tokens } from '@equinor/eds-tokens';
import { SkeletonSize, SkeletonVariant } from '@equinor/fusion-react-skeleton';
import { AppCardType } from '../../types/types';
import { defaultIcon } from './defaultIcon';
import { AppManifest } from '../../types/types';

const primaryColor = tokens.colors.interactive.primary__resting.hex;

export const Styled = {
AppIcon: styled.div<{ $display: AppCardType }>`
${({ $display }) => {
switch ($display) {
default:
case 'item':
return css`
--app-icon-size: 1.5rem;
--background-radius: 0.25rem;
padding: 0.375rem;
`;
case 'card':
return css`
--app-icon-size: 1.5rem;
--background-radius: 0.25rem 0 0 0.25rem;
padding: 0 0.375rem;
`;
case 'portal':
return css`
--app-icon-size: 2.5rem;
--background-radius: 0.25rem 0 0 0.25rem;
padding: 0 1.25rem;
`;
}
}}
align-self: normal;
position: relative;
display: flex;
justify-content: center;
align-items: center;
color: var(--app-color, ${primaryColor});
height: var(--app-icon-size, 1.5rem);
width: var(--app-icon-size, 1.5rem);

&:before {
content: '';
position: absolute;
background-color: var(--app-color, ${primaryColor});
border-radius: var(--background-radius, 0);
height: 100%;
width: 100%;
opacity: 0.2;
}
`,
AppIconSkeletonContainer: styled.div<{ $display: AppCardType }>`
align-self: normal;
position: relative;
display: flex;
justify-content: center;
align-items: center;

${({ $display }) => {
if ($display !== 'item') {
return css`
&:before {
content: '';
position: absolute;
background-color: var(--app-color, ${primaryColor});
border-radius: 0.25rem 0 0 0.25rem;
height: 100%;
width: 100%;
opacity: 0.2;
}
`;
}
return '';
}}
`,
AppIconSkeleton: styled(Skeleton)<{ $display: AppCardType }>`
--fwc-skeleton-fill-color: var(--app-color-skeleton, ${primaryColor}33);
${({ $display }) => {
switch ($display) {
default:
case 'card':
return css`
margin: 1.313rem 0.625rem;
`;
case 'portal':
return css`
margin: 1.625rem 1.25rem;
`;
}
}}
`,
};

type AppIconProps = {
app: Partial<AppManifest>;
display: AppCardType;
loading?: boolean;
};

export const AppIconContainer = ({ app, display, loading }: AppIconProps): JSX.Element => {
const appCategoryIcon = app.category ? app.category.defaultIcon : defaultIcon;
const appIcon = app.icon ? (app.icon !== '' ? app.icon : appCategoryIcon) : appCategoryIcon;

if (loading) {
return (
<Styled.AppIconSkeletonContainer $display={display}>
<Styled.AppIconSkeleton
$display={display}
size={display === 'card' ? SkeletonSize.XSmall : SkeletonSize.small}
variant={SkeletonVariant.Square}
/>
</Styled.AppIconSkeletonContainer>
);
}

return <Styled.AppIcon $display={display} dangerouslySetInnerHTML={{ __html: appIcon || defaultIcon }} />;
};

export default AppIconContainer;
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { Skeleton, SkeletonSize, SkeletonVariant } from '@equinor/fusion-react-skeleton';
import styled from 'styled-components';
import { tokens } from '@equinor/eds-tokens';
import { Link } from 'react-router-dom';
import { Typography } from '@equinor/eds-core-react';

import AppIconContainer, { Styled as IconStyled } from './AppIcon';
import PinButtonContainer from './PinButton';
import { AppManifest } from '../types/types';

export const Styled = {
Favorite: styled(Link)<{ $loading?: boolean }>`
pointer-events: ${(props) => (props.$loading ? 'none' : 'auto')};
position: relative;
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
border-radius: 0.25rem;
box-shadow: ${tokens.elevation.raised};
column-gap: ${tokens.spacings.comfortable.x_small};
padding-right: 0.375rem;
text-decoration: none;

div${IconStyled.AppIcon} {
height: auto;
}

&:hover,
&:focus {
cursor: ${(props) => (props.$loading ? 'default' : 'pointer')};
&:after {
content: '';
width: 100%;
height: 0.25rem;
position: absolute;
bottom: 0;
left: 0;
background-color: var(--app-color, ${tokens.colors.interactive.primary__resting.hex});
border-radius: 0 0 0.25rem 0.25rem;
opacity: 0.3;
}
}
`,
Content: styled.div`
height: 100%;
display: flex;
flex: 1;
align-items: center;
column-gap: ${tokens.spacings.comfortable.small};
`,
Details: styled.div`
display: flex;
flex-direction: column;
row-gap: ${tokens.spacings.comfortable.xx_small};
padding: ${tokens.spacings.comfortable.medium_small} 0;
`,
Name: styled(Typography)`
color: ${tokens.colors.text.static_icons__default.hex};
`,
Category: styled(Typography)`
color: ${tokens.colors.text.static_icons__tertiary.hex};
`,
};

type FavoriteCardProps = {
app: Partial<AppManifest>;
onClick: (app: Partial<AppManifest>) => void;
loading?: boolean;
colorsStyle?: {
[key: string]: string;
};
};

export const FavoriteCard = ({ app, onClick, loading, colorsStyle }: FavoriteCardProps): JSX.Element => {
return (
<Styled.Favorite
$loading={loading}
to={app.url || `/apps/${app.key}/`}
style={colorsStyle}
onClick={() => onClick(app)}
>
<Styled.Content>
<AppIconContainer loading={loading} display="card" app={app} />
<Styled.Details>
<Styled.Name group="paragraph" variant="body_short_bold">
{loading ? <Skeleton size={SkeletonSize.small} variant={SkeletonVariant.Text} /> : app.name}
</Styled.Name>
<Styled.Category group="paragraph" variant="caption">
{loading ? (
<Skeleton size={SkeletonSize.XSmall} variant={SkeletonVariant.Text} />
) : (
app.category?.name
)}
</Styled.Category>
</Styled.Details>
</Styled.Content>
<PinButtonContainer isLoading={loading} app={app} />
</Styled.Favorite>
);
};

export default FavoriteCard;
Loading