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: portal admin landing page #890

Merged
merged 8 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 6 additions & 0 deletions .changeset/pr-890-2226201165.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

---
"fusion-project-portal": minor
---
New portal administration landing page
- Admins can only edit portals
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import styled from 'styled-components';
import { tokens } from '@equinor/eds-tokens';
import { Card, Typography } from '@equinor/eds-core-react';

const Styled = {
FusionInfo: styled(Card).withConfig({ displayName: 'info-box' })`
background: ${tokens.colors.infographic.primary__moss_green_21.hex};
position: relative;
padding: ${tokens.spacings.comfortable.medium};
`,
InfoTitle: styled(Typography).withConfig({ displayName: 'info-box' })`
margin-bottom: ${tokens.spacings.comfortable.small};
`,
Ul: styled.ul`
margin: 0;
padding-left: ${tokens.spacings.comfortable.medium};
`,
CloseButton: styled.div`
position: absolute;
top: ${tokens.spacings.comfortable.small};
right: ${tokens.spacings.comfortable.small};
`,
};

export const InfoBox = (): JSX.Element => {
return (
<Styled.FusionInfo elevation="raised">
<Styled.InfoTitle group="paragraph" variant="body_long_bold">
Portal Administration
</Styled.InfoTitle>
<Styled.Ul>
<li>
<Typography group="paragraph" variant="body_long">
Provides application/applications.
</Typography>
</li>
<li>
<Typography group="paragraph" variant="body_long">
Provides support tools / extensions.
</Typography>
</li>
<li>
<Typography group="paragraph" variant="body_long">
Provides the ability to have application or context driven portals.
</Typography>
</li>
</Styled.Ul>
</Styled.FusionInfo>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Typography } from '@equinor/eds-core-react';
import styled from 'styled-components';

import { PropsWithChildren } from 'react';
import { useCurrentUser } from '@equinor/fusion-framework-react/hooks';
import { getGreeting } from './utils';

export const StyledHeader = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
> :not(:first-child) {
margin-left: 0px;
}
/* margin-top: 2rem; */
`;

export const ProjectHeader = ({ children }: PropsWithChildren) => {
const user = useCurrentUser();

return (
<section>
<StyledHeader>
<Typography variant="h1">Welcome to Portal Administration</Typography>
<Typography variant="h6">
{getGreeting()} {user?.name}
</Typography>
</StyledHeader>
</section>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Returns a greeting message based on the current time of day.
*
* - "Good morning" if the current time is between 5 AM and 12 PM.
* - "Good afternoon" if the current time is between 12 PM and 5 PM.
* - "Good evening" if the current time is after 5 PM or before 5 AM.
*
* @returns {string} A greeting message.
*/
export const getGreeting = () => {
const currTime = new Date();
const currHours = currTime.getHours();

if (currHours >= 5 && currHours < 12) {
return 'Good morning';
} else if (currHours >= 12 && currHours < 17) {
return 'Good afternoon';
} else {
return 'Good evening';
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useCreateContextType, useGetContextTypes } from '../../hooks/use-contex
import { ContextTypeTable } from './ContextTypeTable';
import { InfoPopover } from '../InfoPopover';
import { useState } from 'react';
import { useAccess } from '../../hooks/use-access';

const Style = {
Content: styled.div`
Expand Down Expand Up @@ -52,7 +53,7 @@ const Style = {

export const EditContextTypeForm = () => {
const { mutateAsync: createContextType, reset: resetCreate } = useCreateContextType();

const { data: isAdmin } = useAccess();
const {
register,
handleSubmit,
Expand All @@ -77,45 +78,47 @@ export const EditContextTypeForm = () => {

return (
<Style.Content>
<Style.Card>
<Style.Row onClick={() => setActive((s) => !s)}>
<Style.Row>
<Typography variant="h6">Add Context Type</Typography>
<InfoPopover title="Add Context Type">
<Typography>
Expand the form to add new context type by pressing the chevron icon.
</Typography>
</InfoPopover>
</Style.Row>
<Button
variant="ghost_icon"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
setActive((s) => !s);
}}
>
<Icon data={active ? chevron_down : chevron_left} />
</Button>
</Style.Row>
{active && (
<Style.From onSubmit={handleSubmit(onSubmit)} id="context-type-form">
<TextField
{...register('type')}
id="textfield-context-type"
variant={errors.type && 'error'}
helperText={errors.type?.message}
inputIcon={errors.type && <Icon data={error_filled} title="Error" />}
label="Type *"
maxLength={31}
/>
<Button form="context-type-form" type="submit" disabled={isSubmitting || !isValid}>
Add
{isAdmin && (
<Style.Card>
<Style.Row onClick={() => setActive((s) => !s)}>
<Style.Row>
<Typography variant="h6">Add Context Type</Typography>
<InfoPopover title="Add Context Type">
<Typography>
Expand the form to add new context type by pressing the chevron icon.
</Typography>
</InfoPopover>
</Style.Row>
<Button
variant="ghost_icon"
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
setActive((s) => !s);
}}
>
<Icon data={active ? chevron_down : chevron_left} />
</Button>
</Style.From>
)}
</Style.Card>
{contextTypes?.length && <ContextTypeTable contextTypes={contextTypes} />}
</Style.Row>
{active && (
<Style.From onSubmit={handleSubmit(onSubmit)} id="context-type-form">
<TextField
{...register('type')}
id="textfield-context-type"
variant={errors.type && 'error'}
helperText={errors.type?.message}
inputIcon={errors.type && <Icon data={error_filled} title="Error" />}
label="Type *"
maxLength={31}
/>
<Button form="context-type-form" type="submit" disabled={isSubmitting || !isValid}>
Add
</Button>
</Style.From>
)}
</Style.Card>
)}
{contextTypes?.length && <ContextTypeTable contextTypes={contextTypes} isAdmin={isAdmin} />}
</Style.Content>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Button, Icon } from '@equinor/eds-core-react';
import { delete_to_trash } from '@equinor/eds-icons';
import { useRemoveContextType } from '../../hooks/use-context-type-query';

export function ContextTypeTable({ contextTypes }: { contextTypes?: { type: string }[] }) {
export function ContextTypeTable({ contextTypes, isAdmin }: { contextTypes?: { type: string }[]; isAdmin?: boolean }) {
const ref = useRef(null);
const [_, height] = useResizeObserver(ref);
const { mutateAsync: removeContextType } = useRemoveContextType();
Expand Down Expand Up @@ -54,6 +54,7 @@ export function ContextTypeTable({ contextTypes }: { contextTypes?: { type: stri
<Button
variant="ghost"
title={`Delete ${params.data?.type}`}
disabled={!isAdmin}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,10 @@ export const EditPortalForm = (props: {
<IdInput register={register} errors={errors} />
<NameInput register={register} errors={errors} canEdit={canEdit} />
<Style.Row>
<ShortNameInput register={register} errors={errors} />
<SubtextInput register={register} errors={errors} />
<ShortNameInput register={register} errors={errors} canEdit={canEdit} />
<SubtextInput register={register} errors={errors} canEdit={canEdit} />
</Style.Row>
<DescriptionInput register={register} errors={errors} />
<DescriptionInput register={register} errors={errors} canEdit={canEdit} />
</Style.From>
</Style.Card>
<Style.Card>
Expand All @@ -128,7 +128,7 @@ export const EditPortalForm = (props: {
</Style.Card>
<Style.Card>
<Typography variant="h5">Icon</Typography>
<IconInput register={register} errors={errors} icon={watch().icon} />
<IconInput register={register} errors={errors} icon={watch().icon} canEdit={canEdit} />
</Style.Card>
<Style.Card>
<Typography variant="h5">Portal Type</Typography>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ActionBar } from './ActionBar';
import { AgStyles } from '../AgStyle';
import { Message } from '../Message';

export const PortalAppTable = ({ portalApps }: { portalApps: PortalApplication[] }) => {
export const PortalAppTable = ({ portalApps, canEdit }: { portalApps: PortalApplication[]; canEdit?: boolean }) => {
const ref = useRef(null);
const [_, height] = useResizeObserver(ref);

Expand Down Expand Up @@ -42,10 +42,10 @@ export const PortalAppTable = ({ portalApps }: { portalApps: PortalApplication[]
}}
onRowSelected={(event) => {
const selectedRows = event.api!.getSelectedRows();
setSelectedApps(selectedRows);
canEdit && setSelectedApps(selectedRows);
}}
onRowDataUpdated={() => {
setSelectedApps([]);
canEdit && setSelectedApps([]);
}}
colDefs={[
{
Expand Down Expand Up @@ -159,7 +159,7 @@ export const PortalAppTable = ({ portalApps }: { portalApps: PortalApplication[]
]}
/>

<ActionBar selection={selectedApps} />
{canEdit && <ActionBar selection={selectedApps} />}
</AgStyles.TableContent>
</AgStyles.Wrapper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useGetContextTypes } from '../../hooks/use-context-type-query';
import { EditPortalForm } from '../Portal/EditPortalForm';
import { FormActionBar } from '../Portal/FormActionBar';
import { useState } from 'react';
import { useGetPortal } from '../../hooks/use-portal-query';

const Style = {
Wrapper: styled.div`
Expand All @@ -28,8 +29,11 @@ const Style = {

export function PortalSideSheet({ portal, onClose }: { portal?: Portal; onClose: VoidFunction }) {
const { data: contextTypes } = useGetContextTypes();
const { data: fullPortal } = useGetPortal(portal?.id);
const [isDisabled, setOnDisabled] = useState<boolean>(false);

if (!portal || !contextTypes) return null;

return (
<SideSheet
isOpen={Boolean(portal)}
Expand All @@ -53,7 +57,7 @@ export function PortalSideSheet({ portal, onClose }: { portal?: Portal; onClose:
setOnDisabled(disabled);
}}
isSideSheet
portal={portal}
portal={{ ...portal, ...fullPortal }}
contextTypes={contextTypes}
/>
</Style.Wrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Breadcrumbs, Typography, Tabs, Tooltip, Icon } from '@equinor/eds-core-react';
import { view_list, add } from '@equinor/eds-icons';
import { view_list, add, home } from '@equinor/eds-icons';
import { Link } from 'react-router-dom';
import { TabsList, useTabs } from '../../hooks/use-tabs';
import { DataClarification } from '../DataClarification';
Expand All @@ -8,20 +8,26 @@ import { InfoPopover } from '../InfoPopover';

const Styles = HeaderStyle;

const tabs: TabsList<'portals' | 'create'> = [
const tabs: TabsList<'home' | 'portals' | 'create'> = [
{
title: 'Home',
key: 'home',
route: '/',
icon: home,
description: `Portal Admin Home page.`,
},
{
title: 'Portal List',
key: 'portals',
route: '/portals',

route: '/admin/portals',
icon: view_list,
description: `Portals are the main entry point for users to access the applications. They can be
configured with a set of applications and routes.`,
},
{
title: 'Create New Portal',
key: 'create',
route: '/portals/create',
route: '/admin/create',
icon: add,
description: 'Create a new portal to manage applications and routes.',
},
Expand Down
Loading