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 toast after file upload RFI template 1 and 2 #3353

Merged
Show file tree
Hide file tree
Changes from all 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
34 changes: 34 additions & 0 deletions app/components/Analyst/RFI/RFIAnalystUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ import styled from 'styled-components';
import { useCreateNewFormDataMutation } from 'schema/mutations/application/createNewFormData';
import useHHCountUpdateEmail from 'lib/helpers/useHHCountUpdateEmail';
import useRfiCoverageMapKmzUploadedEmail from 'lib/helpers/useRfiCoverageMapKmzUploadedEmail';
import { useToast } from 'components/AppProvider';
import Link from 'next/link';

const Flex = styled('header')`
display: flex;
justify-content: space-between;
width: 100%;
`;

const StyledLink = styled(Link)`
color: ${(props) => props.theme.color.white};
`;

const RfiAnalystUpload = ({ query }) => {
const queryFragment = useFragment(
graphql`
Expand Down Expand Up @@ -52,6 +58,7 @@ const RfiAnalystUpload = ({ query }) => {
rowId: applicationId,
ccbcNumber,
} = applicationByRowId;
const { showToast, hideToast } = useToast();

const { rfiNumber } = rfiDataByRowId;

Expand Down Expand Up @@ -94,9 +101,30 @@ const RfiAnalystUpload = ({ query }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [templateData]);

const getToastMessage = () => {
return (
<>
{' '}
`Template {templateData?.templateNumber} data changed successfully, new
values for{' '}
{templateData?.templateNumber === 1
? 'Total Households and Indigenous Households'
: 'Total eligible costs and Total project costs'}{' '}
data in the application now reflect template uploads. Please see{' '}
<StyledLink
href={`/analyst/application/${router.query.applicationId}/history`}
>
history page
</StyledLink>{' '}
for details.`
</>
);
};

const handleSubmit = () => {
const updatedExcelFields = excelImportFields.join(', ');
const reasonForChange = `Auto updated from upload of ${updatedExcelFields} for RFI: ${rfiNumber}`;
hideToast();
updateRfi({
variables: {
input: {
Expand Down Expand Up @@ -129,6 +157,12 @@ const RfiAnalystUpload = ({ query }) => {
}
);
}
if (
templateData?.templateNumber === 1 ||
templateData?.templateNumber === 2
) {
showToast(getToastMessage(), 'success', 100000000);
}
},
});
}
Expand Down
63 changes: 63 additions & 0 deletions app/components/AppProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, {
createContext,
useContext,
useState,
useCallback,
useMemo,
ReactNode,
} from 'react';
import Toast, { ToastType } from 'components/Toast';

type AppContextType = {
showToast?: (message: ReactNode, type?: ToastType, timeout?: number) => void;
hideToast?: () => void;
};

const AppContext = createContext<AppContextType>({});

export const useToast = () => {
return useContext(AppContext);
};

export const AppProvider = ({ children }) => {
/**
* handling global toast messages
*/
const [toast, setToast] = useState<{
visible: boolean;
message?: ReactNode;
type?: ToastType;
timeout?: number;
}>({ visible: false });

const showToast = useCallback(
(
message: ReactNode,
type: ToastType = 'success',
timeout: number = 10000
) => {
setToast({ visible: true, message, type, timeout });
},
[]
);

const hideToast = useCallback(() => {
setToast({ visible: false });
}, []);

const contextValue = useMemo(
() => ({ showToast, hideToast }),
[showToast, hideToast]
);

return (
<AppContext.Provider value={contextValue}>
{children}
{toast?.visible && (
<Toast type={toast?.type} onClose={hideToast} timeout={toast.timeout}>
{toast.message}
</Toast>
)}
</AppContext.Provider>
);
};
2 changes: 1 addition & 1 deletion app/components/Toast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { theme } from 'styles/GlobalTheme';

type ToastType = 'success' | 'warning' | 'error';
export type ToastType = 'success' | 'warning' | 'error';

type ToastDirection = 'left' | 'right';

Expand Down
37 changes: 37 additions & 0 deletions app/lib/theme/widgets/FileWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import bytesToSize from 'utils/bytesToText';
import FileComponent from 'lib/theme/components/FileComponent';
import useDisposeOnRouteChange from 'lib/helpers/useDisposeOnRouteChange';
import { DateTime } from 'luxon';
import { useToast } from 'components/AppProvider';
import { ToastType } from 'components/Toast';

type File = {
id: string | number;
Expand Down Expand Up @@ -65,6 +67,7 @@ const FileWidget: React.FC<FileWidgetProps> = ({
const maxFileSizeInBytes = 104857600;
const fileId = isFiles && value[0].id;
const { setTemplateData } = formContext;
const { showToast, hideToast } = useToast();

useEffect(() => {
if (rawErrors?.length > 0) {
Expand All @@ -73,6 +76,7 @@ const FileWidget: React.FC<FileWidgetProps> = ({
}, [rawErrors, setErrors]);

const getValidatedFile = async (file: any, formId: number) => {
let isTemplateValid = true;
if (templateValidate) {
const fileFormData = new FormData();
if (file) {
Expand All @@ -92,6 +96,8 @@ const FileWidget: React.FC<FileWidgetProps> = ({
data,
});
});
} else {
isTemplateValid = false;
}
});
}
Expand All @@ -109,6 +115,7 @@ const FileWidget: React.FC<FileWidgetProps> = ({
}

return {
isTemplateValid,
input: {
attachment: {
file,
Expand Down Expand Up @@ -153,6 +160,19 @@ const FileWidget: React.FC<FileWidgetProps> = ({
});
};

const showToastMessage = (files, type: ToastType = 'success') => {
const fields =
templateNumber === 1
? 'Total Households and Indigenous Households data'
: 'Total eligible costs and Total project costs data';
const message =
type === 'success'
? `Template ${templateNumber} validation successful, new values for ${fields} data in the application will update upon 'Save'`
: `Template ${templateNumber} validation failed: ${files.join(', ')} did not validate due to formatting issues. ${fields} in the application will not update.`;

showToast(message, type, 100000000);
};

const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const transaction = Sentry.startTransaction({ name: 'ccbc.function' });
const span = transaction.startChild({
Expand All @@ -161,6 +181,7 @@ const FileWidget: React.FC<FileWidgetProps> = ({
});

if (loading) return;
hideToast();
const formId =
parseInt(router?.query?.id as string, 10) ||
parseInt(router?.query?.applicationId as string, 10);
Expand All @@ -177,6 +198,8 @@ const FileWidget: React.FC<FileWidgetProps> = ({
const validatedFiles = resp.filter((file) => file.input);
setErrors(resp.filter((file) => file.error));

const validationErrors = resp.filter((file) => !file.isTemplateValid);

const uploadResponse = await Promise.all(
validatedFiles.map(async (payload) => handleUpload(payload))
);
Expand All @@ -190,6 +213,20 @@ const FileWidget: React.FC<FileWidgetProps> = ({
} else {
span.setStatus('ok');
}

if (templateValidate) {
if (validationErrors.length > 0) {
showToastMessage(
validationErrors.map(
(error) => error.fileName || error.input?.attachment?.fileName
),
'error'
);
} else if (validationErrors.length === 0 && uploadErrors.length === 0) {
showToastMessage(fileDetails.map((file) => file.name));
}
}

span.finish();
transaction.finish();

Expand Down
7 changes: 5 additions & 2 deletions app/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import GlobalStyle from 'styles/GobalStyles';
import GlobalTheme from 'styles/GlobalTheme';
import BCGovTypography from 'components/BCGovTypography';
import { SessionExpiryHandler } from 'components';
import { AppProvider } from 'components/AppProvider';

config.autoAddCss = false;

Expand Down Expand Up @@ -94,8 +95,10 @@ const MyApp = ({ Component, pageProps }: AppProps) => {
<BCGovTypography />
<Sentry.ErrorBoundary fallback={<Error500 />}>
<RelayEnvironmentProvider environment={env}>
{typeof window !== 'undefined' && <SessionExpiryHandler />}
{component}
<AppProvider>
{typeof window !== 'undefined' && <SessionExpiryHandler />}
{component}
</AppProvider>
</RelayEnvironmentProvider>
</Sentry.ErrorBoundary>
</ThemeProvider>
Expand Down
Loading
Loading