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

Implementation of Phase 3 & 4 #46

Merged
merged 7 commits into from
Nov 9, 2022
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
15 changes: 8 additions & 7 deletions apps/api/src/app/review/service/AJV.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class AJVService {
const errors: Record<number, any> = this.buildErrorRecords(validator.errors, data);

returnData.invalid = Object.values(errors);
// eslint-disable-next-line no-magic-numbers
Object.keys(errors).forEach((index) => (data as any).splice(index as unknown as number, 1));
}
returnData.valid = data as any;
Expand Down Expand Up @@ -128,7 +129,7 @@ export class AJVService {
message = this.getMessage(error, field);

if (acc[index]) {
acc[index].message += `${message}`;
acc[index].message += `, ${message}`;
} else
acc[index] = {
index,
Expand All @@ -143,24 +144,24 @@ export class AJVService {
let message = '';
switch (error.keyword) {
case 'type':
message = `${field} ${error.message}`;
message = error.message;
break;
case 'enum':
message = `${field} must be from [${error.params.allowedValues}]`;
message = ` must be from [${error.params.allowedValues}]`;
break;
case 'regexp':
message = `${field} must match pattern ${new RegExp(
message = ` must match pattern ${new RegExp(
error.parentSchema?.regexp?.pattern,
error.parentSchema?.regexp?.flags || ''
).toString()}`;
break;
case 'pattern':
message = `${field} must match pattern ${error.params.pattern}`;
message = ` must match pattern ${error.params.pattern}`;
break;
default:
return `${field} contains invalid data`;
return ` contains invalid data`;
}

return `<li>${message}</li>`;
return '`' + field + '`' + message;
}
}
3 changes: 3 additions & 0 deletions apps/widget/src/components/Common/Footer/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export function Footer(props: IFooterProps) {
),
[PhasesEum.CONFIRMATION]: (
<>
<Button loading={secondaryButtonLoading} onClick={onPrevClick} variant="outline">
{TEXTS.PHASE4.CLOSE}
</Button>
<Button loading={primaryButtonLoading} onClick={onNextClick}>
{TEXTS.PHASE2.UPLOAD_AGAIN}
</Button>
Expand Down
88 changes: 39 additions & 49 deletions apps/widget/src/components/widget/Phases/Phase3/Phase3.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,42 @@
import { colors, TEXTS } from '@config';
import { usePhase3 } from '@hooks/Phase3/usePhase3';
import { Download, Warning } from '@icons';
import { Group, Text } from '@mantine/core';
import { PhasesEum } from '@types';
import { Button } from '@ui/Button';
import { LoadingOverlay } from '@ui/LoadingOverlay';
import { Pagination } from '@ui/Pagination';
import { Table } from '@ui/Table';
import { Footer } from 'components/Common/Footer';
import { useRef, useState, useEffect } from 'react';
import { ConfirmModal } from '../ConfirmModal';
import useStyles from './Styles';

interface IPhase3Props {
onNextClick: () => void;
onNextClick: (count: number) => void;
onPrevClick: () => void;
}

export function Phase3(props: IPhase3Props) {
const { classes } = useStyles();
const { onNextClick, onPrevClick } = props;
const [showConfirmModal, setShowConfirmModal] = useState(false);
const {
onPageChange,
heaings,
isInitialDataLoaded,
reviewData,
page,
totalPages,
totalData,
onConfirmReview,
isConfirmReviewLoading,
} = usePhase3({ onNext: onNextClick });
const tableWrapperRef = useRef<HTMLDivElement>() as React.MutableRefObject<HTMLDivElement>;
const [tableWrapperDimensions, setTableWrapperDimentions] = useState({
height: 200,
width: 500,
});
const { classes } = useStyles();
const { onNextClick, onPrevClick } = props;

const data = Array.from({ length: 50 }).map(() => ({
index: 10,
error: '`fname` should not be empty, `email` should be unique',
fname: '',
lname: 'Doe',
surname: 'Doe',
gender: 'Male',
email: '[email protected]',
age: 20,
city: 'Surat',
country: 'India',
state: 'Gujarat',
height: '5.5ft',
weight: '45kg',
bday: '18/9/1999',
mname: 'Ramubhai',
faname: 'Bhagavanbhai',
passed: '10th',
college: 'J P Dawer',
university: 'VNSGU',
}));

useEffect(() => {
// setting wrapper height
Expand All @@ -53,8 +46,13 @@ export function Phase3(props: IPhase3Props) {
});
}, []);

const onConfirmClick = () => {
setShowConfirmModal(true);
};

return (
<>
<LoadingOverlay visible={!isInitialDataLoaded || isConfirmReviewLoading} />
<Group className={classes.textContainer} align="center">
<Group align="center" spacing="xs">
<Warning fill={colors.red} className={classes.warningIcon} />
Expand All @@ -69,34 +67,26 @@ export function Phase3(props: IPhase3Props) {
<Table
style={{
height: tableWrapperDimensions.height,
// width: tableWrapperDimensions.width,
}}
headings={[
{ key: 'index', title: '#', width: '4%' },
{ key: 'fname', title: 'First Name' },
{ key: 'lname', title: 'Last Name' },
{ key: 'surname', title: 'Surname' },
{ key: 'gender', title: 'Gender' },
{ key: 'email', title: 'Email' },
{ key: 'age', title: 'Age' },
{ key: 'city', title: 'City' },
{ key: 'country', title: 'Country' },
{ key: 'state', title: 'State' },
{ key: 'height', title: 'Height' },
{ key: 'weight', title: 'Weight' },
{ key: 'bday', title: 'Birthdate' },
{ key: 'mname', title: 'Mother Name' },
{ key: 'faname', title: 'Father Name' },
{ key: 'passed', title: 'Passed' },
{ key: 'college', title: 'College' },
{ key: 'university', title: 'University' },
]}
data={data}
headings={[{ key: 'index', title: '#', width: '4%' }, ...heaings]}
data={reviewData}
/>
</div>
<Pagination page={1} total={10} />
<Pagination page={page} total={totalPages} onChange={onPageChange} />

<Footer
primaryButtonLoading={isConfirmReviewLoading}
active={PhasesEum.REVIEW}
onNextClick={onConfirmClick}
onPrevClick={onPrevClick}
/>

<Footer active={PhasesEum.REVIEW} onNextClick={onNextClick} onPrevClick={onPrevClick} />
<ConfirmModal
onConfirm={onConfirmReview}
onClose={() => setShowConfirmModal(false)}
opened={showConfirmModal}
wrongDataCount={totalData}
/>
</>
);
}
7 changes: 4 additions & 3 deletions apps/widget/src/components/widget/Phases/Phase4/Phase4.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import useStyles from './Styles';
import { PhasesEum } from '@types';

interface IPhase4Props {
onCloseClick: () => void;
onUploadAgainClick: () => void;
rowsCount: number;
}

export function Phase4(props: IPhase4Props) {
const { classes } = useStyles();
const { rowsCount, onUploadAgainClick } = props;
const { rowsCount, onUploadAgainClick, onCloseClick } = props;

return (
<>
Expand All @@ -22,11 +23,11 @@ export function Phase4(props: IPhase4Props) {
{TEXTS.COMPLETE.greeting}&nbsp;{rowsCount}&nbsp;{TEXTS.COMPLETE.title}
</Title>
<Text size="xl" color="dimmed">
{TEXTS.COMPLETE.subTitle}
&nbsp;{rowsCount}&nbsp;{TEXTS.COMPLETE.subTitle}
</Text>
</Group>

<Footer active={PhasesEum.CONFIRMATION} onNextClick={onUploadAgainClick} onPrevClick={() => {}} />
<Footer active={PhasesEum.CONFIRMATION} onNextClick={onUploadAgainClick} onPrevClick={onCloseClick} />
</>
);
}
28 changes: 12 additions & 16 deletions apps/widget/src/components/widget/Widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,19 @@ import { Container } from './Container';
import { Phase1 } from './Phases/Phase1';
import { Phase2 } from './Phases/Phase2';
import { Phase3 } from './Phases/Phase3';
import { ConfirmModal } from './Phases/ConfirmModal';
import { PromptModal } from './Phases/PromptModal';
import { Phase4 } from './Phases/Phase4';
import { ParentWindow } from '@util';
import { PhasesEum, PromptModalTypesEnum } from '@types';
import { useQueryClient } from '@tanstack/react-query';

export function Widget() {
const defaultDataCount = 0;
const queryClient = useQueryClient();
const [phase, setPhase] = useState<PhasesEum>(PhasesEum.UPLOAD);
const [showConfirmModal, setShowConfirmModal] = useState(false);
const [dataCount, setDataCount] = useState<number>(defaultDataCount);
const [promptContinueAction, setPromptContinueAction] = useState<PromptModalTypesEnum>();

const onConfirm = () => {
setShowConfirmModal(false);
setPhase(PhasesEum.CONFIRMATION);
};
const onUploadResetClick = () => {
setPromptContinueAction(PromptModalTypesEnum.UPLOAD_AGAIN);
};
Expand All @@ -32,8 +28,8 @@ export function Widget() {
setPromptContinueAction(undefined);
};
const onClose = () => {
if (phase !== PhasesEum.UPLOAD) setPromptContinueAction(PromptModalTypesEnum.CLOSE);
else closeWidget();
if ([PhasesEum.UPLOAD, PhasesEum.CONFIRMATION].includes(phase)) closeWidget();
else setPromptContinueAction(PromptModalTypesEnum.CLOSE);
};
const closeWidget = () => {
ParentWindow.Close();
Expand All @@ -42,23 +38,23 @@ export function Widget() {
queryClient.clear();
setPhase(PhasesEum.UPLOAD);
};
const onComplete = (count: number) => {
setDataCount(count);
setPhase(PhasesEum.CONFIRMATION);
};

const PhaseView = {
[PhasesEum.UPLOAD]: <Phase1 onNextClick={() => setPhase(PhasesEum.MAPPING)} />,
[PhasesEum.MAPPING]: <Phase2 onNextClick={() => setPhase(PhasesEum.REVIEW)} onPrevClick={onUploadResetClick} />,
[PhasesEum.REVIEW]: <Phase3 onNextClick={() => setShowConfirmModal(true)} onPrevClick={onUploadResetClick} />,
[PhasesEum.CONFIRMATION]: <Phase4 rowsCount={1000000} onUploadAgainClick={onUploadResetClick} />,
[PhasesEum.REVIEW]: <Phase3 onNextClick={onComplete} onPrevClick={onUploadResetClick} />,
[PhasesEum.CONFIRMATION]: (
<Phase4 rowsCount={dataCount} onUploadAgainClick={resetProgress} onCloseClick={onClose} />
),
};

return (
<Container phase={phase} onClose={onClose}>
{PhaseView[phase]}
<ConfirmModal
onConfirm={onConfirm}
onClose={() => setShowConfirmModal(false)}
opened={showConfirmModal}
wrongDataCount={8}
/>
<PromptModal
onCancel={onPromptCancel}
onConfirm={onPromptConfirm}
Expand Down
1 change: 1 addition & 0 deletions apps/widget/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './app.config';
export * from './theme.config';
export * from './colors.config';
export * from './texts.config';
export * from './variable.config';
5 changes: 4 additions & 1 deletion apps/widget/src/config/texts.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export const TEXTS = {
EXPORT_DATA: 'Export Data',
CONFIRM_UPLOAD: 'Complete Upload',
},
PHASE4: {
CLOSE: 'Close',
},
CONFIRM_MODAL: {
title: 'rows appears to be have wrong data',
subTitle: 'You can choose to exempt wrong data or keep your wrong data and confirm your upload',
Expand All @@ -45,7 +48,7 @@ export const TEXTS = {
COMPLETE: {
greeting: 'Bravo!',
title: 'rows have been uploaded',
subTitle: '700 rows have been uploaded successfully, and currently is in process, it will be ready shortly.',
subTitle: 'rows have been uploaded successfully, and currently is in process, it will be ready shortly.',
UPLOAD_AGAIN: 'Upload new File',
},
PROMPT: {
Expand Down
3 changes: 3 additions & 0 deletions apps/widget/src/config/variable.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const variables = {
error: 'message',
};
3 changes: 2 additions & 1 deletion apps/widget/src/design-system/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Table as MantineTable } from '@mantine/core';
import useStyles from './Table.style';
import { getErrorObject } from '../../util/helpers';
import { InvalidWarning } from '../InvalidWarning';
import { variables } from '../../config';
import React from 'react';

interface IHeadingItem {
Expand Down Expand Up @@ -57,7 +58,7 @@ export function Table(props: ITableProps) {
return (
<tbody>
{data.map((item: Record<string, string>, i: number) => {
errorObject = getErrorObject(item.error);
errorObject = getErrorObject(item[variables.error]);

return (
<tr key={item.id || i}>
Expand Down
59 changes: 59 additions & 0 deletions apps/widget/src/hooks/Phase3/usePhase3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { IErrorObject, IReviewData, IUpload } from '@impler/shared';
import { useAPIState } from '@store/api.context';
import { useAppState } from '@store/app.context';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useState } from 'react';

const defaultPage = 1;

interface IUsePhase3Props {
onNext: (dataCount: number) => void;
}

export function usePhase3({ onNext }: IUsePhase3Props) {
const { api } = useAPIState();
const { uploadInfo } = useAppState();
const [page, setPage] = useState<number>(defaultPage);
const [totalPages, setTotalPages] = useState<number>(defaultPage);
const {
data: reviewData,
isLoading: isReviewDataLoading,
isFetched: isReviewDataFetched,
} = useQuery<IReviewData, IErrorObject, IReviewData, [string, number]>(
[`review`, page],
() => api.getReviewData(uploadInfo._id, page),
{
onSuccess(data) {
setPage(Number(data.page));
setTotalPages(data.totalPages);
},
}
);
const { isLoading: isConfirmReviewLoading, mutate: confirmReview } = useMutation<
IUpload,
IErrorObject,
boolean,
[string]
>([`confirm:${uploadInfo._id}`], (exemptData) => api.confirmReview(uploadInfo._id, exemptData), {
onSuccess() {
onNext(uploadInfo.totalRecords);
},
});

const onPageChange = (newPageNumber: number) => {
setPage(newPageNumber);
};

return {
page,
totalPages,
onPageChange,
onConfirmReview: confirmReview,
heaings: uploadInfo.headings.map((key: string) => ({ title: key, key })),
reviewData: reviewData?.data || [],
// eslint-disable-next-line no-magic-numbers
totalData: reviewData?.totalRecords || 0,
isConfirmReviewLoading,
isInitialDataLoaded: isReviewDataFetched && !isReviewDataLoading,
};
}
Loading