Skip to content

Commit

Permalink
Merge branch 'main' into PIMS-1349
Browse files Browse the repository at this point in the history
  • Loading branch information
Sharala-Perumal authored Mar 19, 2024
2 parents be43ad4 + 0125c53 commit bce41fc
Show file tree
Hide file tree
Showing 23 changed files with 1,062 additions and 381 deletions.
28 changes: 16 additions & 12 deletions express-api/src/controllers/agencies/agenciesController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Request, Response } from 'express';
import { NextFunction, Request, Response } from 'express';
import * as agencyService from '@/services/agencies/agencyServices';
import { AgencyFilterSchema, AgencyPublicResponseSchema } from '@/services/agencies/agencySchema';
import { z } from 'zod';
Expand All @@ -11,25 +11,29 @@ import { Roles } from '@/constants/roles';
* @param {Response} res Outgoing response
* @returns {Response} A 200 status with a list of agencies.
*/
export const getAgencies = async (req: Request, res: Response) => {
export const getAgencies = async (req: Request, res: Response, next: NextFunction) => {
/**
* #swagger.tags = ['Agencies - Admin']
* #swagger.description = 'Gets a paged list of agencies.'
* #swagger.security = [{
"bearerAuth": []
}]
*/
const kcUser = req.user as KeycloakUser;
const filter = AgencyFilterSchema.safeParse(req.query);
if (filter.success) {
const agencies = await agencyService.getAgencies(filter.data);
if (!kcUser.client_roles || !kcUser.client_roles.includes(Roles.ADMIN)) {
const trimmed = AgencyPublicResponseSchema.array().parse(agencies);
return res.status(200).send(trimmed);
try {
const kcUser = req.user as KeycloakUser;
const filter = AgencyFilterSchema.safeParse(req.query);
if (filter.success) {
const agencies = await agencyService.getAgencies(filter.data);
if (!kcUser.client_roles || !kcUser.client_roles.includes(Roles.ADMIN)) {
const trimmed = AgencyPublicResponseSchema.array().parse(agencies);
return res.status(200).send(trimmed);
}
return res.status(200).send(agencies);
} else {
return res.status(400).send('Could not parse filter.');
}
return res.status(200).send(agencies);
} else {
return res.status(400).send('Could not parse filter.');
} catch (e) {
next(e);
}
};

Expand Down
1 change: 1 addition & 0 deletions express-api/src/services/agencies/agencySchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const AgencyPublicResponseSchema = z.object({
Code: z.string(),
Description: z.string().nullable(),
IsDisabled: z.boolean(),
ParentId: z.number().int().nullable(),
});

export type Agency = z.infer<typeof AgencyCreationSchema>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ let mockRequest: Request & MockReq, mockResponse: Response & MockRes;
// const { getAgencies, addAgency, updateAgencyById, getAgencyById, deleteAgencyById } =
// controllers.admin;

const _nextFunction = jest.fn();

const _getAgencies = jest.fn().mockImplementation(() => [produceAgency()]);
const _postAgency = jest.fn().mockImplementation((agency) => agency);
const _getAgencyById = jest
Expand Down Expand Up @@ -47,13 +49,13 @@ describe('UNIT - Agencies Admin', () => {

describe('Controller getAgencies', () => {
it('should return status 200 and a list of agencies', async () => {
await controllers.getAgencies(mockRequest, mockResponse);
await controllers.getAgencies(mockRequest, mockResponse, _nextFunction);
expect(mockResponse.statusValue).toBe(200);
});

it('should return status 200 and a list of agencies', async () => {
_getKeycloakUserRoles.mockImplementationOnce(() => []);
await controllers.getAgencies(mockRequest, mockResponse);
await controllers.getAgencies(mockRequest, mockResponse, _nextFunction);
expect(mockResponse.statusValue).toBe(200);
});

Expand All @@ -63,7 +65,7 @@ describe('UNIT - Agencies Admin', () => {
parentId: '0',
id: '1',
};
await controllers.getAgencies(mockRequest, mockResponse);
await controllers.getAgencies(mockRequest, mockResponse, _nextFunction);
expect(mockResponse.statusValue).toBe(200);
});

Expand All @@ -72,7 +74,7 @@ describe('UNIT - Agencies Admin', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
name: 0 as any,
};
await controllers.getAgencies(mockRequest, mockResponse);
await controllers.getAgencies(mockRequest, mockResponse, _nextFunction);
expect(mockResponse.statusValue).toBe(400);
});
});
Expand Down
2 changes: 2 additions & 0 deletions react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"@mui/icons-material": "5.15.6",
"@mui/material": "5.15.6",
"@mui/x-data-grid": "6.19.2",
"@mui/x-date-pickers": "6.19.5",
"dayjs": "1.11.10",
"node-xlsx": "0.23.0",
"react": "18.2.0",
"react-dom": "18.2.0",
Expand Down
10 changes: 8 additions & 2 deletions react-app/src/components/dialog/DeleteDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,14 @@ const DeleteDialog = (props: IDeleteDialog) => {
<ConfirmDialog
open={open}
title={title}
onConfirm={onDelete}
onCancel={onClose}
onConfirm={async () => {
await onDelete();
setTextFieldValue('');
}}
onCancel={async () => {
await onClose();
setTextFieldValue('');
}}
confirmButtonText={deleteText ?? 'Delete'}
confirmButtonProps={{ color: 'warning', disabled: textFieldValue.toLowerCase() != 'delete' }}
>
Expand Down
2 changes: 1 addition & 1 deletion react-app/src/components/display/DataCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const DataCard = <T,>(props: DataCardProps<T>) => {
/>
<CardContent>
{props.children ??
Object.keys(values).map((key, idx) => (
Object.keys(values ?? {}).map((key, idx) => (
<React.Fragment key={`card-data-fragment-${idx}-${key}`}>
<Box display={'flex'} flexDirection={'row'}>
<Typography width={'150px'} fontWeight={'bold'}>
Expand Down
49 changes: 49 additions & 0 deletions react-app/src/components/form/BoxedIconRadio.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Box, Radio, Icon, Typography, useTheme, SxProps } from '@mui/material';
import React from 'react';

type BoxedIconRadioProps = {
onClick: React.MouseEventHandler<HTMLDivElement>;
checked: boolean;
value: string;
icon: string;
mainText: string;
subText?: string;
iconScale?: number;
boxSx?: SxProps;
};

const BoxedIconRadio = (props: BoxedIconRadioProps) => {
const { onClick, checked, value, icon, mainText, subText, iconScale, boxSx } = props;
const theme = useTheme();
return (
<Box
borderRadius={'4px'}
padding={'1.2rem'}
display={'flex'}
alignItems={'center'}
flexDirection={'row'}
gap={'1.5rem'}
onClick={onClick}
sx={{
'&:hover': {
cursor: 'pointer',
},
borderStyle: 'solid',
borderColor: checked ? theme.palette.primary.main : 'rgba(0, 0, 0, 0.23)',
borderWidth: '1px',
...boxSx,
}}
>
<Radio checked={checked} value={value} sx={{ padding: 0 }} />
<Icon>
<img height={18 * (iconScale ?? 1)} width={18 * (iconScale ?? 1)} src={icon} />
</Icon>
<Box>
<Typography>{mainText}</Typography>
<Typography color={'rgba(0, 0, 0, 0.5)'}>{subText}</Typography>
</Box>
</Box>
);
};

export default BoxedIconRadio;
29 changes: 29 additions & 0 deletions react-app/src/components/form/DateFormField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { DateField, LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';

type DateFieldFormProps = {
name: string;
label: string;
};

const DateFormField = (props: DateFieldFormProps) => {
const { control } = useFormContext();
const { name, label } = props;
return (
<Controller
control={control}
name={name}
render={({ field: { onChange, value } }) => {
return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DateField fullWidth onChange={onChange} value={value} label={label} format={'LL'} />
</LocalizationProvider>
);
}}
/>
);
};

export default DateFormField;
2 changes: 1 addition & 1 deletion react-app/src/components/form/SelectFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface ISelectMenuItem {

interface ISelectInputProps {
name: string;
label: string;
label: string | JSX.Element;
required: boolean;
options: ISelectMenuItem[];
}
Expand Down
50 changes: 41 additions & 9 deletions react-app/src/components/form/TextFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
import React from 'react';
import { TextField, TextFieldProps } from '@mui/material';
import { useFormContext } from 'react-hook-form';
import { Controller, FieldValues, RegisterOptions, useFormContext } from 'react-hook-form';

const TextFormField = (props: TextFieldProps) => {
const { register, formState } = useFormContext();
const { name } = props;
type TextFormFieldProps = {
defaultVal?: string;
name: string;
label: string;
numeric?: boolean;
rules?: Omit<
RegisterOptions<FieldValues, string>,
'disabled' | 'valueAsNumber' | 'valueAsDate' | 'setValueAs'
>;
} & TextFieldProps;

const TextFormField = (props: TextFormFieldProps) => {
const { control } = useFormContext();
const { name, label, rules, numeric, defaultVal, ...restProps } = props;
return (
<TextField
{...props}
{...register(name, { required: props.required })}
error={!!formState.errors?.[name]}
helperText={formState.errors?.[name] ? 'This field is required.' : undefined}
<Controller
control={control}
name={name}
rules={{ required: props.required ? 'Required field.' : undefined, ...rules }}
render={({ field: { onChange, value }, fieldState: { error } }) => {
return (
<TextField
{...restProps}
onChange={(event) => {
if (numeric === undefined) {
onChange(event);
return;
}
if (event.target.value === '' || /^[0-9]*\.?[0-9]*$/.test(event.target.value)) {
onChange(event);
}
}}
value={value ?? defaultVal}
fullWidth
label={label}
type="text"
error={!!error && !!error.message}
helperText={error?.message}
/>
);
}}
/>
);
};
Expand Down
Loading

0 comments on commit bce41fc

Please sign in to comment.