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: Add assistance description to Incident type and enhance HelpMen… #901

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions .changeset/pr-901-2241966290.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

---
"fusion-project-portal": patch
---
Add assistance description to Incident type and enhance HelpMenu with help request option.
9 changes: 9 additions & 0 deletions client/packages/components/src/components/help/HelpMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ export const HelpMenu = ({ setActiveActionById }: { setActiveActionById: (id: st
>
Report an error
</MenuItem>
<MenuItem
iconData={help_outline}
title="Send help request to service@equinor"
onClick={() => {
setActiveActionById('services');
}}
>
Need Help?
</MenuItem>
<MenuItem
iconData={launch}
title="Submit an improvement suggestion"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { useMemo, useState } from 'react';
import styled from 'styled-components';
import ActiveIncidents from './components/ActiveIncidents';
import { HelpNeeded } from './components/Help';
import { NewIncident } from './components/NewIncident';

const Styles = {
Wrapper: styled.div`
padding-top: 1rem;
padding-bottom: 1rem;
`,
};

export const ServiceNow = () => {
const [activeTab, setTab] = useState<'ActiveIncident' | 'NewIncident'>('ActiveIncident');
const [activeTab, setTab] = useState<'ActiveIncident' | 'NewIncident' | 'HelpNeeded'>('ActiveIncident');

const tabs = useMemo(
() => ({
Expand All @@ -20,6 +20,9 @@ export const ServiceNow = () => {
openNewIncident={() => {
setTab('NewIncident');
}}
openNeedHelp={() => {
setTab('HelpNeeded');
}}
/>
),
NewIncident: (
Expand All @@ -29,6 +32,13 @@ export const ServiceNow = () => {
}}
/>
),
HelpNeeded: (
<HelpNeeded
onClose={() => {
setTab('ActiveIncident');
}}
/>
),
}),
[]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Tooltip } from '@equinor/fusion-react-tooltip';
import { ActiveIncidentsList } from './ActiveIncidentsList';
import { info_circle } from '@equinor/eds-icons';
import { ActiveIncidentStateTooltip } from './ActiveIncidentStateTooltip';
import InfoMessage from './InfoMessage';

const Styles = {
Wrapper: styled.div`
Expand All @@ -21,25 +22,26 @@ const Styles = {
HeadingWrapper: styled.div`
display: flex;
justify-content: space-between;
padding-bottom: 1rem;
`,
};

type ActiveIncidentsProps = {
openNewIncident: () => void;
openNeedHelp: () => void;
};

export const ActiveIncidents = ({ openNewIncident }: ActiveIncidentsProps): JSX.Element => {
export const ActiveIncidents = ({ openNewIncident, openNeedHelp }: ActiveIncidentsProps): JSX.Element => {
return (
<Styles.Wrapper>
<Styles.HeadingWrapper>
<Typography variant="h6">My active Fusion incidents</Typography>
<Tooltip content={<ActiveIncidentStateTooltip />}>
<Icon data={info_circle} color={tokens.colors.interactive.primary__resting.hex} />
</Tooltip>
<Typography variant="h5">We are here to help</Typography>
</Styles.HeadingWrapper>
<ActiveIncidentsList />
<Divider />
<InfoMessage message="If you notice any errors or need assistance with applications, please use the forms below to submit a ticket in S@E." />
<Styles.ButtonsWrapper>
<Button color="secondary" onClick={openNeedHelp}>
I need help
</Button>
<Button color="primary" label="Report an Error" onClick={openNewIncident}>
Report an Error
</Button>
Expand All @@ -53,6 +55,14 @@ export const ActiveIncidents = ({ openNewIncident }: ActiveIncidentsProps): JSX.
Submit an improvement suggestion
</Button>
</Styles.ButtonsWrapper>
<Divider />
<Styles.HeadingWrapper>
<Typography variant="h6">My active Fusion incidents</Typography>
<Tooltip content={<ActiveIncidentStateTooltip />}>
<Icon data={info_circle} color={tokens.colors.interactive.primary__resting.hex} />
</Tooltip>
</Styles.HeadingWrapper>
<ActiveIncidentsList />
</Styles.Wrapper>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { TextField, Button, Typography, CircularProgress, Icon } from '@equinor/eds-core-react';
import { useForm, SubmitHandler } from 'react-hook-form';

import styled from 'styled-components';

import { zodResolver } from '@hookform/resolvers/zod';
import { FileUpload } from './file-upload/FileUpload';
import { useCreateServiceNowIncidents, useUploadAttachmentsServiceNowIncidents } from '../hooks/use-service-now-query';
import { useIncidentMeta } from '../hooks/use-incident-meta';

import { useEffect, useState } from 'react';
import { error_filled } from '@equinor/eds-icons';
import { MessageCard } from '@portal/ui';
import { UploadStatus } from '../types/types';
import { AttachmentsApiFailed } from './AttachmentsApiFailed';
import { AttachmentsPartialFail } from './AttachmentsPartialFail';
import { Inputs, inputSchema } from '../schema';
import InfoMessage from './InfoMessage';
import { tokens } from '@equinor/eds-tokens';

type HelpNeededProps = {
onClose: () => void;
};

const Style = {
Wrapper: styled.div`
padding-left: 0.5rem;
padding-right: 0.5rem;
`,
From: styled.form`
padding-top: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
`,
ErrorWrapper: styled.div`
padding-top: 1rem;
padding-bottom: 1rem;
`,
ButtonWrapper: styled.div`
display: flex;
gap: 1rem;
`,
};

const formatDescription = (assistanceDescription: string, detailedDescription: string) => {
return `
Type: I need help\n\nWhat were you doing and what happened?\n${assistanceDescription}\n\nDescribe as detailed as possible:\n${detailedDescription}
`;
};

export const HelpNeeded = ({ onClose }: HelpNeededProps) => {
const {
register,
handleSubmit,
formState: { errors, isSubmitting, isSubmitSuccessful },
reset,
watch,
setError,
clearErrors,
} = useForm<Inputs>({
resolver: zodResolver(inputSchema),
});

const {
data: incident,
mutateAsync: createIncident,
error: createIncidentError,
reset: resetCreate,
} = useCreateServiceNowIncidents();

const { mutateAsync: uploadFiles, error: uploadError } = useUploadAttachmentsServiceNowIncidents();

const [uploadFilesErrors, setUploadFilesErrors] = useState<UploadStatus>();

const metadata = useIncidentMeta();

const onSubmit: SubmitHandler<Inputs> = async ({ shortDescription, assistanceDescription, description, files }) => {
const incident = await createIncident({
shortDescription,
description: formatDescription(assistanceDescription, description),
metadata,
});

const filesArray = Array.from(files) as File[];
if (filesArray.length > 0 && incident?.id) {
const uploadStatus = await uploadFiles({ files: filesArray, incidentId: incident.id });
if (uploadStatus.status !== 'Error') {
setUploadFilesErrors(uploadStatus);
}
}

if (incident) {
reset();
}
};

useEffect(() => {
if (isSubmitSuccessful) {
onClose();
}
}, [isSubmitSuccessful]);

if (uploadFilesErrors?.status === 'Error' || uploadError) {
return (
<AttachmentsApiFailed
error={uploadError}
incident={incident}
failedAttachments={uploadFilesErrors?.failedUploads}
goBack={() => {
onClose();
}}
/>
);
}
if (uploadFilesErrors?.status === 'Waring') {
return (
<AttachmentsPartialFail
incident={incident}
goBack={() => {
onClose();
}}
/>
);
}

return (
<Style.Wrapper>
<Typography variant="h5"> I need help</Typography>

<InfoMessage message="Provide more details for faster, better, and more relevant support." />
{Object.values(errors).length > 0 && (
<Style.ErrorWrapper>
<MessageCard
type="Error"
title="Error submission is not valid"
messages={Object.values(errors).map((error) => error.message?.toString() || '')}
/>
</Style.ErrorWrapper>
)}

{createIncidentError && (
<Style.ErrorWrapper>
<MessageCard type="Error" title={createIncidentError.title} messages={createIncidentError.messages}>
<Style.ButtonWrapper>
<Button
onClick={() => {
reset();
resetCreate();
}}
>
Reset
</Button>
<Button onClick={() => onClose()}>Close</Button>
</Style.ButtonWrapper>
</MessageCard>
</Style.ErrorWrapper>
)}

<Style.From onSubmit={handleSubmit(onSubmit)}>
<TextField
{...register('shortDescription')}
id="textfield-short-description"
variant={errors.shortDescription && 'error'}
helperText={errors.shortDescription?.message}
inputIcon={errors.shortDescription && <Icon data={error_filled} title="Error" />}
label="Short description *"
placeholder="Ticket title, please keep short and concise"
maxLength={51}
required
/>

<TextField
{...register('description')}
id="textfield-description"
variant={errors.description && 'error'}
placeholder="Add description"
helperText={errors.description?.message}
label="What do you need assistance with? *"
inputIcon={errors.description && <Icon data={error_filled} title="Error" />}
multiline
rows={5}
required
/>
<InfoMessage message="Add images you feel might help the S@E team understand what you need help with." />
<FileUpload
title="Drop pictures here, or click browse."
acceptTitle="accept png & jpeg"
inputProps={{ ...register('files') }}
files={watch('files')}
accept="image/png, image/jpeg"
onDrop={(files) => {
clearErrors();

if (files.every((file) => file.type === 'image/jpeg' || file.type === 'image/png')) {
reset({ files });
return;
}
setError('files', {
message: 'One or more files not supported, supported file are jpeg and png',
});
}}
onRemoved={(files) => {
clearErrors();
reset({ files });
}}
/>
<Typography
group="paragraph"
variant="body_short"
color={tokens.colors.text.static_icons__tertiary.hex}
>
NB: Check that you capture the <b>entire</b> screen when uploading a screenshot
</Typography>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? <CircularProgress size={16} /> : 'Submit'}
</Button>
<Button onClick={() => onClose()}>Cancel</Button>
</Style.From>
</Style.Wrapper>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Typography } from '@equinor/eds-core-react';
import { tokens } from '@equinor/eds-tokens';
import { ReactNode } from 'react';
import styled from 'styled-components';

const Styled = {
InfoBar: styled.div`
display: flex;
padding: ${tokens.spacings.comfortable.medium_small};
flex-direction: column;
align-items: flex-start;
gap: ${tokens.spacings.comfortable.medium_small};
align-self: stretch;
border-radius: 5px;
background: ${tokens.colors.ui.background__info.rgba};
`,
Message: styled(Typography)`
color: ${tokens.colors.text.static_icons__tertiary.rgba};
`,
};

type InfoMessageProps = {
message: ReactNode;
};

export const InfoMessage = ({ message }: InfoMessageProps): JSX.Element => {
return (
<Styled.InfoBar>
<Styled.Message group="paragraph" variant="body_short">
{message}
</Styled.Message>
</Styled.InfoBar>
);
};

export default InfoMessage;
Loading