import React, { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { Field, FieldArray, Form, Formik, FormikHelpers } from 'formik'; import * as Yup from 'yup'; import { TextField } from 'formik-material-ui'; import { Button, createStyles, FormControl, Grid, makeStyles, Paper, Table, TableBody, TableCell, TableHead, TableRow, TextField as MaterialTextField, Typography, useTheme } from '@material-ui/core'; import { Skeleton } from '@material-ui/lab'; import { Unit } from '@energyweb/utils-general'; import { DeviceStatus, IExternalDeviceId } from '@energyweb/origin-backend-core'; import { fromGeneralActions, fromGeneralSelectors } from '../../features/general'; import { getConfiguration } from '../../features/configuration'; import { moment } from '../../utils/time'; import { usePermissions } from '../../utils/permissions'; import { PowerFormatter } from '../../utils/PowerFormatter'; import { FormInput, HierarchicalMultiSelect } from '../Form'; import { IUploadedFile, Upload } from '../Documents'; import { Requirements } from '../Layout'; import { DeviceSelectors } from './DeviceSelectors'; import { IOriginDevice } from '../../types'; const MAX_TOTAL_CAPACITY = 5 * Unit.MW; interface IDeviceGroupChild { installationName: string; address: string; city: string; latitude: number | ''; longitude: number | ''; capacity: number | ''; meterId: string; meterType: string; } function getDefaultDeviceData(): IDeviceGroupChild { return { installationName: '', address: '', city: '', latitude: '', longitude: '', capacity: '', meterId: '', meterType: '' }; } interface IFormValues { facilityName: string; children: IDeviceGroupChild[]; } const INITIAL_FORM_VALUES: IFormValues = { facilityName: '', children: [getDefaultDeviceData()] }; function sumCapacityOfDevices(devices: IDeviceGroupChild[]) { const totalCapacityInKW = devices.reduce((a, b) => { if (typeof b.capacity === 'number' && !isNaN(b.capacity)) { return a + b.capacity; } return a; }, 0); return typeof totalCapacityInKW === 'number' && !isNaN(totalCapacityInKW) ? totalCapacityInKW * Unit.kW : 0; } interface IProps { device?: IOriginDevice; readOnly?: boolean; } export function DeviceGroupForm(props: IProps) { const { device, readOnly } = props; const { t } = useTranslation(); const configuration = useSelector(getConfiguration); const compliance = useSelector(fromGeneralSelectors.getCompliance); const country = useSelector(fromGeneralSelectors.getCountry); const externalDeviceIdTypes = useSelector(fromGeneralSelectors.getExternalDeviceIdTypes); const { canAccessPage } = usePermissions(); const [ initialFormValuesFromExistingEntity, setInitialFormValuesFromExistingEntity ] = useState(null); const dispatch = useDispatch(); const selectedDeviceType = ['Solar', 'Solar;Photovoltaic']; const [selectedLocation, setSelectedLocation] = useState([]); const [selectedGridOperator, setSelectedGridOperator] = useState([]); const useStyles = makeStyles(() => createStyles({ container: { padding: '10px' }, selectContainer: { margin: '10px 0' } }) ); const classes = useStyles(useTheme()); const [files, setFiles] = useState([]); const uploadedFiles = files .filter((f) => !f.removed && f.uploadedName) .reduce( (arr, x) => { arr.filenames.push(x.uploadedName); return arr; }, { filenames: [] } ); useEffect(() => { if (!device) { return; } const newInitialFormValuesFromExistingEntity: IFormValues = { facilityName: device?.facilityName, children: JSON.parse(device?.deviceGroup) }; setInitialFormValuesFromExistingEntity(newInitialFormValuesFromExistingEntity); }, [device]); const externalIdSchema = {}; if (externalDeviceIdTypes) { const requiredDeviceIdTypes = externalDeviceIdTypes.filter( (id) => id?.required && !id?.autogenerated ); for (const externalId of requiredDeviceIdTypes) { externalIdSchema[externalId.type] = Yup.string().required(); } } const VALIDATION_SCHEMA = Yup.object().shape({ facilityName: Yup.string().label(t('device.properties.facilityName')).required(), children: Yup.array() .of( Yup.object() .shape({ installationName: Yup.string().required('Installation name'), address: Yup.string().required().label(t('device.properties.address')), city: Yup.string().required().label('City'), latitude: Yup.number() .label(t('device.properties.latitude')) .required() .min(-90) .max(90), longitude: Yup.number() .label(t('device.properties.longitude')) .required() .min(-180) .max(180), capacity: Yup.number().required().min(20).label('Capacity'), meterId: Yup.string().required().label('Meter id'), meterType: Yup.string() .oneOf(['interval', 'scalar']) .required() .label('Meter type') }) // eslint-disable-next-line no-template-curly-in-string .test('is-total-capacity-in-bounds', '${path} threshold invalid', function () { const devices: IDeviceGroupChild[] = this.parent; const totalCapacityInW = sumCapacityOfDevices(devices); if (totalCapacityInW > MAX_TOTAL_CAPACITY) { return this.createError({ path: `${this.path}.capacity`, message: `Total capacity can be maximum: ${PowerFormatter.format( MAX_TOTAL_CAPACITY, true )}` }); } return true; }) ) .min(1), ...externalIdSchema }); async function submitForm( values: typeof INITIAL_FORM_VALUES, formikActions: FormikHelpers ): Promise { const deviceType = 'Solar;Photovoltaic'; formikActions.setSubmitting(true); const externalDeviceIds: IExternalDeviceId[] = externalDeviceIdTypes.map(({ type }) => { return { id: values[type], type }; }); const [region, province] = selectedLocation; dispatch( fromGeneralActions.requestDeviceCreation({ status: DeviceStatus.Submitted, deviceType, complianceRegistry: compliance, facilityName: values.facilityName, capacityInW: sumCapacityOfDevices(values.children), country, address: '', region: region || '', province: province ? province.split(';')[1] : '', gpsLatitude: values.children[0].latitude?.toString(), gpsLongitude: values.children[0].longitude?.toString(), timezone: 'Asia/Bangkok', operationalSince: moment().unix(), otherGreenAttributes: '', typeOfPublicSupport: '', description: '', images: JSON.stringify([]), files: JSON.stringify(uploadedFiles.filenames), deviceGroup: JSON.stringify(values.children), externalDeviceIds, gridOperator: (selectedGridOperator && selectedGridOperator[0]) || '' }) ); formikActions.setSubmitting(false); } const deviceSelectorsFilled = selectedLocation.length === 2 && selectedGridOperator.length === 1; let initialFormValues: IFormValues = null; if (readOnly) { initialFormValues = initialFormValuesFromExistingEntity; } else { initialFormValues = INITIAL_FORM_VALUES; } if (!initialFormValues || !configuration) { return ; } if (!readOnly && !canAccessPage?.value) { return ; } return ( {(formikProps) => { const { isValid, isSubmitting, values } = formikProps; const fieldDisabled = isSubmitting || readOnly; const buttonDisabled = isSubmitting || !isValid || readOnly || !deviceSelectorsFilled; return (
{!readOnly && ( <> General
{externalDeviceIdTypes .filter( (externalDeviceId) => !externalDeviceId?.autogenerated ) .map((externalDeviceIdType, index) => ( ))} setFiles(newFiles)} />
)}
Installation name {t('device.properties.address')} Village/Town/City {t('device.properties.latitude')} {t('device.properties.longitude')} {t('device.properties.capacity')} (kW) Meter id Meter type {!readOnly && } ( <> {values.children.map((child, childIndex) => ( {!readOnly && ( { arrayHelpers.remove( childIndex ); }} style={{ cursor: 'pointer' }} > Remove )} ))} {!readOnly && ( )} )} />
{!readOnly && ( <>
)}
); }}
); }