client/src/containers/interventions/form/component.tsx
import { useEffect, useMemo, useRef } from 'react';
import { useRouter } from 'next/router';
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 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';
import { useLocationTypes } from 'hooks/location-types';
import { useAdminRegionsTrees, useAdminRegionsByCountry } from 'hooks/admin-regions';
import { useSourcingRecordsYears } from 'hooks/sourcing-records';
import MaterialsSelect from 'containers/materials/select';
import BusinessUnitsSelect from 'containers/business-units/select';
import LocationsSelect from 'containers/locations/select';
import SuppliersSelect from 'containers/suppliers/select';
import Input from 'components/forms/input';
import { Anchor, Button } from 'components/button';
import Select, { AutoCompleteSelect } from 'components/forms/select';
import InfoToolTip from 'components/info-tooltip/component';
import { isCoordinates } from 'utils/coordinates';
import Hint from 'components/forms/hint';
import TreeSelect from 'components/tree-select';
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];
type InterventionFormProps = {
intervention?: Intervention;
isCreation?: boolean;
isSubmitting?: boolean;
onSubmit?: (interventionFormData: InterventionFormData) => void;
};
const LABEL_CLASSNAMES = 'text-sm';
const TYPES_OF_INTERVENTIONS = Object.values(InterventionTypes).map((interventionType) => ({
value: interventionType,
label: interventionType,
}));
const InterventionForm: React.FC<InterventionFormProps> = ({
intervention,
isSubmitting,
onSubmit,
}) => {
const {
query: { scenarioId },
} = useRouter();
const { data: indicators } = useIndicators(
{ include: 'unit', sort: 'name' },
{ select: (data) => data.data },
);
const indicatorNameCodes = useMemo(
() =>
indicators?.map(({ nameCode, status }) => ({ nameCode, disabled: status === 'inactive' })),
[indicators],
);
const closeSupplierRef = useRef<() => void>(null);
const closeImpactsRef = useRef<() => void>(null);
// Suppliers
const { data: suppliers, isLoading: isLoadingSuppliers } = useSuppliersTypes({
type: 't1supplier',
});
const optionsSuppliers = useMemo<Option[]>(
() =>
suppliers?.map((supplier) => ({
label: supplier.name,
value: supplier.id,
})),
[suppliers],
);
// Producers
const { data: producers, isLoading: isLoadingProducers } = useSuppliersTypes({
type: 'producer',
});
const optionsProducers = useMemo<Option[]>(
() =>
producers?.map((producer) => ({
label: producer.name,
value: producer.id,
})),
[producers],
);
// Location types
const { data: locationTypes, isLoading: isLoadingLocationTypes } = useLocationTypes(
{ supported: true },
{
select: (_locationTypes) =>
_locationTypes.map((locationTypeOption) => ({
...locationTypeOption,
// ! this is a temporary workaround, disabling should come from API. Remove as soon as it is available in API.
disabled: DISABLED_LOCATION_TYPES.includes(locationTypeOption.value),
})),
},
);
// Countries
const { data: countries, isLoading: isLoadingCountries } = useAdminRegionsTrees({ depth: 0 });
const optionsCountries = useMemo<Option[]>(
() =>
sortBy(
countries.map(({ name, id }) => ({
label: name,
value: id,
})),
'label',
),
[countries],
);
// Years
const { data: years, isLoading: isLoadingYears } = useSourcingRecordsYears();
const optionsYears: Option<number>[] = useMemo(
() =>
years.map((year) => ({
label: year.toString(),
value: year,
})),
[years],
);
const { data: unknowSupplier } = useUnknowSupplier();
const unknowSupplierOption: Option<string> = useMemo(
() => ({ label: unknowSupplier?.name, value: unknowSupplier?.id }),
[unknowSupplier],
);
const {
register,
control,
watch,
setValue,
resetField,
handleSubmit,
formState: { errors },
clearErrors,
} = useForm<SubSchema>({
resolver: yupResolver(schemaValidation),
...(intervention &&
indicatorNameCodes && {
defaultValues: {
title: intervention?.title,
scenarioId: '',
percentage: intervention.percentage,
startYear: {
label: intervention.startYear.toString(),
value: intervention.startYear,
},
materialIds: intervention.replacedMaterials.map(({ id, name }) => ({
label: name,
value: id,
})),
businessUnitIds: intervention.replacedBusinessUnits.map(({ id, name }) => ({
label: name,
value: id,
})),
adminRegionIds: intervention.replacedAdminRegions.map(({ id, name }) => ({
label: name,
value: id,
})),
t1SupplierIds: intervention.replacedT1Suppliers?.map(({ id, name }) => ({
label: name,
value: id,
})),
producerIds: intervention.replacedProducers?.map(({ id, name }) => ({
label: name,
value: id,
})),
interventionType: intervention.type,
newMaterialId: [
{
label: intervention.newMaterial?.name,
value: intervention.newMaterial?.id,
},
],
newLocationType: {
label: locationTypes.find(({ value }) => value === intervention.newLocationType)?.label,
value: intervention.newLocationType,
},
// New location
newLocationCountryInput: intervention.newLocationCountryInput
? {
label: intervention.newLocationCountryInput,
value: intervention.newLocationCountryInput,
}
: {},
cityAddressCoordinates:
intervention.newLocationAddressInput ||
(intervention.newLocationLatitudeInput &&
intervention.newLocationLongitudeInput &&
`${intervention.newLocationLatitudeInput}, ${intervention.newLocationLongitudeInput}`) ||
null,
newLocationAddressInput: intervention?.newLocationAddressInput || null,
newLocationLatitude: intervention?.newLocationLatitudeInput
? Number(intervention?.newLocationLatitudeInput)
: 0,
newLocationLongitude: intervention?.newLocationLongitudeInput
? Number(intervention?.newLocationLongitudeInput)
: 0,
newLocationAdminRegionInput: intervention.newAdminRegion
? {
label: intervention.newAdminRegion.name,
value: intervention.newAdminRegion.name,
}
: null,
// New supplier/producer
newT1SupplierId: intervention?.newT1Supplier
? {
label: intervention.newT1Supplier.name,
value: intervention.newT1Supplier.id,
}
: unknowSupplierOption,
newProducerId: intervention?.newProducer
? {
label: intervention.newProducer.name,
value: intervention.newProducer.id,
}
: unknowSupplierOption,
// coefficients
coefficients: {
...(Object.keys(intervention?.newIndicatorCoefficients || {}).length &&
Object.keys(intervention.newIndicatorCoefficients).reduce(
(prev, current) => ({
...prev,
[current]: intervention.newIndicatorCoefficients[current],
}),
{},
)),
},
},
}),
shouldFocusError: true,
criteriaMode: 'all',
});
const {
materialIds: currentMaterialIds,
businessUnitIds: currentBusinessUnitIds,
adminRegionIds: currentLocationIds,
t1SupplierIds: currentT1SupplierIds,
producerIds: currentProducerIds,
interventionType: currentInterventionType,
newLocationType: locationType,
newT1SupplierId: currentNewT1SupplierId,
newProducerId: currentNewProducerId,
newLocationCountryInput: currentCountry,
coefficients = {},
} = watch();
const countryId = useMemo(() => {
return countries.find(({ name }) => currentCountry?.label === name)?.id || null;
}, [countries, currentCountry]);
const { data: regionsByCountry, isFetching: isFetchingRegions } = useAdminRegionsByCountry(
countryId,
{},
{
enabled:
Boolean(countryId) &&
locationType?.value === LocationTypes.administrativeRegionOfProduction,
select: (_country) =>
recursiveSort(_country.children, 'name').map((child) =>
recursiveMap(child, ({ name }) => ({ value: name, label: name })),
),
},
);
// Populate the new location field when the location type options changes
useEffect(() => {
if (intervention?.newLocationType) {
setValue('newLocationType', {
label: locationTypes.find(({ value }) => value === intervention.newLocationType)?.label,
value: intervention.newLocationType,
});
}
}, [intervention?.newLocationType, locationTypes, setValue]);
useEffect(() => {
// ? defaults to latest year unless the user is editing an intervention,
// ? in that case, the start year will come from the intervention itself.
if (optionsYears?.length && !intervention) {
setValue('startYear', optionsYears[optionsYears.length - 1]);
}
}, [optionsYears, setValue, intervention]);
useEffect(() => {
if (currentInterventionType === InterventionTypes.SupplierLocation) {
(['newMaterialId'] as const).forEach((field) => resetField(field, { defaultValue: null }));
} else if (currentInterventionType === InterventionTypes.Efficiency) {
(
[
'newMaterialId',
'newT1SupplierId',
'newProducerId',
'cityAddressCoordinates',
'newLocationCountryInput',
'newLocationAddressInput',
'newLocationLatitude',
'newLocationLongitude',
] as const
).forEach((field) => resetField(field, { defaultValue: null }));
}
// * resets "impacts per ton" coefficients whenever the intervention type changes
if (!intervention) {
Object.values(indicatorNameCodes).forEach((indicatorKey) => {
// @ts-expect-error not sure how to solve this dynamic typing
resetField(`coefficients.${indicatorKey}`, { defaultValue: 0 });
});
}
// * closes "Supplier" panel whenever the intervention type changes
if (closeSupplierRef.current !== null) {
closeSupplierRef.current();
}
}, [currentInterventionType, resetField, closeSupplierRef, intervention, indicatorNameCodes]);
useEffect(() => {
clearErrors([
'newMaterialId',
'newT1SupplierId',
'newProducerId',
'cityAddressCoordinates',
'newLocationType',
'newLocationCountryInput',
'newLocationAddressInput',
'newLocationLatitude',
'newLocationLongitude',
]);
}, [currentInterventionType, clearErrors]);
useEffect(() => {
// * resets the region field whenever the user chooses any location type but administrive region one
if (![LocationTypes.administrativeRegionOfProduction].includes(locationType?.value)) {
resetField('newLocationAdminRegionInput', { defaultValue: null });
}
// * if a location type doesn't require coordinates, the coordinates fields are reset to avoid sending them unintentionally
if (
[
LocationTypes.countryOfProduction,
LocationTypes.countryOfDelivery,
LocationTypes.unknown,
].includes(locationType?.value)
) {
resetField('cityAddressCoordinates', { defaultValue: null });
resetField('newLocationLatitude', { defaultValue: null });
resetField('newLocationLongitude', { defaultValue: null });
}
}, [locationType, resetField, setValue]);
useEffect(() => setValue('scenarioId', scenarioId as string), [scenarioId, setValue]);
// When city, address or coordinates input are valid coordinates, set the location coordinates inputs
useEffect(() => {
const subscription = watch(({ cityAddressCoordinates }, { name, type }) => {
if (name === 'cityAddressCoordinates' && type === 'change') {
if (isCoordinates(cityAddressCoordinates)) {
const [lat, lng]: number[] = cityAddressCoordinates
.split(',')
.map((coordinate: string) => Number.parseFloat(coordinate));
setValue('newLocationLatitude', lat);
setValue('newLocationLongitude', lng);
resetField('newLocationAddressInput', { defaultValue: null });
} else {
setValue('newLocationAddressInput', cityAddressCoordinates);
resetField('newLocationLatitude', { defaultValue: 0 });
resetField('newLocationLongitude', { defaultValue: 0 });
}
}
});
return () => subscription.unsubscribe();
}, [resetField, setValue, watch]);
// ? I had to put this variable out of useMemo because it looks like
// ? the form mutates the coefficients object and the below useMemo doesn't trigger after a second change.
const coefficientValues = Object.values(coefficients);
const areCoefficientsEdited = useMemo(() => {
if (!coefficientValues) return false;
return coefficientValues.filter((v) => v).some((v) => +v !== 0);
}, [coefficientValues]);
const areSupplierEdited = useMemo(
() => Boolean(currentNewT1SupplierId || currentNewProducerId),
[currentNewT1SupplierId, currentNewProducerId],
);
useEffect(() => {
// * closes "Impacts per ton" panel whenever the intervention type changes and coefficients are not edited
if (
closeImpactsRef.current !== null &&
!areCoefficientsEdited &&
currentInterventionType !== InterventionTypes.Efficiency
) {
closeImpactsRef.current();
}
}, [currentInterventionType, areCoefficientsEdited]);
useEffect(() => {
if (Object.keys(errors).length > 0) toast.error('Please fill all required fields.');
}, [errors]);
useEffect(() => {
if (!currentNewT1SupplierId?.value) setValue('newT1SupplierId', unknowSupplierOption);
if (!currentNewProducerId?.value) setValue('newProducerId', unknowSupplierOption);
}, [currentNewT1SupplierId, currentNewProducerId, setValue, unknowSupplierOption]);
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="grid grid-cols-2 gap-6 space-y-10"
data-testid="intervention-form"
>
<input {...register('scenarioId')} type="hidden" value={watch('scenarioId')} />
<div className="flex flex-col justify-center pr-10">
<h2>1. Apply intervention to...</h2>
<p className="text-sm text-gray-500">
Choose to which data of your supply chain you want to apply the intervention in order to
analyze changes.
</p>
</div>
<div className="space-y-4 border-l-2 border-gray-100 pl-10">
<div>
<label className={LABEL_CLASSNAMES}>
Title <sup>*</sup>
</label>
<Input
{...register('title')}
type="text"
data-testid="title-input"
error={errors?.title?.message}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>
Percentage <sup>*</sup>
</label>
<Input
{...register('percentage')}
type="number"
placeholder="100"
defaultValue={100}
error={errors?.percentage?.message}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>
Raw material <sup>*</sup>
</label>
<Controller
name="materialIds"
control={control}
render={({ field: { onChange, value, ...field }, fieldState: { invalid } }) => (
<div data-testid="materials-select">
<MaterialsSelect
{...field}
multiple={false}
withSourcingLocations
current={value?.[0]}
businessUnitIds={currentBusinessUnitIds?.map(({ value }) => value)}
t1SupplierIds={currentT1SupplierIds?.map(({ value }) => value)}
producerIds={currentProducerIds?.map(({ value }) => value)}
originIds={currentLocationIds?.map(({ value }) => value)}
onChange={(selected) => {
if (invalid) clearErrors('materialIds');
onChange?.(selected && [selected]);
}}
error={!!errors?.materialIds}
/>
</div>
)}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>Business Units</label>
<Controller
name="businessUnitIds"
control={control}
render={({ field: { value, ...field } }) => {
return (
<BusinessUnitsSelect
{...field}
multiple
placeholder="All business units"
materialIds={currentMaterialIds?.map(({ value }) => value)}
t1SupplierIds={currentT1SupplierIds?.map(({ value }) => value)}
producerIds={currentProducerIds?.map(({ value }) => value)}
originIds={currentLocationIds?.map(({ value }) => value)}
withSourcingLocations
current={value}
error={!!errors?.businessUnitIds}
data-testid="business-units-select"
/>
);
}}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>Region</label>
<Controller
name="adminRegionIds"
control={control}
render={({ field: { value, ...field } }) => (
<LocationsSelect
{...field}
multiple
placeholder="All regions"
materialIds={currentMaterialIds?.map(({ value }) => value)}
t1SupplierIds={currentT1SupplierIds?.map(({ value }) => value)}
producerIds={currentProducerIds?.map(({ value }) => value)}
businessUnitIds={currentBusinessUnitIds?.map(({ value }) => value)}
withSourcingLocations
current={value}
error={!!errors?.adminRegionIds}
data-testid="location-select"
/>
)}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>T1 Suppliers</label>
<Controller
name="t1SupplierIds"
control={control}
render={({ field: { value, ...field } }) => (
<SuppliersSelect
{...field}
type="t1supplier"
multiple
current={value}
producerIds={currentProducerIds?.map(({ value }) => value)}
originIds={currentLocationIds?.map(({ value }) => value)}
businessUnitIds={currentBusinessUnitIds?.map(({ value }) => value)}
materialIds={currentMaterialIds?.map(({ value }) => value)}
error={!!errors?.t1SupplierIds}
/>
)}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>Producers</label>
<Controller
name="producerIds"
control={control}
render={({ field: { value, ...field } }) => (
<SuppliersSelect
{...field}
type="producer"
multiple
current={value}
t1SupplierIds={currentT1SupplierIds?.map(({ value }) => value)}
originIds={currentLocationIds?.map(({ value }) => value)}
businessUnitIds={currentBusinessUnitIds?.map(({ value }) => value)}
materialIds={currentMaterialIds?.map(({ value }) => value)}
/>
)}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>
Start year of implementation <sup>*</sup>
</label>
<Controller
name="startYear"
control={control}
render={({ field: { value, ...field } }) => (
<Select<number>
{...omit(field, 'ref')}
id="startYear"
value={value}
options={optionsYears}
placeholder="Select a year"
onChange={(value: Option<number>) => setValue('startYear', value)}
loading={isLoadingYears}
required
error={errors?.startYear?.value?.message}
/>
)}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>End year of implementation</label>
<Controller
name="endYear"
control={control}
render={({ field: { value, ...field } }) => (
<Select<number>
{...omit(field, 'ref')}
id="endYear"
defaultValue={value}
options={[]}
placeholder="Select a year"
onChange={(value: Option<number>) => setValue('endYear', value)}
loading={isLoadingYears}
error={errors?.endYear?.value?.message}
disabled
/>
)}
/>
</div>
</div>
<div className="flex flex-col justify-center pr-10">
<h2>2. Type of intervention *</h2>
</div>
<div className="border-l-2 border-gray-100 pl-10">
<Controller
name="interventionType"
control={control}
render={({ field, fieldState: { invalid } }) => (
<RadioGroup
{...field}
onChange={(value) => {
if (invalid) clearErrors('interventionType');
setValue('interventionType', value);
}}
>
<RadioGroup.Label className="sr-only">Type of intervention</RadioGroup.Label>
<div className="space-y-4">
{TYPES_OF_INTERVENTIONS.map(({ label, value }) => (
<RadioGroup.Option
value={value}
key={value}
className={({ active, checked }) =>
classNames('rounded-md border p-4', {
'border-orange-100 bg-orange-50 text-gray-900': active || checked,
'border-gray-300 text-gray-500':
!(active || checked) && !errors.interventionType,
'border-red-400': errors.interventionType,
})
}
data-testid="intervention-type-option"
>
{({ active, checked }) => (
<div className="flex items-center space-x-4">
<InterventionTypeIcon
interventionType={value}
variant={active || checked ? 'light' : 'default'}
/>
<RadioGroup.Label>{label}</RadioGroup.Label>
</div>
)}
</RadioGroup.Option>
))}
{errors.interventionType && (
<Hint data-testid={'hint-input-interventionType'}>
{errors.interventionType.message}
</Hint>
)}
</div>
</RadioGroup>
)}
/>
</div>
{currentInterventionType && (
<>
<div className="flex flex-col justify-center pr-10">
<h2>3. Set up intervention</h2>
<p className="text-sm text-gray-500">
Select the new material you want to switch to and also the supplier, location or
impact if you want.
</p>
</div>
{/* Those options depending on intervention type selected by the user */}
<div className="space-y-10 border-l-2 border-gray-100 pl-10">
{currentInterventionType === InterventionTypes.Material && (
<div className="space-y-4">
<div className="flex items-center space-x-1">
<h3>New material</h3>
<InfoToolTip info={InfoTooltip.newMaterial} />
</div>
<div>
<label className={LABEL_CLASSNAMES}>
New material <sup>*</sup>
</label>
<Controller
name="newMaterialId"
control={control}
render={({ field: { onChange, value, ...field }, fieldState: { invalid } }) => (
<div data-testid="new-material-select">
<MaterialsSelect
{...field}
placeholder="Select a material"
multiple={false}
current={value?.[0]}
onChange={(selected) => {
if (invalid) clearErrors('newMaterialId');
onChange?.(selected ? [selected] : []);
}}
error={!!errors?.newMaterialId}
/>
</div>
)}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>Tons of new material per tone</label>
<Input
{...register('volume')}
type="text"
data-testid="volume-input"
error={errors?.volume?.message}
placeholder="0"
disabled
/>
</div>
</div>
)}
{(currentInterventionType === InterventionTypes.Material ||
currentInterventionType === InterventionTypes.SupplierLocation) && (
<Disclosure as="div" className="space-y-4">
{({ open }) => (
<>
<div className="flex w-full items-center justify-between">
<div className="flex items-center space-x-1">
<h3>New location</h3>
<InfoToolTip info={InfoTooltip.supplierLocation} />
</div>
<Disclosure.Button
className={classNames(
'flex h-6 w-6 items-center justify-center rounded border border-navy-400',
open ? 'bg-navy-400' : 'bg-transparent',
{
hidden:
currentInterventionType === InterventionTypes.Material ||
currentInterventionType === InterventionTypes.SupplierLocation,
},
)}
>
{open ? (
<MinusIcon className="h-5 w-5 text-white" />
) : (
<PlusIcon className="h-5 w-5 text-navy-400" />
)}
</Disclosure.Button>
</div>
<Disclosure.Panel
static={
currentInterventionType === InterventionTypes.SupplierLocation ||
currentInterventionType === InterventionTypes.Material
}
>
<div className="space-y-4">
<div>
<label className={LABEL_CLASSNAMES}>
Location type <sup>*</sup>
</label>
<Controller
name="newLocationType"
control={control}
render={({ field, fieldState: { invalid } }) => (
<AutoCompleteSelect<LocationTypes>
{...omit(field, 'ref')}
loading={isLoadingLocationTypes}
value={field.value}
options={locationTypes}
placeholder="Select"
onChange={(value) => {
if (invalid) clearErrors('newLocationType');
setValue('newLocationType', value);
}}
required
error={errors?.newLocationType?.message}
/>
)}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>
Country <sup>*</sup>
</label>
<Controller
name="newLocationCountryInput"
control={control}
render={({ field, fieldState: { invalid } }) => (
<AutoCompleteSelect
{...omit(field, 'ref')}
loading={isLoadingCountries}
value={field.value}
options={optionsCountries}
placeholder="Select"
onChange={(value) => {
if (invalid) clearErrors('newLocationCountryInput');
if (
locationType?.value ===
LocationTypes.administrativeRegionOfProduction
) {
resetField('newLocationAdminRegionInput', {
defaultValue: null,
});
}
setValue('newLocationCountryInput', value);
}}
error={errors?.newLocationCountryInput?.message}
/>
)}
/>
</div>
{[LocationTypes.aggregationPoint, LocationTypes.pointOfProduction].includes(
locationType?.value,
) && (
<>
<div data-testid="city-address-coordinates-field">
<label className={LABEL_CLASSNAMES}>
City, address or coordinates <sup>*</sup>
</label>
<Input
type="text"
{...register('cityAddressCoordinates')}
error={
errors?.cityAddressCoordinates?.message ||
errors?.newLocationAddressInput?.message ||
errors?.newLocationLatitude?.message ||
errors?.newLocationLongitude?.message
}
/>
<div className="mt-1 text-xs text-gray-500">
Add lat and long coordinates separated by comma, e.g. 40, -3
</div>
</div>
<div className="hidden">
<Input type="text" {...register('newLocationAddressInput')} />
</div>
</>
)}
{[LocationTypes.administrativeRegionOfProduction].includes(
locationType?.value,
) && (
<>
<div data-testid="administrative-region-production-field">
<label className={LABEL_CLASSNAMES}>
Region <sup>*</sup>
</label>
<Controller
name="newLocationAdminRegionInput"
control={control}
render={({
field: { onChange, value },
fieldState: { invalid },
}) => (
<div data-testid="new-location-region-select">
<TreeSelect
options={regionsByCountry}
current={value}
placeholder="Select a region"
multiple={false}
showSearch
disabled={
!Boolean(currentCountry) ||
(currentCountry && isFetchingRegions) ||
regionsByCountry?.length === 0
}
loading={isFetchingRegions}
onChange={(option) => {
if (invalid) clearErrors('newLocationAdminRegionInput');
if (option) {
onChange({ label: option.label, value: option.value });
} else {
resetField('newLocationAdminRegionInput', {
defaultValue: null,
});
}
}}
error={!!errors?.newLocationAdminRegionInput}
/>
{errors.newLocationAdminRegionInput && (
<Hint data-testid={'hint-input-newLocationAdminRegionInput'}>
{errors.newLocationAdminRegionInput.message}
</Hint>
)}
</div>
)}
/>
</div>
</>
)}
{(locationType?.value === LocationTypes.aggregationPoint ||
locationType?.value === LocationTypes.pointOfProduction) && (
<div className="hidden">
<div className="flex w-full space-x-2">
<Input
{...register('newLocationLatitude')}
type="text"
placeholder="Latitude"
className="w-full"
/>
<Input
{...register('newLocationLongitude')}
type="text"
placeholder="Longitude"
className="w-full"
/>
</div>
</div>
)}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
)}
{(currentInterventionType === InterventionTypes.Material ||
currentInterventionType === InterventionTypes.SupplierLocation) && (
<Disclosure as="div" className="space-y-4">
{({ open, close }) => {
closeSupplierRef.current = close;
return (
<>
<div className="flex w-full items-center justify-between">
<div className="flex items-center space-x-1">
<h3 className="inline-block">Supplier</h3>{' '}
<span className="text-regular text-gray-500">(optional)</span>
<InfoToolTip info={InfoTooltip.newSupplier} />
</div>
{areSupplierEdited || (
<Disclosure.Button
className={classNames(
'flex h-6 w-6 items-center justify-center rounded border border-navy-400',
open ? 'bg-navy-400' : 'bg-transparent',
)}
>
{open ? (
<MinusIcon className="h-5 w-5 text-white" />
) : (
<PlusIcon className="h-5 w-5 text-navy-400" />
)}
</Disclosure.Button>
)}
</div>
<Disclosure.Panel static={areSupplierEdited}>
<div className="space-y-4">
<div>
<label className={LABEL_CLASSNAMES}>Tier 1 supplier</label>
<Controller
name="newT1SupplierId"
control={control}
render={({ field }) => (
<div data-testid="new-t1-supplier-select">
<AutoCompleteSelect
{...omit(field, 'ref')}
loading={isLoadingSuppliers}
value={field.value}
options={optionsSuppliers}
placeholder="Select"
onChange={(value) => setValue('newT1SupplierId', value)}
error={errors?.newT1SupplierId?.message?.toString()}
clearable={
currentNewT1SupplierId?.value !== unknowSupplierOption.value
}
onClearSelection={() =>
setValue('newT1SupplierId', unknowSupplierOption)
}
/>
</div>
)}
/>
</div>
<div>
<label className={LABEL_CLASSNAMES}>Producer</label>
<Controller
name="newProducerId"
control={control}
render={({ field }) => (
<div data-testid="new-producer-select">
<AutoCompleteSelect
{...omit(field, 'ref')}
loading={isLoadingProducers}
value={field.value}
options={optionsProducers}
placeholder="Select"
onChange={(value) => setValue('newProducerId', value)}
error={errors?.newProducerId?.message?.toString()}
clearable={
currentNewProducerId?.value !== unknowSupplierOption.value
}
onClearSelection={() =>
setValue('newProducerId', unknowSupplierOption)
}
/>
</div>
)}
/>
</div>
</div>
</Disclosure.Panel>
</>
);
}}
</Disclosure>
)}
<Disclosure
as="div"
className="space-y-4"
defaultOpen={
currentInterventionType === InterventionTypes.Efficiency || areCoefficientsEdited
}
>
{({ open, close }) => {
closeImpactsRef.current = close;
return (
<div data-testid="fieldset-impacts-per-ton">
<div className="flex w-full items-center justify-between">
<div className="flex items-center space-x-1">
<h3>Impacts per ton</h3>
{currentInterventionType !== InterventionTypes.Efficiency && (
<span className="text-regular text-gray-500">(optional)</span>
)}
<InfoToolTip info={InfoTooltip.supplierImpactsPerTon} />
</div>
<Disclosure.Button
className={classNames(
'flex h-6 w-6 items-center justify-center rounded border border-navy-400',
open ? 'bg-navy-400' : 'bg-transparent',
currentInterventionType === InterventionTypes.Efficiency && 'hidden',
)}
>
{open ? (
<MinusIcon className="h-5 w-5 text-white" />
) : (
<PlusIcon className="h-5 w-5 text-navy-400" />
)}
</Disclosure.Button>
</div>
<Disclosure.Panel
static={currentInterventionType === InterventionTypes.Efficiency}
>
<div className="space-y-4">
{indicators?.map((indicator) => (
<div key={indicator.id}>
<label
className={classNames(LABEL_CLASSNAMES, {
'text-gray-300': indicator.status === 'inactive',
})}
>
{indicator?.metadata?.short_name}
</label>
<Input
// @ts-expect-error not sure how to solve this dynamic typing
{...register(`coefficients.${indicator.nameCode}`)}
type="number"
step={0.001}
min={0}
defaultValue={0}
error={errors?.coefficients?.[indicator.nameCode]?.message}
unit={indicator.unit.symbol}
data-testid={`${indicator.nameCode}-input`}
disabled={indicator.status === 'inactive'}
/>
</div>
))}
</div>
</Disclosure.Panel>
</div>
);
}}
</Disclosure>
</div>
</>
)}
<div className="col-span-2">
<div className="mb-6 text-right text-sm text-gray-500">
Fields marked with (*) are mandatory.
</div>
<div className="flex justify-end space-x-6">
<Anchor variant="secondary" href={`/data/scenarios/${scenarioId}/edit`}>
Cancel
</Anchor>
<Button loading={isSubmitting} type="submit" data-testid="intervention-submit-btn">
Save intervention
</Button>
</div>
</div>
</form>
);
};
export default InterventionForm;