Skip to content

Commit

Permalink
feat: [P4ADEV-2011] file uploader (#49)
Browse files Browse the repository at this point in the history
  • Loading branch information
EmanueleValentini1 authored Feb 6, 2025
2 parents 830a60f + c95fcf7 commit 98d6f4e
Show file tree
Hide file tree
Showing 19 changed files with 336 additions and 248 deletions.
45 changes: 20 additions & 25 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,19 @@ import TelematicReceiptSearchResults from './routes/TelematicReceiptSearchResult
import TelematicReceipt from './routes/TelematicReceipt';
import TelematicReceiptDetail from './routes/TelematicReceiptDetail';
import TelematicReceiptExportFlowThankYouPage from './routes/TelematicReceiptExportFlowThankYouPage';
import TelematicReceiptFlowImport from './routes/TelematicReceiptFlowImport';
import TelematicReceiptFlowImportThankYouPage from './routes/TelematicReceiptFlowImportThankYouPage';
import TelematicReceiptImportFlowOverview from './routes/TelematicReceiptImportFlowOverview';
import ReportingSearchResults from './routes/ReportingSearchResults';
import Reporting from './routes/Reporting';
import ReportingImportFlowOverview from './routes/ReportingImportFlowOverview';
import ReportingFlowImport from './routes/ReportingFlowImport';
import ReportingFlowImportThankYouPage from './routes/ReportingFlowImportThankYouPage';
import ReportingDetail from './routes/ReportingDetail';
import Treasury from './routes/Treasury';
import TreasuryImportFlowOverview from './routes/TreasuryImportFlowOverview';

import { Overlay } from './components/Overlay';
import { useStore } from './store/GlobalStore';


import ImportFlow from './routes/ImportFlowPage';

const router = createBrowserRouter([
{
Expand Down Expand Up @@ -155,16 +152,6 @@ const router = createBrowserRouter([
backButton: true,
} as RouteHandleObject,
},
{
path: PageRoutesConf.TELEMATIC_RECEIPT.children?.IMPORT_FLOW.path,
element: <TelematicReceiptFlowImport />,
handle: {
backButton: true,
sidebar: {
visibile: false
},
} as RouteHandleObject,
},
{
path: PageRoutesConf.TELEMATIC_RECEIPT.children?.IMPORT_FLOW_THANK_YOU_PAGE.path,
element: <TelematicReceiptFlowImportThankYouPage />,
Expand Down Expand Up @@ -226,16 +213,6 @@ const router = createBrowserRouter([
backButton: true
}) as RouteHandleObject,
},
{
path: PageRoutesConf.REPORTING.children?.IMPORT_FLOW.path,
element: <ReportingFlowImport />,
handle: {
backButton: true,
sidebar: {
visibile: false
},
} as RouteHandleObject,
},
{
path: PageRoutesConf.REPORTING.children?.IMPORT_FLOW_THANK_YOU_PAGE.path,
element: <ReportingFlowImportThankYouPage />,
Expand Down Expand Up @@ -297,8 +274,26 @@ const router = createBrowserRouter([
} as RouteHandleObject,
},
]
}
},
/* -- END - TREASURY SECTION -- */
/* -- IMPORT SECTION -- */
{
path: PageRoutesConf.IMPORT.path,
element: <Layout />,
handle: {
backButton: true,
sidebar: {
visible: false
}
} as RouteHandleObject,
children: [
{
path: PageRoutesConf.IMPORT.children?.FLOWS.path,
element: <ImportFlow />,
}
]
}
/* -- END - IMPORT SECTION -- */
]
}
]);
Expand Down
4 changes: 2 additions & 2 deletions src/components/FileUploader/FileUploader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('FileUploader Component', () => {
file: null,
setFile: mockSetFile,
description: 'Drag and drop or upload a file',
requiredFieldText: 'This field is required',
requiredFileText: 'This field is required',
fileExtensionsAllowed: ['zip'],
};

Expand All @@ -40,7 +40,7 @@ describe('FileUploader Component', () => {

expect(screen.getByText('commons.files.file')).toBeDefined();
expect(screen.getByText(defaultProps.description)).toBeDefined();
expect(screen.getByText(defaultProps.requiredFieldText)).toBeDefined();
expect(screen.getByText(defaultProps.requiredFileText)).toBeDefined();
expect(screen.getByTestId('drop-zone')).toBeDefined();
expect(screen.getByText('commons.files.upload')).toBeDefined();
});
Expand Down
6 changes: 3 additions & 3 deletions src/components/FileUploader/FileUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type FileUploaderProps = {
file: File | null;
setFile: React.Dispatch<React.SetStateAction<File | null>>;
description: string;
requiredFieldText: string;
requiredFileText: string;
fileExtensionsAllowed: string[];
};

Expand All @@ -34,7 +34,7 @@ const FileUploader = ({
file,
setFile,
description,
requiredFieldText,
requiredFileText,
fileExtensionsAllowed,
}: FileUploaderProps) => {
const { t } = useTranslation();
Expand Down Expand Up @@ -178,7 +178,7 @@ const FileUploader = ({
</Button>
</Box>
<Typography variant="body2" mt={2} color="textSecondary">
{requiredFieldText}
{requiredFileText}
</Typography>
</>
)}
Expand Down
114 changes: 114 additions & 0 deletions src/components/ImportFlowPage/ImportFlowPage.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, it, vi, expect, beforeEach } from 'vitest';
import { fireEvent, render, screen, within } from '@testing-library/react';
import ImportFlow from './ImportFlowPage';
import { useParams } from 'react-router-dom';

vi.mock('react-router-dom', () => ({
useNavigate: vi.fn(),
useParams: vi.fn(),
}));

describe('ImportFlow', () => {
const mockUseParams = vi.mocked(useParams);

beforeEach(() => {
vi.clearAllMocks();
});

describe('no select Config', () => {
beforeEach(() => {
mockUseParams.mockReturnValue({ category: 'reporting' });
});

it('renders without select', () => {
render(<ImportFlow/>);

expect(screen.getByText('commons.routes.REPORTING_IMPORT_FLOW')).toBeDefined();
expect(screen.getByText('commons.flowImport.description')).toBeDefined();
expect(screen.getByText('commons.flowImport.boxTitle')).toBeDefined();
expect(screen.getByText('commons.flowImport.boxDescription')).toBeDefined();
expect(screen.getByText('commons.flowImport.manualLink')).toBeDefined();
expect(screen.queryByText('commons.requiredFieldDescription')).toBeNull();
expect(screen.queryByLabelText('commons.flowType')).toBeNull();
});

it('should enable button when a file is uploaded', async () => {

render(<ImportFlow />);

const file = new File(['content'], 'test.zip', { type: 'application/zip' });
const dropZone = screen.getByTestId('drop-zone');

fireEvent.dragOver(dropZone);
fireEvent.drop(dropZone, {
dataTransfer: {
files: [file]
}
});

await vi.waitFor(() => expect(screen.getAllByText('test.zip')).toBeDefined());
const successButton = screen.getByTestId('success-button');

expect(successButton).toHaveProperty('disabled', false);
});
});

describe('select config', () => {
beforeEach(() => {
mockUseParams.mockReturnValue({ category: 'treasury' });
});

it('renders with select', () => {
render(<ImportFlow />);

expect(screen.getByText('commons.routes.TREASURY_IMPORT_FLOW')).toBeDefined();
expect(screen.getByText('commons.requiredFieldDescription')).toBeDefined();
expect(screen.getByRole('select-flowType')).toBeDefined();
expect(screen.getByTestId('success-button')).toHaveProperty('disabled', true);
});

it('should show all flow type options when select is clicked', () => {
render(<ImportFlow />);

const selectCombo = screen.getByRole('combobox', { name: 'commons.flowType' });
fireEvent.mouseDown(selectCombo);

const listbox = within(screen.getByRole('listbox'));

const options = [
'Giornale di Cassa XLS',
'Giornale di Cassa CSV',
'Giornale di Cassa OPI',
'Estrato conto poste'
];

options.forEach(option => {
expect(listbox.getByText(option)).toBeDefined();
});
});
it('should enable button when a file is uploaded and a flow type is selected', async () => {

render(<ImportFlow />);

const file = new File(['content'], 'test.zip', { type: 'application/zip' });
const dropZone = screen.getByTestId('drop-zone');

fireEvent.dragOver(dropZone);
fireEvent.drop(dropZone, {
dataTransfer: {
files: [file]
}
});

await vi.waitFor(() => expect(screen.getAllByText('test.zip')).toBeDefined());

const selectCombo = screen.getByRole('combobox', { name: 'commons.flowType' });
fireEvent.mouseDown(selectCombo);

const firstOption = within(screen.getByRole('listbox')).getAllByRole('option')[0];
fireEvent.click(firstOption);

expect(screen.getByTestId('success-button')).toHaveProperty('disabled', false);
});
});
});
119 changes: 119 additions & 0 deletions src/components/ImportFlowPage/ImportFlowPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { Box, Button, FormControl, Grid, InputLabel, MenuItem, Select, Typography } from '@mui/material';
import { theme } from '@pagopa/mui-italia';
import FileUploader from '../FileUploader/FileUploader';
import { useTranslation } from 'react-i18next';
import { AltRoute, ArrowBack } from '@mui/icons-material';
import { useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import TitleComponent from '../TitleComponent/TitleComponent';
import { importFlowConfig } from '../../models/ImportDetails';

const ImportFlow = () => {

const { t } = useTranslation();
const navigate = useNavigate();
const { category } = useParams<{category: string}>();

const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [flowType, setFlowType] = useState('');
const [file, setFile] = useState<File | null>(null);

const config = importFlowConfig[category as keyof typeof importFlowConfig];

const handleDisabledButton = () => {
const defaultCondition = uploading || !file;
return config?.flowTypes ? (!flowType || defaultCondition) : defaultCondition;
};

return (
<>
<Grid container direction="column" alignItems="center" marginTop={2}>
<Grid container direction="column" alignItems="left" marginTop={2} ml={1} mb={4}>
<TitleComponent
title={t(config.title)}
description={t('commons.flowImport.description')}
/>
<Box bgcolor={theme.palette.common.white} borderRadius={0.5} p={3} gap={3}>
<Grid item lg={12} mb={2}>
<Grid item lg={12} mb={2}>
<Typography variant='h6' gutterBottom>{t('commons.flowImport.boxTitle')}</Typography>
<Typography variant='caption' gutterBottom>{t('commons.flowImport.boxDescription')}</Typography>
</Grid>
<Button variant='naked' size='small'>{t('commons.flowImport.manualLink')}</Button>
</Grid>
{config?.requiredFieldDescription &&
<Typography variant="caption" mb={3} display={'block'} sx={{ color: theme.palette.error.dark }}>
{t(config.requiredFieldDescription)}
</Typography>
}
<Box borderRadius={1} border={1} p={3} gap={2} borderColor={theme.palette.divider}>
<FileUploader
uploading={uploading}
setUploading={setUploading}
progress={progress}
setProgress={setProgress}
file={file} setFile={setFile}
description={t('FileUploaderFlowImport.description')}
requiredFileText={t('FileUploaderFlowImport.requiredFileText')}
fileExtensionsAllowed={config.fileExtensionsAllowed}
/>
</Box>
{config?.flowTypes &&
<Box borderRadius={1} border={1} p={3} gap={2} mt={3} borderColor={theme.palette.divider}>
<Grid container direction={'row'} mb={3}>
<AltRoute sx={{ transform: 'rotate(90deg)' }}/>
<Typography fontWeight={600} ml={1}>
{t('commons.flowType')}
</Typography>
</Grid>
<FormControl role='select-flowType' required fullWidth size="small">
<InputLabel id='select-label'>{t('commons.flowType')}</InputLabel>
<Select
value={flowType}
labelId='select-label'
label={t('commons.flowType')}
onChange={(event) => setFlowType(event.target.value)}
>
{config.flowTypes.map((option, i) => (
<MenuItem key={i} value={option}>
{option}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
}
</Box>
</Grid>
</Grid>
<Grid container direction={'row'} justifyContent={'space-between'} ml={1}>
<Grid item>
<Button
size="large"
variant="outlined"
fullWidth
startIcon={<ArrowBack />}
onClick={() => navigate(config.backRoute) }
>
{t('commons.exit')}
</Button>
</Grid>
<Grid item>
<Button
data-testid="success-button"
size="large"
variant="contained"
fullWidth
disabled = {handleDisabledButton()}
onClick={() => navigate(config.successRoute) }
>
{t('commons.flowImport.uploadButton')}
</Button>
</Grid>
</Grid>
</>
);
};

export default ImportFlow;
4 changes: 2 additions & 2 deletions src/components/Reporting/Reporting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CalendarToday, FileUpload, Search } from '@mui/icons-material';
import { Grid } from '@mui/material';
import { useTranslation } from 'react-i18next';
import TitleComponent from '../TitleComponent/TitleComponent';
import { useNavigate } from 'react-router';
import { generatePath, useNavigate } from 'react-router';
import { PageRoutes } from '../../routes/routes';

export const Reporting = () => {
Expand Down Expand Up @@ -44,7 +44,7 @@ export const Reporting = () => {
actionLabel={t('reporting.importFlowButton')}
actionIcon={<FileUpload/>}
linkLabel={t('reporting.showAllFlows')}
onActionClick={() => navigate(PageRoutes.REPORTING_IMPORT_FLOW)}
onActionClick={() => navigate(generatePath(PageRoutes.IMPORT_FLOWS, {category: 'reporting'}))}
onLinkClick={() => navigate(PageRoutes.REPORTING_IMPORT_OVERVIEW)}
/>
</Grid>
Expand Down
Loading

0 comments on commit 98d6f4e

Please sign in to comment.