Skip to content

Commit

Permalink
refactor: Update tsconfig.json and remove unused imports
Browse files Browse the repository at this point in the history
- Update tsconfig.json to target ESNext and update lib options
- Remove unused imports in UserCard.tsx and ProjectPortalPage.tsx
- Export useRelationsByType and types/relations from context/index.ts
- Update package.json version to 0.0.3
- Add useRelationsByType hook to context/hooks/use-relations-by-type.ts
- Add useContextRelationsQuery and getContextRelations to context/queries/get-context-relations.ts
- Add ProfileCardHeader component to components/ProfileCardHeader.tsx
- Add Skeleton component to components/skeleton/Skeleton.tsx
  • Loading branch information
Noggling committed Oct 29, 2024
1 parent b8e6425 commit ef16789
Show file tree
Hide file tree
Showing 11 changed files with 455 additions and 8 deletions.
2 changes: 1 addition & 1 deletion client/apps/project-portal-landingpage/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "project-portal-landingpage",
"version": "0.0.2",
"version": "0.0.3",
"description": "",
"private": true,
"type": "module",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { Typography } from '@equinor/eds-core-react';
import styled from 'styled-components';
import background from './assets/background.svg';
import { PropsWithChildren } from 'react';
import { useCurrentUser } from '@equinor/fusion-framework-react/hooks';

export const StyledBackgroundWrapper = styled.section`
background-image: url(${background});
width: 100%;
height: 100%;
height: calc(100vh - var(--header-height, 48px));
background-size: cover;
background-repeat: no-repeat;
background-position: bottom;
Expand Down Expand Up @@ -46,14 +47,14 @@ export const getGreeting = () => {
};

export const ProjectHeader = ({ children }: PropsWithChildren) => {
const { data } = { data: { name: 'chris' } }; //useCurrentUser();
const user = useCurrentUser();

return (
<StyledBackgroundWrapper>
<StyledHeader>
<Typography variant="h1">Welcome to Project Portal</Typography>
<Typography variant="h6">
{getGreeting()} {data?.name || 'Chris'}
{getGreeting()} {user?.name}
</Typography>
</StyledHeader>
<StyledWrapper>{children}</StyledWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import styled from 'styled-components';
import { Typography } from '@equinor/eds-core-react';

import { PersonAvatar } from '@equinor/fusion-react-person';
import { PersonDetails } from '../types/person-details';
import { Skeleton } from './skeleton/Skeleton';
import { getDepartment, getJobTitle } from '../hooks/user';

const Style = {
InfoWrapper: styled.div<{ paddingNone?: boolean }>`
display: flex;
align-items: center;
gap: 1rem;
padding: ${({ paddingNone }) => (paddingNone ? '0px' : '1rem')};
`,
};

export const ProfileCardHeader = ({
user,
trigger = 'none',
paddingNone,
}: {
user?: PersonDetails;
trigger?: 'click' | 'hover' | 'none';
paddingNone?: boolean;
}) => {
if (!user) {
return (
<Style.InfoWrapper paddingNone={paddingNone}>
<Skeleton variant="circle" size="medium" />

<div>
<Skeleton width="200px" />
<div style={{ paddingTop: '0.5rem', gap: '0.2rem', display: 'flex', flexDirection: 'column' }}>
<Skeleton width={60} />
<Skeleton width={60} />
</div>
</div>
</Style.InfoWrapper>
);
}

return (
<Style.InfoWrapper paddingNone={paddingNone}>
<PersonAvatar azureId={user.azureUniqueId} trigger={trigger} />
<div>
<Typography variant="h6">{user?.name}</Typography>
<div>
<Typography>{getDepartment(user)}</Typography>
<Typography>{getJobTitle(user)}</Typography>
</div>
</div>
</Style.InfoWrapper>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { useFeature } from '@equinor/fusion-framework-react-app/feature-flag';

import { useUserOrgDetails } from '../hooks/user';
import InfoBox from './InfoBox/InfoBox';
import { useNavigateOnContextChange } from '../hooks/use-navigate-on-context-change';
// import { useNavigateOnContextChange } from '../hooks/use-navigate-on-context-change';
import { User } from './user/UserCard';

// const styles = {
// contentSection: css`
Expand Down Expand Up @@ -129,7 +130,7 @@ export const ProjectPortalPage = (): JSX.Element => {
<Styles.Wrapper>
<ProjectHeader>
<Styles.Details>
{/* <User /> */}
<User />
<InfoBox />
</Styles.Details>
<Styles.Content>
Expand Down
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}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { Card, 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 { useCurrentUser } from '../../hooks/user';
import { useRelationsByType } from '../../context';
import { PersonPosition } from '../../types/person-details';
import { ProfileCardHeader } from '../ProfileCardHeader';
import { useFrameworkCurrentContext } from '@equinor/fusion-framework-react-app/context';

declare global {
interface Window {
_config_: {
fusionLegacyEnvIdentifier: string;
};
}
}

export const getFusionPortalURL = () => {
switch (window._config_.fusionLegacyEnvIdentifier.toLowerCase()) {
case 'fprd':
return 'https://fusion.equinor.com';
case 'ci':
return 'https://fusion-s-portal-ci.azurewebsites.net';
case 'fqa':
return 'https://fusion-s-portal-fqa.azurewebsites.net';
default:
return 'https://fusion-s-portal-ci.azurewebsites.net';
}
};

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 { data: 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 //<Message title="You have no allocation for the selected project " />
}
</>
);
};

export const User = () => {
const user = useCurrentUser();

return (
<Card elevation="raised">
<ProfileCardHeader user={user.data} trigger="click" />
<ProjectPosition positions={user.data?.positions} />
</Card>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { useEffect, useMemo, useState } from 'react';
import { RelationsTypes } from '../types/relations';
import { useContextRelationsQuery } from '../queries/get-context-relations';

export function useRelationsByType<RT extends RelationsTypes>(type: RT, contextId?: string) {
const [error, setError] = useState<Error | undefined>();
const { data, isLoading } = useContextRelationsQuery<RT>(contextId);

const filteredRelations = useMemo(() => {
setError(undefined);
return data?.filter((relation) => relation.type.id === type) || [];
}, [data]);

useEffect(() => {
if (!isLoading && filteredRelations?.length === 0) {
setError(Error(`No context relation found for ${type}`));
}
}, [isLoading, filteredRelations]);

return { data: filteredRelations, isLoading, error };
}
2 changes: 2 additions & 0 deletions client/apps/project-portal-landingpage/src/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './types/relations';
export { useRelationsByType } from './hooks/use-relations-by-type';
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useFramework } from '@equinor/fusion-framework-react';
import { useQuery } from '@tanstack/react-query';
import { IHttpClient } from '@equinor/fusion-framework-module-http';
import { RelationReturnType, RelationsTypes } from '../types/relations';

export async function getContextRelations<RT extends RelationsTypes>(
client: IHttpClient,
contextId?: string,
signal?: AbortSignal
): Promise<RelationReturnType<RT>[] | undefined> {
if (!contextId) return;
const res = await client.fetch(`/contexts/${contextId}/relations`, { signal });
if (!res.ok) throw res;
return (await res.json()) as RelationReturnType<RT>[];
}

export const useContextRelationsQuery = <RT extends RelationsTypes>(contextId?: string) => {
const client = useFramework().modules.serviceDiscovery.createClient('context');

return useQuery({
queryKey: ['context-relations', contextId],
queryFn: async ({ signal }) => getContextRelations<RT>(await client, contextId, signal),
enabled: Boolean(contextId),
});
};
Loading

0 comments on commit ef16789

Please sign in to comment.