Skip to content

Commit

Permalink
feat: project portal landing page (#1175)
Browse files Browse the repository at this point in the history
* feat: Add f@equinor/fusion-portal-react-utils

This is user utils for portal components and hooks for getting the context relations

* feat: Add @equinor/fusion-portal-react-context

Adding all context related portal functionality needed for portal landing pages

* feat: Add @equinor/fusion-portal-react-components
Adding user components and use-full portal components

* feat: Add @equinor/project-portal-common
This adds common components use between the project portal landing pages

* feat: Add project-portal-landingpage

* feat: Update project-portal-landingpage to version 0.0.10 and modify manual-deploy.yml

- Update the version of project-portal-landingpage to 0.0.10 in package.json
- Modify manual-deploy.yml to include project-portal-landingpage in the list of triggers
manual-deploy.yml

* chore:  fix entry file to be ts not tsx

* chore: add prod skip

* refactor: Remove ContextNotSupported component

* refactor: Add ContextNotSupported component
  • Loading branch information
Noggling authored Nov 8, 2024
1 parent 1ce2cf7 commit ee3dcee
Show file tree
Hide file tree
Showing 99 changed files with 3,772 additions and 133 deletions.
1 change: 1 addition & 0 deletions .github/workflows/manual-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ on:
- punch
- scopechangerequest
- workorder
- project-portal-landingpage
- "*"
permissions:
actions: read
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.



# compiled output
/.nx
/workspace
Expand Down Expand Up @@ -47,3 +49,5 @@ Thumbs.db

.astro
.turbo

!import.d.ts
21 changes: 21 additions & 0 deletions fusion-portal/react/components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@equinor/fusion-portal-react-components",
"version": "0.0.0",
"license": "MIT",
"type": "module",
"main": "./dist/src/index.js",
"module": "./dist/src/index.js",
"types": "./dist/src/index.d.ts",
"private": true,
"scripts": {
"dev": "npm run build -- --watch",
"dev:local": "npm run dev -- --watch",
"build": "tsc -b -f"
},
"dependencies": {
"@equinor/fusion-react-skeleton": "^0.3.0",
"@equinor/fusion-react-person": "^0.9.2",
"@equinor/fusion-portal-react-context": "workspace:^",
"@equinor/fusion-portal-react-utils": "workspace:^"
}
}
4 changes: 4 additions & 0 deletions fusion-portal/react/components/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './skeleton/Skeleton';
export { User } from './user/UserCard';

export { Message } from './message/Message';
113 changes: 113 additions & 0 deletions fusion-portal/react/components/src/components/message/Message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { IconData, error_outlined, warning_outlined } from '@equinor/eds-icons';
import { Icon } from '@equinor/eds-core-react';
import { tokens } from '@equinor/eds-tokens';
import styled from 'styled-components';
import { PropsWithChildren } from 'react';

export type Variant = 'Warning' | 'Error' | 'Info' | 'NoContent';

export const getIconVariant = (type: Variant) => {
const variant: Record<Variant, { data: IconData; color: string; backColor: string; type: Variant }> = {
Error: {
data: warning_outlined,
color: tokens.colors.interactive.danger__resting.rgba,
backColor: tokens.colors.interactive.danger__highlight.hex,
type: 'Error',
},
Warning: {
data: error_outlined,
color: tokens.colors.interactive.warning__resting.rgba,
backColor: tokens.colors.interactive.warning__highlight.hex,
type: 'Warning',
},
Info: {
data: error_outlined,
color: tokens.colors.infographic.primary__moss_green_100.rgba,
backColor: tokens.colors.interactive.primary__selected_highlight.hex,
type: 'Info',
},
NoContent: {
data: error_outlined,
color: tokens.colors.infographic.primary__moss_green_100.rgba,
backColor: tokens.colors.interactive.primary__selected_highlight.hex,
type: 'NoContent',
},
};
return variant[type];
};

export type MessageProps = {
type?: Variant;
title: string;
messages?: string[];
};

export const Styled = {
StyledCardIndicator: styled.div<{ color: string }>`
position: absolute;
display: block;
left: 0;
width: 8px;
height: 100%;
background-color: ${({ color }) => color};
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
`,
Content: styled.div`
padding: 1rem 1.5rem;
display: flex;
flex-direction: column;
`,
Header: styled.div`
display: flex;
flex-direction: row;
align-items: center;
`,
Icon: styled.span<{ color: string }>`
flex: none;
border-radius: 100px;
background-color: ${({ color }) => color};
width: 40px;
height: 40px;
margin-right: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
overflow: visible;
`,
UL: styled.ul`
display: block;
list-style-type: disc;
margin-block-start: 1em;
margin-block-end: 0px;
margin-inline-start: 0px;
margin-inline-end: 0px;
padding-inline-start: 35px;
& > li {
padding-bottom: 0.5rem;
}
`,
};

export const Message = ({ title, messages, type = 'Info', children }: PropsWithChildren<MessageProps>) => {
const variant = getIconVariant(type);

return (
<Styled.Content>
<Styled.Header>
<Styled.Icon color={variant.backColor} title="Icon">
<Icon data={variant.data} color={variant.color} type={variant.type} />
</Styled.Icon>
<span>{title}</span>
</Styled.Header>
{messages && (
<Styled.UL>
{messages?.map((message, i) => (
<li key={message || '' + i}>{message}</li>
))}
</Styled.UL>
)}
{children && children}
</Styled.Content>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Typography, Icon } from '@equinor/eds-core-react';

import { tokens } from '@equinor/eds-tokens';
import styled from 'styled-components';

import { external_link, tag_relations } from '@equinor/eds-icons';

import { useMemo } from 'react';

import { useRelationsByType } from '@equinor/fusion-portal-react-context';

import { useFrameworkCurrentContext } from '@equinor/fusion-framework-react-app/context';
import { PersonPosition, getFusionPortalURL } from '@equinor/fusion-portal-react-utils';

const Style = {
Wrapper: styled.div`
display: flex;
flex-direction: column;
gap: ${tokens.spacings.comfortable.medium};
padding: ${tokens.spacings.comfortable.medium};
`,
PositionWrapper: styled.span`
display: flex;
flex-direction: column;
`,
ProjectHeader: styled.div`
display: flex;
justify-content: space-between;
border-bottom: 1px solid ${tokens.colors.ui.background__medium.hex};
padding: ${tokens.spacings.comfortable.small};
`,
PositionLink: styled.a`
height: auto;
border: 1px solid ${tokens.colors.ui.background__medium.hex};
color: ${tokens.colors.interactive.primary__resting.hex};
border-radius: 4px;
width: 100%;
text-decoration: none;
:hover {
background-color: ${tokens.colors.interactive.primary__hover_alt.hex};
}
:focus {
background-color: ${tokens.colors.interactive.primary__hover_alt.hex};
}
`,
Content: styled.div`
padding: ${tokens.spacings.comfortable.medium_small};
display: flex;
align-items: center;
height: auto;
`,
Icon: styled(Icon)`
padding-right: 1rem;
`,
};

export const ProjectPosition = ({ positions }: { positions?: PersonPosition[] }) => {
const { currentContext } = useFrameworkCurrentContext();
const { relations: equinorTask } = useRelationsByType('OrgChart', currentContext?.id);

const projectPositions = useMemo(() => {
return (
positions?.filter((item) => {
return (
item.appliesTo &&
new Date(item.appliesTo) > new Date() &&
item.project.id === equinorTask[0]?.externalId
);
}) || []
);
}, [positions, equinorTask]);

return (
<>
{projectPositions.length > 0 ? (
<Style.Wrapper>
{projectPositions.map((position) => (
<Style.PositionLink
key={position.id}
target="_blank"
aria-label={position.name}
href={`${getFusionPortalURL()}/apps/pro-org/${position.project.id}/chart/${
position.parentPositionId
}`}
role="link"
>
<Style.PositionWrapper>
<Style.ProjectHeader>
<Typography>{position.project.name}</Typography>
<Icon data={external_link} size={16} />
</Style.ProjectHeader>
<Style.Content>
<Style.Icon data={tag_relations} />
<div>
<Typography color={tokens.colors.interactive.primary__resting.hex}>
{position.name}
</Typography>
<Typography>
<>
{position.appliesFrom &&
new Date(position.appliesFrom).toLocaleDateString('en-US')}
{' - '}
{position.appliesTo &&
new Date(position.appliesTo).toLocaleDateString('en-US')}
({position.workload}%)
</>
</Typography>
</div>
</Style.Content>
</Style.PositionWrapper>
</Style.PositionLink>
))}
</Style.Wrapper>
) : null}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import FusionSkeleton, { SkeletonSize, SkeletonVariant } from '@equinor/fusion-react-skeleton';
import { CSSProperties, FC } from 'react';

type SkeletonProps = {
width?: number | string;
height?: number | string;
size?: keyof typeof skeletonSize;
variant?: keyof typeof skeletonVariant;
fluid?: boolean;
};

const skeletonVariant = {
circle: SkeletonVariant.Circle,
rectangle: SkeletonVariant.Rectangle,
square: SkeletonVariant.Square,
text: SkeletonVariant.Text,
};

const skeletonSize = {
xSmall: SkeletonSize.XSmall,
small: SkeletonSize.small,
large: SkeletonSize.Large,
medium: SkeletonSize.Medium,
};

/**
* Skeleton Component
*
* The `Skeleton` component is a simplified ree-export of `@equinor/fusion-react-skeleton` a React component used to render skeleton loading elements.
*
* @param width - number (optional) - Specifies the width of the skeleton element in present%
* @param type - string (optional) - Specifies the type of skeleton to render. Should be one of "xSmall" | "small" | "large" | "medium" default is xSmall.
* @param variant - string (optional) - Specifies the variant or shape of the skeleton. Should be one of "circle" | "rectangle" | "square" | "text", default is text.
* @param fluid - boolean (optional) - Expands the skeleton element width to the width of the parent
*
* @returns JSX.Element - A skeleton loading element with the specified type, variant, and width (if provided).
*/
export const Skeleton: FC<SkeletonProps & { style?: CSSProperties }> = ({
width,
height,
size,
variant,
style,
fluid,
}) => {
return (
<FusionSkeleton
size={size ? skeletonSize[size] : skeletonSize.xSmall}
variant={variant ? skeletonVariant[variant] : skeletonVariant.text}
style={{
...style,
...(width && { width: typeof width === 'number' ? `${height}%` : height }),
...(height && { height: typeof height === 'number' ? `${height}%` : height }),
}}
fluid={fluid}
/>
);
};
17 changes: 17 additions & 0 deletions fusion-portal/react/components/src/components/user/UserCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Card } from '@equinor/eds-core-react';

import { useCurrentUserInfo } from '@equinor/fusion-portal-react-utils';

import { ProfileCardHeader } from '../../profile-card-header/ProfileCardHeader';
import { ProjectPosition } from '../project-position/ProjectPosition';

export const User = () => {
const { currentUserInfo } = useCurrentUserInfo();

return (
<Card elevation="raised">
<ProfileCardHeader user={currentUserInfo} trigger="click" />
<ProjectPosition positions={currentUserInfo?.positions} />
</Card>
);
};
1 change: 1 addition & 0 deletions fusion-portal/react/components/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './components';
Loading

0 comments on commit ee3dcee

Please sign in to comment.