From 6f3ee9fa3ebaa693e2a3a0f005c759242449bbea Mon Sep 17 00:00:00 2001 From: Juan Gutierrez Date: Thu, 18 Jul 2024 11:02:17 -0700 Subject: [PATCH 01/50] Changing maps page to cards with modals --- .../maps/CreateMapModalComponent.tsx | 72 +++ .../components/maps/EditMapModalComponent.tsx | 147 ++++++ .../app/components/maps/MapViewComponent.tsx | 481 +++--------------- .../components/maps/MapsDetailComponent.tsx | 100 ++-- 4 files changed, 321 insertions(+), 479 deletions(-) create mode 100644 src/client/app/components/maps/CreateMapModalComponent.tsx create mode 100644 src/client/app/components/maps/EditMapModalComponent.tsx diff --git a/src/client/app/components/maps/CreateMapModalComponent.tsx b/src/client/app/components/maps/CreateMapModalComponent.tsx new file mode 100644 index 000000000..bde6ab39e --- /dev/null +++ b/src/client/app/components/maps/CreateMapModalComponent.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import { useState } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input } from 'reactstrap'; +import { Link } from 'react-router-dom'; +import { CalibrationModeTypes } from '../../types/redux/map'; + +interface CreateMapModalProps { + show: boolean; + handleClose: () => void; + createNewMap: () => void; +} + +/** + * + */ +function CreateMapModalComponent({ show, handleClose, createNewMap }: CreateMapModalProps) { + const [nameInput, setNameInput] = useState(''); + const [noteInput, setNoteInput] = useState(''); + + const handleCreate = () => { + // TODO: Implement create functionality + createNewMap(); + handleClose(); + }; + + return ( + + + + + +
+ + + setNameInput(e.target.value)} + /> + + + + setNoteInput(e.target.value)} + /> + +
+
+ createNewMap()}> + + +
+
+ + + + +
+ ); +} + +export default CreateMapModalComponent; \ No newline at end of file diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx new file mode 100644 index 000000000..b1054ccca --- /dev/null +++ b/src/client/app/components/maps/EditMapModalComponent.tsx @@ -0,0 +1,147 @@ +import * as React from 'react'; +import { useState } from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input } from 'reactstrap'; +import { Link } from 'react-router-dom'; +import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map'; +import { showErrorNotification } from '../../utils/notifications'; + +interface EditMapModalProps { + show: boolean; + handleClose: () => void; + map: MapMetadata; + editMapDetails(map: MapMetadata): any; + setCalibration(mode: CalibrationModeTypes, mapID: number): any; + removeMap(id: number): any; +} + +/** + *Defines the edit maps modal form + * @param props state variables needed to define the component + * @returns Map edit element + */ +function EditMapModalComponent(props: EditMapModalProps) { + const [nameInput, setNameInput] = useState(props.map.name); + const [noteInput, setNoteInput] = useState(props.map.note || ''); + const [circleInput, setCircleInput] = useState(props.map.circleSize.toString()); + const [displayable, setDisplayable] = useState(props.map.displayable); + + const intl = useIntl(); + + const handleSave = () => { + const updatedMap = { + ...props.map, + name: nameInput, + note: noteInput, + circleSize: parseFloat(circleInput), + displayable: displayable + }; + props.editMapDetails(updatedMap); + props.handleClose(); + }; + + const handleDelete = () => { + const consent = window.confirm(intl.formatMessage({ id: 'map.confirm.remove' }, { name: props.map.name })); + if (consent) { + props.removeMap(props.map.id); + props.handleClose(); + } + }; + + const handleCalibrationSetting = (mode: CalibrationModeTypes) => { + props.setCalibration(mode, props.map.id); + props.handleClose(); + }; + + const toggleCircleEdit = () => { + const regtest = /^\d+(\.\d+)?$/; + if (regtest.test(circleInput) && parseFloat(circleInput) <= 2.0) { + setCircleInput(circleInput); + } else { + showErrorNotification(intl.formatMessage({ id: 'invalid.number' })); + } + }; + + return ( + + + + + +
+ + + setNameInput(e.target.value)} + /> + + + + setDisplayable(e.target.value === 'true')} + > + + + + + + + setCircleInput(e.target.value)} + onBlur={toggleCircleEdit} + /> + + + + setNoteInput(e.target.value)} + /> + +
+
+ +

{props.map.filename}

+ handleCalibrationSetting(CalibrationModeTypes.initiate)}> + + +
+
+ +

+ +

+ handleCalibrationSetting(CalibrationModeTypes.calibrate)}> + + +
+
+ + + + + +
+ ); +} + +export default EditMapModalComponent; \ No newline at end of file diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx index b8b91a698..28b853638 100644 --- a/src/client/app/components/maps/MapViewComponent.tsx +++ b/src/client/app/components/maps/MapViewComponent.tsx @@ -2,436 +2,91 @@ * 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/. */ -import * as moment from 'moment'; import * as React from 'react'; -import { FormattedMessage, injectIntl, WrappedComponentProps } from 'react-intl'; -import { Link } from 'react-router-dom'; +import { useState, useEffect } from 'react'; +import { FormattedMessage} from 'react-intl'; import { Button } from 'reactstrap'; +import * as moment from 'moment'; import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map'; -import { showErrorNotification } from '../../utils/notifications'; import { hasToken } from '../../utils/token'; +import '../../styles/card-page.css'; +import EditMapModalComponent from './EditMapModalComponent'; + interface MapViewProps { - // The ID of the map to be displayed id: number; - // The map metadata being displayed by this row map: MapMetadata; isEdited: boolean; isSubmitting: boolean; - // The function used to dispatch the action to edit map details editMapDetails(map: MapMetadata): any; setCalibration(mode: CalibrationModeTypes, mapID: number): any; removeMap(id: number): any; } -interface MapViewState { - nameFocus: boolean; - nameInput: string; - circleFocus: boolean; - circleInput: string; - noteFocus: boolean; - noteInput: string; -} - -type MapViewPropsWithIntl = MapViewProps & WrappedComponentProps; - -class MapViewComponent extends React.Component { - constructor(props: MapViewPropsWithIntl) { - super(props); - this.state = { - nameFocus: false, - nameInput: this.props.map.name, - noteFocus: false, - noteInput: (this.props.map.note) ? this.props.map.note : '', - circleFocus: false, - // circleSize should always be a valid string due to how stored and mapRow. - circleInput: this.props.map.circleSize.toString() - }; - this.handleCalibrationSetting = this.handleCalibrationSetting.bind(this); - this.toggleMapDisplayable = this.toggleMapDisplayable.bind(this); - this.toggleNameInput = this.toggleNameInput.bind(this); - this.handleNameChange = this.handleNameChange.bind(this); - this.toggleNoteInput = this.toggleNoteInput.bind(this); - this.handleNoteChange = this.handleNoteChange.bind(this); - this.toggleDelete = this.toggleDelete.bind(this); - this.notifyCalibrationNeeded = this.notifyCalibrationNeeded.bind(this); - this.handleSizeChange = this.handleSizeChange.bind(this); - this.toggleCircleInput = this.toggleCircleInput.bind(this); - } - - public render() { - return ( - - {this.props.map.id} {this.formatStatus()} - {this.formatName()} - {hasToken() && {this.formatDisplayable()} } - {hasToken() && {this.formatCircleSize()} } - {/* This was stored as UTC but with the local time at that point. - Thus, moment will not modify the date/time given when done this way. */} - {hasToken() && {moment.parseZone(this.props.map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')} } - {hasToken() && {this.formatFilename()} } - {hasToken() && {this.formatNote()} } - {hasToken() && {this.formatCalibrationStatus()} } - {hasToken() && {this.formatDeleteButton()} } - - ); - } - - componentDidMount() { - if (this.props.isEdited) { - // When the props.isEdited is true after loading the page, there are unsaved changes - this.updateUnsavedChanges(); - } - } - - componentDidUpdate(prevProps: MapViewProps) { - if (this.props.isEdited && !prevProps.isEdited) { - // When the props.isEdited changes from false to true, there are unsaved changes - this.updateUnsavedChanges(); - } - } - - // Re-implement After RTK migration - // private removeUnsavedChangesFunction(callback: () => void) { - // // This function is called to reset all the inputs to the initial state - // store.dispatch(confirmEditedMaps()).then(() => { - // store.dispatch(fetchMapsDetails()).then(callback); - // }); - // } - - // Re-implement After RTK migration - // private submitUnsavedChangesFunction(successCallback: () => void, failureCallback: () => void) { - // // This function is called to submit the unsaved changes - // store.dispatch(submitEditedMaps()).then(successCallback, failureCallback); - // } - - private updateUnsavedChanges() { - // Re-implement After RTK migration - // Notify that there are unsaved changes - // store.dispatch(unsavedWarningSlice.actions.updateUnsavedChanges({ - // removeFunction: this.removeUnsavedChangesFunction, - // submitFunction: this.submitUnsavedChangesFunction - // })); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } - - private handleSizeChange(event: React.ChangeEvent) { - this.setState({ circleInput: event.target.value }); - } - - private toggleCircleInput() { - let checkval: boolean = true; - // if trying to submit an updated value - if (this.state.circleFocus) { - const regtest = /^\d+(\.\d+)?$/; - checkval = regtest.test(this.state.circleInput); - if (checkval) { - if (parseFloat(this.state.circleInput) > 2.0) { - checkval = false; - } - else { - const editedMap = { - ...this.props.map, - circleSize: parseFloat(this.state.circleInput) - }; - this.props.editMapDetails(editedMap); - } - } - } - if (checkval) { - this.setState({ circleFocus: !this.state.circleFocus }); - } - else { - showErrorNotification(`${this.props.intl.formatMessage({ id: 'invalid.number' })}`); - } - } - - private formatCircleSize() { - let formattedCircleSize; - let buttonMessageId; - if (this.state.circleFocus) { - // default value for autoFocus is true and for all attributes that would be set autoFocus={true} - formattedCircleSize =