Skip to content

Commit

Permalink
feat: Add assistance description to Incident type and enhance HelpMen…
Browse files Browse the repository at this point in the history
…u with help request option
  • Loading branch information
Noggling committed Dec 18, 2024
1 parent 9b0c0a4 commit 3108c1e
Show file tree
Hide file tree
Showing 11 changed files with 410 additions and 30 deletions.
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

0 comments on commit 3108c1e

Please sign in to comment.