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

fix intervention dto in creation when location type changed #1082

Merged
merged 1 commit into from
Oct 17, 2023
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
167 changes: 4 additions & 163 deletions client/src/containers/interventions/form/component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { useForm, Controller } from 'react-hook-form';
import { RadioGroup, Disclosure } from '@headlessui/react';
import { yupResolver } from '@hookform/resolvers/yup';
import { PlusIcon, MinusIcon } from '@heroicons/react/solid';
import * as yup from 'yup';
import classNames from 'classnames';
import { sortBy, omit } from 'lodash-es';
import toast from 'react-hot-toast';

import { InterventionTypes, LocationTypes, InfoTooltip } from '../enums';

import InterventionTypeIcon from './intervention-type-icon';
import schemaValidation from './schema-validation';

import { useIndicators } from 'hooks/indicators';
import { useSuppliersTypes, useUnknowSupplier } from 'hooks/suppliers';
Expand All @@ -33,6 +33,7 @@ import { recursiveMap, recursiveSort } from 'components/tree-select/utils';

import type { Option } from 'components/forms/select';
import type { Intervention, InterventionFormData } from '../types';
import type { SubSchema } from './types';

const DISABLED_LOCATION_TYPES = [LocationTypes.unknown, LocationTypes.countryOfDelivery];

Expand All @@ -43,173 +44,13 @@ type InterventionFormProps = {
onSubmit?: (interventionFormData: InterventionFormData) => void;
};

const optionSchema = yup
.object({
label: yup.string(),
value: yup.string(),
})
.default(undefined);

const locationTypeSchema = yup
.object({
label: yup.string().nullable(),
value: yup.mixed<LocationTypes>(),
})
.default(undefined);

const schemaValidation = yup.object({
title: yup.string().label('Title').max(60).required(),
volume: yup.number().optional().typeError('Volume should be a number'),
interventionType: yup
.string()
.label('Intervention type')
.required('Type of intervention is required'),
startYear: yup
.object({
label: yup.string(),
value: yup.number(),
})
.label('Start year')
.required()
.typeError('Start should be a number'),
endYear: yup
.object({
label: yup.string(),
value: yup.number(),
})
.label('End year')
.optional()
.typeError('Start should be a number'),
percentage: yup
.number()
.label('Percentage')
.moreThan(0)
.max(100)
.required()
.typeError('Percentage should be a number greater than 0 and less or equal than 100'),
scenarioId: yup.string().label('Scenario ID').required(),

// Filters
materialIds: yup.array().label('Material IDs').of(optionSchema).required(),
businessUnitIds: yup.array().label('Business Unit IDs').of(optionSchema),
t1SupplierIds: yup.array().label('T1 Supplier IDs').of(optionSchema),
producerIds: yup.array().label('Producer IDs').of(optionSchema),
adminRegionIds: yup.array().label('Admin region IDs').of(optionSchema),

// Supplier
newT1SupplierId: optionSchema.label('New T1 Supplier ID').required(),
newProducerId: optionSchema.label('New producer ID').required(),

// Location
newLocationType: locationTypeSchema.label('New location type').when('interventionType', {
is: (interventionType: InterventionTypes) => {
return [InterventionTypes.Material, InterventionTypes.SupplierLocation].includes(
interventionType,
);
},
then: locationTypeSchema.required(),
otherwise: locationTypeSchema.notRequired(),
}),
newLocationCountryInput: optionSchema.label('New location Country').when('interventionType', {
is: (interventionType: InterventionTypes) =>
[InterventionTypes.Material, InterventionTypes.SupplierLocation].includes(interventionType),
then: (schema) => schema.required('Country field is required'),
otherwise: (schema) => schema.nullable(),
}),

cityAddressCoordinates: yup
.string()
.label('City, addres or coordinates')
.when('newLocationType', {
is: (newLocationType) =>
[LocationTypes.aggregationPoint, LocationTypes.pointOfProduction].includes(
newLocationType?.value,
),
then: (schema) =>
schema
.test('is-coordinates', 'Coordinates should be valid (-90/90, -180/180)', (value) => {
if (!isCoordinates(value)) {
return true;
}
const [lat, lng] = value.split(',').map((coordinate) => parseFloat(coordinate));
return lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180;
})
.required('City, address or coordinates is required'),
otherwise: (schema) => schema.nullable(),
}),

// location region
newLocationAdminRegionInput: optionSchema.when('newLocationType', {
is: (newLocationType) =>
[LocationTypes.administrativeRegionOfProduction].includes(newLocationType?.value),
then: (schema) => schema.required('Country region is required').nullable(),
otherwise: (schema) => schema.nullable(),
}),

// New material
newMaterialId: yup
.array()
.label('New material')
.of(optionSchema)
.when('interventionType', (interventionType) => {
if (InterventionTypes.Material === interventionType) {
return yup.array().of(optionSchema).required('New material field is required');
}

return yup.array().of(optionSchema).nullable();
}),
newLocationAddressInput: yup.string().label('Address').nullable(),
newLocationLongitude: yup
.number()
.label('Longitude')
.when('newLocationType', {
is: (newLocationType) =>
[LocationTypes.aggregationPoint, LocationTypes.pointOfProduction].includes(
newLocationType?.value,
),
then: (schema) => schema.min(-180).max(180).required('Longitude field is required'),
otherwise: (schema) => schema.nullable(),
})
.typeError('Longitude should be a number'),
newLocationLatitude: yup
.number()
.label('Latitude')
.when('newLocationType', {
is: (newLocationType) =>
[LocationTypes.aggregationPoint, LocationTypes.pointOfProduction].includes(
newLocationType?.value,
),
then: (schema) => schema.min(-90).max(90).required('Latitude field is required'),
otherwise: (schema) => schema.nullable(),
})
.typeError('Latitude should be a number'),

// Coefficients
coefficients: yup.lazy((coefficientObject = {}) => {
const schema = Object.keys(coefficientObject).reduce(
(prevValue, currentValue) => ({
...prevValue,
[currentValue]: yup.lazy((v) => {
if (v === '') return yup.string().required('This coefficient is required.');

return yup.number().typeError('Coefficient should be a number');
}),
}),
{},
);
return yup.object(schema);
}),
});

const LABEL_CLASSNAMES = 'text-sm';

const TYPES_OF_INTERVENTIONS = Object.values(InterventionTypes).map((interventionType) => ({
value: interventionType,
label: interventionType,
}));

type SubSchema = yup.InferType<typeof schemaValidation>;

const InterventionForm: React.FC<InterventionFormProps> = ({
intervention,
isSubmitting,
Expand Down Expand Up @@ -524,8 +365,8 @@ const InterventionForm: React.FC<InterventionFormProps> = ({
].includes(locationType?.value)
) {
resetField('cityAddressCoordinates', { defaultValue: null });
resetField('newLocationLatitude', { defaultValue: 0 });
resetField('newLocationLongitude', { defaultValue: 0 });
resetField('newLocationLatitude', { defaultValue: null });
resetField('newLocationLongitude', { defaultValue: null });
}
}, [locationType, resetField, setValue]);

Expand Down
165 changes: 165 additions & 0 deletions client/src/containers/interventions/form/schema-validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import * as yup from 'yup';

import { InterventionTypes, LocationTypes } from '../enums';

import { isCoordinates } from 'utils/coordinates';

const optionSchema = yup
.object({
label: yup.string(),
value: yup.string(),
})
.default(undefined);

const locationTypeSchema = yup
.object({
label: yup.string().nullable(),
value: yup.mixed<LocationTypes>(),
})
.default(undefined);

const schemaValidation = yup.object({
title: yup.string().label('Title').max(60).required(),
volume: yup.number().optional().typeError('Volume should be a number'),
interventionType: yup
.string()
.label('Intervention type')
.required('Type of intervention is required'),
startYear: yup
.object({
label: yup.string(),
value: yup.number(),
})
.label('Start year')
.required()
.typeError('Start should be a number'),
endYear: yup
.object({
label: yup.string(),
value: yup.number(),
})
.label('End year')
.optional()
.typeError('Start should be a number'),
percentage: yup
.number()
.label('Percentage')
.moreThan(0)
.max(100)
.required()
.typeError('Percentage should be a number greater than 0 and less or equal than 100'),
scenarioId: yup.string().label('Scenario ID').required(),

// Filters
materialIds: yup.array().label('Material IDs').of(optionSchema).required(),
businessUnitIds: yup.array().label('Business Unit IDs').of(optionSchema),
t1SupplierIds: yup.array().label('T1 Supplier IDs').of(optionSchema),
producerIds: yup.array().label('Producer IDs').of(optionSchema),
adminRegionIds: yup.array().label('Admin region IDs').of(optionSchema),

// Supplier
newT1SupplierId: optionSchema.label('New T1 Supplier ID').required(),
newProducerId: optionSchema.label('New producer ID').required(),

// Location
newLocationType: locationTypeSchema.label('New location type').when('interventionType', {
is: (interventionType: InterventionTypes) => {
return [InterventionTypes.Material, InterventionTypes.SupplierLocation].includes(
interventionType,
);
},
then: locationTypeSchema.required(),
otherwise: locationTypeSchema.notRequired(),
}),
newLocationCountryInput: optionSchema.label('New location Country').when('interventionType', {
is: (interventionType: InterventionTypes) =>
[InterventionTypes.Material, InterventionTypes.SupplierLocation].includes(interventionType),
then: (schema) => schema.required('Country field is required'),
otherwise: (schema) => schema.nullable(),
}),

cityAddressCoordinates: yup
.string()
.label('City, addres or coordinates')
.when('newLocationType', {
is: (newLocationType) =>
[LocationTypes.aggregationPoint, LocationTypes.pointOfProduction].includes(
newLocationType?.value,
),
then: (schema) =>
schema
.test('is-coordinates', 'Coordinates should be valid (-90/90, -180/180)', (value) => {
if (!isCoordinates(value)) {
return true;
}
const [lat, lng] = value.split(',').map((coordinate) => parseFloat(coordinate));
return lat >= -90 && lat <= 90 && lng >= -180 && lng <= 180;
})
.required('City, address or coordinates is required'),
otherwise: (schema) => schema.nullable(),
}),

// location region
newLocationAdminRegionInput: optionSchema.when('newLocationType', {
is: (newLocationType) =>
[LocationTypes.administrativeRegionOfProduction].includes(newLocationType?.value),
then: (schema) => schema.required('Country region is required').nullable(),
otherwise: (schema) => schema.nullable(),
}),

// New material
newMaterialId: yup
.array()
.label('New material')
.of(optionSchema)
.when('interventionType', (interventionType) => {
if (InterventionTypes.Material === interventionType) {
return yup.array().of(optionSchema).required('New material field is required');
}

return yup.array().of(optionSchema).nullable();
}),
newLocationAddressInput: yup.string().label('Address').nullable(),
newLocationLongitude: yup
.number()
.label('Longitude')
.when('newLocationType', {
is: (newLocationType) =>
[LocationTypes.aggregationPoint, LocationTypes.pointOfProduction].includes(
newLocationType?.value,
),
then: (schema) => schema.min(-180).max(180).required('Longitude field is required'),
otherwise: (schema) => schema.nullable(),
})
.typeError('Longitude should be a number'),
newLocationLatitude: yup
.number()
.label('Latitude')
.when('newLocationType', {
is: (newLocationType) =>
[LocationTypes.aggregationPoint, LocationTypes.pointOfProduction].includes(
newLocationType?.value,
),
then: (schema) => schema.min(-90).max(90).required('Latitude field is required'),
otherwise: (schema) => schema.nullable(),
})
.typeError('Latitude should be a number'),

// Coefficients
coefficients: yup.lazy((coefficientObject = {}) => {
const schema = Object.keys(coefficientObject).reduce(
(prevValue, currentValue) => ({
...prevValue,
[currentValue]: yup.lazy((v) => {
if (v === '') return yup.string().required('This coefficient is required.');

return yup.number().typeError('Coefficient should be a number');
}),
}),
{},
);
return yup.object(schema);
}),
});

export default schemaValidation;
4 changes: 4 additions & 0 deletions client/src/containers/interventions/form/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type * as yup from 'yup';
import type schemaValidation from './schema-validation';

export type SubSchema = yup.InferType<typeof schemaValidation>;
Loading