Skip to content

Commit

Permalink
Merge pull request #1065 from sparakala21/deleteUnits
Browse files Browse the repository at this point in the history
Delete units
  • Loading branch information
huss authored Mar 9, 2024
2 parents 9537fe8 + 02bcf0b commit b3aa0ee
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 17 deletions.
16 changes: 6 additions & 10 deletions src/client/app/components/conversion/ConversionViewComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Button } from 'reactstrap';
import { ConversionData } from 'types/redux/conversions';
import { selectUnitDataById } from '../../redux/api/unitsApi';
import { useAppSelector } from '../../redux/reduxHooks';
import '../../styles/card-page.css';
import { conversionArrow } from '../../utils/conversionArrow';
import translate from '../../utils/translate';
import EditConversionModalComponent from './EditConversionModalComponent';
import { useAppSelector } from '../../redux/reduxHooks';
import { selectUnitDataById } from '../../redux/api/unitsApi';

interface ConversionViewComponentProps {
conversion: ConversionData;
Expand All @@ -37,15 +38,10 @@ export default function ConversionViewComponent(props: ConversionViewComponentPr
const handleClose = () => {
setShowEditModal(false);
};

// Create header from sourceId, destinationId identifiers
// Arrow is bidirectional if conversion is bidirectional and one way if not.
let arrowShown: string;
if (props.conversion.bidirectional) {
arrowShown = ' ↔ ';
} else {
arrowShown = ' → ';
}
const header = String(unitDataById[props.conversion.sourceId]?.identifier + arrowShown + unitDataById[props.conversion.destinationId]?.identifier);
const header = String(unitDataById[props.conversion.sourceId]?.identifier + conversionArrow(props.conversion.bidirectional) +
unitDataById[props.conversion.destinationId]?.identifier);

// Unlike the details component, we don't check if units are loaded since must come through that page.

Expand Down
32 changes: 31 additions & 1 deletion src/client/app/components/meters/EditMeterModalComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import * as React from 'react';
import { useEffect, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Button, Col, Container, FormFeedback, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader, Row } from 'reactstrap';
import { metersApi, selectMeterById } from '../../redux/api/metersApi';
import { selectGroupDataById } from '../../redux/api/groupsApi';
import { metersApi, selectMeterById, selectMeterDataById } from '../../redux/api/metersApi';
import { selectUnitDataById } from '../../redux/api/unitsApi';
import { useAppSelector } from '../../redux/reduxHooks';
import {
Expand Down Expand Up @@ -54,6 +55,9 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr
compatibleUnits,
incompatibleUnits
} = useAppSelector(state => selectGraphicUnitCompatibility(state, localMeterEdits));
const groupDataByID = useAppSelector(selectGroupDataById);
// TODO should this state be used for the meterState above or would that cause issues?
const meterDataByID = useAppSelector(selectMeterDataById);

useEffect(() => { setLocalMeterEdits(_.cloneDeep(meterState)); }, [meterState]);
/* State */
Expand Down Expand Up @@ -113,6 +117,23 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr
}
}

// The message if issue with meter and groups. If blank then no issue.
let error_message = '';
// See if the meter unit changed since only allowed if not already in a group.
if (meterState.unitId !== localMeterEdits.unitId) {
// Check if the deep meters of groups in the redux state depend on the meter being edited.
// If so, the meter should not be edited.
for (const value of Object.values(groupDataByID)) {
for (let i = 0; i < value.deepMeters.length; i++) {
if (value.deepMeters[i] == props.meter.id) {
inputOk = false;
// TODO Would like line break between messages. See below on issue.
error_message += `${translate('group')} "${value.name}" ${translate('uses')} ${translate('meter')} "${meterDataByID[value.deepMeters[i]].name}"; `;
}
}
}
}

if (inputOk) {
// The input passed validation.
// GPS may have been updated so create updated state to submit.
Expand All @@ -130,6 +151,15 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr
&& unitDataById[localMeterEdits.unitId].unitRepresent == UnitRepresentType.quantity));
// Submit new meter if checks where ok.
editMeter({ meterData: submitState, shouldRefreshViews: shouldRefreshReadingViews });
} else if (error_message) {
// Display an error message if there are dependent deep meters and checked.
// Undo the unit change.
setLocalMeterEdits({ ...localMeterEdits, ['unitId']: props.meter.unitId });
error_message = translate('meter.unit.is.not.editable') + error_message;
// TODO Attempts to add a line break with \n, <br />, etc. failed when using showErrorNotification.
// This is going to be a general problem. See https://github.com/fkhadra/react-toastify/issues/687
// and https://github.com/fkhadra/react-toastify/issues/201.
showErrorNotification(error_message);
} else {
// Tell user that not going to update due to input issues.
showErrorNotification(translate('meter.input.error'));
Expand Down
51 changes: 50 additions & 1 deletion src/client/app/components/unit/EditUnitModalComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import { Button, Col, Container, FormFeedback, FormGroup, Input, Label, Modal, M
import TooltipHelpComponent from '../../components/TooltipHelpComponent';
import { selectConversionsDetails } from '../../redux/api/conversionsApi';
import { selectMeterDataById } from '../../redux/api/metersApi';
import { unitsApi } from '../../redux/api/unitsApi';
import { selectUnitDataById, unitsApi } from '../../redux/api/unitsApi';
import { useTranslate } from '../../redux/componentHooks';
import { useAppSelector } from '../../redux/reduxHooks';
import '../../styles/modal.css';
import { tooltipBaseStyle } from '../../styles/modalStyle';
import { TrueFalseType } from '../../types/items';
import { DisplayableType, UnitData, UnitRepresentType, UnitType } from '../../types/redux/units';
import { conversionArrow } from '../../utils/conversionArrow';
import { showErrorNotification, showSuccessNotification } from '../../utils/notifications';
import TooltipMarkerComponent from '../TooltipMarkerComponent';

Expand All @@ -34,6 +35,7 @@ interface EditUnitModalComponentProps {
*/
export default function EditUnitModalComponent(props: EditUnitModalComponentProps) {
const [submitEditedUnit] = unitsApi.useEditUnitMutation();
const [deleteUnit] = unitsApi.useDeleteUnitMutation();
const translate = useTranslate();

// Set existing unit values
Expand All @@ -55,6 +57,7 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp
const [state, setState] = useState(values);
const conversionData = useAppSelector(selectConversionsDetails);
const meterDataByID = useAppSelector(selectMeterDataById);
const unitDataByID = useAppSelector(selectUnitDataById);

const handleStringChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setState({ ...state, [e.target.name]: e.target.value });
Expand All @@ -68,6 +71,49 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp
setState({ ...state, [e.target.name]: Number(e.target.value) });
};

const handleDeleteUnit = () => {
let error_message = '';
for (const value of Object.values(meterDataByID)) {
// This unit is used by a meter so cannot be deleted. Note if in a group then in a meter so covers both.
if (value.unitId == state.id) {
// TODO see EditMeterModalComponent for issue with line breaks. Same issue in strings below.
error_message += ` ${translate('meter')} "${value.name}" ${translate('uses')} ${translate('unit')} ` +
`"${state.name}" ${translate('as.meter.unit')};`;
}
if (value.defaultGraphicUnit == state.id) {
error_message += ` ${translate('meter')} "${value.name}" ${translate('uses')} ${translate('unit')} ` +
`"${state.name}" ${translate('as.meter.defaultgraphicunit')};`;
}
}
for (let i = 0; i < conversionData.length; i++) {
if (conversionData[i].sourceId == state.id) {
// This unit is the source of a conversion so cannot be deleted.
error_message += ` ${translate('conversion')} ${unitDataByID[conversionData[i].sourceId].name}` +
`${conversionArrow(conversionData[i].bidirectional)}` +
`${unitDataByID[conversionData[i].destinationId].name} ${translate('uses')} ${translate('unit')}` +
` "${state.name}" ${translate('unit.source.error')};`;
}

if (conversionData[i].destinationId == state.id) {
// This unit is the destination of a conversion so cannot be deleted.
error_message += ` ${translate('conversion')} ${unitDataByID[conversionData[i].sourceId].name}` +
`${conversionArrow(conversionData[i].bidirectional)}` +
`${unitDataByID[conversionData[i].destinationId].name} ${translate('uses')} ${translate('unit')}` +
` "${state.name}" ${translate('unit.destination.error')};`;
}
}
if (error_message) {
error_message = `${translate('unit.failed.to.delete.unit')}: ${error_message}`;
showErrorNotification(error_message);
} else {
// It is okay to delete this unit.
deleteUnit(state.id)
.unwrap()
.then(() => { showSuccessNotification(translate('unit.delete.success')); })
.catch(error => { showErrorNotification(translate('unit.delete.failure') + error.data); });
}
};

/* Edit Unit Validation:
Name cannot be blank
Sec in Rate must be greater than zero
Expand Down Expand Up @@ -352,6 +398,9 @@ export default function EditUnitModalComponent(props: EditUnitModalComponentProp
</FormGroup>
</Container></ModalBody>
<ModalFooter>
<Button variant="warning" color='danger' onClick={handleDeleteUnit}>
<FormattedMessage id="unit.delete.unit" />
</Button>
{/* Hides the modal */}
<Button color='secondary' onClick={handleClose}>
<FormattedMessage id="discard.changes" />
Expand Down
10 changes: 10 additions & 0 deletions src/client/app/redux/api/unitsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ export const unitsApi = baseApi.injectEndpoints({
});
},
invalidatesTags: ['Units']
}),
deleteUnit: builder.mutation<void, number>({
query: unitId => ({
url: 'api/units/delete',
method: 'POST',
body: { id: unitId }
}),
// You should not be able to delete a unit that is used in a meter or conversion
// so no invalidation for those.
invalidatesTags: ['Units']
})
})
});
Expand Down
30 changes: 30 additions & 0 deletions src/client/app/translations/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const LocaleTranslationData = {
"AreaUnitType.meters": "sq. meters",
"AreaUnitType.none": "no unit",
"ascending": "Ascending",
"as.meter.unit": "as meter unit",
"as.meter.defaultgraphicunit": "as meter unit",
"bar": "Bar",
"bar.interval": "Bar Interval",
"bar.raw": "Cannot create bar graph on raw units such as temperature",
Expand Down Expand Up @@ -343,6 +345,7 @@ const LocaleTranslationData = {
"meter.is.enabled": "Updates Enabled",
"meter.is.not.displayable": "Display Disabled",
"meter.is.not.enabled": "Updates Disabled",
"meter.unit.is.not.editable": "This meter's unit cannot be changed and was put back to the original value because: ",
"meter.previousEnd": "Previous End Time Stamp:",
"meter.reading": "Reading:",
"meter.readingDuplication": "Reading Duplication:",
Expand Down Expand Up @@ -439,16 +442,22 @@ const LocaleTranslationData = {
"UnitType.unit": "unit",
"UnitType.meter": "meter",
"UnitType.suffix": "suffix",
"unit.delete.failure": "Failed to deleted unit with error: ",
"unit.delete.success": "Successfully deleted unit",
"unit.delete.unit": "Delete Unit",
"unit.destination.error": "as the destination unit",
"unit.dropdown.displayable.option.none": "None",
"unit.dropdown.displayable.option.all": "All",
"unit.dropdown.displayable.option.admin": "admin",
"unit.failed.to.create.unit": "Failed to create a unit.",
"unit.failed.to.delete.unit": "Delete cannot be done because this unit is used by the following",
"unit.failed.to.edit.unit": "Failed to edit unit.",
"unit.input.error": "Input invalid so unit not created or edited.",
"unit.none": "no unit",
"unit.preferred.display": "Preferred Display:",
"unit.represent": "Unit Represent:",
"unit.sec.in.rate": "Sec in Rate:",
"unit.source.error": "as the source unit",
"unit.submit.new.unit": "Submit New Unit",
"unit.successfully.create.unit": "Successfully created a unit.",
"unit.successfully.edited.unit": "Successfully edited unit.",
Expand Down Expand Up @@ -478,6 +487,7 @@ const LocaleTranslationData = {
"users.successfully.create.user": "Successfully created a user.",
"users.successfully.delete.user": "Successfully deleted user.",
"users.successfully.edit.users": "Successfully edited users.",
"uses":"uses",
"view.groups": "View Groups",
"visit": " or visit our ",
"website": "website",
Expand Down Expand Up @@ -506,6 +516,8 @@ const LocaleTranslationData = {
"AreaUnitType.meters": "mètre carré",
"AreaUnitType.none": "(Need French) no unit",
"ascending": "Ascendant",
"as.meter.unit": "(Need French) as meter unit",
"as.meter.defaultgraphicunit": "(Need French) as meter unit",
"bar": "Bande",
"bar.interval": "Intervalle du Diagramme à Bandes",
"bar.raw": "(Need French) Cannot create bar graph on raw units such as temperature",
Expand Down Expand Up @@ -823,6 +835,7 @@ const LocaleTranslationData = {
"meter.is.enabled": "Mises à Jour Activées",
"meter.is.not.displayable": "Affichage Désactivé",
"meter.is.not.enabled": "Mises à Jour Désactivées",
"meter.unit.is.not.editable": "(need French) This meter's unit cannot be changed and was put back to the original value because: ",
"meter.previousEnd": "(need French) Previous End Time Stamp:",
"meter.reading": "(need French) Reading:",
"meter.readingDuplication": "(need French) Reading Duplication:",
Expand Down Expand Up @@ -919,16 +932,22 @@ const LocaleTranslationData = {
"UnitType.unit": "(Need French) unit",
"UnitType.meter": "(Need French) meter",
"UnitType.suffix": "(Need French) suffix",
"unit.delete.failure": "(Need French) Failed to deleted unit with error: ",
"unit.delete.success": "(Need French) Successfully deleted unit",
"unit.delete.unit": "(Need French) Delete Unit",
"unit.destination.error": "(Need French) as the destination unit",
"unit.dropdown.displayable.option.none": "(Need French) None",
"unit.dropdown.displayable.option.all": "(Need French) All",
"unit.dropdown.displayable.option.admin": "(Need French) admin",
"unit.failed.to.create.unit": "(Need French) Failed to create a unit.",
"unit.failed.to.delete.unit": "(Need French) Delete cannot be done because this unit is used by the following",
"unit.failed.to.edit.unit": "(Need French) Failed to edit unit.",
"unit.input.error": "(Need French) Input invalid so unit not created or edited.",
"unit.none": "(Need French) no unit",
"unit.preferred.display": "(Need French) Preferred Display:",
"unit.represent": "(Need French) Unit Represent:",
"unit.sec.in.rate": "(Need French) Sec in Rate:",
"unit.source.error": "(Need French) as the source unit",
"unit.submit.new.unit": "(Need French) Submit New Unit",
"unit.successfully.create.unit": "(Need French) Successfully created a unit.",
"unit.successfully.edited.unit": "(Need French) Successfully edited unit.",
Expand Down Expand Up @@ -958,6 +977,7 @@ const LocaleTranslationData = {
"users.successfully.create.user": "(Need French) Successfully created a user.",
"users.successfully.delete.user": "(Need French) Successfully deleted user.",
"users.successfully.edit.users": "(Need French) Successfully edited users.",
"uses":"(Need French) uses",
"view.groups": "Visionner les groupes",
"visit": " ou visitez notre ",
"website": "site web",
Expand Down Expand Up @@ -986,6 +1006,8 @@ const LocaleTranslationData = {
"AreaUnitType.meters": "metros cuadrados",
"AreaUnitType.none": "sin unidad",
"ascending": "Ascendente",
"as.meter.unit": "(Need Spanish) as meter unit",
"as.meter.defaultgraphicunit": "(Need Spanish) as meter unit",
"bar": "Barra",
"bar.interval": "Intervalo de barra",
"bar.raw": "No se puede crear un gráfico de barras con unidades crudas como temperatura",
Expand Down Expand Up @@ -1300,6 +1322,7 @@ const LocaleTranslationData = {
"meter.is.enabled": "Actualizaciones Habilitado",
"meter.is.not.displayable": "El medidor es visualizable",
"meter.is.not.enabled": "El medidor no está activada",
"meter.unit.is.not.editable": "(need Spanish) This meter's unit cannot be changed and was put back to the original value because: ",
"meter.previousEnd": "Marca de tiempo de fin previo:",
"meter.reading": "Lectura:",
"meter.readingDuplication": "Duplicación de Lectura:",
Expand Down Expand Up @@ -1397,16 +1420,22 @@ const LocaleTranslationData = {
"UnitType.unit": "unidad",
"UnitType.meter": "medidor",
"UnitType.suffix": "sufijo",
"unit.delete.failure": "(Need Spanish) Failed to deleted unit with error: ",
"unit.delete.success": "(Need Spanish) Successfully deleted unit",
"unit.delete.unit": "(Need Spanish) Delete Unit",
"unit.destination.error": "(Need Spanish) as the destination unit",
"unit.dropdown.displayable.option.none": "Nada",
"unit.dropdown.displayable.option.all": "Todo",
"unit.dropdown.displayable.option.admin": "administrador",
"unit.failed.to.create.unit": "No se pudo crear unidad",
"unit.failed.to.delete.unit": "(Need Spanish) Delete cannot be done because this unit is used by the following",
"unit.failed.to.edit.unit": "No se pudo editar unidad",
"unit.input.error": "Entrada no válida por eso unidad no se creó ni se editó.",
"unit.none": "Sin unidad",
"unit.preferred.display": "Visualización preferida:",
"unit.represent": "Unidad Representa:",
"unit.sec.in.rate": "Segundo en Tasa:",
"unit.source.error": "(Need Spanish) as the source unit",
"unit.submit.new.unit": "Ingresar nueva unidad",
"unit.successfully.create.unit": "Unidad creado con éxito.",
"unit.successfully.edited.unit": "Unidad editado con éxito.",
Expand All @@ -1433,6 +1462,7 @@ const LocaleTranslationData = {
"users.successfully.create.user": "Usuario creado con éxito.",
"users.successfully.delete.user": "Usuario borrado con éxito.",
"users.successfully.edit.users": "Usuarios editados con éxito.",
"uses":"(Need Spanish) uses",
"view.groups": "Ver grupos",
"visit": " o visite nuestra ",
"website": "sitio web",
Expand Down
19 changes: 19 additions & 0 deletions src/client/app/utils/conversionArrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
* Returns proper string to use for conversion representation of direction
* @param bidirectional if the conversion is bidirectional (true) or not (false)
* @returns double-ended arrow if bidirectional or single if not
*/
export function conversionArrow(bidirectional: boolean) {
// Arrow is bidirectional if conversion is bidirectional and one way if not.
let arrowShown: string;
if (bidirectional) {
arrowShown = ' ↔ ';
} else {
arrowShown = ' → ';
}
return arrowShown;
}
10 changes: 10 additions & 0 deletions src/server/models/Unit.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,16 @@ class Unit {
log.warn(`Automatically set identifier of the unit "${unit.name}" to "${unit.name}"`);
}
}

/**
* Returns a promise to delete a unit
* @param mapID The ID of the unit to be deleted
* @param conn the connection to be used.
* @returns {Promise.<void>}
*/
static async delete(unitID, conn) {
await conn.none(sqlFile('unit/delete_unit.sql'), { id: unitID });
}
}

Unit.unitType = Object.freeze({
Expand Down
2 changes: 1 addition & 1 deletion src/server/routes/conversions.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ router.post('/delete', async (req, res) => {
log.error(`Error while deleting conversion with error(s): ${err}`);
failure(res, 500, `Error while deleting conversion with errors(s): ${err}`);
}
success(res, `Successfully deleted conversion ${req.body.sourceId} -> ${req.body.destinationId}`);
success(res, 'Successfully deleted conversion');
}
});

Expand Down
Loading

0 comments on commit b3aa0ee

Please sign in to comment.