From 6f3ee9fa3ebaa693e2a3a0f005c759242449bbea Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
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 (
+		<Modal isOpen={show} toggle={handleClose}>
+			<ModalHeader toggle={handleClose}>
+				<FormattedMessage id="create.map" />
+			</ModalHeader>
+			<ModalBody>
+				<Form>
+					<FormGroup>
+						<Label for="mapName"><FormattedMessage id="map.name" /></Label>
+						<Input
+							id="mapName"
+							value={nameInput}
+							onChange={(e) => setNameInput(e.target.value)}
+						/>
+					</FormGroup>
+					<FormGroup>
+						<Label for="mapNote"><FormattedMessage id="note" /></Label>
+						<Input
+							id="mapNote"
+							type="textarea"
+							value={noteInput}
+							onChange={(e) => setNoteInput(e.target.value)}
+						/>
+					</FormGroup>
+				</Form>
+				<div>
+					<Link to='/calibration' onClick={() => createNewMap()}>
+						<Button color='primary'>
+							<FormattedMessage id='map.upload.file' />
+						</Button>
+					</Link>
+				</div>
+			</ModalBody>
+			<ModalFooter>
+				<Button color="secondary" onClick={handleClose}>
+					<FormattedMessage id="cancel" />
+				</Button>
+				<Button color="primary" onClick={handleCreate}>
+					<FormattedMessage id="create" />
+				</Button>
+			</ModalFooter>
+		</Modal>
+	);
+}
+
+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 (
+		<Modal isOpen={props.show} toggle={props.handleClose}>
+			<ModalHeader toggle={props.handleClose}>
+				<FormattedMessage id="edit.map" />
+			</ModalHeader>
+			<ModalBody>
+				<Form>
+					<FormGroup>
+						<Label for="mapName"><FormattedMessage id="map.name" /></Label>
+						<Input
+							id="mapName"
+							value={nameInput}
+							onChange={e => setNameInput(e.target.value)}
+						/>
+					</FormGroup>
+					<FormGroup>
+						<Label for="mapDisplayable"><FormattedMessage id="map.displayable" /></Label>
+						<Input
+							id="mapDisplayable"
+							type="select"
+							value={displayable.toString()}
+							onChange={e => setDisplayable(e.target.value === 'true')}
+						>
+							<option value="true">{intl.formatMessage({ id: 'map.is.displayable' })}</option>
+							<option value="false">{intl.formatMessage({ id: 'map.is.not.displayable' })}</option>
+						</Input>
+					</FormGroup>
+					<FormGroup>
+						<Label for="mapCircleSize"><FormattedMessage id="map.circle.size" /></Label>
+						<Input
+							id="mapCircleSize"
+							value={circleInput}
+							onChange={e => setCircleInput(e.target.value)}
+							onBlur={toggleCircleEdit}
+						/>
+					</FormGroup>
+					<FormGroup>
+						<Label for="mapNote"><FormattedMessage id="note" /></Label>
+						<Input
+							id="mapNote"
+							type="textarea"
+							value={noteInput}
+							onChange={e => setNoteInput(e.target.value)}
+						/>
+					</FormGroup>
+				</Form>
+				<div>
+					<Label><FormattedMessage id="map.filename" /></Label>
+					<p>{props.map.filename}</p>
+					<Link to='/calibration' onClick={() => handleCalibrationSetting(CalibrationModeTypes.initiate)}>
+						<Button color='primary'>
+							<FormattedMessage id='map.upload.new.file' />
+						</Button>
+					</Link>
+				</div>
+				<div>
+					<Label><FormattedMessage id="map.calibration" /></Label>
+					<p>
+						<FormattedMessage id={props.map.origin && props.map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
+					</p>
+					<Link to='/calibration' onClick={() => handleCalibrationSetting(CalibrationModeTypes.calibrate)}>
+						<Button color='primary'>
+							<FormattedMessage id='map.calibrate' />
+						</Button>
+					</Link>
+				</div>
+			</ModalBody>
+			<ModalFooter>
+				<Button color="danger" onClick={handleDelete}>
+					<FormattedMessage id="delete.map" />
+				</Button>
+				<Button color="secondary" onClick={props.handleClose}>
+					<FormattedMessage id="cancel" />
+				</Button>
+				<Button color="primary" onClick={handleSave}>
+					<FormattedMessage id="done.editing" />
+				</Button>
+			</ModalFooter>
+		</Modal>
+	);
+}
+
+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<MapViewPropsWithIntl, MapViewState> {
-	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 (
-			<tr>
-				<td> {this.props.map.id} {this.formatStatus()}</td>
-				<td> {this.formatName()} </td>
-				{hasToken() && <td> {this.formatDisplayable()} </td>}
-				{hasToken() && <td> {this.formatCircleSize()} </td>}
-				{/* 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() && <td> {moment.parseZone(this.props.map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')} </td>}
-				{hasToken() && <td> {this.formatFilename()} </td>}
-				{hasToken() && <td> {this.formatNote()} </td>}
-				{hasToken() && <td> {this.formatCalibrationStatus()} </td>}
-				{hasToken() && <td> {this.formatDeleteButton()} </td>}
-			</tr>
-		);
-	}
-
-	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<any>(confirmEditedMaps()).then(() => {
-	// 		store.dispatch<any>(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<any>(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<HTMLTextAreaElement>) {
-		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 = <textarea id={'csize'} autoFocus value={this.state.circleInput} onChange={event => this.handleSizeChange(event)} />;
-			buttonMessageId = 'update';
-		} else {
-			formattedCircleSize = <div>{this.state.circleInput}</div>;
-			buttonMessageId = 'edit';
-		}
-
-		let toggleButton;
-		if (hasToken()) {
-			toggleButton = <Button style={this.styleToggleBtn()} color='primary' onClick={this.toggleCircleInput}>
-				<FormattedMessage id={buttonMessageId} />
-			</Button>;
-		} else {
-			toggleButton = <div />;
-		}
-
-		if (hasToken()) {
-			return (
-				<div>
-					{formattedCircleSize}
-					{toggleButton}
-				</div>
-			);
-		} else {
-			return (
-				<div>
-					{this.props.map.circleSize}
-					{toggleButton}
-				</div>
-			);
-		}
-	}
-
-	private formatStatus(): string {
-		if (this.props.isSubmitting) {
-			return '(' + this.props.intl.formatMessage({ id: 'submitting' }) + ')';
-		}
-		if (this.props.isEdited) {
-			return this.props.intl.formatMessage({ id: 'edited' });
-		}
-		return '';
-	}
-
-	private toggleDelete() {
-		const consent = window.confirm(`${this.props.intl.formatMessage({ id: 'map.confirm.remove' })} "${this.props.map.name}"?`);
-		if (consent) { this.props.removeMap(this.props.id); }
-	}
-
-	private formatDeleteButton() {
-		const editButtonStyle: React.CSSProperties = {
-			display: 'inline', // or 'none'
-			paddingLeft: '5px'
-		};
-		return <Button style={editButtonStyle} color='primary' onClick={this.toggleDelete}>
-			<FormattedMessage id={'delete.map'} />
-		</Button>;
-	}
-
-	private styleEnabled(): React.CSSProperties {
-		return { color: 'green' };
-	}
-
-	private styleDisabled(): React.CSSProperties {
-		return { color: 'red' };
-	}
-
-	private styleToggleBtn(): React.CSSProperties {
-		return { float: 'right' };
-	}
-
-	private toggleMapDisplayable() {
-		const editedMap = {
-			...this.props.map,
-			displayable: !this.props.map.displayable
-		};
-		this.props.editMapDetails(editedMap);
-	}
-
-	private formatDisplayable() {
-		let styleFn;
-		let messageId;
-		let buttonMessageId;
-
-		if (this.props.map.displayable) {
-			styleFn = this.styleEnabled;
-			messageId = 'map.is.displayable';
-			buttonMessageId = 'hide';
-		} else {
-			styleFn = this.styleDisabled;
-			messageId = 'map.is.not.displayable';
-			buttonMessageId = 'show';
-		}
-
-		let toggleButton;
-		if (hasToken()) {
-			// throw out alert if the admin wants to display uncalibrated map
-			if (!(this.props.map.origin && this.props.map.opposite)) {
-				toggleButton = <Button style={this.styleToggleBtn()} color='primary' onClick={this.notifyCalibrationNeeded}>
-					<FormattedMessage id={buttonMessageId} />
-				</Button>;
-			}
-			// if map is already calibrated, the button will allow it to be displayed
-			else {
-				toggleButton = <Button style={this.styleToggleBtn()} color='primary' onClick={this.toggleMapDisplayable}>
-					<FormattedMessage id={buttonMessageId} />
-				</Button>;
-			}
-		} else {
-			toggleButton = <div />;
-		}
-
-		return (
-			<span>
-				<span style={styleFn()}>
-					<FormattedMessage id={messageId} />
+/**
+ * Defines the map info card
+ * @param props variables passed in to define
+ * @returns Map info card element
+ */
+function MapViewComponent(props: MapViewProps) {
+	const [showEditModal, setShowEditModal] = useState(false);
+
+	useEffect(() => {
+		if (props.isEdited) {
+			//updateUnsavedChanges();
+		}
+	}, [props.isEdited]);
+
+	const handleShowModal = () => setShowEditModal(true);
+	const handleCloseModal = () => setShowEditModal(false);
+
+	return (
+		<div className="card">
+			<div className="identifier-container">
+				{props.map.name} {props.isSubmitting ? '(Submitting)' : props.isEdited ? '(Edited)' : ''}
+			</div>
+			<div className="item-container">
+				<b><FormattedMessage id="map.displayable" /></b>
+				<span style={{ color: props.map.displayable ? 'green' : 'red' }}>
+					<FormattedMessage id={props.map.displayable ? 'map.is.displayable' : 'map.is.not.displayable'} />
 				</span>
-				{toggleButton}
-			</span>
-		);
-	}
-
-	// this function throws alert on the browser notifying that map needs calibrating before display
-	private notifyCalibrationNeeded() {
-		showErrorNotification(`${this.props.intl.formatMessage({ id: 'map.notify.calibration.needed' })} "${this.props.map.name}"`);
-	}
-
-	private toggleNameInput() {
-		if (this.state.nameFocus) {
-			const editedMap = {
-				...this.props.map,
-				name: this.state.nameInput
-			};
-			this.props.editMapDetails(editedMap);
-		}
-		this.setState({ nameFocus: !this.state.nameFocus });
-	}
-
-	private handleNameChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
-		this.setState({ nameInput: event.target.value });
-	}
-
-	private formatName() {
-		let formattedName;
-		let buttonMessageId;
-		if (this.state.nameFocus) {
-			// default value for autoFocus is true and for all attributes that would be set autoFocus={true}
-			formattedName = <textarea id={'name'} autoFocus value={this.state.nameInput} onChange={event => this.handleNameChange(event)} />;
-			buttonMessageId = 'update';
-		} else {
-			formattedName = <div>{this.state.nameInput}</div>;
-			buttonMessageId = 'edit';
-		}
-
-		let toggleButton;
-		if (hasToken()) {
-			toggleButton = <Button style={this.styleToggleBtn()} color='primary' onClick={this.toggleNameInput}>
-				<FormattedMessage id={buttonMessageId} />
-			</Button>;
-		} else {
-			toggleButton = <div />;
-		}
-
-		if (hasToken()) {
-			return (
-				<div>
-					{formattedName}
-					{toggleButton}
-				</div>
-			);
-		} else {
-			return (
-				<div>
-					{this.props.map.name}
-					{toggleButton}
-				</div>
-			);
-		}
-	}
-
-	private toggleNoteInput() {
-		if (this.state.noteFocus) {
-			const editedMap = {
-				...this.props.map,
-				note: this.state.noteInput
-			};
-			this.props.editMapDetails(editedMap);
-		}
-		this.setState({ noteFocus: !this.state.noteFocus });
-	}
-
-	private handleNoteChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
-		this.setState({ noteInput: event.target.value });
-	}
-
-	private formatNote() {
-		let formattedNote;
-		let buttonMessageId;
-		if (this.state.noteFocus) {
-			// default value for autoFocus is true and for all attributes that would be set autoFocus={true}
-			formattedNote = <textarea id={'note'} autoFocus value={this.state.noteInput} onChange={event => this.handleNoteChange(event)} />;
-			buttonMessageId = 'update';
-		} else {
-			formattedNote = <div>{this.state.noteInput}</div>;
-			buttonMessageId = 'edit';
-		}
-
-		let toggleButton;
-		if (hasToken()) {
-			toggleButton = <Button style={this.styleToggleBtn()} color='primary' onClick={this.toggleNoteInput}>
-				<FormattedMessage id={buttonMessageId} />
-			</Button>;
-		} else {
-			toggleButton = <div />;
-		}
-
-		if (hasToken()) {
-			return (
-				<div>
-					{formattedNote}
-					{toggleButton}
-				</div>
-			);
-		} else {
-			return (
-				<div>
-					{this.props.map.note}
-					{toggleButton}
-				</div>
-			);
-		}
-	}
-
-	private styleCalibrated(): React.CSSProperties {
-		return { color: 'black' };
-	}
-
-	private styleNotCalibrated(): React.CSSProperties {
-		return { color: 'gray' };
-	}
-
-	private formatCalibrationStatus() {
-		let styleFn;
-		let messageID;
-		if (this.props.map.origin && this.props.map.opposite) {
-			styleFn = this.styleCalibrated;
-			messageID = 'map.is.calibrated';
-		} else {
-			styleFn = this.styleNotCalibrated;
-			messageID = 'map.is.not.calibrated';
-		}
-		return (
-			<span>
-				<span style={styleFn()}>
-					<FormattedMessage id={messageID} />
+			</div>
+			<div className="item-container">
+				<b><FormattedMessage id="map.circle.size" /></b> {props.map.circleSize}
+			</div>
+			<div className="item-container">
+				<b><FormattedMessage id="map.modified.date" /></b>
+				{moment.parseZone(props.map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
+			</div>
+			<div className="item-container">
+				<b><FormattedMessage id="map.filename" /></b> {props.map.filename}
+			</div>
+			<div className="item-container">
+				<b><FormattedMessage id="note" /></b> {props.map.note}
+			</div>
+			<div className="item-container">
+				<b><FormattedMessage id="map.calibration" /></b>
+				<span style={{ color: props.map.origin && props.map.opposite ? 'black' : 'gray' }}>
+					<FormattedMessage id={props.map.origin && props.map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
 				</span>
-				<Link to='/calibration' onClick={() => this.handleCalibrationSetting(CalibrationModeTypes.calibrate)}>
-					<Button style={this.styleToggleBtn()} color='primary'>
-						<FormattedMessage id='map.calibrate' />
+			</div>
+			{hasToken() && (
+				<div className="edit-btn">
+					<Button color='secondary' onClick={handleShowModal}>
+						<FormattedMessage id="edit.map" />
 					</Button>
-				</Link>
-			</span>
-		);
-	}
-
-	private formatFilename() {
-		return (
-			<span>
-				<span>{this.props.map.filename}</span>
-				<Link to='/calibration' onClick={() => this.handleCalibrationSetting(CalibrationModeTypes.initiate)}>
-					<Button style={this.styleToggleBtn()} color='primary'>
-						<FormattedMessage id='map.upload.new.file' />
-					</Button>
-				</Link>
-			</span>
-		);
-	}
-
-	private handleCalibrationSetting(mode: CalibrationModeTypes) {
-		this.props.setCalibration(mode, this.props.id);
-		this.updateUnsavedChanges();
-	}
+				</div>
+			)}
+			<EditMapModalComponent
+				show={showEditModal}
+				handleClose={handleCloseModal}
+				map={props.map}
+				editMapDetails={props.editMapDetails}
+				setCalibration={props.setCalibration}
+				removeMap={props.removeMap}
+			/>
+		</div>
+	);
 }
 
-export default injectIntl(MapViewComponent);
+export default MapViewComponent;
\ No newline at end of file
diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index 2cc9868d0..d154133da 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -5,11 +5,12 @@
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { Link } from 'react-router-dom';
-import { Button, Table } from 'reactstrap';
+import { Button } from 'reactstrap';
 import TooltipHelpComponent from '../../components/TooltipHelpComponent';
 import MapViewContainer from '../../containers/maps/MapViewContainer';
 import { hasToken } from '../../utils/token';
 import TooltipMarkerComponent from '../TooltipMarkerComponent';
+import '../../styles/card-page.css';
 
 interface MapsDetailProps {
 	maps: number[];
@@ -30,31 +31,8 @@ export default class MapsDetailComponent extends React.Component<MapsDetailProps
 	}
 
 	public render() {
-
-		const titleStyle: React.CSSProperties = {
-			textAlign: 'center'
-		};
-
-		const tableStyle: React.CSSProperties = {
-			marginLeft: '5%',
-			marginRight: '5%'
-		};
-
-		const buttonContainerStyle: React.CSSProperties = {
-			minWidth: '150px',
-			width: '10%',
-			marginLeft: '40%',
-			marginRight: '40%'
-		};
-
-		const tooltipStyle = {
-			display: 'inline-block',
-			fontSize: '50%'
-		};
-
 		return (
 			<div className='flexGrowOne'>
-				{/* <UnsavedWarningContainer /> */}
 				<TooltipHelpComponent page='maps' />
 				<div className='container-fluid'>
 					<h2 style={titleStyle}>
@@ -63,56 +41,46 @@ export default class MapsDetailComponent extends React.Component<MapsDetailProps
 							<TooltipMarkerComponent page='maps' helpTextId='help.admin.mapview' />
 						</div>
 					</h2>
-					<div style={tableStyle}>
-						<Table striped bordered hover>
-							<thead>
-								<tr>
-									<th> <FormattedMessage id='map.id' /> </th>
-									<th> <FormattedMessage id='map.name' /> </th>
-									{hasToken() && <th> <FormattedMessage id='map.displayable' /> </th>}
-									{hasToken() && <th> <FormattedMessage id='map.circle.size' /> </th>}
-									{hasToken() && <th> <FormattedMessage id='map.modified.date' /> </th>}
-									{hasToken() && <th> <FormattedMessage id='map.filename' /> </th>}
-									{hasToken() && <th> <FormattedMessage id='note' /> </th>}
-									{hasToken() && <th> <FormattedMessage id='map.calibration' /> </th>}
-									{hasToken() && <th> <FormattedMessage id='remove' /> </th>}
-								</tr>
-							</thead>
-							<tbody>
-								{this.props.maps.map(mapID =>
-									(<MapViewContainer key={mapID} id={mapID} />))}
-								<tr>
-									<td colSpan={8}>
-										<Link to='/calibration' onClick={() => this.props.createNewMap()}>
-											<Button style={buttonContainerStyle} color='primary'>
-												<FormattedMessage id='create.map' />
-											</Button>
-										</Link>
-									</td>
-								</tr>
-							</tbody>
-						</Table>
+					<div className="edit-btn">
+						<Link to='/calibration' onClick={() => this.props.createNewMap()}>
+							<Button color='primary'>
+								<FormattedMessage id='create.map' />
+							</Button>
+						</Link>
+					</div>
+					<div className="card-container">
+						{this.props.maps.map(mapID => (
+							<MapViewContainer key={mapID} id={mapID} />
+						))}
 					</div>
-					{hasToken() && <Button
-						color='success'
-						style={buttonContainerStyle}
-						disabled={!this.props.unsavedChanges}
-						onClick={this.handleSubmitClicked}
-					>
-						<FormattedMessage id='save.map.edits' />
-					</Button>}
+					{hasToken() && (
+						<div className="edit-btn">
+							<Button
+								color='success'
+								disabled={!this.props.unsavedChanges}
+								onClick={this.handleSubmitClicked}
+							>
+								<FormattedMessage id='save.map.edits' />
+							</Button>
+						</div>
+					)}
 				</div>
 			</div>
 		);
 	}
 
-	private removeUnsavedChanges() {
-		// store.dispatch(unsavedWarningSlice.actions.removeUnsavedChanges());
-	}
-
 	private handleSubmitClicked() {
 		this.props.submitEditedMaps();
 		// Notify that the unsaved changes have been submitted
-		this.removeUnsavedChanges();
+		// this.removeUnsavedChanges();
 	}
 }
+
+const titleStyle: React.CSSProperties = {
+	textAlign: 'center'
+};
+
+const tooltipStyle = {
+	display: 'inline-block',
+	fontSize: '50%'
+};
\ No newline at end of file

From 17d26a069184eceb9214c8a5dbe796785ae40499 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Thu, 18 Jul 2024 11:16:53 -0700
Subject: [PATCH 02/50] Adding messagesfor map modal

---
 src/client/app/translations/data.ts | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index aa9ff683a..213651a02 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -151,6 +151,7 @@ const LocaleTranslationData = {
 		"DisplayableType.none": "none",
 		"DisplayableType.all": "all",
 		"DisplayableType.admin": "admin",
+		"done.editing":"Done editing",
 		"error.bounds": "Must be between {min} and {max}.",
 		"error.displayable": "Displayable will be set to false because no unit is selected.",
 		"error.displayable.meter": "Meter units will set displayable to none.",
@@ -165,6 +166,7 @@ const LocaleTranslationData = {
 		"edit.a.group": "Edit a Group",
 		"edit.a.meter": "Edit a Meter",
 		"edit.group": "Edit Group",
+		"edit.map":"Edit Map",
 		"edit.meter": "Details/Edit Meter",
 		"edit.unit": "Edit Unit",
 		"email": "Email",
@@ -646,6 +648,7 @@ const LocaleTranslationData = {
 		"DisplayableType.none": "none\u{26A1}",
 		"DisplayableType.all": "all\u{26A1}",
 		"DisplayableType.admin": "admin\u{26A1}",
+		"done.editing":"Done editing\u{26A1}",
 		"error.bounds": "Must be between {min} and {max}.\u{26A1}",
 		"error.displayable": "Displayable will be set to false because no unit is selected.\u{26A1}",
 		"error.displayable.meter": "Meter units will set displayable to none.\u{26A1}",
@@ -660,6 +663,7 @@ const LocaleTranslationData = {
 		"edit.a.group": "Modifier le Groupe",
 		"edit.a.meter": "Modifier le Métre",
 		"edit.group": "Modifier Groupe",
+		"edit.map":"Edit Map\u{26A1}",
 		"edit.meter": "Details/Modifier Métre\u{26A1}",
 		"edit.unit": "Edit Unit\u{26A1}",
 		"email": "E-mail",
@@ -1141,6 +1145,7 @@ const LocaleTranslationData = {
 		"DisplayableType.none": "ninguno",
 		"DisplayableType.all": "todo",
 		"DisplayableType.admin": "administrador",
+		"done.editing":"Acabar de editar",
 		"error.bounds": "Debe ser entre {min} y {max}.",
 		"error.displayable": "El elemento visual determinado como falso porque no hay unidad seleccionada.",
 		"error.displayable.meter": "Las unidades de medición determinarán al elemento visual como ninguno.",
@@ -1155,7 +1160,8 @@ const LocaleTranslationData = {
 		"edit.a.group": "Editar un grupo",
 		"edit.a.meter": "Editar un medidor",
 		"edit.group": "Editar grupo",
-		"edit.meter": "Details/Editar medidor\u{26A1}",
+		"edit.map":"Editar mapa",
+		"edit.meter": "Editar medidor",
 		"edit.unit": "Editar unidad",
 		"email": "Correo electrónico",
 		"enable": "Activar",

From 823b1614d82e36bc5bbdf1bbf40d2baa6e3831c5 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Thu, 18 Jul 2024 11:29:08 -0700
Subject: [PATCH 03/50] Adding licence header

---
 src/client/app/components/maps/CreateMapModalComponent.tsx | 4 ++++
 src/client/app/components/maps/EditMapModalComponent.tsx   | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/src/client/app/components/maps/CreateMapModalComponent.tsx b/src/client/app/components/maps/CreateMapModalComponent.tsx
index bde6ab39e..a578a0ba5 100644
--- a/src/client/app/components/maps/CreateMapModalComponent.tsx
+++ b/src/client/app/components/maps/CreateMapModalComponent.tsx
@@ -1,3 +1,7 @@
+/* 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/. */
+
 import * as React from 'react';
 import { useState } from 'react';
 import { FormattedMessage } from 'react-intl';
diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index b1054ccca..012388827 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -1,3 +1,7 @@
+/* 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/. */
+
 import * as React from 'react';
 import { useState } from 'react';
 import { FormattedMessage, useIntl } from 'react-intl';

From 09b333a934f2c422fd0deaffc9fc4def93cc0821 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Thu, 18 Jul 2024 11:32:54 -0700
Subject: [PATCH 04/50] Removing unused line

---
 .../app/components/maps/CreateMapModalComponent.tsx       | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/client/app/components/maps/CreateMapModalComponent.tsx b/src/client/app/components/maps/CreateMapModalComponent.tsx
index a578a0ba5..222113734 100644
--- a/src/client/app/components/maps/CreateMapModalComponent.tsx
+++ b/src/client/app/components/maps/CreateMapModalComponent.tsx
@@ -7,7 +7,6 @@ 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;
@@ -16,7 +15,8 @@ interface CreateMapModalProps {
 }
 
 /**
- *
+ * Defines the create map modal form
+ * @returns Map create element
  */
 function CreateMapModalComponent({ show, handleClose, createNewMap }: CreateMapModalProps) {
 	const [nameInput, setNameInput] = useState('');
@@ -40,7 +40,7 @@ function CreateMapModalComponent({ show, handleClose, createNewMap }: CreateMapM
 						<Input
 							id="mapName"
 							value={nameInput}
-							onChange={(e) => setNameInput(e.target.value)}
+							onChange={e => setNameInput(e.target.value)}
 						/>
 					</FormGroup>
 					<FormGroup>
@@ -49,7 +49,7 @@ function CreateMapModalComponent({ show, handleClose, createNewMap }: CreateMapM
 							id="mapNote"
 							type="textarea"
 							value={noteInput}
-							onChange={(e) => setNoteInput(e.target.value)}
+							onChange={e => setNoteInput(e.target.value)}
 						/>
 					</FormGroup>
 				</Form>

From 8ff0ebe6cc924be1a133005301115070a97e9c1c Mon Sep 17 00:00:00 2001
From: Severin L <severinlight3@gmail.com>
Date: Wed, 24 Jul 2024 18:45:38 +0000
Subject: [PATCH 05/50] Fix to allow saves in map modal

---
 .../components/maps/EditMapModalComponent.tsx | 73 +++++++++----------
 .../app/components/maps/MapViewComponent.tsx  | 45 ++++++------
 src/client/app/redux/actions/map.ts           | 48 ++++++------
 src/client/app/redux/reducers/maps.ts         |  3 +-
 4 files changed, 83 insertions(+), 86 deletions(-)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index 012388827..00394842d 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -4,11 +4,15 @@
 
 import * as React from 'react';
 import { useState } from 'react';
+import { useDispatch } from 'react-redux';
+import { ThunkDispatch } from 'redux-thunk';
 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 { editMapDetails, submitEditedMap, removeMap } from '../../redux/actions/map';
 import { showErrorNotification } from '../../utils/notifications';
+import { State } from '../../types/redux/state';
+import { AnyAction } from 'redux';
 
 interface EditMapModalProps {
 	show: boolean;
@@ -19,42 +23,41 @@ interface EditMapModalProps {
 	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 EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose, map, setCalibration }) => {
+	const dispatch: ThunkDispatch<State, void, AnyAction> = useDispatch();
+	const [nameInput, setNameInput] = useState(map.name);
+	const [noteInput, setNoteInput] = useState(map.note || '');
+	const [circleInput, setCircleInput] = useState(map.circleSize.toString());
+	const [displayable, setDisplayable] = useState(map.displayable);
 
 	const intl = useIntl();
 
 	const handleSave = () => {
 		const updatedMap = {
-			...props.map,
+			...map,
 			name: nameInput,
 			note: noteInput,
 			circleSize: parseFloat(circleInput),
-			displayable: displayable
+			displayable
 		};
-		props.editMapDetails(updatedMap);
-		props.handleClose();
+		dispatch(editMapDetails(updatedMap));
+		dispatch(submitEditedMap(updatedMap.id) as any).then(() => {
+			handleClose();
+		});
 	};
 
 	const handleDelete = () => {
-		const consent = window.confirm(intl.formatMessage({ id: 'map.confirm.remove' }, { name: props.map.name }));
+		const consent = window.confirm(intl.formatMessage({ id: 'map.confirm.remove' }, { name: map.name }));
 		if (consent) {
-			props.removeMap(props.map.id);
-			props.handleClose();
+			dispatch(removeMap(map.id) as any).then(() => {
+				handleClose();
+			});
 		}
 	};
 
 	const handleCalibrationSetting = (mode: CalibrationModeTypes) => {
-		props.setCalibration(mode, props.map.id);
-		props.handleClose();
+		setCalibration(mode, map.id);
+		handleClose();
 	};
 
 	const toggleCircleEdit = () => {
@@ -67,8 +70,8 @@ function EditMapModalComponent(props: EditMapModalProps) {
 	};
 
 	return (
-		<Modal isOpen={props.show} toggle={props.handleClose}>
-			<ModalHeader toggle={props.handleClose}>
+		<Modal isOpen={show} toggle={handleClose}>
+			<ModalHeader toggle={handleClose}>
 				<FormattedMessage id="edit.map" />
 			</ModalHeader>
 			<ModalBody>
@@ -114,38 +117,34 @@ function EditMapModalComponent(props: EditMapModalProps) {
 				</Form>
 				<div>
 					<Label><FormattedMessage id="map.filename" /></Label>
-					<p>{props.map.filename}</p>
-					<Link to='/calibration' onClick={() => handleCalibrationSetting(CalibrationModeTypes.initiate)}>
-						<Button color='primary'>
-							<FormattedMessage id='map.upload.new.file' />
-						</Button>
-					</Link>
+					<p>{map.filename}</p>
+					<Button color='primary' onClick={() => handleCalibrationSetting(CalibrationModeTypes.initiate)}>
+						<FormattedMessage id='map.upload.new.file' />
+					</Button>
 				</div>
 				<div>
 					<Label><FormattedMessage id="map.calibration" /></Label>
 					<p>
-						<FormattedMessage id={props.map.origin && props.map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
+						<FormattedMessage id={map.origin && map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
 					</p>
-					<Link to='/calibration' onClick={() => handleCalibrationSetting(CalibrationModeTypes.calibrate)}>
-						<Button color='primary'>
-							<FormattedMessage id='map.calibrate' />
-						</Button>
-					</Link>
+					<Button color='primary' onClick={() => handleCalibrationSetting(CalibrationModeTypes.calibrate)}>
+						<FormattedMessage id='map.calibrate' />
+					</Button>
 				</div>
 			</ModalBody>
 			<ModalFooter>
 				<Button color="danger" onClick={handleDelete}>
 					<FormattedMessage id="delete.map" />
 				</Button>
-				<Button color="secondary" onClick={props.handleClose}>
+				<Button color="secondary" onClick={handleClose}>
 					<FormattedMessage id="cancel" />
 				</Button>
 				<Button color="primary" onClick={handleSave}>
-					<FormattedMessage id="done.editing" />
+					<FormattedMessage id="save.all" />
 				</Button>
 			</ModalFooter>
 		</Modal>
 	);
-}
+};
 
 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 28b853638..6f5b70e08 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -4,7 +4,7 @@
 
 import * as React from 'react';
 import { useState, useEffect } from 'react';
-import { FormattedMessage} from 'react-intl';
+import { FormattedMessage } from 'react-intl';
 import { Button } from 'reactstrap';
 import * as moment from 'moment';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
@@ -23,19 +23,18 @@ interface MapViewProps {
 	removeMap(id: number): any;
 }
 
-/**
- * Defines the map info card
- * @param props variables passed in to define
- * @returns Map info card element
- */
-function MapViewComponent(props: MapViewProps) {
+// editMapDetails: (map: MapMetadata) => void;
+// setCalibration: (mode: CalibrationModeTypes, mapID: number) => void;
+// removeMap: (id: number) => void;
+
+const MapViewComponent: React.FC<MapViewProps> = ({ map, isEdited, isSubmitting, editMapDetails, setCalibration, removeMap }) => {
 	const [showEditModal, setShowEditModal] = useState(false);
 
 	useEffect(() => {
-		if (props.isEdited) {
+		if (isEdited) {
 			//updateUnsavedChanges();
 		}
-	}, [props.isEdited]);
+	}, [isEdited]);
 
 	const handleShowModal = () => setShowEditModal(true);
 	const handleCloseModal = () => setShowEditModal(false);
@@ -43,31 +42,31 @@ function MapViewComponent(props: MapViewProps) {
 	return (
 		<div className="card">
 			<div className="identifier-container">
-				{props.map.name} {props.isSubmitting ? '(Submitting)' : props.isEdited ? '(Edited)' : ''}
+				{map.name} {isSubmitting ? '(Submitting)' : isEdited ? '(Edited)' : ''}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.displayable" /></b>
-				<span style={{ color: props.map.displayable ? 'green' : 'red' }}>
-					<FormattedMessage id={props.map.displayable ? 'map.is.displayable' : 'map.is.not.displayable'} />
+				<span style={{ color: map.displayable ? 'green' : 'red' }}>
+					<FormattedMessage id={map.displayable ? 'map.is.displayable' : 'map.is.not.displayable'} />
 				</span>
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="map.circle.size" /></b> {props.map.circleSize}
+				<b><FormattedMessage id="map.circle.size" /></b> {map.circleSize}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.modified.date" /></b>
-				{moment.parseZone(props.map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
+				{moment.parseZone(map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="map.filename" /></b> {props.map.filename}
+				<b><FormattedMessage id="map.filename" /></b> {map.filename}
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="note" /></b> {props.map.note}
+				<b><FormattedMessage id="note" /></b> {map.note}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.calibration" /></b>
-				<span style={{ color: props.map.origin && props.map.opposite ? 'black' : 'gray' }}>
-					<FormattedMessage id={props.map.origin && props.map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
+				<span style={{ color: map.origin && map.opposite ? 'black' : 'gray' }}>
+					<FormattedMessage id={map.origin && map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
 				</span>
 			</div>
 			{hasToken() && (
@@ -80,13 +79,13 @@ function MapViewComponent(props: MapViewProps) {
 			<EditMapModalComponent
 				show={showEditModal}
 				handleClose={handleCloseModal}
-				map={props.map}
-				editMapDetails={props.editMapDetails}
-				setCalibration={props.setCalibration}
-				removeMap={props.removeMap}
+				map={map}
+				setCalibration={setCalibration}
+				editMapDetails={editMapDetails}
+				removeMap={removeMap}
 			/>
 		</div>
 	);
-}
+};
 
 export default MapViewComponent;
\ No newline at end of file
diff --git a/src/client/app/redux/actions/map.ts b/src/client/app/redux/actions/map.ts
index 8fb9d33aa..27bc4b47a 100644
--- a/src/client/app/redux/actions/map.ts
+++ b/src/client/app/redux/actions/map.ts
@@ -2,24 +2,20 @@
  * 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 {ActionType, Dispatch, GetState, Thunk} from '../../types/redux/actions';
+import { ActionType, Dispatch, GetState, Thunk } from '../../types/redux/actions';
 import * as t from '../../types/redux/map';
-import {CalibrationModeTypes, MapData, MapMetadata} from '../../types/redux/map';
-import {
-	calibrate,
-	CalibratedPoint,
-	CalibrationResult,
-	CartesianPoint,
-	Dimensions,
-	GPSPoint
-} from '../../utils/calibration';
-import {State} from '../../types/redux/state';
-import {mapsApi} from '../../utils/api';
-import {showErrorNotification, showSuccessNotification} from '../../utils/notifications';
+import { CalibrationModeTypes, MapData, MapMetadata } from '../../types/redux/map';
+import { calibrate, CalibratedPoint, CalibrationResult, CartesianPoint, Dimensions, GPSPoint } from '../../utils/calibration';
+import { State } from '../../types/redux/state';
+import MapsApi from '../../utils/api/MapsApi';
+import ApiBackend from '../../utils/api/ApiBackend';
+import { showErrorNotification, showSuccessNotification } from '../../utils/notifications';
 import translate from '../../utils/translate';
 import * as moment from 'moment';
-import {browserHistory} from '../../utils/history';
-import {logToServer} from './logs';
+import { browserHistory } from '../../utils/history';
+import { logToServer } from './logs';
+
+const mapsApi = new MapsApi(new ApiBackend());
 
 function requestMapsDetails(): t.RequestMapsDetailsAction {
 	return { type: ActionType.RequestMapsDetails };
@@ -34,23 +30,27 @@ function submitMapEdits(mapID: number): t.SubmitEditedMapAction {
 }
 
 function confirmMapEdits(mapID: number): t.ConfirmEditedMapAction {
-	return { type: ActionType.ConfirmEditedMap, mapID};
+	return { type: ActionType.ConfirmEditedMap, mapID };
 }
 
 export function fetchMapsDetails(): Thunk {
 	return async (dispatch: Dispatch) => {
 		dispatch(requestMapsDetails());
-		const mapsDetails = await mapsApi.details();
-		dispatch(receiveMapsDetails(mapsDetails));
+		try {
+			const mapsDetails = await mapsApi.details();
+			dispatch(receiveMapsDetails(mapsDetails));
+		} catch (error) {
+			showErrorNotification(translate('failed.to.fetch.maps'));
+		}
 	};
 }
 
 export function editMapDetails(map: MapMetadata): t.EditMapDetailsAction {
-	return {type: ActionType.EditMapDetails, map};
+	return { type: ActionType.EditMapDetails, map };
 }
 
 function incrementCounter(): t.IncrementCounterAction {
-	return { type: ActionType.IncrementCounter};
+	return { type: ActionType.IncrementCounter };
 }
 
 export function setNewMap(): Thunk {
@@ -103,7 +103,7 @@ export function dropCalibration(): Thunk {
 }
 
 function resetCalibration(mapToReset: number): t.ResetCalibrationAction {
-	return { type: ActionType.ResetCalibration, mapID: mapToReset};
+	return { type: ActionType.ResetCalibration, mapID: mapToReset };
 }
 
 export function updateMapSource(data: MapMetadata): t.UpdateMapSourceAction {
@@ -163,7 +163,7 @@ function hasCartesian(point: CalibratedPoint) {
 }
 
 function updateCalibrationSet(calibratedPoint: CalibratedPoint): t.AppendCalibrationSetAction {
-	return { type: ActionType.AppendCalibrationSet, calibratedPoint};
+	return { type: ActionType.AppendCalibrationSet, calibratedPoint };
 }
 
 /**
@@ -199,11 +199,11 @@ function prepareDataToCalculation(state: State): CalibrationResult {
 }
 
 function updateResult(result: CalibrationResult): t.UpdateCalibrationResultAction {
-	return { type: ActionType.UpdateCalibrationResults, result};
+	return { type: ActionType.UpdateCalibrationResults, result };
 }
 
 export function resetCurrentPoint(): t.ResetCurrentPointAction {
-	return { type: ActionType.ResetCurrentPoint } ;
+	return { type: ActionType.ResetCurrentPoint };
 }
 
 export function submitEditedMaps(): Thunk {
diff --git a/src/client/app/redux/reducers/maps.ts b/src/client/app/redux/reducers/maps.ts
index 82ffb055c..be63ba71e 100644
--- a/src/client/app/redux/reducers/maps.ts
+++ b/src/client/app/redux/reducers/maps.ts
@@ -157,10 +157,10 @@ export default function maps(state = defaultState, action: MapsAction) {
 			};
 		case ActionType.ConfirmEditedMap:
 			submitting = state.submitting;
+			submitting.splice(submitting.indexOf(action.mapID), 1);
 			byMapID = state.byMapID;
 			editedMaps = state.editedMaps;
 			if (action.mapID > 0) {
-				submitting.splice(submitting.indexOf(action.mapID));
 				byMapID[action.mapID] = { ...editedMaps[action.mapID] };
 			}
 			delete editedMaps[action.mapID];
@@ -232,7 +232,6 @@ export default function maps(state = defaultState, action: MapsAction) {
 			return {
 				...state,
 				editedMaps: {
-					...state.editedMaps,
 					[calibrated]: {
 						...state.editedMaps[calibrated],
 						calibrationResult: action.result

From dc87b561c7155ada5c850a269da977b06d154a87 Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Sun, 28 Jul 2024 01:36:34 -0700
Subject: [PATCH 06/50] Create and utilize new maps selector

---
 .../app/components/maps/MapsDetailComponent.tsx     |  3 +++
 src/client/app/redux/reducers/maps.ts               |  4 +---
 src/client/app/redux/selectors/maps.ts              | 13 +++++++++++++
 src/client/app/redux/selectors/uiSelectors.ts       |  2 +-
 4 files changed, 18 insertions(+), 4 deletions(-)
 create mode 100644 src/client/app/redux/selectors/maps.ts

diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index d154133da..0d6d96772 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -11,6 +11,9 @@ import MapViewContainer from '../../containers/maps/MapViewContainer';
 import { hasToken } from '../../utils/token';
 import TooltipMarkerComponent from '../TooltipMarkerComponent';
 import '../../styles/card-page.css';
+import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
+import { selectMaps } from '../../redux/selectors/maps';
+	const maps: number[] = useAppSelector(selectMaps);
 
 interface MapsDetailProps {
 	maps: number[];
diff --git a/src/client/app/redux/reducers/maps.ts b/src/client/app/redux/reducers/maps.ts
index be63ba71e..3f5ce4253 100644
--- a/src/client/app/redux/reducers/maps.ts
+++ b/src/client/app/redux/reducers/maps.ts
@@ -6,7 +6,6 @@ import { MapMetadata, MapsAction, MapState } from '../../types/redux/map';
 import { ActionType } from '../../types/redux/actions';
 import { keyBy } from 'lodash';
 import { CalibratedPoint } from '../../utils/calibration';
-import { RootState } from '../../store';
 
 const defaultState: MapState = {
 	isLoading: false,
@@ -241,5 +240,4 @@ export default function maps(state = defaultState, action: MapsAction) {
 		default:
 			return state;
 	}
-}
-export const selectMapState = (state: RootState) => state.maps;
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/src/client/app/redux/selectors/maps.ts b/src/client/app/redux/selectors/maps.ts
new file mode 100644
index 000000000..0d208a1ca
--- /dev/null
+++ b/src/client/app/redux/selectors/maps.ts
@@ -0,0 +1,13 @@
+/* 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/. */
+
+import { RootState } from "store";
+import { createSelector } from "@reduxjs/toolkit";
+
+export const selectMapState = (state: RootState) => state.maps;
+export const selectMaps = createSelector([selectMapState], maps => {
+	return Object.keys(maps.byMapID)
+	.map(key => parseInt(key))
+	.filter(key => !isNaN(key));
+});
\ No newline at end of file
diff --git a/src/client/app/redux/selectors/uiSelectors.ts b/src/client/app/redux/selectors/uiSelectors.ts
index 6baecc5db..0eeff547a 100644
--- a/src/client/app/redux/selectors/uiSelectors.ts
+++ b/src/client/app/redux/selectors/uiSelectors.ts
@@ -19,7 +19,7 @@ import {
 } from '../../utils/calibration';
 import { metersInGroup, unitsCompatibleWithMeters } from '../../utils/determineCompatibleUnits';
 import { AreaUnitType } from '../../utils/getAreaUnitConversion';
-import { selectMapState } from '../reducers/maps';
+import { selectMapState } from '../selectors/maps';
 import {
 	selectChartToRender, selectGraphAreaNormalization, selectGraphState,
 	selectSelectedGroups, selectSelectedMeters, selectSelectedUnit, selectSliderRangeInterval

From c799c95f13330f25a9483376705fa99811f4b3cf Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Sun, 28 Jul 2024 01:46:24 -0700
Subject: [PATCH 07/50] Class Component to Redux function component update

---
 src/client/app/components/RouteComponent.tsx  |   4 +-
 .../components/maps/MapsDetailComponent.tsx   | 114 +++++++-----------
 2 files changed, 46 insertions(+), 72 deletions(-)

diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx
index ddeb46469..42d8401b5 100644
--- a/src/client/app/components/RouteComponent.tsx
+++ b/src/client/app/components/RouteComponent.tsx
@@ -6,7 +6,7 @@ import { IntlProvider } from 'react-intl';
 import { RouterProvider, createBrowserRouter } from 'react-router-dom';
 import UploadCSVContainer from '../containers/csv/UploadCSVContainer';
 import MapCalibrationContainer from '../containers/maps/MapCalibrationContainer';
-import MapsDetailContainer from '../containers/maps/MapsDetailContainer';
+import MapsDetailComponent from './maps/MapsDetailComponent';
 import { useAppSelector } from '../redux/reduxHooks';
 import LocaleTranslationData from '../translations/data';
 import { UserRole } from '../types/items';
@@ -56,7 +56,7 @@ const router = createBrowserRouter([
 				children: [
 					{ path: 'admin', element: <AdminComponent /> },
 					{ path: 'calibration', element: <MapCalibrationContainer /> },
-					{ path: 'maps', element: <MapsDetailContainer /> },
+					{ path: 'maps', element: <MapsDetailComponent /> },
 					{ path: 'users/new', element: <CreateUserComponent /> },
 					{ path: 'units', element: <UnitsDetailComponent /> },
 					{ path: 'conversions', element: <ConversionsDetailComponent /> },
diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index 0d6d96772..7a06e272c 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -11,79 +11,53 @@ import MapViewContainer from '../../containers/maps/MapViewContainer';
 import { hasToken } from '../../utils/token';
 import TooltipMarkerComponent from '../TooltipMarkerComponent';
 import '../../styles/card-page.css';
+import { fetchMapsDetails, setNewMap, submitEditedMaps } from '../../redux/actions/map';
 import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
 import { selectMaps } from '../../redux/selectors/maps';
-	const maps: number[] = useAppSelector(selectMaps);
-
-interface MapsDetailProps {
-	maps: number[];
-	unsavedChanges: boolean;
-	fetchMapsDetails(): Promise<any>;
-	submitEditedMaps(): Promise<any>;
-	createNewMap(): any;
-}
-
-export default class MapsDetailComponent extends React.Component<MapsDetailProps> {
-	constructor(props: MapsDetailProps) {
-		super(props);
-		this.handleSubmitClicked = this.handleSubmitClicked.bind(this);
-	}
-
-	public componentDidMount() {
-		this.props.fetchMapsDetails();
-	}
+import { AppDispatch } from 'store';
 
-	public render() {
-		return (
-			<div className='flexGrowOne'>
-				<TooltipHelpComponent page='maps' />
-				<div className='container-fluid'>
-					<h2 style={titleStyle}>
-						<FormattedMessage id='maps' />
-						<div style={tooltipStyle}>
-							<TooltipMarkerComponent page='maps' helpTextId='help.admin.mapview' />
-						</div>
-					</h2>
-					<div className="edit-btn">
-						<Link to='/calibration' onClick={() => this.props.createNewMap()}>
-							<Button color='primary'>
-								<FormattedMessage id='create.map' />
-							</Button>
-						</Link>
-					</div>
-					<div className="card-container">
-						{this.props.maps.map(mapID => (
-							<MapViewContainer key={mapID} id={mapID} />
-						))}
+export default function MapsDetailComponent() {
+	const dispatch: AppDispatch = useAppDispatch();
+	// Load map IDs from state and store in number array
+	const maps: number[] = useAppSelector(selectMaps);
+	React.useEffect(() => {
+		// Load maps from state on component mount (componentDidMount)
+		dispatch(fetchMapsDetails());
+	}, []);
+
+	return (
+		<div className='flexGrowOne'>
+			<TooltipHelpComponent page='maps' />
+			<div className='container-fluid'>
+				<h2 className='text-center'>
+					<FormattedMessage id='maps' />
+					<div className='d-inline-block fs-5'>
+						<TooltipMarkerComponent page='maps' helpTextId='help.admin.mapview' />
 					</div>
-					{hasToken() && (
-						<div className="edit-btn">
-							<Button
-								color='success'
-								disabled={!this.props.unsavedChanges}
-								onClick={this.handleSubmitClicked}
-							>
-								<FormattedMessage id='save.map.edits' />
-							</Button>
-						</div>
-					)}
+				</h2>
+				<div className="edit-btn">
+					<Link to='/calibration' onClick={() => dispatch(setNewMap())}>
+						<Button color='primary'>
+							<FormattedMessage id='create.map' />
+						</Button>
+					</Link>
 				</div>
+				<div className="card-container">
+					{maps.map(mapID => (
+						<MapViewContainer key={mapID} id={mapID} />
+					))}
+				</div>
+				{hasToken() && (
+					<div className="edit-btn">
+						<Button
+							color='success'
+							onClick={() => dispatch(submitEditedMaps())}
+						>
+							<FormattedMessage id='save.map.edits' />
+						</Button>
+					</div>
+				)}
 			</div>
-		);
-	}
-
-	private handleSubmitClicked() {
-		this.props.submitEditedMaps();
-		// Notify that the unsaved changes have been submitted
-		// this.removeUnsavedChanges();
-	}
-}
-
-const titleStyle: React.CSSProperties = {
-	textAlign: 'center'
-};
-
-const tooltipStyle = {
-	display: 'inline-block',
-	fontSize: '50%'
-};
\ No newline at end of file
+		</div>
+	);
+}
\ No newline at end of file

From 0a941daa48913d86541c3d361d4c6b2d74a00e40 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Wed, 24 Jul 2024 16:12:43 -0700
Subject: [PATCH 08/50] Adding ": " to map card descriptors for formatting

---
 src/client/app/translations/data.ts | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index 213651a02..e8378e3be 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -302,11 +302,11 @@ const LocaleTranslationData = {
 		"map.bad.digita": "Greater than 360, please change angle to a number between 0 and 360",
 		"map.bad.digitb": "Less than 0, please change angle to a number between 0 and 360",
 		"map.calibrate": "Calibrate",
-		"map.calibration": "Calibration status",
-		"map.circle.size": "Map Circle Size",
+		"map.calibration": "Calibration status: ",
+		"map.circle.size": "Map Circle Size: ",
 		"map.confirm.remove": "Are you sure you want to remove map",
 		"map.displayable": "Map Display",
-		"map.filename": "Map file",
+		"map.filename": "Map File: ",
 		"map.id": "Map ID",
 		"map.interval": "Map Interval",
 		"map.is.calibrated": "Calibration Complete",
@@ -315,7 +315,7 @@ const LocaleTranslationData = {
 		"map.is.not.calibrated": "Calibration Needed",
 		"map.is.not.displayable": "Display Disabled",
 		"map.load.complete": "Map load complete from",
-		"map.modified.date": "Last Modified",
+		"map.modified.date": "Last Modified: ",
 		"map.name": "Map Name",
 		"map.new.angle": "List the angle with respect to true north (0 to 360)",
 		"map.new.name": "Define a name for the map:",
@@ -799,11 +799,11 @@ const LocaleTranslationData = {
 		"map.bad.digita": "Supérieur à 360, veuillez changer l'angle en un nombre compris entre 0 et 360",
 		"map.bad.digitb": "Moins de 0, veuillez changer l'angle en un nombre compris entre 0 et 360",
 		"map.calibrate": "Étalonner",
-		"map.calibration": "Statut d'étalonnage",
-		"map.circle.size": "Taille du cercle de la carte",
+		"map.calibration": "Statut d'étalonnage: ",
+		"map.circle.size": "Taille du cercle de la carte: ",
 		"map.confirm.remove": "Voulez-vous vraiment supprimer la carte?",
-		"map.displayable": "Affichage de la carte",
-		"map.filename": "Fichier de carte",
+		"map.displayable": "Affichage de la carte: ",
+		"map.filename": "Fichier de carte: ",
 		"map.id": "ID de la carte",
 		"map.interval": "Intervalle de carte",
 		"map.is.calibrated": "Étalonnage terminé",
@@ -812,7 +812,7 @@ const LocaleTranslationData = {
 		"map.is.not.calibrated": "Étalonnage nécessaire",
 		"map.is.not.displayable": "Affichage désactivé",
 		"map.load.complete": "Chargement de la carte terminé à partir de",
-		"map.modified.date": "Dernière modification",
+		"map.modified.date": "Dernière modification: ",
 		"map.name": "Nom de la carte",
 		"map.new.angle": "Indiquez l'angle par rapport au vrai nord (0 à 360)",
 		"map.new.name": "Définir un nom pour la carte",
@@ -1296,11 +1296,11 @@ const LocaleTranslationData = {
 		"map.bad.digita": "Mayor a 360, por favor cambiar el angúlo a un número entre 0 a 360",
 		"map.bad.digitb": "Menor a 0, por favor cambiar el angúlo a un número entre 0 a 360",
 		"map.calibrate": "Calibrar",
-		"map.calibration": "Estado de calibración",
-		"map.circle.size": "Tamaño del círculo en el mapa",
+		"map.calibration": "Estado de calibración: ",
+		"map.circle.size": "Tamaño del círculo en el mapa: ",
 		"map.confirm.remove": "¿Estás seguro de que quieres eliminar el mapa",
-		"map.displayable": "Visuilización del mapa",
-		"map.filename": "Archivo del mapa",
+		"map.displayable": "Visuilización del mapa: ",
+		"map.filename": "Archivo del mapa: ",
 		"map.id": "ID del Mapa",
 		"map.interval": "Intervalo de mapa",
 		"map.is.calibrated": "Calibración completa",
@@ -1309,7 +1309,7 @@ const LocaleTranslationData = {
 		"map.is.not.calibrated": "Necesita calibración",
 		"map.is.not.displayable": "Visualización desactivada",
 		"map.load.complete": "Carga de mapas completada de",
-		"map.modified.date": "Última modificación",
+		"map.modified.date": "Última modificación: ",
 		"map.name": "Nombre del mapa",
 		"map.new.angle": " Liste el ángulo con respecto al norte verdadero (0 a 360)",
 		"map.new.name": "Defina un nombre para el mapa:",

From af01ec7397d21a13197a57c43e66cfd80b05bec7 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Wed, 24 Jul 2024 16:30:23 -0700
Subject: [PATCH 09/50] Removing unnecessary button

---
 src/client/app/components/maps/MapsDetailComponent.tsx | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index 7a06e272c..fad19d1e3 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -8,7 +8,6 @@ import { Link } from 'react-router-dom';
 import { Button } from 'reactstrap';
 import TooltipHelpComponent from '../../components/TooltipHelpComponent';
 import MapViewContainer from '../../containers/maps/MapViewContainer';
-import { hasToken } from '../../utils/token';
 import TooltipMarkerComponent from '../TooltipMarkerComponent';
 import '../../styles/card-page.css';
 import { fetchMapsDetails, setNewMap, submitEditedMaps } from '../../redux/actions/map';

From b55b6724cb64998b0c637f7d7d4041be312fd822 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Tue, 30 Jul 2024 09:26:39 -0700
Subject: [PATCH 10/50] Adding missing : on a map descriptor

---
 src/client/app/components/maps/MapsDetailComponent.tsx | 10 ----------
 src/client/app/translations/data.ts                    |  4 ++--
 2 files changed, 2 insertions(+), 12 deletions(-)

diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index fad19d1e3..d5cebb026 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -46,16 +46,6 @@ export default function MapsDetailComponent() {
 						<MapViewContainer key={mapID} id={mapID} />
 					))}
 				</div>
-				{hasToken() && (
-					<div className="edit-btn">
-						<Button
-							color='success'
-							onClick={() => dispatch(submitEditedMaps())}
-						>
-							<FormattedMessage id='save.map.edits' />
-						</Button>
-					</div>
-				)}
 			</div>
 		</div>
 	);
diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index e8378e3be..2b5e2a486 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -305,7 +305,7 @@ const LocaleTranslationData = {
 		"map.calibration": "Calibration status: ",
 		"map.circle.size": "Map Circle Size: ",
 		"map.confirm.remove": "Are you sure you want to remove map",
-		"map.displayable": "Map Display",
+		"map.displayable": "Map Display: ",
 		"map.filename": "Map File: ",
 		"map.id": "Map ID",
 		"map.interval": "Map Interval",
@@ -808,7 +808,7 @@ const LocaleTranslationData = {
 		"map.interval": "Intervalle de carte",
 		"map.is.calibrated": "Étalonnage terminé",
 		"map.is.deleted": "Carte supprimée de la base de données",
-		"map.is.displayable": "Affichage activé",
+		"map.is.displayable": "Affichage activé: ",
 		"map.is.not.calibrated": "Étalonnage nécessaire",
 		"map.is.not.displayable": "Affichage désactivé",
 		"map.load.complete": "Chargement de la carte terminé à partir de",

From 2b3885dbc3b0cda87fb12d46f93b8bad5b471312 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Tue, 30 Jul 2024 10:31:05 -0700
Subject: [PATCH 11/50] Removing item name from descriptor (so no map)

---
 src/client/app/translations/data.ts | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index 2b5e2a486..8a79eb692 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -303,10 +303,10 @@ const LocaleTranslationData = {
 		"map.bad.digitb": "Less than 0, please change angle to a number between 0 and 360",
 		"map.calibrate": "Calibrate",
 		"map.calibration": "Calibration status: ",
-		"map.circle.size": "Map Circle Size: ",
+		"map.circle.size": "Circle Size: ",
 		"map.confirm.remove": "Are you sure you want to remove map",
-		"map.displayable": "Map Display: ",
-		"map.filename": "Map File: ",
+		"map.displayable": "Display: ",
+		"map.filename": "File: ",
 		"map.id": "Map ID",
 		"map.interval": "Map Interval",
 		"map.is.calibrated": "Calibration Complete",
@@ -800,10 +800,10 @@ const LocaleTranslationData = {
 		"map.bad.digitb": "Moins de 0, veuillez changer l'angle en un nombre compris entre 0 et 360",
 		"map.calibrate": "Étalonner",
 		"map.calibration": "Statut d'étalonnage: ",
-		"map.circle.size": "Taille du cercle de la carte: ",
+		"map.circle.size": "Taille du cercle: ",
 		"map.confirm.remove": "Voulez-vous vraiment supprimer la carte?",
-		"map.displayable": "Affichage de la carte: ",
-		"map.filename": "Fichier de carte: ",
+		"map.displayable": "Affichage: ",
+		"map.filename": "Fichier: ",
 		"map.id": "ID de la carte",
 		"map.interval": "Intervalle de carte",
 		"map.is.calibrated": "Étalonnage terminé",
@@ -1297,10 +1297,10 @@ const LocaleTranslationData = {
 		"map.bad.digitb": "Menor a 0, por favor cambiar el angúlo a un número entre 0 a 360",
 		"map.calibrate": "Calibrar",
 		"map.calibration": "Estado de calibración: ",
-		"map.circle.size": "Tamaño del círculo en el mapa: ",
+		"map.circle.size": "Tamaño del círculo: ",
 		"map.confirm.remove": "¿Estás seguro de que quieres eliminar el mapa",
-		"map.displayable": "Visuilización del mapa: ",
-		"map.filename": "Archivo del mapa: ",
+		"map.displayable": "Visuilización: ",
+		"map.filename": "Archivo: ",
 		"map.id": "ID del Mapa",
 		"map.interval": "Intervalo de mapa",
 		"map.is.calibrated": "Calibración completa",

From a7e6ddf456ef3b236a4e7a666c8e069c4341d1ef Mon Sep 17 00:00:00 2001
From: root <root@Phi.lan>
Date: Tue, 30 Jul 2024 23:24:35 -0700
Subject: [PATCH 12/50] Limiting map note to 30 characters.

---
 src/client/app/components/maps/EditMapModalComponent.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index 00394842d..462665477 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -111,7 +111,7 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose,
 							id="mapNote"
 							type="textarea"
 							value={noteInput}
-							onChange={e => setNoteInput(e.target.value)}
+							onChange={e => setNoteInput(e.target.value.slice(0,30))}
 						/>
 					</FormGroup>
 				</Form>

From 6397225bc53603d722ea7249e9ce138741cb91a7 Mon Sep 17 00:00:00 2001
From: root <root@Phi.lan>
Date: Tue, 30 Jul 2024 23:46:12 -0700
Subject: [PATCH 13/50] Treating circle size as a positive number

---
 src/client/app/components/maps/EditMapModalComponent.tsx | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index 462665477..2e14fcbdb 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -100,8 +100,10 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose,
 						<Label for="mapCircleSize"><FormattedMessage id="map.circle.size" /></Label>
 						<Input
 							id="mapCircleSize"
+							type='number'
 							value={circleInput}
 							onChange={e => setCircleInput(e.target.value)}
+							invalid={parseFloat(circleInput)<0}
 							onBlur={toggleCircleEdit}
 						/>
 					</FormGroup>

From 0fb3b4523c47a54b56ceba19da741f74062a2bf2 Mon Sep 17 00:00:00 2001
From: root <root@Phi.lan>
Date: Tue, 30 Jul 2024 23:50:54 -0700
Subject: [PATCH 14/50] Changing the order of descriptors on card to match the
 modal.

---
 src/client/app/components/maps/MapViewComponent.tsx | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index 6f5b70e08..bc9a3229e 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -54,14 +54,14 @@ const MapViewComponent: React.FC<MapViewProps> = ({ map, isEdited, isSubmitting,
 				<b><FormattedMessage id="map.circle.size" /></b> {map.circleSize}
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="map.modified.date" /></b>
-				{moment.parseZone(map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
+				<b><FormattedMessage id="note" /></b> {map.note}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.filename" /></b> {map.filename}
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="note" /></b> {map.note}
+				<b><FormattedMessage id="map.modified.date" /></b>
+				{moment.parseZone(map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.calibration" /></b>

From 76ddde9b96340b116a760936e0494e82efe4c146 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Tue, 30 Jul 2024 23:58:08 -0700
Subject: [PATCH 15/50] Removing unused variable

---
 src/client/app/components/maps/MapsDetailComponent.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index d5cebb026..4b92bc2b8 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -10,7 +10,7 @@ import TooltipHelpComponent from '../../components/TooltipHelpComponent';
 import MapViewContainer from '../../containers/maps/MapViewContainer';
 import TooltipMarkerComponent from '../TooltipMarkerComponent';
 import '../../styles/card-page.css';
-import { fetchMapsDetails, setNewMap, submitEditedMaps } from '../../redux/actions/map';
+import { fetchMapsDetails, setNewMap} from '../../redux/actions/map';
 import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
 import { selectMaps } from '../../redux/selectors/maps';
 import { AppDispatch } from 'store';

From 815f9989ff813c4a1803a78914307c905f2701b7 Mon Sep 17 00:00:00 2001
From: root <root@Phi.lan>
Date: Wed, 31 Jul 2024 00:06:50 -0700
Subject: [PATCH 16/50] Changing the name of the button to upload a new map
 image file.

---
 src/client/app/translations/data.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index 8a79eb692..58a339921 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -322,7 +322,7 @@ const LocaleTranslationData = {
 		"map.new.submit": "Save and continue",
 		"map.new.upload": "Upload map image to begin.",
 		"map.notify.calibration.needed": "Calibration needed before display",
-		"map.upload.new.file": "Redo",
+		"map.upload.new.file": "Upload New File",
 		"max": "max",
 		"menu": "Menu",
 		"meter": "Meter",
@@ -819,7 +819,7 @@ const LocaleTranslationData = {
 		"map.new.submit": "Sauvegarder et continuer",
 		"map.new.upload": "Téléchargez l'image de la carte pour commencer.",
 		"map.notify.calibration.needed": "Étalonnage nécessaire pour l'affichage",
-		"map.upload.new.file": "Refaire",
+		"map.upload.new.file": "Upload New File\u{26A1}",
 		"max": "max\u{26A1}",
 		"menu": "Menu",
 		"meter": "Mèter",
@@ -1316,7 +1316,7 @@ const LocaleTranslationData = {
 		"map.new.submit": "Guardar y continuar",
 		"map.new.upload": "Subir la imagen del mapa para empezar.",
 		"map.notify.calibration.needed": "Necesita calibración antes de visualizar",
-		"map.upload.new.file": "Rehacer",
+		"map.upload.new.file": "Subir un nuevo archivo",
 		"max": "máximo",
 		"menu": "Menú",
 		"meter": "Medidor",

From b427cbf3ff60055756c1db820e8059fd71ca44fc Mon Sep 17 00:00:00 2001
From: root <root@Phi.lan>
Date: Wed, 31 Jul 2024 00:16:13 -0700
Subject: [PATCH 17/50] Resolving syntax and jsdoc declaration requirements

---
 src/client/app/components/maps/CreateMapModalComponent.tsx | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/client/app/components/maps/CreateMapModalComponent.tsx b/src/client/app/components/maps/CreateMapModalComponent.tsx
index 222113734..213113abf 100644
--- a/src/client/app/components/maps/CreateMapModalComponent.tsx
+++ b/src/client/app/components/maps/CreateMapModalComponent.tsx
@@ -16,6 +16,10 @@ interface CreateMapModalProps {
 
 /**
  * Defines the create map modal form
+ * @param root0
+ * @param root0.show
+ * @param root0.handleClose
+ * @param root0.createNewMap
  * @returns Map create element
  */
 function CreateMapModalComponent({ show, handleClose, createNewMap }: CreateMapModalProps) {

From b6875697eb5576a90a0e5e20e84c817dbaab837f Mon Sep 17 00:00:00 2001
From: root <root@Phi.lan>
Date: Wed, 31 Jul 2024 00:19:42 -0700
Subject: [PATCH 18/50] Resolving syntax and jsdoc declaration requirements

---
 src/client/app/components/maps/MapsDetailComponent.tsx | 4 ++++
 src/client/app/redux/reducers/maps.ts                  | 2 +-
 src/client/app/redux/selectors/maps.ts                 | 8 ++++----
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index 4b92bc2b8..18d231643 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -15,6 +15,10 @@ import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
 import { selectMaps } from '../../redux/selectors/maps';
 import { AppDispatch } from 'store';
 
+/**
+ * Defines the maps page card view
+ * @returns Maps page element
+ */
 export default function MapsDetailComponent() {
 	const dispatch: AppDispatch = useAppDispatch();
 	// Load map IDs from state and store in number array
diff --git a/src/client/app/redux/reducers/maps.ts b/src/client/app/redux/reducers/maps.ts
index 3f5ce4253..714980d5d 100644
--- a/src/client/app/redux/reducers/maps.ts
+++ b/src/client/app/redux/reducers/maps.ts
@@ -240,4 +240,4 @@ export default function maps(state = defaultState, action: MapsAction) {
 		default:
 			return state;
 	}
-};
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/src/client/app/redux/selectors/maps.ts b/src/client/app/redux/selectors/maps.ts
index 0d208a1ca..d1c0c5c3e 100644
--- a/src/client/app/redux/selectors/maps.ts
+++ b/src/client/app/redux/selectors/maps.ts
@@ -2,12 +2,12 @@
  * 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 { RootState } from "store";
-import { createSelector } from "@reduxjs/toolkit";
+import { RootState } from 'store';
+import { createSelector } from '@reduxjs/toolkit';
 
 export const selectMapState = (state: RootState) => state.maps;
 export const selectMaps = createSelector([selectMapState], maps => {
 	return Object.keys(maps.byMapID)
-	.map(key => parseInt(key))
-	.filter(key => !isNaN(key));
+		.map(key => parseInt(key))
+		.filter(key => !isNaN(key));
 });
\ No newline at end of file

From d57da8c46cc97b71d7ad7618f97899332d091d57 Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Wed, 31 Jul 2024 01:27:35 -0700
Subject: [PATCH 19/50] use useAppDispatch for type safety

---
 src/client/app/components/maps/EditMapModalComponent.tsx | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index 2e14fcbdb..36aa92bfb 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -4,15 +4,13 @@
 
 import * as React from 'react';
 import { useState } from 'react';
-import { useDispatch } from 'react-redux';
-import { ThunkDispatch } from 'redux-thunk';
 import { FormattedMessage, useIntl } from 'react-intl';
 import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input } from 'reactstrap';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
 import { editMapDetails, submitEditedMap, removeMap } from '../../redux/actions/map';
 import { showErrorNotification } from '../../utils/notifications';
-import { State } from '../../types/redux/state';
-import { AnyAction } from 'redux';
+import { useAppDispatch } from '../../redux/reduxHooks';
+import { AppDispatch } from 'store';
 
 interface EditMapModalProps {
 	show: boolean;
@@ -24,7 +22,7 @@ interface EditMapModalProps {
 }
 
 const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose, map, setCalibration }) => {
-	const dispatch: ThunkDispatch<State, void, AnyAction> = useDispatch();
+	const dispatch: AppDispatch = useAppDispatch();
 	const [nameInput, setNameInput] = useState(map.name);
 	const [noteInput, setNoteInput] = useState(map.note || '');
 	const [circleInput, setCircleInput] = useState(map.circleSize.toString());

From bc49a65c3a878625fdbd7a7ae17e0c29eee85ea9 Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Wed, 31 Jul 2024 01:30:05 -0700
Subject: [PATCH 20/50] Remove container props, utilize dispatch for redux
 actions

---
 .../components/maps/EditMapModalComponent.tsx | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index 36aa92bfb..d904da1e5 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -7,7 +7,7 @@ import { useState } from 'react';
 import { FormattedMessage, useIntl } from 'react-intl';
 import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input } from 'reactstrap';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
-import { editMapDetails, submitEditedMap, removeMap } from '../../redux/actions/map';
+import { editMapDetails, submitEditedMap, removeMap, setCalibration } from '../../redux/actions/map';
 import { showErrorNotification } from '../../utils/notifications';
 import { useAppDispatch } from '../../redux/reduxHooks';
 import { AppDispatch } from 'store';
@@ -16,12 +16,9 @@ interface EditMapModalProps {
 	show: boolean;
 	handleClose: () => void;
 	map: MapMetadata;
-	editMapDetails(map: MapMetadata): any;
-	setCalibration(mode: CalibrationModeTypes, mapID: number): any;
-	removeMap(id: number): any;
 }
 
-const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose, map, setCalibration }) => {
+const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose, map}) => {
 	const dispatch: AppDispatch = useAppDispatch();
 	const [nameInput, setNameInput] = useState(map.name);
 	const [noteInput, setNoteInput] = useState(map.note || '');
@@ -39,22 +36,20 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose,
 			displayable
 		};
 		dispatch(editMapDetails(updatedMap));
-		dispatch(submitEditedMap(updatedMap.id) as any).then(() => {
-			handleClose();
-		});
+		dispatch(submitEditedMap(updatedMap.id));
+		handleClose();
 	};
 
 	const handleDelete = () => {
 		const consent = window.confirm(intl.formatMessage({ id: 'map.confirm.remove' }, { name: map.name }));
 		if (consent) {
-			dispatch(removeMap(map.id) as any).then(() => {
-				handleClose();
-			});
+			dispatch(removeMap(map.id));
+			handleClose();
 		}
 	};
 
 	const handleCalibrationSetting = (mode: CalibrationModeTypes) => {
-		setCalibration(mode, map.id);
+		dispatch(setCalibration(mode, map.id));
 		handleClose();
 	};
 

From 105395dda748000cdc2cc2636067dfd4a11d7bc3 Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Wed, 31 Jul 2024 01:30:24 -0700
Subject: [PATCH 21/50] Use the new component instead of old container

---
 src/client/app/components/maps/MapCalibrationComponent.tsx | 5 +++--
 src/client/app/components/maps/MapsDetailComponent.tsx     | 4 ++--
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/client/app/components/maps/MapCalibrationComponent.tsx b/src/client/app/components/maps/MapCalibrationComponent.tsx
index 1d60d51cf..cfdbbd97c 100644
--- a/src/client/app/components/maps/MapCalibrationComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationComponent.tsx
@@ -6,8 +6,9 @@ import * as React from 'react';
 import MapCalibrationChartDisplayContainer from '../../containers/maps/MapCalibrationChartDisplayContainer';
 import MapCalibrationInfoDisplayContainer from '../../containers/maps/MapCalibrationInfoDisplayContainer';
 import MapCalibrationInitiateContainer from '../../containers/maps/MapCalibrationInitiateContainer';
-import MapsDetailContainer from '../../containers/maps/MapsDetailContainer';
+//import MapsDetailContainer from '../../containers/maps/MapsDetailContainer';
 import { CalibrationModeTypes } from '../../types/redux/map';
+import MapsDetailComponent from './MapsDetailComponent';
 
 interface MapCalibrationProps {
 	mode: CalibrationModeTypes;
@@ -47,7 +48,7 @@ export default class MapCalibrationComponent extends React.Component<MapCalibrat
 		} else { // preview mode containers
 			return (
 				<div className='container-fluid'>
-					<MapsDetailContainer />
+					<MapsDetailComponent />
 				</div>
 			);
 		}
diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index 18d231643..957f20791 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -7,7 +7,7 @@ import { FormattedMessage } from 'react-intl';
 import { Link } from 'react-router-dom';
 import { Button } from 'reactstrap';
 import TooltipHelpComponent from '../../components/TooltipHelpComponent';
-import MapViewContainer from '../../containers/maps/MapViewContainer';
+import MapViewComponent from './MapViewComponent';
 import TooltipMarkerComponent from '../TooltipMarkerComponent';
 import '../../styles/card-page.css';
 import { fetchMapsDetails, setNewMap} from '../../redux/actions/map';
@@ -47,7 +47,7 @@ export default function MapsDetailComponent() {
 				</div>
 				<div className="card-container">
 					{maps.map(mapID => (
-						<MapViewContainer key={mapID} id={mapID} />
+						<MapViewComponent key={mapID} mapID={mapID} />
 					))}
 				</div>
 			</div>

From 0d0a421db3e34b4800833404e7c23a6796102472 Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Wed, 31 Jul 2024 01:35:11 -0700
Subject: [PATCH 22/50] Remove props used for container, removed isEdited and
 isSubmitting status as they are no longer relevant in the new modals

---
 .../app/components/maps/MapViewComponent.tsx  | 23 +++----------------
 1 file changed, 3 insertions(+), 20 deletions(-)

diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index bc9a3229e..56d018d83 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -14,35 +14,18 @@ import EditMapModalComponent from './EditMapModalComponent';
 
 
 interface MapViewProps {
-	id: number;
-	map: MapMetadata;
-	isEdited: boolean;
-	isSubmitting: boolean;
-	editMapDetails(map: MapMetadata): any;
-	setCalibration(mode: CalibrationModeTypes, mapID: number): any;
-	removeMap(id: number): any;
+	mapID: number;
 }
 
-// editMapDetails: (map: MapMetadata) => void;
-// setCalibration: (mode: CalibrationModeTypes, mapID: number) => void;
-// removeMap: (id: number) => void;
-
-const MapViewComponent: React.FC<MapViewProps> = ({ map, isEdited, isSubmitting, editMapDetails, setCalibration, removeMap }) => {
+const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 	const [showEditModal, setShowEditModal] = useState(false);
-
-	useEffect(() => {
-		if (isEdited) {
-			//updateUnsavedChanges();
-		}
-	}, [isEdited]);
-
 	const handleShowModal = () => setShowEditModal(true);
 	const handleCloseModal = () => setShowEditModal(false);
 
 	return (
 		<div className="card">
 			<div className="identifier-container">
-				{map.name} {isSubmitting ? '(Submitting)' : isEdited ? '(Edited)' : ''}
+				{map.name}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.displayable" /></b>

From 75c5609d3a4a891825160d11657b8b18c50bcfd5 Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Wed, 31 Jul 2024 01:37:18 -0700
Subject: [PATCH 23/50] Delete unused types and only import necessary functions
 from moment package

---
 src/client/app/components/maps/MapViewComponent.tsx | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index 56d018d83..3e95eaaa4 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -6,8 +6,7 @@ import * as React from 'react';
 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 { parseZone } from 'moment';
 import { hasToken } from '../../utils/token';
 import '../../styles/card-page.css';
 import EditMapModalComponent from './EditMapModalComponent';
@@ -44,7 +43,7 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.modified.date" /></b>
-				{moment.parseZone(map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
+				{parseZone(map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.calibration" /></b>

From 8430932f3fb4d5ea0be5e520487facc0974c776c Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Wed, 31 Jul 2024 01:40:06 -0700
Subject: [PATCH 24/50] Delete unused props & functions, add new memoized
 selector

---
 src/client/app/components/maps/MapViewComponent.tsx | 12 ++++++------
 src/client/app/redux/selectors/maps.ts              | 10 +++++++++-
 2 files changed, 15 insertions(+), 7 deletions(-)

diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index 3e95eaaa4..d6282ecf8 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -3,15 +3,15 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import * as React from 'react';
-import { useState, useEffect } from 'react';
+import { useState } from 'react';
 import { FormattedMessage } from 'react-intl';
 import { Button } from 'reactstrap';
 import { parseZone } from 'moment';
 import { hasToken } from '../../utils/token';
 import '../../styles/card-page.css';
 import EditMapModalComponent from './EditMapModalComponent';
-
-
+import { makeSelectMapById } from '../../redux/selectors/maps';
+import { useSelector } from 'react-redux';
 interface MapViewProps {
 	mapID: number;
 }
@@ -21,6 +21,9 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 	const handleShowModal = () => setShowEditModal(true);
 	const handleCloseModal = () => setShowEditModal(false);
 
+	const selectMapById = React.useMemo(makeSelectMapById, []);
+	const map = useSelector(state => selectMapById(state, mapID));
+
 	return (
 		<div className="card">
 			<div className="identifier-container">
@@ -62,9 +65,6 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 				show={showEditModal}
 				handleClose={handleCloseModal}
 				map={map}
-				setCalibration={setCalibration}
-				editMapDetails={editMapDetails}
-				removeMap={removeMap}
 			/>
 		</div>
 	);
diff --git a/src/client/app/redux/selectors/maps.ts b/src/client/app/redux/selectors/maps.ts
index d1c0c5c3e..909990754 100644
--- a/src/client/app/redux/selectors/maps.ts
+++ b/src/client/app/redux/selectors/maps.ts
@@ -10,4 +10,12 @@ export const selectMaps = createSelector([selectMapState], maps => {
 	return Object.keys(maps.byMapID)
 		.map(key => parseInt(key))
 		.filter(key => !isNaN(key));
-});
\ No newline at end of file
+});
+
+export const makeSelectMapById = () => {
+	const selectMapById = createSelector(
+		[selectMapState, (state, id) => id],
+		(maps, id) => maps.byMapID[id]
+	);
+	return selectMapById;
+};
\ No newline at end of file

From 6db61deb2f6222eec93869b567a4848cdd908985 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Wed, 31 Jul 2024 19:43:05 -0700
Subject: [PATCH 25/50] Treating map file name as an uneditable item

---
 src/client/app/components/maps/EditMapModalComponent.tsx | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index d904da1e5..77efc860e 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -112,7 +112,13 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose,
 				</Form>
 				<div>
 					<Label><FormattedMessage id="map.filename" /></Label>
-					<p>{map.filename}</p>
+					<Input
+						id='mapFilename'
+						name='mapFilename'
+						type='text'
+						defaultValue={map.filename}
+						disabled>
+					</Input>
 					<Button color='primary' onClick={() => handleCalibrationSetting(CalibrationModeTypes.initiate)}>
 						<FormattedMessage id='map.upload.new.file' />
 					</Button>

From e775c62d9e4c3bf0ffb2fe7cb02e385aeab0ab3b Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Thu, 1 Aug 2024 08:38:49 -0700
Subject: [PATCH 26/50] Handling displayable with an enum instead of a boolean
 and fixing a typo in the translations.

---
 .../components/maps/EditMapModalComponent.tsx | 23 +++++++++++--------
 src/client/app/translations/data.ts           |  2 +-
 src/client/app/types/redux/map.ts             |  7 +++++-
 3 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index 77efc860e..8eb543da7 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -8,6 +8,7 @@ import { FormattedMessage, useIntl } from 'react-intl';
 import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input } from 'reactstrap';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
 import { editMapDetails, submitEditedMap, removeMap, setCalibration } from '../../redux/actions/map';
+import { DisplayableType } from '../../types/redux/map';
 import { showErrorNotification } from '../../utils/notifications';
 import { useAppDispatch } from '../../redux/reduxHooks';
 import { AppDispatch } from 'store';
@@ -23,7 +24,7 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose,
 	const [nameInput, setNameInput] = useState(map.name);
 	const [noteInput, setNoteInput] = useState(map.note || '');
 	const [circleInput, setCircleInput] = useState(map.circleSize.toString());
-	const [displayable, setDisplayable] = useState(map.displayable);
+	const [displayable, setDisplayable] = useState<DisplayableType>(map.displayable);
 
 	const intl = useIntl();
 
@@ -33,7 +34,7 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose,
 			name: nameInput,
 			note: noteInput,
 			circleSize: parseFloat(circleInput),
-			displayable
+			displayable: displayable as DisplayableType
 		};
 		dispatch(editMapDetails(updatedMap));
 		dispatch(submitEditedMap(updatedMap.id));
@@ -78,15 +79,19 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose,
 						/>
 					</FormGroup>
 					<FormGroup>
-						<Label for="mapDisplayable"><FormattedMessage id="map.displayable" /></Label>
+						<Label for='mapDisplayable'><FormattedMessage id='map.displayable' /></Label>
 						<Input
-							id="mapDisplayable"
-							type="select"
-							value={displayable.toString()}
-							onChange={e => setDisplayable(e.target.value === 'true')}
+							id='mapDisplayable'
+							name='mapDisplayable'
+							type='select'
+							value={displayable}
+							onChange={e => setDisplayable(e.target.value as DisplayableType)}
 						>
-							<option value="true">{intl.formatMessage({ id: 'map.is.displayable' })}</option>
-							<option value="false">{intl.formatMessage({ id: 'map.is.not.displayable' })}</option>
+							{Object.keys(DisplayableType).map(key => (
+								<option value={key} key={key}>
+									{intl.formatMessage({ id: `map.is.${key}` })}
+								</option>
+							))}
 						</Input>
 					</FormGroup>
 					<FormGroup>
diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index 58a339921..48d338614 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -1299,7 +1299,7 @@ const LocaleTranslationData = {
 		"map.calibration": "Estado de calibración: ",
 		"map.circle.size": "Tamaño del círculo: ",
 		"map.confirm.remove": "¿Estás seguro de que quieres eliminar el mapa",
-		"map.displayable": "Visuilización: ",
+		"map.displayable": "Visualización: ",
 		"map.filename": "Archivo: ",
 		"map.id": "ID del Mapa",
 		"map.interval": "Intervalo de mapa",
diff --git a/src/client/app/types/redux/map.ts b/src/client/app/types/redux/map.ts
index d1700c035..d171dd272 100644
--- a/src/client/app/types/redux/map.ts
+++ b/src/client/app/types/redux/map.ts
@@ -14,6 +14,11 @@ export enum CalibrationModeTypes {
 	unavailable = 'unavailable'
 }
 
+export enum DisplayableType {
+	displayable = 'displayable',
+	notDisplayable = 'not.displayable'
+}
+
 export interface ChangeMapModeAction {
 	type: ActionType.UpdateCalibrationMode;
 	nextMode: CalibrationModeTypes;
@@ -148,7 +153,7 @@ export interface MapData {
 export interface MapMetadata {
 	id: number;
 	name: string;
-	displayable: boolean;
+	displayable: DisplayableType;
 	note?: string;
 	filename: string;
 	modifiedDate: string;

From 4c5f66cb471aba9e03e9fa910f3e6e4f5ed69ce5 Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Thu, 1 Aug 2024 11:23:00 -0700
Subject: [PATCH 27/50] Bug squashing

---
 src/client/app/components/maps/EditMapModalComponent.tsx | 6 +++---
 src/client/app/components/maps/MapViewComponent.tsx      | 3 +--
 src/client/app/translations/data.ts                      | 8 ++++----
 3 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index 8eb543da7..d51974aad 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -79,10 +79,10 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose,
 						/>
 					</FormGroup>
 					<FormGroup>
-						<Label for='mapDisplayable'><FormattedMessage id='map.displayable' /></Label>
+						<Label for='map.displayable'><FormattedMessage id='map.displayable' /></Label>
 						<Input
-							id='mapDisplayable'
-							name='mapDisplayable'
+							id='map.displayable'
+							name='map.displayable'
 							type='select'
 							value={displayable}
 							onChange={e => setDisplayable(e.target.value as DisplayableType)}
diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index d6282ecf8..26090a602 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -7,7 +7,6 @@ import { useState } from 'react';
 import { FormattedMessage } from 'react-intl';
 import { Button } from 'reactstrap';
 import { parseZone } from 'moment';
-import { hasToken } from '../../utils/token';
 import '../../styles/card-page.css';
 import EditMapModalComponent from './EditMapModalComponent';
 import { makeSelectMapById } from '../../redux/selectors/maps';
@@ -54,7 +53,7 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 					<FormattedMessage id={map.origin && map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
 				</span>
 			</div>
-			{hasToken() && (
+			{(
 				<div className="edit-btn">
 					<Button color='secondary' onClick={handleShowModal}>
 						<FormattedMessage id="edit.map" />
diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index 48d338614..7c5170f37 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -313,7 +313,7 @@ const LocaleTranslationData = {
 		"map.is.deleted": "Map removed from database",
 		"map.is.displayable": "Display Enabled",
 		"map.is.not.calibrated": "Calibration Needed",
-		"map.is.not.displayable": "Display Disabled",
+		"map.is.notDisplayable": "Display Disabled",
 		"map.load.complete": "Map load complete from",
 		"map.modified.date": "Last Modified: ",
 		"map.name": "Map Name",
@@ -808,9 +808,9 @@ const LocaleTranslationData = {
 		"map.interval": "Intervalle de carte",
 		"map.is.calibrated": "Étalonnage terminé",
 		"map.is.deleted": "Carte supprimée de la base de données",
-		"map.is.displayable": "Affichage activé: ",
+		"map.is.displayable": "Affichage activé",
 		"map.is.not.calibrated": "Étalonnage nécessaire",
-		"map.is.not.displayable": "Affichage désactivé",
+		"map.is.notDisplayable": "Affichage désactivé",
 		"map.load.complete": "Chargement de la carte terminé à partir de",
 		"map.modified.date": "Dernière modification: ",
 		"map.name": "Nom de la carte",
@@ -1307,7 +1307,7 @@ const LocaleTranslationData = {
 		"map.is.deleted": "Mapa borrado de la base de datos",
 		"map.is.displayable": "Visualización activada",
 		"map.is.not.calibrated": "Necesita calibración",
-		"map.is.not.displayable": "Visualización desactivada",
+		"map.is.notDisplayable": "Visualización desactivada",
 		"map.load.complete": "Carga de mapas completada de",
 		"map.modified.date": "Última modificación: ",
 		"map.name": "Nombre del mapa",

From 09e0738cb4ad20b148bef82c6cf930e4d0c151fd Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Thu, 1 Aug 2024 21:29:40 -0700
Subject: [PATCH 28/50] Aligning modal call function names with other
 components and limiting note length

---
 src/client/app/components/maps/MapViewComponent.tsx | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index 26090a602..26588afd2 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -17,8 +17,8 @@ interface MapViewProps {
 
 const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 	const [showEditModal, setShowEditModal] = useState(false);
-	const handleShowModal = () => setShowEditModal(true);
-	const handleCloseModal = () => setShowEditModal(false);
+	const handleShow = () => setShowEditModal(true);
+	const handleClose = () => setShowEditModal(false);
 
 	const selectMapById = React.useMemo(makeSelectMapById, []);
 	const map = useSelector(state => selectMapById(state, mapID));
@@ -38,7 +38,7 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 				<b><FormattedMessage id="map.circle.size" /></b> {map.circleSize}
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="note" /></b> {map.note}
+				<b><FormattedMessage id="note" /></b> {map.note ? map.note.slice(0,29) : ''}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.filename" /></b> {map.filename}
@@ -55,14 +55,14 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 			</div>
 			{(
 				<div className="edit-btn">
-					<Button color='secondary' onClick={handleShowModal}>
+					<Button color='secondary' onClick={handleShow}>
 						<FormattedMessage id="edit.map" />
 					</Button>
 				</div>
 			)}
 			<EditMapModalComponent
 				show={showEditModal}
-				handleClose={handleCloseModal}
+				handleClose={handleClose}
 				map={map}
 			/>
 		</div>

From 811dc6693629b4bef6f78de78981095a93f66e0d Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Fri, 2 Aug 2024 22:37:12 -0700
Subject: [PATCH 29/50] Update code to use simple selector

---
 src/client/app/components/maps/MapViewComponent.tsx | 9 +++++----
 src/client/app/redux/selectors/maps.ts              | 9 ++-------
 2 files changed, 7 insertions(+), 11 deletions(-)

diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index 26588afd2..27d15ac02 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -9,8 +9,10 @@ import { Button } from 'reactstrap';
 import { parseZone } from 'moment';
 import '../../styles/card-page.css';
 import EditMapModalComponent from './EditMapModalComponent';
-import { makeSelectMapById } from '../../redux/selectors/maps';
-import { useSelector } from 'react-redux';
+import { selectMapById } from '../../redux/selectors/maps';
+import { RootState } from '../../store';
+import { useAppSelector } from '../../redux/reduxHooks';
+import { MapMetadata } from 'types/redux/map';
 interface MapViewProps {
 	mapID: number;
 }
@@ -20,8 +22,7 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 	const handleShow = () => setShowEditModal(true);
 	const handleClose = () => setShowEditModal(false);
 
-	const selectMapById = React.useMemo(makeSelectMapById, []);
-	const map = useSelector(state => selectMapById(state, mapID));
+	const map: MapMetadata = useAppSelector((state: RootState) => selectMapById(state, mapID));
 
 	return (
 		<div className="card">
diff --git a/src/client/app/redux/selectors/maps.ts b/src/client/app/redux/selectors/maps.ts
index 909990754..bf4f67a38 100644
--- a/src/client/app/redux/selectors/maps.ts
+++ b/src/client/app/redux/selectors/maps.ts
@@ -4,6 +4,7 @@
 
 import { RootState } from 'store';
 import { createSelector } from '@reduxjs/toolkit';
+import { MapMetadata } from 'types/redux/map';
 
 export const selectMapState = (state: RootState) => state.maps;
 export const selectMaps = createSelector([selectMapState], maps => {
@@ -12,10 +13,4 @@ export const selectMaps = createSelector([selectMapState], maps => {
 		.filter(key => !isNaN(key));
 });
 
-export const makeSelectMapById = () => {
-	const selectMapById = createSelector(
-		[selectMapState, (state, id) => id],
-		(maps, id) => maps.byMapID[id]
-	);
-	return selectMapById;
-};
\ No newline at end of file
+export const selectMapById = (state: RootState, id: number): MapMetadata => state.maps.byMapID[id];
\ No newline at end of file

From 964122039ec094917fc4e17426e6eddff660f8b0 Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Sat, 3 Aug 2024 03:35:54 -0700
Subject: [PATCH 30/50] Put modal state where it belongs

---
 .../components/maps/EditMapModalComponent.tsx | 178 +++++++++---------
 .../app/components/maps/MapViewComponent.tsx  |  14 --
 2 files changed, 93 insertions(+), 99 deletions(-)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index d51974aad..2654f26ed 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -14,12 +14,13 @@ import { useAppDispatch } from '../../redux/reduxHooks';
 import { AppDispatch } from 'store';
 
 interface EditMapModalProps {
-	show: boolean;
-	handleClose: () => void;
 	map: MapMetadata;
 }
 
-const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose, map}) => {
+const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
+	const [showModal, setShowModal] = useState(false);
+	const handleShow = () => setShowModal(true);
+	const handleClose = () => setShowModal(false);
 	const dispatch: AppDispatch = useAppDispatch();
 	const [nameInput, setNameInput] = useState(map.name);
 	const [noteInput, setNoteInput] = useState(map.note || '');
@@ -64,92 +65,99 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ show, handleClose,
 	};
 
 	return (
-		<Modal isOpen={show} toggle={handleClose}>
-			<ModalHeader toggle={handleClose}>
-				<FormattedMessage id="edit.map" />
-			</ModalHeader>
-			<ModalBody>
-				<Form>
-					<FormGroup>
-						<Label for="mapName"><FormattedMessage id="map.name" /></Label>
-						<Input
-							id="mapName"
-							value={nameInput}
-							onChange={e => setNameInput(e.target.value)}
-						/>
-					</FormGroup>
-					<FormGroup>
-						<Label for='map.displayable'><FormattedMessage id='map.displayable' /></Label>
+		<>
+			<div className="edit-btn">
+				<Button color='secondary' onClick={handleShow}>
+					<FormattedMessage id="edit.map" />
+				</Button>
+			</div>
+			<Modal isOpen={showModal} toggle={handleClose}>
+				<ModalHeader toggle={handleClose}>
+					<FormattedMessage id="edit.map" />
+				</ModalHeader>
+				<ModalBody>
+					<Form>
+						<FormGroup>
+							<Label for="mapName"><FormattedMessage id="map.name" /></Label>
+							<Input
+								id="mapName"
+								value={nameInput}
+								onChange={e => setNameInput(e.target.value)}
+							/>
+						</FormGroup>
+						<FormGroup>
+							<Label for='map.displayable'><FormattedMessage id='map.displayable' /></Label>
+							<Input
+								id='map.displayable'
+								name='map.displayable'
+								type='select'
+								value={displayable}
+								onChange={e => setDisplayable(e.target.value as DisplayableType)}
+							>
+								{Object.keys(DisplayableType).map(key => (
+									<option value={key} key={key}>
+										{intl.formatMessage({ id: `map.is.${key}` })}
+									</option>
+								))}
+							</Input>
+						</FormGroup>
+						<FormGroup>
+							<Label for="mapCircleSize"><FormattedMessage id="map.circle.size" /></Label>
+							<Input
+								id="mapCircleSize"
+								type='number'
+								value={circleInput}
+								onChange={e => setCircleInput(e.target.value)}
+								invalid={parseFloat(circleInput) < 0}
+								onBlur={toggleCircleEdit}
+							/>
+						</FormGroup>
+						<FormGroup>
+							<Label for="mapNote"><FormattedMessage id="note" /></Label>
+							<Input
+								id="mapNote"
+								type="textarea"
+								value={noteInput}
+								onChange={e => setNoteInput(e.target.value.slice(0, 30))}
+							/>
+						</FormGroup>
+					</Form>
+					<div>
+						<Label><FormattedMessage id="map.filename" /></Label>
 						<Input
-							id='map.displayable'
-							name='map.displayable'
-							type='select'
-							value={displayable}
-							onChange={e => setDisplayable(e.target.value as DisplayableType)}
-						>
-							{Object.keys(DisplayableType).map(key => (
-								<option value={key} key={key}>
-									{intl.formatMessage({ id: `map.is.${key}` })}
-								</option>
-							))}
+							id='mapFilename'
+							name='mapFilename'
+							type='text'
+							defaultValue={map.filename}
+							disabled>
 						</Input>
-					</FormGroup>
-					<FormGroup>
-						<Label for="mapCircleSize"><FormattedMessage id="map.circle.size" /></Label>
-						<Input
-							id="mapCircleSize"
-							type='number'
-							value={circleInput}
-							onChange={e => setCircleInput(e.target.value)}
-							invalid={parseFloat(circleInput)<0}
-							onBlur={toggleCircleEdit}
-						/>
-					</FormGroup>
-					<FormGroup>
-						<Label for="mapNote"><FormattedMessage id="note" /></Label>
-						<Input
-							id="mapNote"
-							type="textarea"
-							value={noteInput}
-							onChange={e => setNoteInput(e.target.value.slice(0,30))}
-						/>
-					</FormGroup>
-				</Form>
-				<div>
-					<Label><FormattedMessage id="map.filename" /></Label>
-					<Input
-						id='mapFilename'
-						name='mapFilename'
-						type='text'
-						defaultValue={map.filename}
-						disabled>
-					</Input>
-					<Button color='primary' onClick={() => handleCalibrationSetting(CalibrationModeTypes.initiate)}>
-						<FormattedMessage id='map.upload.new.file' />
+						<Button color='primary' onClick={() => handleCalibrationSetting(CalibrationModeTypes.initiate)}>
+							<FormattedMessage id='map.upload.new.file' />
+						</Button>
+					</div>
+					<div>
+						<Label><FormattedMessage id="map.calibration" /></Label>
+						<p>
+							<FormattedMessage id={map.origin && map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
+						</p>
+						<Button color='primary' onClick={() => handleCalibrationSetting(CalibrationModeTypes.calibrate)}>
+							<FormattedMessage id='map.calibrate' />
+						</Button>
+					</div>
+				</ModalBody>
+				<ModalFooter>
+					<Button color="danger" onClick={handleDelete}>
+						<FormattedMessage id="delete.map" />
 					</Button>
-				</div>
-				<div>
-					<Label><FormattedMessage id="map.calibration" /></Label>
-					<p>
-						<FormattedMessage id={map.origin && map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
-					</p>
-					<Button color='primary' onClick={() => handleCalibrationSetting(CalibrationModeTypes.calibrate)}>
-						<FormattedMessage id='map.calibrate' />
+					<Button color="secondary" onClick={handleClose}>
+						<FormattedMessage id="cancel" />
 					</Button>
-				</div>
-			</ModalBody>
-			<ModalFooter>
-				<Button color="danger" onClick={handleDelete}>
-					<FormattedMessage id="delete.map" />
-				</Button>
-				<Button color="secondary" onClick={handleClose}>
-					<FormattedMessage id="cancel" />
-				</Button>
-				<Button color="primary" onClick={handleSave}>
-					<FormattedMessage id="save.all" />
-				</Button>
-			</ModalFooter>
-		</Modal>
+					<Button color="primary" onClick={handleSave}>
+						<FormattedMessage id="save.all" />
+					</Button>
+				</ModalFooter>
+			</Modal>
+		</>
 	);
 };
 
diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index 27d15ac02..768df3b84 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -3,9 +3,7 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import * as React from 'react';
-import { useState } from 'react';
 import { FormattedMessage } from 'react-intl';
-import { Button } from 'reactstrap';
 import { parseZone } from 'moment';
 import '../../styles/card-page.css';
 import EditMapModalComponent from './EditMapModalComponent';
@@ -18,9 +16,6 @@ interface MapViewProps {
 }
 
 const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
-	const [showEditModal, setShowEditModal] = useState(false);
-	const handleShow = () => setShowEditModal(true);
-	const handleClose = () => setShowEditModal(false);
 
 	const map: MapMetadata = useAppSelector((state: RootState) => selectMapById(state, mapID));
 
@@ -54,16 +49,7 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 					<FormattedMessage id={map.origin && map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
 				</span>
 			</div>
-			{(
-				<div className="edit-btn">
-					<Button color='secondary' onClick={handleShow}>
-						<FormattedMessage id="edit.map" />
-					</Button>
-				</div>
-			)}
 			<EditMapModalComponent
-				show={showEditModal}
-				handleClose={handleClose}
 				map={map}
 			/>
 		</div>

From 1e0b61b3bfa887c829d8816355c303788d890d6f Mon Sep 17 00:00:00 2001
From: Juan Gutierrez <juanjoseguva@gmail.com>
Date: Sat, 3 Aug 2024 09:34:04 -0700
Subject: [PATCH 31/50] Reverting to treating map displayability variable as a
 boolean instead of enum

---
 .../components/maps/EditMapModalComponent.tsx | 21 +++++++------------
 src/client/app/translations/data.ts           |  2 --
 src/client/app/types/redux/map.ts             |  7 +------
 3 files changed, 9 insertions(+), 21 deletions(-)

diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index 2654f26ed..fba8a82a0 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -8,7 +8,6 @@ import { FormattedMessage, useIntl } from 'react-intl';
 import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, FormGroup, Label, Input } from 'reactstrap';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
 import { editMapDetails, submitEditedMap, removeMap, setCalibration } from '../../redux/actions/map';
-import { DisplayableType } from '../../types/redux/map';
 import { showErrorNotification } from '../../utils/notifications';
 import { useAppDispatch } from '../../redux/reduxHooks';
 import { AppDispatch } from 'store';
@@ -25,7 +24,7 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 	const [nameInput, setNameInput] = useState(map.name);
 	const [noteInput, setNoteInput] = useState(map.note || '');
 	const [circleInput, setCircleInput] = useState(map.circleSize.toString());
-	const [displayable, setDisplayable] = useState<DisplayableType>(map.displayable);
+	const [displayable, setDisplayable] = useState(map.displayable);
 
 	const intl = useIntl();
 
@@ -35,7 +34,7 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 			name: nameInput,
 			note: noteInput,
 			circleSize: parseFloat(circleInput),
-			displayable: displayable as DisplayableType
+			displayable: displayable
 		};
 		dispatch(editMapDetails(updatedMap));
 		dispatch(submitEditedMap(updatedMap.id));
@@ -88,17 +87,13 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 						<FormGroup>
 							<Label for='map.displayable'><FormattedMessage id='map.displayable' /></Label>
 							<Input
-								id='map.displayable'
-								name='map.displayable'
-								type='select'
-								value={displayable}
-								onChange={e => setDisplayable(e.target.value as DisplayableType)}
+								id="mapDisplayable"
+								type="select"
+								value={displayable.toString()}
+								onChange={e => setDisplayable(e.target.value === 'true')}
 							>
-								{Object.keys(DisplayableType).map(key => (
-									<option value={key} key={key}>
-										{intl.formatMessage({ id: `map.is.${key}` })}
-									</option>
-								))}
+								<option value="true">{intl.formatMessage({ id: 'map.is.displayable' })}</option>
+								<option value="false">{intl.formatMessage({ id: 'map.is.not.displayable' })}</option>
 							</Input>
 						</FormGroup>
 						<FormGroup>
diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index c77041f84..d4d1aaf2e 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -825,7 +825,6 @@ const LocaleTranslationData = {
 		"map.notify.calibration.needed": "Étalonnage nécessaire pour l'affichage",
 		"map.upload.new.file": "Upload New File\u{26A1}",
 		"map.unavailable": "There's not an available map\u{26A1}",
-		"map.upload.new.file": "Refaire",
 		"max": "max\u{26A1}",
 		"menu": "Menu",
 		"meter": "Mèter",
@@ -1327,7 +1326,6 @@ const LocaleTranslationData = {
 		"map.notify.calibration.needed": "Necesita calibración antes de visualizar",
 		"map.upload.new.file": "Subir un nuevo archivo",
 		"map.unavailable": "No hay un mapa disponible",
-		"map.upload.new.file": "Rehacer",
 		"max": "máximo",
 		"menu": "Menú",
 		"meter": "Medidor",
diff --git a/src/client/app/types/redux/map.ts b/src/client/app/types/redux/map.ts
index d171dd272..d1700c035 100644
--- a/src/client/app/types/redux/map.ts
+++ b/src/client/app/types/redux/map.ts
@@ -14,11 +14,6 @@ export enum CalibrationModeTypes {
 	unavailable = 'unavailable'
 }
 
-export enum DisplayableType {
-	displayable = 'displayable',
-	notDisplayable = 'not.displayable'
-}
-
 export interface ChangeMapModeAction {
 	type: ActionType.UpdateCalibrationMode;
 	nextMode: CalibrationModeTypes;
@@ -153,7 +148,7 @@ export interface MapData {
 export interface MapMetadata {
 	id: number;
 	name: string;
-	displayable: DisplayableType;
+	displayable: boolean;
 	note?: string;
 	filename: string;
 	modifiedDate: string;

From 51a220f23a762ee9ed372cce910ea7915b292766 Mon Sep 17 00:00:00 2001
From: Severin L <severinlight3@gmail.com>
Date: Sat, 3 Aug 2024 18:46:57 +0000
Subject: [PATCH 32/50] Add coloring to map Display and add TODO for date
 internationalization

---
 .../app/components/maps/MapViewComponent.tsx  | 26 +++++++++++++------
 src/client/app/translations/data.ts           | 20 +++++++-------
 2 files changed, 28 insertions(+), 18 deletions(-)

diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index 768df3b84..5211edb1b 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -11,6 +11,8 @@ import { selectMapById } from '../../redux/selectors/maps';
 import { RootState } from '../../store';
 import { useAppSelector } from '../../redux/reduxHooks';
 import { MapMetadata } from 'types/redux/map';
+import translate from '../../utils/translate';
+import { LocaleDataKey } from 'translations/data';
 interface MapViewProps {
 	mapID: number;
 }
@@ -19,34 +21,42 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 
 	const map: MapMetadata = useAppSelector((state: RootState) => selectMapById(state, mapID));
 
+	// Helper function checks map to see if it's calibrated
+	const getCalibrationStatus = () => {
+		const isCalibrated = map.origin && map.opposite;
+		return {
+			color: isCalibrated ? 'black' : 'gray',
+			messageId: isCalibrated ? 'map.is.calibrated' : 'map.is.not.calibrated'
+		};
+	};
+	const { color, messageId } = getCalibrationStatus();
+
 	return (
 		<div className="card">
 			<div className="identifier-container">
 				{map.name}
 			</div>
-			<div className="item-container">
-				<b><FormattedMessage id="map.displayable" /></b>
-				<span style={{ color: map.displayable ? 'green' : 'red' }}>
-					<FormattedMessage id={map.displayable ? 'map.is.displayable' : 'map.is.not.displayable'} />
-				</span>
+			<div className={map.displayable.toString()}>
+				<b><FormattedMessage id="map.displayable" /></b> {translate(`TrueFalseType.${map.displayable.toString()}` as LocaleDataKey)}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.circle.size" /></b> {map.circleSize}
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="note" /></b> {map.note ? map.note.slice(0,29) : ''}
+				<b><FormattedMessage id="note" /></b> {map.note ? map.note.slice(0, 29) : ''}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.filename" /></b> {map.filename}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.modified.date" /></b>
+				{/* TODO I don't think this will properly internationalize. */}
 				{parseZone(map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.calibration" /></b>
-				<span style={{ color: map.origin && map.opposite ? 'black' : 'gray' }}>
-					<FormattedMessage id={map.origin && map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
+				<span style={{ color }}>
+					<FormattedMessage id={messageId} />
 				</span>
 			</div>
 			<EditMapModalComponent
diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts
index d4d1aaf2e..deb2c9fc7 100644
--- a/src/client/app/translations/data.ts
+++ b/src/client/app/translations/data.ts
@@ -151,7 +151,7 @@ const LocaleTranslationData = {
 		"DisplayableType.none": "none",
 		"DisplayableType.all": "all",
 		"DisplayableType.admin": "admin",
-		"done.editing":"Done editing",
+		"done.editing": "Done editing",
 		"error.bounds": "Must be between {min} and {max}.",
 		"error.displayable": "Displayable will be set to false because no unit is selected.",
 		"error.displayable.meter": "Meter units will set displayable to none.",
@@ -166,7 +166,7 @@ const LocaleTranslationData = {
 		"edit.a.group": "Edit a Group",
 		"edit.a.meter": "Edit a Meter",
 		"edit.group": "Edit Group",
-		"edit.map":"Edit Map",
+		"edit.map": "Edit Map",
 		"edit.meter": "Details/Edit Meter",
 		"edit.unit": "Edit Unit",
 		"email": "Email",
@@ -311,9 +311,9 @@ const LocaleTranslationData = {
 		"map.interval": "Map Interval",
 		"map.is.calibrated": "Calibration Complete",
 		"map.is.deleted": "Map removed from database",
-		"map.is.displayable": "Display Enabled",
+		"map.is.displayable": "true",
 		"map.is.not.calibrated": "Calibration Needed",
-		"map.is.notDisplayable": "Display Disabled",
+		"map.is.not.displayable": "false",
 		"map.load.complete": "Map load complete from",
 		"map.modified.date": "Last Modified: ",
 		"map.name": "Map Name",
@@ -652,7 +652,7 @@ const LocaleTranslationData = {
 		"DisplayableType.none": "none\u{26A1}",
 		"DisplayableType.all": "all\u{26A1}",
 		"DisplayableType.admin": "admin\u{26A1}",
-		"done.editing":"Done editing\u{26A1}",
+		"done.editing": "Done editing\u{26A1}",
 		"error.bounds": "Must be between {min} and {max}.\u{26A1}",
 		"error.displayable": "Displayable will be set to false because no unit is selected.\u{26A1}",
 		"error.displayable.meter": "Meter units will set displayable to none.\u{26A1}",
@@ -667,7 +667,7 @@ const LocaleTranslationData = {
 		"edit.a.group": "Modifier le Groupe",
 		"edit.a.meter": "Modifier le Métre",
 		"edit.group": "Modifier Groupe",
-		"edit.map":"Edit Map\u{26A1}",
+		"edit.map": "Edit Map\u{26A1}",
 		"edit.meter": "Details/Modifier Métre\u{26A1}",
 		"edit.unit": "Edit Unit\u{26A1}",
 		"email": "E-mail",
@@ -1153,7 +1153,7 @@ const LocaleTranslationData = {
 		"DisplayableType.none": "ninguno",
 		"DisplayableType.all": "todo",
 		"DisplayableType.admin": "administrador",
-		"done.editing":"Acabar de editar",
+		"done.editing": "Acabar de editar",
 		"error.bounds": "Debe ser entre {min} y {max}.",
 		"error.displayable": "El elemento visual determinado como falso porque no hay unidad seleccionada.",
 		"error.displayable.meter": "Las unidades de medición determinarán al elemento visual como ninguno.",
@@ -1162,13 +1162,13 @@ const LocaleTranslationData = {
 		"error.gps": "Latitud deber ser entre -90 y 90, y longitud de entre -180 y 180.",
 		"error.negative": "No puede ser negativo.",
 		"error.required": "Campo requerido",
-		"error.unknown":"¡Ups! Ha ocurrido un error.",
+		"error.unknown": "¡Ups! Ha ocurrido un error.",
 		"edit": "Editar",
 		"edited": "editado",
 		"edit.a.group": "Editar un grupo",
 		"edit.a.meter": "Editar un medidor",
 		"edit.group": "Editar grupo",
-		"edit.map":"Editar mapa",
+		"edit.map": "Editar mapa",
 		"edit.meter": "Editar medidor",
 		"edit.unit": "Editar unidad",
 		"email": "Correo electrónico",
@@ -1499,7 +1499,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":"usos",
+		"uses": "usos",
 		"view.groups": "Ver grupos",
 		"visit": " o visite nuestro ",
 		"website": "sitio web",

From d270242dbf65ef6dce23cefc65ce95fb3610b163 Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Sat, 3 Aug 2024 20:11:56 -0700
Subject: [PATCH 33/50] Use memoized createAppSelector instead of directly
 handling state in selectors.

---
 .../app/components/maps/MapViewComponent.tsx  |  3 +--
 src/client/app/redux/selectors/maps.ts        | 19 ++++++++++---------
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index 5211edb1b..a2bd3a276 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -8,7 +8,6 @@ import { parseZone } from 'moment';
 import '../../styles/card-page.css';
 import EditMapModalComponent from './EditMapModalComponent';
 import { selectMapById } from '../../redux/selectors/maps';
-import { RootState } from '../../store';
 import { useAppSelector } from '../../redux/reduxHooks';
 import { MapMetadata } from 'types/redux/map';
 import translate from '../../utils/translate';
@@ -19,7 +18,7 @@ interface MapViewProps {
 
 const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 
-	const map: MapMetadata = useAppSelector((state: RootState) => selectMapById(state, mapID));
+	const map: MapMetadata = useAppSelector(selectMapById(mapID));
 
 	// Helper function checks map to see if it's calibrated
 	const getCalibrationStatus = () => {
diff --git a/src/client/app/redux/selectors/maps.ts b/src/client/app/redux/selectors/maps.ts
index bf4f67a38..020fa7275 100644
--- a/src/client/app/redux/selectors/maps.ts
+++ b/src/client/app/redux/selectors/maps.ts
@@ -2,15 +2,16 @@
  * 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 { RootState } from 'store';
-import { createSelector } from '@reduxjs/toolkit';
-import { MapMetadata } from 'types/redux/map';
+import { RootState } from "store";
+import { MapState } from "types/redux/map";
+import { createAppSelector } from "./selectors";
 
 export const selectMapState = (state: RootState) => state.maps;
-export const selectMaps = createSelector([selectMapState], maps => {
-	return Object.keys(maps.byMapID)
-		.map(key => parseInt(key))
-		.filter(key => !isNaN(key));
-});
+export const selectMaps = createAppSelector([selectMapState], (maps) =>
+	Object.keys(maps.byMapID)
+		.map((key) => parseInt(key))
+		.filter((key) => !isNaN(key))
+);
 
-export const selectMapById = (state: RootState, id: number): MapMetadata => state.maps.byMapID[id];
\ No newline at end of file
+export const selectMapById = (id: number) =>
+	createAppSelector([selectMapState], (maps: MapState) => maps.byMapID[id]);

From 18fa27558489e3bcb1a8526480e7c7fba98d67ed Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Sat, 3 Aug 2024 20:17:08 -0700
Subject: [PATCH 34/50] Fix linting errors

---
 src/client/app/redux/selectors/maps.ts | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/client/app/redux/selectors/maps.ts b/src/client/app/redux/selectors/maps.ts
index 020fa7275..f0fd34ce1 100644
--- a/src/client/app/redux/selectors/maps.ts
+++ b/src/client/app/redux/selectors/maps.ts
@@ -2,15 +2,15 @@
  * 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 { RootState } from "store";
-import { MapState } from "types/redux/map";
-import { createAppSelector } from "./selectors";
+import { RootState } from 'store';
+import { MapState } from 'types/redux/map';
+import { createAppSelector } from './selectors';
 
 export const selectMapState = (state: RootState) => state.maps;
-export const selectMaps = createAppSelector([selectMapState], (maps) =>
+export const selectMaps = createAppSelector([selectMapState], maps =>
 	Object.keys(maps.byMapID)
-		.map((key) => parseInt(key))
-		.filter((key) => !isNaN(key))
+		.map(key => parseInt(key))
+		.filter(key => !isNaN(key))
 );
 
 export const selectMapById = (id: number) =>

From ca0860660f15275e2a40771fa99cb0471d4d2403 Mon Sep 17 00:00:00 2001
From: hazeltonbw <hazeltonbw@gmail.com>
Date: Sun, 4 Aug 2024 18:33:05 -0700
Subject: [PATCH 35/50] Leave TODO notes for future updates

---
 src/client/app/components/maps/CreateMapModalComponent.tsx | 4 +++-
 src/client/app/components/maps/EditMapModalComponent.tsx   | 3 ++-
 src/client/app/components/maps/MapViewComponent.tsx        | 3 ++-
 src/client/app/components/maps/MapsDetailComponent.tsx     | 4 +++-
 src/client/app/redux/actions/map.ts                        | 4 +++-
 src/client/app/redux/reducers/maps.ts                      | 4 +++-
 src/client/app/redux/selectors/maps.ts                     | 2 ++
 7 files changed, 18 insertions(+), 6 deletions(-)

diff --git a/src/client/app/components/maps/CreateMapModalComponent.tsx b/src/client/app/components/maps/CreateMapModalComponent.tsx
index 213113abf..607e71d24 100644
--- a/src/client/app/components/maps/CreateMapModalComponent.tsx
+++ b/src/client/app/components/maps/CreateMapModalComponent.tsx
@@ -23,6 +23,8 @@ interface CreateMapModalProps {
  * @returns Map create element
  */
 function CreateMapModalComponent({ show, handleClose, createNewMap }: CreateMapModalProps) {
+	// TODO: Get rid of props, migrate to RTK, finish modal
+	// Once modal is finished, it will be used in MapsDetailComponent
 	const [nameInput, setNameInput] = useState('');
 	const [noteInput, setNoteInput] = useState('');
 
@@ -77,4 +79,4 @@ function CreateMapModalComponent({ show, handleClose, createNewMap }: CreateMapM
 	);
 }
 
-export default CreateMapModalComponent;
\ No newline at end of file
+export default CreateMapModalComponent;
diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index fba8a82a0..588f4bfd3 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -16,6 +16,7 @@ interface EditMapModalProps {
 	map: MapMetadata;
 }
 
+// TODO: Migrate to RTK
 const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 	const [showModal, setShowModal] = useState(false);
 	const handleShow = () => setShowModal(true);
@@ -156,4 +157,4 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 	);
 };
 
-export default EditMapModalComponent;
\ No newline at end of file
+export default EditMapModalComponent;
diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index a2bd3a276..4156ef9bc 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -16,6 +16,7 @@ interface MapViewProps {
 	mapID: number;
 }
 
+//TODO: Migrate to RTK
 const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 
 	const map: MapMetadata = useAppSelector(selectMapById(mapID));
@@ -65,4 +66,4 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 	);
 };
 
-export default MapViewComponent;
\ No newline at end of file
+export default MapViewComponent;
diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index 957f20791..0f7be9289 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -19,6 +19,7 @@ import { AppDispatch } from 'store';
  * Defines the maps page card view
  * @returns Maps page element
  */
+// TODO: Migrate to RTK
 export default function MapsDetailComponent() {
 	const dispatch: AppDispatch = useAppDispatch();
 	// Load map IDs from state and store in number array
@@ -38,6 +39,7 @@ export default function MapsDetailComponent() {
 						<TooltipMarkerComponent page='maps' helpTextId='help.admin.mapview' />
 					</div>
 				</h2>
+					{ /* TODO: Change Link to <CreateMapModalComponent /> when it is completed */ }
 				<div className="edit-btn">
 					<Link to='/calibration' onClick={() => dispatch(setNewMap())}>
 						<Button color='primary'>
@@ -53,4 +55,4 @@ export default function MapsDetailComponent() {
 			</div>
 		</div>
 	);
-}
\ No newline at end of file
+}
diff --git a/src/client/app/redux/actions/map.ts b/src/client/app/redux/actions/map.ts
index 27bc4b47a..5af42df04 100644
--- a/src/client/app/redux/actions/map.ts
+++ b/src/client/app/redux/actions/map.ts
@@ -2,6 +2,8 @@
  * 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/. */
 
+// TODO: Migrate to RTK
+
 import { ActionType, Dispatch, GetState, Thunk } from '../../types/redux/actions';
 import * as t from '../../types/redux/map';
 import { CalibrationModeTypes, MapData, MapMetadata } from '../../types/redux/map';
@@ -335,4 +337,4 @@ export function confirmEditedMaps() {
 			dispatch(confirmMapEdits(mapID));
 		});
 	};
-}
\ No newline at end of file
+}
diff --git a/src/client/app/redux/reducers/maps.ts b/src/client/app/redux/reducers/maps.ts
index 714980d5d..b43a0e789 100644
--- a/src/client/app/redux/reducers/maps.ts
+++ b/src/client/app/redux/reducers/maps.ts
@@ -2,6 +2,8 @@
  * 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/. */
 
+// TODO: Migrate to RTK
+
 import { MapMetadata, MapsAction, MapState } from '../../types/redux/map';
 import { ActionType } from '../../types/redux/actions';
 import { keyBy } from 'lodash';
@@ -240,4 +242,4 @@ export default function maps(state = defaultState, action: MapsAction) {
 		default:
 			return state;
 	}
-}
\ No newline at end of file
+}
diff --git a/src/client/app/redux/selectors/maps.ts b/src/client/app/redux/selectors/maps.ts
index f0fd34ce1..fe068ee73 100644
--- a/src/client/app/redux/selectors/maps.ts
+++ b/src/client/app/redux/selectors/maps.ts
@@ -2,6 +2,8 @@
  * 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/. */
 
+// TODO: Migrate to RTK
+
 import { RootState } from 'store';
 import { MapState } from 'types/redux/map';
 import { createAppSelector } from './selectors';

From 8a276b4ea7856c669e49326dcb4a35b099d7a76c Mon Sep 17 00:00:00 2001
From: Steven Huss-Lederman <huss@beloit.edu>
Date: Mon, 5 Aug 2024 10:44:26 -0500
Subject: [PATCH 36/50] fix linting

---
 src/client/app/components/maps/CreateMapModalComponent.tsx | 6 ++----
 src/client/app/components/maps/MapsDetailComponent.tsx     | 4 ++--
 2 files changed, 4 insertions(+), 6 deletions(-)

diff --git a/src/client/app/components/maps/CreateMapModalComponent.tsx b/src/client/app/components/maps/CreateMapModalComponent.tsx
index 607e71d24..7980a82aa 100644
--- a/src/client/app/components/maps/CreateMapModalComponent.tsx
+++ b/src/client/app/components/maps/CreateMapModalComponent.tsx
@@ -16,12 +16,10 @@ interface CreateMapModalProps {
 
 /**
  * Defines the create map modal form
- * @param root0
- * @param root0.show
- * @param root0.handleClose
- * @param root0.createNewMap
+ * params not given since props should be going away and painful. Remove eslint command when fixed.
  * @returns Map create element
  */
+/* eslint-disable-next-line */
 function CreateMapModalComponent({ show, handleClose, createNewMap }: CreateMapModalProps) {
 	// TODO: Get rid of props, migrate to RTK, finish modal
 	// Once modal is finished, it will be used in MapsDetailComponent
diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index 0f7be9289..5d6d8183c 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -10,7 +10,7 @@ import TooltipHelpComponent from '../../components/TooltipHelpComponent';
 import MapViewComponent from './MapViewComponent';
 import TooltipMarkerComponent from '../TooltipMarkerComponent';
 import '../../styles/card-page.css';
-import { fetchMapsDetails, setNewMap} from '../../redux/actions/map';
+import { fetchMapsDetails, setNewMap } from '../../redux/actions/map';
 import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
 import { selectMaps } from '../../redux/selectors/maps';
 import { AppDispatch } from 'store';
@@ -39,7 +39,7 @@ export default function MapsDetailComponent() {
 						<TooltipMarkerComponent page='maps' helpTextId='help.admin.mapview' />
 					</div>
 				</h2>
-					{ /* TODO: Change Link to <CreateMapModalComponent /> when it is completed */ }
+				{ /* TODO: Change Link to <CreateMapModalComponent /> when it is completed */}
 				<div className="edit-btn">
 					<Link to='/calibration' onClick={() => dispatch(setNewMap())}>
 						<Button color='primary'>

From 21de91613eef696c0896d6de0eb3d8c27044e0ac Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Mon, 5 Aug 2024 12:09:07 -0700
Subject: [PATCH 37/50] Initial RTKQ Maps

---
 .../app/components/MapChartComponent.tsx      | 499 +++++++++---------
 .../maps/MapCalibrationInitiateComponent.tsx  | 276 +++++-----
 .../app/components/maps/MapViewComponent.tsx  |  13 +-
 .../components/maps/MapsDetailComponent.tsx   |  19 +-
 .../app/containers/MapChartContainer.ts       |   8 +-
 .../MapCalibrationChartDisplayContainer.ts    |   6 +-
 src/client/app/redux/actions/map.ts           |  18 +-
 src/client/app/redux/api/baseApi.ts           |   1 +
 src/client/app/redux/api/logApi.ts            |  14 +
 src/client/app/redux/api/mapsApi.ts           | 147 ++++++
 src/client/app/redux/devToolConfig.ts         |  21 +
 src/client/app/redux/reducers/maps.ts         |   3 +-
 src/client/app/redux/selectors/uiSelectors.ts |   5 +-
 src/client/app/redux/slices/appStateSlice.ts  |   5 +-
 src/client/app/store.ts                       |  14 +-
 src/client/app/types/redux/map.ts             |  12 +-
 16 files changed, 616 insertions(+), 445 deletions(-)
 create mode 100644 src/client/app/redux/api/logApi.ts
 create mode 100644 src/client/app/redux/api/mapsApi.ts
 create mode 100644 src/client/app/redux/devToolConfig.ts

diff --git a/src/client/app/components/MapChartComponent.tsx b/src/client/app/components/MapChartComponent.tsx
index e1a5dde28..7ce9d34b8 100644
--- a/src/client/app/components/MapChartComponent.tsx
+++ b/src/client/app/components/MapChartComponent.tsx
@@ -5,8 +5,6 @@
 import { orderBy } from 'lodash';
 import * as moment from 'moment';
 import * as React from 'react';
-import Plot from 'react-plotly.js';
-import { useSelector } from 'react-redux';
 import {
 	selectAreaUnit, selectBarWidthDays,
 	selectGraphAreaNormalization, selectSelectedGroups,
@@ -19,7 +17,6 @@ import { selectUnitDataById } from '../redux/api/unitsApi';
 import { useAppSelector } from '../redux/reduxHooks';
 import { selectMapChartQueryArgs } from '../redux/selectors/chartQuerySelectors';
 import { DataType } from '../types/Datasources';
-import { State } from '../types/redux/state';
 import { UnitRepresentType } from '../types/redux/units';
 import {
 	CartesianPoint,
@@ -34,6 +31,8 @@ import { AreaUnitType, getAreaUnitConversion } from '../utils/getAreaUnitConvers
 import getGraphColor from '../utils/getGraphColor';
 import translate from '../utils/translate';
 import SpinnerComponent from './SpinnerComponent';
+import { selectMapById } from '../redux/api/mapsApi';
+import Plot from 'react-plotly.js';
 
 /**
  * @returns map component
@@ -57,285 +56,271 @@ export default function MapChartComponent() {
 
 	// RTK Types Disagree with maps ts types so, use old until migration completer for maps.
 	// This is also an issue when trying to refactor maps reducer into slice.
-	const selectedMap = useSelector((state: State) => state.maps.selectedMap);
-	const byMapID = useSelector((state: State) => state.maps.byMapID);
-	const editedMaps = useSelector((state: State) => state.maps.editedMaps);
+	const selectedMap = useAppSelector(state => state.maps.selectedMap);
+	const map = useAppSelector(state => selectMapById(state, selectedMap));
+
 	if (meterIsFetching || groupIsFetching) {
 		return <SpinnerComponent loading width={50} height={50} />;
 	}
 
-
-	// Map to use.
-	let map;
 	// Holds Plotly mapping info.
 	const data = [];
-	// Holds the image to use.
-	let image;
-	if (selectedMap !== 0) {
-		const mapID = selectedMap;
-		if (byMapID[mapID]) {
-			map = byMapID[mapID];
-			if (editedMaps[mapID]) {
-				map = editedMaps[mapID];
+
+	// Holds the hover text for each point for Plotly
+	const hoverText: string[] = [];
+	// Holds the size of each circle for Plotly.
+	const size: number[] = [];
+	// Holds the color of each circle for Plotly.
+	const colors: string[] = [];
+	// If there is no map then use a new, empty image as the map. I believe this avoids errors
+	// and gives the blank screen.
+	// Arrays to hold the Plotly grid location (x, y) for circles to place on map.
+	const x: number[] = [];
+	const y: number[] = [];
+
+	// const timeInterval = state.graph.queryTimeInterval;
+	// const barDuration = state.graph.barDuration
+	// Make sure there is a map with values so avoid issues.
+	if (map && map.origin && map.opposite) {
+		// The size of the original map loaded into OED.
+		const imageDimensions: Dimensions = {
+			width: map.imgWidth,
+			height: map.imgHeight
+		};
+		// Determine the dimensions so within the Plotly coordinates on the user map.
+		const imageDimensionNormalized = normalizeImageDimensions(imageDimensions);
+		// This is the origin & opposite from the calibration. It is the lower, left
+		// and upper, right corners of the user map.
+		const origin = map.origin;
+		const opposite = map.opposite;
+		// Get the GPS degrees per unit of Plotly grid for x and y. By knowing the two corners
+		// (or really any two distinct points) you can calculate this by the change in GPS over the
+		// change in x or y which is the map's width & height in this case.
+		const scaleOfMap = calculateScaleFromEndpoints(origin, opposite, imageDimensionNormalized, map.northAngle);
+		// Loop over all selected meters. Maps only work for meters at this time.
+		// The y-axis label depends on the unit which is in selectUnit state.
+		let unitLabel: string = '';
+		// If graphingUnit is -99 then none selected and nothing to graph so label is empty.
+		// This will probably happen when the page is first loaded.
+		if (unitID !== -99) {
+			const selectUnitState = unitDataById[unitID];
+			if (selectUnitState !== undefined) {
+				// Quantity and flow units have different unit labels.
+				// Look up the type of unit if it is for quantity/flow (should not be raw) and decide what to do.
+				// Bar graphics are always quantities.
+				if (selectUnitState.unitRepresent === UnitRepresentType.quantity) {
+					// If it is a quantity unit then that is the unit you are graphing but it is normalized to per day.
+					unitLabel = selectUnitState.identifier + ' / day';
+				} else if (selectUnitState.unitRepresent === UnitRepresentType.flow) {
+					// If it is a flow meter then you need to multiply by time to get the quantity unit then show as per day.
+					// The quantity/time for flow has varying time so label by multiplying by time.
+					// To make sure it is clear, also indicate it is a quantity.
+					// Note this should not be used for raw data.
+					// It might not be usual to take a flow and make it into a quantity so this label is a little different to
+					// catch people's attention. If sites/users don't like OED doing this then we can eliminate flow for these types
+					// of graphics as we are doing for rate.
+					unitLabel = selectUnitState.identifier + ' * time / day ≡ quantity / day';
+				}
+				if (areaNormalization) {
+					unitLabel += ' / ' + translate(`AreaUnitType.${selectedAreaUnit}`);
+				}
 			}
 		}
-		// Holds the hover text for each point for Plotly
-		const hoverText: string[] = [];
-		// Holds the size of each circle for Plotly.
-		const size: number[] = [];
-		// Holds the color of each circle for Plotly.
-		const colors: string[] = [];
-		// If there is no map then use a new, empty image as the map. I believe this avoids errors
-		// and gives the blank screen.
-		image = (map) ? map.image : new Image();
-		// Arrays to hold the Plotly grid location (x, y) for circles to place on map.
-		const x: number[] = [];
-		const y: number[] = [];
 
-		// const timeInterval = state.graph.queryTimeInterval;
-		// const barDuration = state.graph.barDuration
-		// Make sure there is a map with values so avoid issues.
-		if (map && map.origin && map.opposite) {
-			// The size of the original map loaded into OED.
-			const imageDimensions: Dimensions = {
-				width: image.width,
-				height: image.height
-			};
-			// Determine the dimensions so within the Plotly coordinates on the user map.
-			const imageDimensionNormalized = normalizeImageDimensions(imageDimensions);
-			// This is the origin & opposite from the calibration. It is the lower, left
-			// and upper, right corners of the user map.
-			const origin = map.origin;
-			const opposite = map.opposite;
-			// Get the GPS degrees per unit of Plotly grid for x and y. By knowing the two corners
-			// (or really any two distinct points) you can calculate this by the change in GPS over the
-			// change in x or y which is the map's width & height in this case.
-			const scaleOfMap = calculateScaleFromEndpoints(origin, opposite, imageDimensionNormalized, map.northAngle);
-			// Loop over all selected meters. Maps only work for meters at this time.
-			// The y-axis label depends on the unit which is in selectUnit state.
-			let unitLabel: string = '';
-			// If graphingUnit is -99 then none selected and nothing to graph so label is empty.
-			// This will probably happen when the page is first loaded.
-			if (unitID !== -99) {
-				const selectUnitState = unitDataById[unitID];
-				if (selectUnitState !== undefined) {
-					// Quantity and flow units have different unit labels.
-					// Look up the type of unit if it is for quantity/flow (should not be raw) and decide what to do.
-					// Bar graphics are always quantities.
-					if (selectUnitState.unitRepresent === UnitRepresentType.quantity) {
-						// If it is a quantity unit then that is the unit you are graphing but it is normalized to per day.
-						unitLabel = selectUnitState.identifier + ' / day';
-					} else if (selectUnitState.unitRepresent === UnitRepresentType.flow) {
-						// If it is a flow meter then you need to multiply by time to get the quantity unit then show as per day.
-						// The quantity/time for flow has varying time so label by multiplying by time.
-						// To make sure it is clear, also indicate it is a quantity.
-						// Note this should not be used for raw data.
-						// It might not be usual to take a flow and make it into a quantity so this label is a little different to
-						// catch people's attention. If sites/users don't like OED doing this then we can eliminate flow for these types
-						// of graphics as we are doing for rate.
-						unitLabel = selectUnitState.identifier + ' * time / day ≡ quantity / day';
-					}
+		for (const meterID of selectedMeters) {
+			// Get meter id number.
+			// Get meter GPS value.
+			const gps = meterDataById[meterID].gps;
+			// filter meters with actual gps coordinates.
+			if (gps !== undefined && gps !== null && meterReadings !== undefined) {
+				let meterArea = meterDataById[meterID].area;
+				// we either don't care about area, or we do in which case there needs to be a nonzero area
+				if (!areaNormalization || (meterArea > 0 && meterDataById[meterID].areaUnit != AreaUnitType.none)) {
 					if (areaNormalization) {
-						unitLabel += ' / ' + translate(`AreaUnitType.${selectedAreaUnit}`);
+						// convert the meter area into the proper unit, if needed
+						meterArea *= getAreaUnitConversion(meterDataById[meterID].areaUnit, selectedAreaUnit);
 					}
-				}
-			}
-
-			for (const meterID of selectedMeters) {
-				// Get meter id number.
-				// Get meter GPS value.
-				const gps = meterDataById[meterID].gps;
-				// filter meters with actual gps coordinates.
-				if (gps !== undefined && gps !== null && meterReadings !== undefined) {
-					let meterArea = meterDataById[meterID].area;
-					// we either don't care about area, or we do in which case there needs to be a nonzero area
-					if (!areaNormalization || (meterArea > 0 && meterDataById[meterID].areaUnit != AreaUnitType.none)) {
-						if (areaNormalization) {
-							// convert the meter area into the proper unit, if needed
-							meterArea *= getAreaUnitConversion(meterDataById[meterID].areaUnit, selectedAreaUnit);
-						}
-						// Convert the gps value to the equivalent Plotly grid coordinates on user map.
-						// First, convert from GPS to grid units. Since we are doing a GPS calculation, this happens on the true north map.
-						// It must be on true north map since only there are the GPS axis parallel to the map axis.
-						// To start, calculate the user grid coordinates (Plotly) from the GPS value. This involves calculating
-						// it coordinates on the true north map and then rotating/shifting to the user map.
-						const meterGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, map.northAngle);
-						// Only display items within valid info and within map.
-						if (itemMapInfoOk(meterID, DataType.Meter, map, gps) && itemDisplayableOnMap(imageDimensionNormalized, meterGPSInUserGrid)) {
-							// The x, y value for Plotly to use that are on the user map.
-							x.push(meterGPSInUserGrid.x);
-							y.push(meterGPSInUserGrid.y);
-							// Make sure the bar reading data is available. The timeInterval should be fine (but checked) but the barDuration might have changed
-							// and be fetching. The unit could change from that menu so also need to check.
-							// Get the bar data to use for the map circle.
-							// const readingsData = meterReadings[timeInterval.toString()][barDuration.toISOString()][unitID];
-							const readingsData = meterReadings[meterID];
-							// This protects against there being no readings or that the data is being updated.
-							if (readingsData !== undefined && !meterIsFetching) {
-								// Meter name to include in hover on graph.
-								const label = meterDataById[meterID].identifier;
-								// The usual color for this meter.
-								colors.push(getGraphColor(meterID, DataType.Meter));
-								if (!readingsData) {
-									throw new Error('Unacceptable condition: readingsData.readings is undefined.');
+					// Convert the gps value to the equivalent Plotly grid coordinates on user map.
+					// First, convert from GPS to grid units. Since we are doing a GPS calculation, this happens on the true north map.
+					// It must be on true north map since only there are the GPS axis parallel to the map axis.
+					// To start, calculate the user grid coordinates (Plotly) from the GPS value. This involves calculating
+					// it coordinates on the true north map and then rotating/shifting to the user map.
+					const meterGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, map.northAngle);
+					// Only display items within valid info and within map.
+					if (itemMapInfoOk(meterID, DataType.Meter, map, gps) && itemDisplayableOnMap(imageDimensionNormalized, meterGPSInUserGrid)) {
+						// The x, y value for Plotly to use that are on the user map.
+						x.push(meterGPSInUserGrid.x);
+						y.push(meterGPSInUserGrid.y);
+						// Make sure the bar reading data is available. The timeInterval should be fine (but checked) but the barDuration might have changed
+						// and be fetching. The unit could change from that menu so also need to check.
+						// Get the bar data to use for the map circle.
+						// const readingsData = meterReadings[timeInterval.toString()][barDuration.toISOString()][unitID];
+						const readingsData = meterReadings[meterID];
+						// This protects against there being no readings or that the data is being updated.
+						if (readingsData !== undefined && !meterIsFetching) {
+							// Meter name to include in hover on graph.
+							const label = meterDataById[meterID].identifier;
+							// The usual color for this meter.
+							colors.push(getGraphColor(meterID, DataType.Meter));
+							if (!readingsData) {
+								throw new Error('Unacceptable condition: readingsData.readings is undefined.');
+							}
+							// Use the most recent time reading for the circle on the map.
+							// This has the limitations of the bar value where the last one can include ranges without
+							// data (GitHub issue on this).
+							// TODO: It might be better to do this similarly to compare. (See GitHub issue)
+							const readings = orderBy(readingsData, ['startTimestamp'], ['desc']);
+							const mapReading = readings[0];
+							let timeReading: string;
+							let averagedReading = 0;
+							if (readings.length === 0) {
+								// No data. The next lines causes an issue so set specially.
+								// There may be a better overall fix for no data.
+								timeReading = 'no data to display';
+								size.push(0);
+							} else {
+								// only display a range of dates for the hover text if there is more than one day in the range
+								// Shift to UTC since want database time not local/browser time which is what moment does.
+								timeReading = `${moment.utc(mapReading.startTimestamp).format('ll')}`;
+								if (barDuration.asDays() != 1) {
+									// subtracting one extra day caused by day ending at midnight of the next day.
+									// Going from DB unit timestamp that is UTC so force UTC with moment, as usual.
+									timeReading += ` - ${moment.utc(mapReading.endTimestamp).subtract(1, 'days').format('ll')}`;
 								}
-								// Use the most recent time reading for the circle on the map.
-								// This has the limitations of the bar value where the last one can include ranges without
-								// data (GitHub issue on this).
-								// TODO: It might be better to do this similarly to compare. (See GitHub issue)
-								const readings = orderBy(readingsData, ['startTimestamp'], ['desc']);
-								const mapReading = readings[0];
-								let timeReading: string;
-								let averagedReading = 0;
-								if (readings.length === 0) {
-									// No data. The next lines causes an issue so set specially.
-									// There may be a better overall fix for no data.
-									timeReading = 'no data to display';
-									size.push(0);
-								} else {
-									// only display a range of dates for the hover text if there is more than one day in the range
-									// Shift to UTC since want database time not local/browser time which is what moment does.
-									timeReading = `${moment.utc(mapReading.startTimestamp).format('ll')}`;
-									if (barDuration.asDays() != 1) {
-										// subtracting one extra day caused by day ending at midnight of the next day.
-										// Going from DB unit timestamp that is UTC so force UTC with moment, as usual.
-										timeReading += ` - ${moment.utc(mapReading.endTimestamp).subtract(1, 'days').format('ll')}`;
-									}
-									// The value for the circle is the average daily usage.
-									averagedReading = mapReading.reading / barDuration.asDays();
-									if (areaNormalization) {
-										averagedReading /= meterArea;
-									}
-									// The size is the reading value. It will be scaled later.
-									size.push(averagedReading);
+								// The value for the circle is the average daily usage.
+								averagedReading = mapReading.reading / barDuration.asDays();
+								if (areaNormalization) {
+									averagedReading /= meterArea;
 								}
-								// The hover text.
-								hoverText.push(`<b> ${timeReading} </b> <br> ${label}: ${averagedReading.toPrecision(6)} ${unitLabel}`);
+								// The size is the reading value. It will be scaled later.
+								size.push(averagedReading);
 							}
+							// The hover text.
+							hoverText.push(`<b> ${timeReading} </b> <br> ${label}: ${averagedReading.toPrecision(6)} ${unitLabel}`);
 						}
 					}
 				}
 			}
+		}
 
-			for (const groupID of selectedGroups) {
-				// Get group id number.
-				// Get group GPS value.
-				const gps = groupDataById[groupID].gps;
-				// Filter groups with actual gps coordinates.
-				if (gps !== undefined && gps !== null && groupData !== undefined) {
-					let groupArea = groupDataById[groupID].area;
-					if (!areaNormalization || (groupArea > 0 && groupDataById[groupID].areaUnit != AreaUnitType.none)) {
-						if (areaNormalization) {
-							// convert the meter area into the proper unit, if needed
-							groupArea *= getAreaUnitConversion(groupDataById[groupID].areaUnit, selectedAreaUnit);
-						}
-						// Convert the gps value to the equivalent Plotly grid coordinates on user map.
-						// First, convert from GPS to grid units. Since we are doing a GPS calculation, this happens on the true north map.
-						// It must be on true north map since only there are the GPS axis parallel to the map axis.
-						// To start, calculate the user grid coordinates (Plotly) from the GPS value. This involves calculating
-						// it coordinates on the true north map and then rotating/shifting to the user map.
-						const groupGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, map.northAngle);
-						// Only display items within valid info and within map.
-						if (itemMapInfoOk(groupID, DataType.Group, map, gps) && itemDisplayableOnMap(imageDimensionNormalized, groupGPSInUserGrid)) {
-							// The x, y value for Plotly to use that are on the user map.
-							x.push(groupGPSInUserGrid.x);
-							y.push(groupGPSInUserGrid.y);
-							// Make sure the bar reading data is available. The timeInterval should be fine (but checked) but the barDuration might have changed
-							// and be fetching. The unit could change from that menu so also need to check.
-							// Get the bar data to use for the map circle.
-							const readingsData = groupData[groupID];
-							// This protects against there being no readings or that the data is being updated.
-							if (readingsData && !groupIsFetching) {
-								// Group name to include in hover on graph.
-								const label = groupDataById[groupID].name;
-								// The usual color for this group.
-								colors.push(getGraphColor(groupID, DataType.Group));
-								if (!readingsData) {
-									throw new Error('Unacceptable condition: readingsData.readings is undefined.');
+		for (const groupID of selectedGroups) {
+			// Get group id number.
+			// Get group GPS value.
+			const gps = groupDataById[groupID].gps;
+			// Filter groups with actual gps coordinates.
+			if (gps !== undefined && gps !== null && groupData !== undefined) {
+				let groupArea = groupDataById[groupID].area;
+				if (!areaNormalization || (groupArea > 0 && groupDataById[groupID].areaUnit != AreaUnitType.none)) {
+					if (areaNormalization) {
+						// convert the meter area into the proper unit, if needed
+						groupArea *= getAreaUnitConversion(groupDataById[groupID].areaUnit, selectedAreaUnit);
+					}
+					// Convert the gps value to the equivalent Plotly grid coordinates on user map.
+					// First, convert from GPS to grid units. Since we are doing a GPS calculation, this happens on the true north map.
+					// It must be on true north map since only there are the GPS axis parallel to the map axis.
+					// To start, calculate the user grid coordinates (Plotly) from the GPS value. This involves calculating
+					// it coordinates on the true north map and then rotating/shifting to the user map.
+					const groupGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, map.northAngle);
+					// Only display items within valid info and within map.
+					if (itemMapInfoOk(groupID, DataType.Group, map, gps) && itemDisplayableOnMap(imageDimensionNormalized, groupGPSInUserGrid)) {
+						// The x, y value for Plotly to use that are on the user map.
+						x.push(groupGPSInUserGrid.x);
+						y.push(groupGPSInUserGrid.y);
+						// Make sure the bar reading data is available. The timeInterval should be fine (but checked) but the barDuration might have changed
+						// and be fetching. The unit could change from that menu so also need to check.
+						// Get the bar data to use for the map circle.
+						const readingsData = groupData[groupID];
+						// This protects against there being no readings or that the data is being updated.
+						if (readingsData && !groupIsFetching) {
+							// Group name to include in hover on graph.
+							const label = groupDataById[groupID].name;
+							// The usual color for this group.
+							colors.push(getGraphColor(groupID, DataType.Group));
+							if (!readingsData) {
+								throw new Error('Unacceptable condition: readingsData.readings is undefined.');
+							}
+							// Use the most recent time reading for the circle on the map.
+							// This has the limitations of the bar value where the last one can include ranges without
+							// data (GitHub issue on this).
+							// TODO: It might be better to do this similarly to compare. (See GitHub issue)
+							const readings = orderBy(readingsData, ['startTimestamp'], ['desc']);
+							const mapReading = readings[0];
+							let timeReading: string;
+							let averagedReading = 0;
+							if (readings.length === 0) {
+								// No data. The next lines causes an issue so set specially.
+								// There may be a better overall fix for no data.
+								timeReading = 'no data to display';
+								size.push(0);
+							} else {
+								// only display a range of dates for the hover text if there is more than one day in the range
+								timeReading = `${moment.utc(mapReading.startTimestamp).format('ll')}`;
+								if (barDuration.asDays() != 1) {
+									// subtracting one extra day caused by day ending at midnight of the next day.
+									// Going from DB unit timestamp that is UTC so force UTC with moment, as usual.
+									timeReading += ` - ${moment.utc(mapReading.endTimestamp).subtract(1, 'days').format('ll')}`;
 								}
-								// Use the most recent time reading for the circle on the map.
-								// This has the limitations of the bar value where the last one can include ranges without
-								// data (GitHub issue on this).
-								// TODO: It might be better to do this similarly to compare. (See GitHub issue)
-								const readings = orderBy(readingsData, ['startTimestamp'], ['desc']);
-								const mapReading = readings[0];
-								let timeReading: string;
-								let averagedReading = 0;
-								if (readings.length === 0) {
-									// No data. The next lines causes an issue so set specially.
-									// There may be a better overall fix for no data.
-									timeReading = 'no data to display';
-									size.push(0);
-								} else {
-									// only display a range of dates for the hover text if there is more than one day in the range
-									timeReading = `${moment.utc(mapReading.startTimestamp).format('ll')}`;
-									if (barDuration.asDays() != 1) {
-										// subtracting one extra day caused by day ending at midnight of the next day.
-										// Going from DB unit timestamp that is UTC so force UTC with moment, as usual.
-										timeReading += ` - ${moment.utc(mapReading.endTimestamp).subtract(1, 'days').format('ll')}`;
-									}
-									// The value for the circle is the average daily usage.
-									averagedReading = mapReading.reading / barDuration.asDays();
-									if (areaNormalization) {
-										averagedReading /= groupArea;
-									}
-									// The size is the reading value. It will be scaled later.
-									size.push(averagedReading);
+								// The value for the circle is the average daily usage.
+								averagedReading = mapReading.reading / barDuration.asDays();
+								if (areaNormalization) {
+									averagedReading /= groupArea;
 								}
-								// The hover text.
-								hoverText.push(`<b> ${timeReading} </b> <br> ${label}: ${averagedReading.toPrecision(6)} ${unitLabel}`);
+								// The size is the reading value. It will be scaled later.
+								size.push(averagedReading);
 							}
+							// The hover text.
+							hoverText.push(`<b> ${timeReading} </b> <br> ${label}: ${averagedReading.toPrecision(6)} ${unitLabel}`);
 						}
 					}
 				}
 			}
-			// TODO Using the following seems to have no impact on the code. It has been noticed that this function is called
-			// many times for each change. Someone should look at why that is happening and why some have no items in the arrays.
-			// if (size.length > 0) {
-			// TODO The max circle diameter should come from admin/DB.
-			const maxFeatureFraction = map.circleSize;
-			// Find the smaller of width and height. This is used since it means the circle size will be
-			// scaled to that dimension and smaller relative to the other coordinate.
-			const minDimension = Math.min(imageDimensionNormalized.width, imageDimensionNormalized.height);
-			// The circle size is set to area below. Thus, we need to convert from wanting a max
-			// diameter of minDimension * maxFeatureFraction to an area.
-			const maxCircleSize = Math.PI * Math.pow(minDimension * maxFeatureFraction / 2, 2);
-			// Find the largest circle which is usage.
-			const largestCircleSize = Math.max(...size);
-			// Scale largest circle to the max size and others will be scaled to be smaller.
-			// Not that < 1 => a larger circle.
-			const scaling = largestCircleSize / maxCircleSize;
-
-			// Per https://plotly.com/javascript/reference/scatter/:
-			// The opacity of 0.5 makes it possible to see the map even when there is a circle but the hover
-			// opacity is 1 so it is easy to see.
-			// Set the sizemode to area not diameter.
-			// Set the sizemin so a circle cannot get so small that it might disappear. Unsure the best size.
-			// Set the sizeref to scale each point to the desired area.
-			// Note all sizes are in px so have to estimate the actual size. This could be an issue but maps are currently
-			// a fixed size so not too much of an issue.
-			// Also note that the circle can go off the edge of the map. At some point it would be nice to have a border
-			// around the map to avoid this.
-			const traceOne = {
-				x,
-				y,
-				type: 'scatter',
-				mode: 'markers',
-				marker: {
-					color: colors,
-					opacity: 0.5,
-					size,
-					sizemin: 6,
-					sizeref: scaling,
-					sizemode: 'area'
-				},
-				text: hoverText,
-				hoverinfo: 'text',
-				opacity: 1,
-				showlegend: false
-			};
-			data.push(traceOne);
 		}
+		// TODO Using the following seems to have no impact on the code. It has been noticed that this function is called
+		// many times for each change. Someone should look at why that is happening and why some have no items in the arrays.
+		// if (size.length > 0) {
+		// TODO The max circle diameter should come from admin/DB.
+		const maxFeatureFraction = map.circleSize;
+		// Find the smaller of width and height. This is used since it means the circle size will be
+		// scaled to that dimension and smaller relative to the other coordinate.
+		const minDimension = Math.min(imageDimensionNormalized.width, imageDimensionNormalized.height);
+		// The circle size is set to area below. Thus, we need to convert from wanting a max
+		// diameter of minDimension * maxFeatureFraction to an area.
+		const maxCircleSize = Math.PI * Math.pow(minDimension * maxFeatureFraction / 2, 2);
+		// Find the largest circle which is usage.
+		const largestCircleSize = Math.max(...size);
+		// Scale largest circle to the max size and others will be scaled to be smaller.
+		// Not that < 1 => a larger circle.
+		const scaling = largestCircleSize / maxCircleSize;
+
+		// Per https://plotly.com/javascript/reference/scatter/:
+		// The opacity of 0.5 makes it possible to see the map even when there is a circle but the hover
+		// opacity is 1 so it is easy to see.
+		// Set the sizemode to area not diameter.
+		// Set the sizemin so a circle cannot get so small that it might disappear. Unsure the best size.
+		// Set the sizeref to scale each point to the desired area.
+		// Note all sizes are in px so have to estimate the actual size. This could be an issue but maps are currently
+		// a fixed size so not too much of an issue.
+		// Also note that the circle can go off the edge of the map. At some point it would be nice to have a border
+		// around the map to avoid this.
+		const traceOne = {
+			x,
+			y,
+			type: 'scatter',
+			mode: 'markers',
+			marker: {
+				color: colors,
+				opacity: 0.5,
+				size,
+				sizemin: 6,
+				sizeref: scaling,
+				sizemode: 'area'
+			},
+			text: hoverText,
+			hoverinfo: 'text',
+			opacity: 1,
+			showlegend: false
+		};
+		data.push(traceOne);
 	}
 
 	// set map background image
@@ -357,7 +342,7 @@ export default function MapChartComponent() {
 		},
 		images: [{
 			layer: 'below',
-			source: (image) ? image.src : '',
+			source: map?.mapSource,
 			xref: 'x',
 			yref: 'y',
 			x: 0,
@@ -377,4 +362,4 @@ export default function MapChartComponent() {
 			layout={layout}
 		/>
 	);
-}
+}
\ No newline at end of file
diff --git a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
index 1fcd0463b..1a7cc4d06 100644
--- a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
@@ -4,8 +4,12 @@
 
 import * as React from 'react';
 import { ChangeEvent } from 'react';
-import { FormattedMessage, WrappedComponentProps, injectIntl } from 'react-intl';
-import { logToServer } from '../../redux/actions/logs';
+import { FormattedMessage } from 'react-intl';
+import { updateMapMode, updateMapSource } from '../../redux/actions/map';
+import { logsApi } from '../../redux/api/logApi';
+import { selectMapById } from '../../redux/api/mapsApi';
+import { useTranslate } from '../../redux/componentHooks';
+import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
 import { showErrorNotification } from '../../utils/notifications';
 
@@ -16,169 +20,167 @@ import { showErrorNotification } from '../../utils/notifications';
  * Other configurations could also be selected during this phase;
  */
 
-interface MapInitiateProps {
-	map: MapMetadata
-	updateMapMode(nextMode: CalibrationModeTypes): any;
-	onSourceChange(data: MapMetadata): any;
-}
-
-interface MapInitiateState {
-	filename: string;
-	mapName: string;
-	angle: string;
-}
-
-type MapInitiatePropsWithIntl = MapInitiateProps & WrappedComponentProps;
-
-class MapCalibrationInitiateComponent extends React.Component<MapInitiatePropsWithIntl, MapInitiateState > {
-	private readonly fileInput: any;
-	private notifyBadNumber() {
-		showErrorNotification(`${this.props.intl.formatMessage({id: 'map.bad.number'})}`);
-	}
-	private notifyBadDigit360() {
-		showErrorNotification(`${this.props.intl.formatMessage({id: 'map.bad.digita'})}`);
-	}
-	private notifyBadDigit0() {
-		showErrorNotification(`${this.props.intl.formatMessage({id: 'map.bad.digitb'})}`);
-	}
-	private notifyBadMapLoad() {
-		showErrorNotification(`${this.props.intl.formatMessage({id: 'map.bad.load'})}`);
-	}
-	private notifyBadName() {
-		showErrorNotification(`${this.props.intl.formatMessage({id: 'map.bad.name'})}`);
-	}
-
-	constructor(props: MapInitiatePropsWithIntl) {
-		super(props);
-		this.state = {
-			filename: '',
-			mapName: '',
-			angle: ''
-		};
-		this.fileInput = React.createRef();
-		this.handleInput = this.handleInput.bind(this);
-		this.confirmUpload = this.confirmUpload.bind(this);
-		this.handleNameInput = this.handleNameInput.bind(this);
-		this.handleAngleInput = this.handleAngleInput.bind(this);
-		this.handleAngle = this.handleAngle.bind(this);
-		this.notifyBadNumber = this.notifyBadNumber.bind(this);
-		this.notifyBadDigit360 = this.notifyBadDigit360.bind(this);
-		this.notifyBadDigit0 = this.notifyBadDigit0.bind(this);
-		this.notifyBadMapLoad = this.notifyBadMapLoad.bind(this);
-		this.notifyBadName = this.notifyBadName.bind(this);
-	}
-
-	public render() {
-		return (
-			<form onSubmit={this.confirmUpload}>
-				<label>
-					<FormattedMessage id='map.new.upload' />
-					<br/>
-					<input type='file' ref={this.fileInput} />
-				</label>
-				<br />
-				<label>
-					<FormattedMessage id='map.new.name' />
-					<br/>
-					<textarea id={'text'} cols={50} value={this.state.mapName} onChange={this.handleNameInput}/>
-				</label>
-				<br/>
-				<label>
-					<FormattedMessage id='map.new.angle'/>
-					<br/>
-					<input type='text' value={this.state.angle} onChange={this.handleAngleInput}/>
-				</label>
-				<br/>
-				<FormattedMessage id='map.new.submit'>
-					{placeholder => <input type='submit' value={(placeholder !== null && placeholder !== undefined) ? placeholder.toString() : 'undefined'} />}
-				</FormattedMessage>
-			</form>
-		);
-	}
-
-	private async confirmUpload(event: any) {
-		const bcheck = this.handleAngle(event);
+// interface MapInitiateProps {
+// 	map: MapMetadata
+// 	updateMapMode(nextMode: CalibrationModeTypes): any;
+// 	onSourceChange(data: MapMetadata): any;
+// }
+
+// interface MapInitiateState {
+// 	filename: string;
+// 	mapName: string;
+// 	angle: string;
+// }
+
+// type MapInitiatePropsWithIntl = MapInitiateProps & WrappedComponentProps;
+
+/**
+ * @returns TODO
+ */
+export default function MapCalibrationInitiateComponent() {
+	const translate = useTranslate();
+	const [logToServer] = logsApi.useLogToServerMutation();
+	const dispatch = useAppDispatch();
+	const [mapName, setMapName] = React.useState<string>('');
+	const [angle, setAngle] = React.useState<string>('');
+	const fileRef = React.useRef<HTMLInputElement>(null);
+	const mapData = useAppSelector(state => selectMapById(state, state.maps.selectedMap));
+	// const [mapData] = useAppSelector(state => selectEntityDisplayData(state, {
+	// 	type: EntityType.MAP,
+	// 	id: state.localEdits.mapCalibration.calibratingMap
+	// }));
+
+
+	const notify = (key: 'map.bad.number' | 'map.bad.digita' | 'map.bad.digitb' | 'map.bad.load' | 'map.bad.name') => {
+		showErrorNotification(translate(key));
+	};
+	const confirmUpload = async (event: React.FormEvent<HTMLFormElement>) => {
+		const bcheck = handleAngle(event);
 		if (bcheck) {
-			if (this.fileInput.current.files.length === 0) {
-				this.notifyBadMapLoad();
+			if (!fileRef.current?.files || fileRef.current.files.length === 0) {
+				notify('map.bad.load');
 			}
-			else if (this.state.mapName.trim() === '')	{
-				this.notifyBadName();
+			else if (mapName.trim() === '') {
+				notify('map.bad.name');
 			}
 			else {
-				await this.handleInput(event);
-				this.props.updateMapMode(CalibrationModeTypes.calibrate);
+				await processImgUpload(event);
 			}
 		}
-	}
+	};
 
-	private handleAngle(event: any) {
+	const handleAngle = (event: React.FormEvent<HTMLFormElement>) => {
 		event.preventDefault();
 		const pattern = /^[-+]?\d+(\.\d+)?$/;
-		if (!pattern.test(this.state.angle)) {
-			this.notifyBadNumber();
+		if (!pattern.test(angle)) {
+			notify('map.bad.number');
+
 			return false;
 		}
 		else {
-			if (parseFloat(this.state.angle) > 360) {
-				this.notifyBadDigit360();
+			if (parseFloat(angle) > 360) {
+				notify('map.bad.digita');
 				return false;
 			}
-			else if (parseFloat(this.state.angle) < 0) {
-				this.notifyBadDigit0();
+			else if (parseFloat(angle) < 0) {
+				notify('map.bad.digitb');
 				return false;
 			}
 			else {
 				return true;
 			}
 		}
-	}
+	};
 
-	private async handleInput(event: any) {
+	const processImgUpload = async (event: React.FormEvent<HTMLFormElement>) => {
 		event.preventDefault();
 		try {
-			const imageURL = await this.getDataURL();
-			this.setState({filename: this.fileInput.current.files[0].name});
-			const image = new Image();
-			image.src = imageURL;
-			const source: MapMetadata = {
-				...this.props.map,
-				name: this.state.mapName,
-				filename: this.fileInput.current.files[0].name,
-				image,
-				northAngle: parseFloat(this.state.angle)
-			};
-			await this.props.onSourceChange(source);
+			const mapMetaData = await processImgMapMetaData();
+			dispatch(updateMapSource(mapMetaData));
+			dispatch(updateMapMode(CalibrationModeTypes.calibrate));
 		} catch (err) {
-			logToServer('error', `Error, map source image uploading: ${err}`)();
+			logToServer({ level: 'error', message: `Error, map source image uploading: ${err}` });
 		}
-	}
+	};
 
-	private handleNameInput(event: ChangeEvent<HTMLTextAreaElement>) {
-		this.setState({
-			mapName: event.target.value
-		});
-	}
+	const handleNameInput = (event: ChangeEvent<HTMLTextAreaElement>) => { setMapName(event.target.value); };
 
-	private handleAngleInput(event: React.FormEvent<HTMLInputElement>) {
-		this.setState({
-			angle: event.currentTarget.value
-		});
-	}
+	const handleAngleInput = (event: React.FormEvent<HTMLInputElement>) => { setAngle(event.currentTarget.value); };
 
-	private getDataURL(): Promise<string> {
+	// Takes image from upload, derives dimensions, and generates MapMetaData Object for redux state.
+	// No longer using Image element in Redux state for serializability purposes. Store img.src only.
+	const processImgMapMetaData = (): Promise<MapMetadata> => {
 		return new Promise((resolve, reject) => {
-			const file = this.fileInput.current.files[0];
-			const fileReader = new FileReader();
-			fileReader.onloadend = () => {
-				if (typeof fileReader.result === 'string') {
-					resolve(fileReader.result);
-				}
-			};
-			fileReader.onerror = reject;
-			fileReader.readAsDataURL(file);
+			const file = fileRef.current?.files?.[0];
+			if (!file) {
+				reject('No File Found');
+
+			} else {
+
+				const fileReader = new FileReader();
+				// Fire when loading complete
+				fileReader.onloadend = () => {
+					// When file upload completed, use the result to create an image
+					// use image, to extract image dimensions;
+					if (typeof fileReader.result === 'string') {
+						img.src = fileReader.result;
+					}
+				};
+				fileReader.onerror = reject;
+				// begin file read
+				fileReader.readAsDataURL(file);
+				const img = new Image();
+				// Fire when image load complete.
+				img.onload = () => {
+					// resolve mapMetadata from image.
+					// Not storing image in state, instead extract relevang values
+					resolve({
+						...mapData,
+						imgWidth: img.width,
+						imgHeight: img.height,
+						filename: file.name,
+						name: mapName,
+						northAngle: parseFloat(angle),
+						// Save the image source only
+						// Does not store the Image Obpect in redux for serializability reasons.
+						// use mapSource to recreate images when needed.
+						mapSource: img.src
+					});
+
+				};
+				// file when image error
+				img.onerror = error => {
+					reject(error);
+				};
+
+			}
+
 		});
-	}
-}
+	};
 
-export default injectIntl(MapCalibrationInitiateComponent);
+	return (
+		<form onSubmit={confirmUpload}>
+			<label>
+				<FormattedMessage id='map.new.upload' />
+				<br />
+				<input type='file' ref={fileRef} />
+			</label>
+			<br />
+			<label>
+				<FormattedMessage id='map.new.name' />
+				<br />
+				<textarea id={'text'} cols={50} value={mapName} onChange={handleNameInput} />
+			</label>
+			<br />
+			<label>
+				<FormattedMessage id='map.new.angle' />
+				<br />
+				<input type='text' value={angle} onChange={handleAngleInput} />
+			</label>
+			<br />
+			<FormattedMessage id='map.new.submit'>
+				{placeholder => <input type='submit' value={(placeholder !== null && placeholder !== undefined) ? placeholder.toString() : 'undefined'} />}
+			</FormattedMessage>
+		</form>
+	);
+}
\ 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 4156ef9bc..937f65430 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -2,16 +2,15 @@
  * 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 { parseZone } from 'moment';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { parseZone } from 'moment';
-import '../../styles/card-page.css';
-import EditMapModalComponent from './EditMapModalComponent';
-import { selectMapById } from '../../redux/selectors/maps';
+import { LocaleDataKey } from 'translations/data';
 import { useAppSelector } from '../../redux/reduxHooks';
-import { MapMetadata } from 'types/redux/map';
+import { selectMapById } from '../../redux/selectors/maps';
+import '../../styles/card-page.css';
 import translate from '../../utils/translate';
-import { LocaleDataKey } from 'translations/data';
+import EditMapModalComponent from './EditMapModalComponent';
 interface MapViewProps {
 	mapID: number;
 }
@@ -19,7 +18,7 @@ interface MapViewProps {
 //TODO: Migrate to RTK
 const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 
-	const map: MapMetadata = useAppSelector(selectMapById(mapID));
+	const map = useAppSelector(selectMapById(mapID));
 
 	// Helper function checks map to see if it's calibrated
 	const getCalibrationStatus = () => {
diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index 5d6d8183c..c5a34ef17 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -6,14 +6,13 @@ import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { Link } from 'react-router-dom';
 import { Button } from 'reactstrap';
+import { selectMapIds } from '../../redux/api/mapsApi';
 import TooltipHelpComponent from '../../components/TooltipHelpComponent';
-import MapViewComponent from './MapViewComponent';
-import TooltipMarkerComponent from '../TooltipMarkerComponent';
-import '../../styles/card-page.css';
-import { fetchMapsDetails, setNewMap } from '../../redux/actions/map';
+import { setNewMap } from '../../redux/actions/map';
 import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
-import { selectMaps } from '../../redux/selectors/maps';
-import { AppDispatch } from 'store';
+import '../../styles/card-page.css';
+import TooltipMarkerComponent from '../TooltipMarkerComponent';
+import MapViewComponent from './MapViewComponent';
 
 /**
  * Defines the maps page card view
@@ -21,13 +20,9 @@ import { AppDispatch } from 'store';
  */
 // TODO: Migrate to RTK
 export default function MapsDetailComponent() {
-	const dispatch: AppDispatch = useAppDispatch();
+	const dispatch = useAppDispatch();
 	// Load map IDs from state and store in number array
-	const maps: number[] = useAppSelector(selectMaps);
-	React.useEffect(() => {
-		// Load maps from state on component mount (componentDidMount)
-		dispatch(fetchMapsDetails());
-	}, []);
+	const maps = useAppSelector(state => selectMapIds(state));
 
 	return (
 		<div className='flexGrowOne'>
diff --git a/src/client/app/containers/MapChartContainer.ts b/src/client/app/containers/MapChartContainer.ts
index 45a8049c6..28320c022 100644
--- a/src/client/app/containers/MapChartContainer.ts
+++ b/src/client/app/containers/MapChartContainer.ts
@@ -31,7 +31,6 @@ function mapStateToProps(state: State) {
 	// Holds Plotly mapping info.
 	const data = [];
 	// Holds the image to use.
-	let image;
 	if (state.maps.selectedMap !== 0) {
 		const mapID = state.maps.selectedMap;
 		if (state.maps.byMapID[mapID]) {
@@ -48,7 +47,6 @@ function mapStateToProps(state: State) {
 		const colors: string[] = [];
 		// If there is no map then use a new, empty image as the map. I believe this avoids errors
 		// and gives the blank screen.
-		image = (map) ? map.image : new Image();
 		// Arrays to hold the Plotly grid location (x, y) for circles to place on map.
 		const x: number[] = [];
 		const y: number[] = [];
@@ -60,8 +58,8 @@ function mapStateToProps(state: State) {
 		if (map && map.origin && map.opposite) {
 			// The size of the original map loaded into OED.
 			const imageDimensions: Dimensions = {
-				width: image.width,
-				height: image.height
+				width: map.imgWidth,
+				height: map.imgHeight
 			};
 			// Determine the dimensions so within the Plotly coordinates on the user map.
 			const imageDimensionNormalized = normalizeImageDimensions(imageDimensions);
@@ -325,7 +323,7 @@ function mapStateToProps(state: State) {
 		},
 		images: [{
 			layer: 'below',
-			source: (image) ? image.src : '',
+			source: map?.mapSource,
 			xref: 'x',
 			yref: 'y',
 			x: 0,
diff --git a/src/client/app/containers/maps/MapCalibrationChartDisplayContainer.ts b/src/client/app/containers/maps/MapCalibrationChartDisplayContainer.ts
index 6dfcd905a..64208c7fe 100644
--- a/src/client/app/containers/maps/MapCalibrationChartDisplayContainer.ts
+++ b/src/client/app/containers/maps/MapCalibrationChartDisplayContainer.ts
@@ -28,8 +28,8 @@ function mapStateToProps(state: State) {
 		}
 	}
 	const imageDimensions: Dimensions = normalizeImageDimensions({
-		width: map.image.width,
-		height: map.image.height
+		width: map.imgWidth,
+		height: map.imgHeight
 	});
 	const settings = state.maps.calibrationSettings;
 	const backgroundTrace = createBackgroundTrace(imageDimensions, settings);
@@ -49,7 +49,7 @@ function mapStateToProps(state: State) {
 	};
 	const data = [backgroundTrace, dataPointTrace];
 
-	const imageSource = map.image.src;
+	const imageSource = map.mapSource;
 
 	// for a detailed description of layout attributes: https://plotly.com/javascript/reference/#layout
 	const layout: any = {
diff --git a/src/client/app/redux/actions/map.ts b/src/client/app/redux/actions/map.ts
index 5af42df04..688183839 100644
--- a/src/client/app/redux/actions/map.ts
+++ b/src/client/app/redux/actions/map.ts
@@ -80,7 +80,7 @@ export function setCalibration(mode: CalibrationModeTypes, mapID: number): Thunk
 	};
 }
 
-function prepareCalibration(mode: CalibrationModeTypes, mapID: number): t.SetCalibrationAction {
+export function prepareCalibration(mode: CalibrationModeTypes, mapID: number): t.SetCalibrationAction {
 	return { type: ActionType.SetCalibration, mode, mapID };
 }
 
@@ -190,8 +190,8 @@ function prepareDataToCalculation(state: State): CalibrationResult {
 	const mapID = state.maps.calibratingMap;
 	const mp = state.maps.editedMaps[mapID];
 	const imageDimensions: Dimensions = {
-		width: mp.image.width,
-		height: mp.image.height
+		width: mp.imgWidth,
+		height: mp.imgHeight
 	};
 	// Since mp is defined above, calibrationSet is defined.
 	/* eslint-disable @typescript-eslint/no-non-null-assertion */
@@ -240,11 +240,11 @@ export function submitNewMap(): Thunk {
 		try {
 			const acceptableMap: MapData = {
 				...map,
-				mapSource: map.image.src,
+				mapSource: map.mapSource,
 				displayable: false,
 				modifiedDate: moment().toISOString(),
-				origin: (map.calibrationResult) ? map.calibrationResult.origin : undefined,
-				opposite: (map.calibrationResult) ? map.calibrationResult.opposite : undefined
+				origin: map.calibrationResult?.origin,
+				opposite: map.calibrationResult?.opposite
 			};
 			await mapsApi.create(acceptableMap);
 			if (map.calibrationResult) {
@@ -274,15 +274,15 @@ export function submitEditedMap(mapID: number): Thunk {
 		try {
 			const acceptableMap: MapData = {
 				...map,
-				mapSource: map.image.src,
+				mapSource: map.mapSource,
 				// As in other place, this take the time, in this case the current time, grabs the
 				// date and time without timezone and then set it to UTC. This allows the software
 				// to recreate it with the same date/time as it is on this web browser when it is
 				// displayed later (without the timezone shown).
 				// It might be better to use the server time but this is good enough.
 				modifiedDate: moment().format('YYYY-MM-DD HH:mm:ss') + '+00:00',
-				origin: (map.calibrationResult) ? map.calibrationResult.origin : map.origin,
-				opposite: (map.calibrationResult) ? map.calibrationResult.opposite : map.opposite,
+				origin: map.calibrationResult?.origin,
+				opposite: map.calibrationResult?.opposite,
 				circleSize: map.circleSize
 			};
 			await mapsApi.edit(acceptableMap);
diff --git a/src/client/app/redux/api/baseApi.ts b/src/client/app/redux/api/baseApi.ts
index 58d58b5e1..3750dfe3f 100644
--- a/src/client/app/redux/api/baseApi.ts
+++ b/src/client/app/redux/api/baseApi.ts
@@ -27,6 +27,7 @@ export const baseApi = createApi({
 	// The types of tags that any injected endpoint may, provide, or invalidate.
 	// Must be defined here, for use in injected endpoints
 	tagTypes: [
+		'MapsData',
 		'MeterData',
 		'GroupData',
 		'GroupChildrenData',
diff --git a/src/client/app/redux/api/logApi.ts b/src/client/app/redux/api/logApi.ts
new file mode 100644
index 000000000..f601e469a
--- /dev/null
+++ b/src/client/app/redux/api/logApi.ts
@@ -0,0 +1,14 @@
+import { LogData } from 'types/redux/logs';
+import { baseApi } from './baseApi';
+
+export const logsApi = baseApi.injectEndpoints({
+	endpoints: builder => ({
+		logToServer: builder.mutation<void, LogData & { level: 'info' | 'warn' | 'error' }>({
+			query: ({ level, ...logData }) => ({
+				url: `api/logs/${level}`,
+				method: 'POST',
+				body: logData
+			})
+		})
+	})
+});
\ No newline at end of file
diff --git a/src/client/app/redux/api/mapsApi.ts b/src/client/app/redux/api/mapsApi.ts
new file mode 100644
index 000000000..9f1afcd21
--- /dev/null
+++ b/src/client/app/redux/api/mapsApi.ts
@@ -0,0 +1,147 @@
+import { createEntityAdapter, EntityState } from '@reduxjs/toolkit';
+import { pick } from 'lodash';
+import * as moment from 'moment';
+import { RootState } from '../../store';
+import { MapData, MapMetadata } from '../../types/redux/map';
+import { showErrorNotification, showSuccessNotification } from '../../utils/notifications';
+import translate from '../../utils/translate';
+import { baseApi } from './baseApi';
+// import { logToServer } from '../../redux/actions/logs';
+
+// Helper function to extract image dimensions from the mapSource
+const mapResponseImgSrcToDimensions = (response: MapMetadata[]) => Promise.all(
+	response.map(mapData =>
+		new Promise<MapMetadata>(resolve => {
+			const img = new Image();
+			img.onload = () => {
+				resolve({ ...mapData, imgWidth: img.width, imgHeight: img.height });
+			};
+			img.onerror = () => {
+				// TODO default to falsy value, 0, on error.
+				resolve({ ...mapData, imgWidth: 0, imgHeight: 0 });
+			};
+			img.src = mapData.mapSource;
+		})
+	)
+);
+
+
+export const mapsAdapter = createEntityAdapter<MapMetadata>({
+	sortComparer: (meterA, meterB) => meterA.name?.localeCompare(meterB.name, undefined, { sensitivity: 'accent' })
+
+});
+export const mapsInitialState = mapsAdapter.getInitialState();
+export type MapDataState = EntityState<MapMetadata, number>;
+
+
+export const mapsApi = baseApi.injectEndpoints({
+	endpoints: build => ({
+		getMapDetails: build.query<MapDataState, void>({
+			query: () => 'api/maps/',
+			transformResponse: async (response: MapMetadata[]) => {
+				// To avoid saving unserializable image(s) in state, extract the image dimensions and only store the mapSource (string)
+				return mapsAdapter.setAll(mapsInitialState, await mapResponseImgSrcToDimensions(response));
+			},
+			providesTags: ['MapsData']
+		}),
+		getMapByName: build.query<MapData, string>({
+			query: name => ({
+				url: 'api/maps/getByName',
+				params: { name }
+			})
+		}),
+		createMap: build.mutation<void, MapMetadata>({
+			query: map => ({
+				url: 'api/maps/create',
+				method: 'POST',
+				body: {
+					//  send only what backend expects.
+					...pick(map, ['name', 'note', 'filename', 'mapSource', 'northAngle', 'circleSize']),
+					modifiedDate: moment().toISOString(),
+					origin: (map.calibrationResult) ? map.calibrationResult.origin : undefined,
+					opposite: (map.calibrationResult) ? map.calibrationResult.opposite : undefined
+				}
+			}),
+			onQueryStarted: (map, api) => {
+				api.queryFulfilled
+					// TODO Serverlogs migrate to rtk Query to drop axios?
+					// Requires dispatch so inconvenient
+					.then(() => {
+						if (map.calibrationResult) {
+							// logToServer('info', 'New calibrated map uploaded to database');
+							showSuccessNotification(translate('upload.new.map.with.calibration'));
+						} else {
+							// logToServer('info', 'New map uploaded to database(without calibration)');
+							showSuccessNotification(translate('upload.new.map.without.calibration'));
+						}
+						// TODO DELETE ME
+						// api.dispatch(localEditsSlice.actions.removeOneEdit({ type: EntityType.MAP, id: map.id }));
+					}).catch(() => {
+						showErrorNotification(translate('failed.to.edit.map'));
+					});
+			},
+			invalidatesTags: ['MapsData']
+		}),
+		editMap: build.mutation<MapData, MapMetadata>({
+			query: map => ({
+				url: 'api/maps/edit',
+				method: 'POST',
+				body: {
+					//  send only what backend expects.
+					...pick(map, ['id', 'name', 'displayable', 'note', 'filename', 'mapSource', 'northAngle', 'circleSize']),
+					// As in other place, this take the time, in this case the current time, grabs the
+					// date and time without timezone and then set it to UTC. This allows the software
+					// to recreate it with the same date/time as it is on this web browser when it is
+					// displayed later (without the timezone shown).
+					// It might be better to use the server time but this is good enough.
+					modifiedDate: moment().format('YYYY-MM-DD HH:mm:ss') + '+00:00',
+					origin: map.calibrationResult ? map.calibrationResult.origin : map.origin,
+					opposite: map.calibrationResult ? map.calibrationResult.opposite : map.opposite
+				}
+			}),
+			onQueryStarted: (map, api) => {
+				api.queryFulfilled
+					// TODO Serverlogs migrate to rtk Query to drop axios?
+					// Requires dispatch so inconvenient
+					.then(() => {
+						if (map.calibrationResult) {
+							// logToServer('info', 'Edited map uploaded to database(newly calibrated)');
+							showSuccessNotification(translate('updated.map.with.calibration'));
+						} else if (map.origin && map.opposite) {
+							// logToServer('info', 'Edited map uploaded to database(calibration not updated)');
+							showSuccessNotification(translate('updated.map.without.new.calibration'));
+						} else {
+							// logToServer('info', 'Edited map uploaded to database(without calibration)');
+							showSuccessNotification(translate('updated.map.without.calibration'));
+						}
+						// Cleanup LocalEditsSLice
+						// TODO Centralize localEditCleanup. Should be same as others.
+						// api.dispatch(localEditsSlice.actions.removeOneEdit({ type: EntityType.MAP, id: map.id }));
+					}).catch(() => {
+						showErrorNotification(translate('failed.to.edit.map'));
+					});
+			},
+			invalidatesTags: ['MapsData']
+		}),
+		deleteMap: build.mutation<void, number>({
+			query: id => ({
+				url: 'api/maps/delete',
+				method: 'POST',
+				body: { id }
+			})
+		}),
+		getMapById: build.query<MapData, number>({
+			query: id => `api/maps/${id}`
+		})
+	})
+});
+
+const selectMapDataResult = mapsApi.endpoints.getMapDetails.select();
+export const selectMapApiData = (state: RootState) => selectMapDataResult(state).data ?? mapsInitialState;
+export const {
+	selectAll: selectAllMaps,
+	selectById: selectMapById,
+	selectIds: selectMapIds,
+	selectEntities: selectMapDataById,
+	selectTotal: selectTotalMaps
+} = mapsAdapter.getSelectors(selectMapApiData);
diff --git a/src/client/app/redux/devToolConfig.ts b/src/client/app/redux/devToolConfig.ts
new file mode 100644
index 000000000..70d5a31b0
--- /dev/null
+++ b/src/client/app/redux/devToolConfig.ts
@@ -0,0 +1,21 @@
+import { DevToolsEnhancerOptions } from '@reduxjs/toolkit';
+import { mapsAdapter, mapsApi, mapsInitialState } from './api/mapsApi';
+
+export const devToolsConfig: DevToolsEnhancerOptions = {
+	actionSanitizer: action => {
+		switch (true) {
+			// Sanitize MapSource so it does not bloat the devtools with a longBlobs.
+			case mapsApi.endpoints.getMapDetails.matchFulfilled(action): {
+				// omitMapSource from metaData
+				const sanitizedMapMetadata = Object.values(action.payload.entities)
+					.map(data => ({ ...data, mapSource: 'Omitted From Devtools Serialization' }));
+
+				// sanitized devtool Action
+				return { ...action, payload: { ...mapsAdapter.setAll(mapsInitialState, sanitizedMapMetadata) } };
+			}
+			default:
+				return action;
+		}
+
+	}
+};
\ No newline at end of file
diff --git a/src/client/app/redux/reducers/maps.ts b/src/client/app/redux/reducers/maps.ts
index b43a0e789..3cd683941 100644
--- a/src/client/app/redux/reducers/maps.ts
+++ b/src/client/app/redux/reducers/maps.ts
@@ -123,7 +123,8 @@ export default function maps(state = defaultState, action: MapsAction) {
 			const mapToReset = { ...editedMaps[action.mapID] };
 			delete mapToReset.currentPoint;
 			delete mapToReset.calibrationResult;
-			delete mapToReset.calibrationSet;
+			// TODO FIX mapsDataFetch
+			// delete mapToReset.calibrationSet;
 			return {
 				...state,
 				editedMaps: {
diff --git a/src/client/app/redux/selectors/uiSelectors.ts b/src/client/app/redux/selectors/uiSelectors.ts
index 0eeff547a..b80edd585 100644
--- a/src/client/app/redux/selectors/uiSelectors.ts
+++ b/src/client/app/redux/selectors/uiSelectors.ts
@@ -147,11 +147,10 @@ export const selectChartTypeCompatibility = createAppSelector(
 		if (chartToRender === ChartTypes.map && mapState.selectedMap !== 0) {
 			const mp = mapState.byMapID[mapState.selectedMap];
 			// filter meters;
-			const image = mp.image;
 			// The size of the original map loaded into OED.
 			const imageDimensions: Dimensions = {
-				width: image.width,
-				height: image.height
+				width: mp.imgWidth,
+				height: mp.imgHeight
 			};
 			// Determine the dimensions so within the Plotly coordinates on the user map.
 			const imageDimensionNormalized = normalizeImageDimensions(imageDimensions);
diff --git a/src/client/app/redux/slices/appStateSlice.ts b/src/client/app/redux/slices/appStateSlice.ts
index edcc479e9..8099e5def 100644
--- a/src/client/app/redux/slices/appStateSlice.ts
+++ b/src/client/app/redux/slices/appStateSlice.ts
@@ -3,6 +3,8 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import * as moment from 'moment';
+import { processGraphLink } from '../../redux/actions/extraActions';
+import { mapsApi } from '../../redux/api/mapsApi';
 import { LanguageTypes } from '../../types/redux/i18n';
 import { deleteToken, getToken, hasToken } from '../../utils/token';
 import { fetchMapsDetails } from '../actions/map';
@@ -16,7 +18,6 @@ import { userApi } from '../api/userApi';
 import { versionApi } from '../api/versionApi';
 import { createThunkSlice } from '../sliceCreators';
 import { currentUserSlice } from './currentUserSlice';
-import { processGraphLink } from '../../redux/actions/extraActions';
 
 export interface AppState {
 	initComplete: boolean;
@@ -96,6 +97,8 @@ export const appStateSlice = createThunkSlice({
 				// Request meter/group/details post-auth
 				dispatch(metersApi.endpoints.getMeters.initiate());
 				dispatch(groupsApi.endpoints.getGroups.initiate());
+				dispatch(mapsApi.endpoints.getMapDetails.initiate());
+
 			},
 			{
 				settled: state => {
diff --git a/src/client/app/store.ts b/src/client/app/store.ts
index 5d227d250..3a1cce927 100644
--- a/src/client/app/store.ts
+++ b/src/client/app/store.ts
@@ -3,20 +3,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import { configureStore } from '@reduxjs/toolkit';
-import { rootReducer } from './redux/rootReducer';
+import { setGlobalDevModeChecks } from 'reselect';
 import { baseApi } from './redux/api/baseApi';
-import { Dispatch } from './types/redux/actions';
+import { devToolsConfig } from './redux/devToolConfig';
 import { listenerMiddleware } from './redux/listenerMiddleware';
-import { setGlobalDevModeChecks } from 'reselect';
+import { rootReducer } from './redux/rootReducer';
+import { Dispatch } from './types/redux/actions';
 
 export const store = configureStore({
 	reducer: rootReducer,
 	middleware: getDefaultMiddleware => getDefaultMiddleware({
 		immutableCheck: false,
 		serializableCheck: false
-	})
-		.prepend(listenerMiddleware.middleware)
-		.concat(baseApi.middleware)
+	}).prepend(listenerMiddleware.middleware)
+		.concat(baseApi.middleware),
+	devTools: devToolsConfig
+
 });
 
 // stability check for ALL createSelector instances.
diff --git a/src/client/app/types/redux/map.ts b/src/client/app/types/redux/map.ts
index d1700c035..5a07976bb 100644
--- a/src/client/app/types/redux/map.ts
+++ b/src/client/app/types/redux/map.ts
@@ -145,6 +145,7 @@ export interface MapData {
  *  @param name
  *  @param displayable
  */
+
 export interface MapMetadata {
 	id: number;
 	name: string;
@@ -154,13 +155,16 @@ export interface MapMetadata {
 	modifiedDate: string;
 	origin?: GPSPoint;
 	opposite?: GPSPoint;
-	image: HTMLImageElement;
+	mapSource: string;
+	northAngle: number;
+	circleSize: number;
+	// image: HTMLImageElement;
+	imgHeight: number;
+	imgWidth: number;
 	calibrationMode?: CalibrationModeTypes;
 	currentPoint?: CalibratedPoint;
-	calibrationSet?: CalibratedPoint[];
+	calibrationSet: CalibratedPoint[];
 	calibrationResult?: CalibrationResult;
-	northAngle: number;
-	circleSize: number;
 }
 
 /**

From 45d0db6253b4e1b2e974f1ba1833392927f361cf Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Sun, 11 Aug 2024 08:57:16 -0700
Subject: [PATCH 38/50] Remove Maps from RootState - maps no longer a state
 property, - old State type deleted, only use RootState now - admin pages
 untested, graph seems okay. - needs testing.

---
 .../app/components/ChartSelectComponent.tsx   |  17 +-
 .../app/components/MapChartComponent.tsx      |   3 +-
 .../components/MapChartSelectComponent.tsx    |  28 ++-
 src/client/app/components/RouteComponent.tsx  |  10 +-
 .../MapCalibrationChartDisplayComponent.tsx   | 142 ++++++++++++++
 .../maps/MapCalibrationComponent.tsx          |  36 ++++
 .../MapCalibrationInfoDisplayComponent.tsx    | 178 +++++++++---------
 .../maps/MapCalibrationInitiateComponent.tsx  |   8 +-
 .../app/components/maps/MapViewComponent.tsx  |   4 +-
 src/client/app/redux/actions/map.ts           |  16 +-
 src/client/app/redux/api/baseApi.ts           |   4 -
 src/client/app/redux/api/mapsApi.ts           |  10 +-
 src/client/app/redux/rootReducer.ts           |   8 +-
 .../redux/selectors/chartQuerySelectors.ts    |  16 +-
 src/client/app/redux/selectors/maps.ts        |  19 --
 src/client/app/redux/selectors/uiSelectors.ts |  38 ++--
 src/client/app/redux/slices/graphSlice.ts     |  22 ++-
 .../app/redux/slices/localEditsSlice.ts       | 122 ++++++++++++
 src/client/app/types/redux/graph.ts           |   1 +
 src/client/app/types/redux/state.ts           |  37 ----
 src/client/app/utils/calibration.ts           |  15 +-
 21 files changed, 493 insertions(+), 241 deletions(-)
 create mode 100644 src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
 delete mode 100644 src/client/app/redux/selectors/maps.ts
 create mode 100644 src/client/app/redux/slices/localEditsSlice.ts
 delete mode 100644 src/client/app/types/redux/state.ts

diff --git a/src/client/app/components/ChartSelectComponent.tsx b/src/client/app/components/ChartSelectComponent.tsx
index 9625734ae..296d37048 100644
--- a/src/client/app/components/ChartSelectComponent.tsx
+++ b/src/client/app/components/ChartSelectComponent.tsx
@@ -2,17 +2,13 @@
  * 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 { sortBy, values } from 'lodash';
 import * as React from 'react';
 import { useState } from 'react';
 import { FormattedMessage } from 'react-intl';
-import { useSelector } from 'react-redux';
 import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
 import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
 import { graphSlice, selectChartToRender } from '../redux/slices/graphSlice';
-import { SelectOption } from '../types/items';
 import { ChartTypes } from '../types/redux/graph';
-import { State } from '../types/redux/state';
 import translate from '../utils/translate';
 import TooltipMarkerComponent from './TooltipMarkerComponent';
 
@@ -24,10 +20,10 @@ export default function ChartSelectComponent() {
 	const currentChartToRender = useAppSelector(selectChartToRender);
 	const dispatch = useAppDispatch();
 	const [expand, setExpand] = useState(false);
-	const mapsById = useSelector((state: State) => state.maps.byMapID);
-	const sortedMaps = sortBy(values(mapsById).map(map => (
-		{ value: map.id, label: map.name, isDisabled: !(map.origin && map.opposite) } as SelectOption
-	)), 'label');
+	// const mapsById = useAppSelector(selectMapDataById);
+	// const sortedMaps = sortBy(values(mapsById).map(map => (
+	// 	{ value: map.id, label: map.name, isDisabled: !(map.origin && map.opposite) } as SelectOption
+	// )), 'label');
 
 	return (
 		<>
@@ -52,11 +48,6 @@ export default function ChartSelectComponent() {
 									key={chartType}
 									onClick={() => {
 										dispatch(graphSlice.actions.changeChartToRender(chartType));
-										if (chartType === ChartTypes.map && Object.keys(sortedMaps).length === 1) {
-											// If there is only one map, selectedMap is the id of the only map. ie; display map automatically if only 1 map
-											dispatch({ type: 'UPDATE_SELECTED_MAPS', mapID: sortedMaps[0].value });
-
-										}
 									}}
 								>
 									{translate(`${chartType}`)}
diff --git a/src/client/app/components/MapChartComponent.tsx b/src/client/app/components/MapChartComponent.tsx
index 7ce9d34b8..8d23dd1bd 100644
--- a/src/client/app/components/MapChartComponent.tsx
+++ b/src/client/app/components/MapChartComponent.tsx
@@ -8,6 +8,7 @@ import * as React from 'react';
 import {
 	selectAreaUnit, selectBarWidthDays,
 	selectGraphAreaNormalization, selectSelectedGroups,
+	selectSelectedMap,
 	selectSelectedMeters, selectSelectedUnit
 } from '../redux/slices/graphSlice';
 import { selectGroupDataById } from '../redux/api/groupsApi';
@@ -56,7 +57,7 @@ export default function MapChartComponent() {
 
 	// RTK Types Disagree with maps ts types so, use old until migration completer for maps.
 	// This is also an issue when trying to refactor maps reducer into slice.
-	const selectedMap = useAppSelector(state => state.maps.selectedMap);
+	const selectedMap = useAppSelector(selectSelectedMap);
 	const map = useAppSelector(state => selectMapById(state, selectedMap));
 
 	if (meterIsFetching || groupIsFetching) {
diff --git a/src/client/app/components/MapChartSelectComponent.tsx b/src/client/app/components/MapChartSelectComponent.tsx
index bea22af7f..4504c1847 100644
--- a/src/client/app/components/MapChartSelectComponent.tsx
+++ b/src/client/app/components/MapChartSelectComponent.tsx
@@ -3,11 +3,10 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import * as React from 'react';
-import { sortBy, values } from 'lodash';
-import { useDispatch, useSelector } from 'react-redux';
-import { State } from '../types/redux/state';
-import { SelectOption } from '../types/items';
 import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
+import { selectMapById, selectMapSelectOptions } from '../redux/api/mapsApi';
+import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
+import { selectSelectedMap, updateSelectedMaps } from '../redux/slices/graphSlice';
 import SingleSelectComponent from './SingleSelectComponent';
 import TooltipMarkerComponent from './TooltipMarkerComponent';
 
@@ -24,19 +23,20 @@ export default function MapChartSelectComponent() {
 		margin: 0
 	};
 	const messages = defineMessages({
-		selectMap: {id: 'select.map'}
+		selectMap: { id: 'select.map' }
 	});
 
 	// TODO When this is converted to RTK then should use useAppDispatch().
 	//Utilizes useDispatch and useSelector hooks
-	const dispatch = useDispatch();
-	const sortedMaps = sortBy(values(useSelector((state: State) => state.maps.byMapID)).map(map => (
-		{ value: map.id, label: map.name, isDisabled: !(map.origin && map.opposite) } as SelectOption
-	)), 'label');
+	const dispatch = useAppDispatch();
+
+	const sortedMaps = useAppSelector(selectMapSelectOptions);
+	const selectedMapData = useAppSelector(state => selectMapById(state, selectSelectedMap(state)));
+
 
 	const selectedMap = {
-		label: useSelector((state: State) => state.maps.byMapID[state.maps.selectedMap] ? state.maps.byMapID[state.maps.selectedMap].name : ''),
-		value: useSelector((state: State) => state.maps.selectedMap)
+		label: selectedMapData.name,
+		value: selectedMapData.id
 	};
 
 	//useIntl instead of injectIntl and WrappedComponentProps
@@ -46,16 +46,14 @@ export default function MapChartSelectComponent() {
 		<div>
 			<p style={labelStyle}>
 				<FormattedMessage id='maps' />:
-				<TooltipMarkerComponent page='home' helpTextId='help.home.select.maps'/>
+				<TooltipMarkerComponent page='home' helpTextId='help.home.select.maps' />
 			</p>
 			<div style={divBottomPadding}>
 				<SingleSelectComponent
 					options={sortedMaps}
 					selectedOption={(selectedMap.value === 0) ? undefined : selectedMap}
 					placeholder={intl.formatMessage(messages.selectMap)}
-					onValueChange={selected => dispatch({type: 'UPDATE_SELECTED_MAPS', mapID: selected.value})}
-					//When we specify stuff in actions files, we also specify other variables, in this case mapID.
-					//This is where we specify values instead of triggering the action by itself.
+					onValueChange={selected => dispatch(updateSelectedMaps(selected.value))}
 				/>
 			</div>
 		</div>
diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx
index 1f9ec4e13..827645741 100644
--- a/src/client/app/components/RouteComponent.tsx
+++ b/src/client/app/components/RouteComponent.tsx
@@ -5,9 +5,8 @@ import * as React from 'react';
 import { IntlProvider } from 'react-intl';
 import { RouterProvider, createBrowserRouter } from 'react-router-dom';
 import UploadCSVContainer from '../containers/csv/UploadCSVContainer';
-import MapCalibrationContainer from '../containers/maps/MapCalibrationContainer';
-import MapsDetailComponent from './maps/MapsDetailComponent';
 import { useAppSelector } from '../redux/reduxHooks';
+import { selectSelectedLanguage } from '../redux/slices/appStateSlice';
 import LocaleTranslationData from '../translations/data';
 import { UserRole } from '../types/items';
 import AppLayout from './AppLayout';
@@ -17,14 +16,15 @@ import AdminComponent from './admin/AdminComponent';
 import UsersDetailComponent from './admin/users/UsersDetailComponent';
 import ConversionsDetailComponent from './conversion/ConversionsDetailComponent';
 import GroupsDetailComponent from './groups/GroupsDetailComponent';
+import { MapCalibrationComponent2 } from './maps/MapCalibrationComponent';
+import MapsDetailComponent from './maps/MapsDetailComponent';
 import MetersDetailComponent from './meters/MetersDetailComponent';
 import AdminOutlet from './router/AdminOutlet';
+import ErrorComponent from './router/ErrorComponent';
 import { GraphLink } from './router/GraphLinkComponent';
 import NotFound from './router/NotFoundOutlet';
 import RoleOutlet from './router/RoleOutlet';
 import UnitsDetailComponent from './unit/UnitsDetailComponent';
-import ErrorComponent from './router/ErrorComponent';
-import { selectSelectedLanguage } from '../redux/slices/appStateSlice';
 
 /**
  * @returns the router component Responsible for client side routing.
@@ -54,7 +54,7 @@ const router = createBrowserRouter([
 				element: <AdminOutlet />,
 				children: [
 					{ path: 'admin', element: <AdminComponent /> },
-					{ path: 'calibration', element: <MapCalibrationContainer /> },
+					{ path: 'calibration', element: <MapCalibrationComponent2 /> },
 					{ path: 'maps', element: <MapsDetailComponent /> },
 					{ path: 'units', element: <UnitsDetailComponent /> },
 					{ path: 'conversions', element: <ConversionsDetailComponent /> },
diff --git a/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
new file mode 100644
index 000000000..a2f0e071a
--- /dev/null
+++ b/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
@@ -0,0 +1,142 @@
+/* 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/. */
+
+import { PlotData, PlotMouseEvent } from 'plotly.js';
+import * as React from 'react';
+import Plot from 'react-plotly.js';
+import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
+import { selectSelectedLanguage } from '../../redux/slices/appStateSlice';
+import { localEditsSlice } from '../../redux/slices/localEditsSlice';
+import Locales from '../../types/locales';
+import { CalibrationSettings } from '../../types/redux/map';
+import { Dimensions, normalizeImageDimensions } from '../../utils/calibration';
+import { mapsAdapter } from '../../redux/api/mapsApi';
+
+/**
+ * @returns TODO DO ME
+ */
+export default function MapCalibrationChartDisplayContainer() {
+	const dispatch = useAppDispatch();
+	const x: number[] = [];
+	const y: number[] = [];
+	const texts: string[] = [];
+	const currentLanguange = useAppSelector(selectSelectedLanguage);
+	const map = useAppSelector(state => mapsAdapter.getSelectors().selectById(state.localEdits.mapEdits, state.localEdits.calibratingMap));
+
+	const settings = useAppSelector(state => state.localEdits.calibrationSettings);
+	const points = map.calibrationSet;
+	if (points) {
+		for (const point of points) {
+			x.push(point.cartesian.x);
+			y.push(point.cartesian.y);
+			texts.push(`latitude: ${point.gps.latitude}, longitude: ${point.gps.longitude}`);
+		}
+	}
+	const imageDimensions: Dimensions = normalizeImageDimensions({
+		width: map.imgWidth,
+		height: map.imgHeight
+	});
+	const backgroundTrace = createBackgroundTrace(imageDimensions, settings);
+	const dataPointTrace = {
+		x,
+		y,
+		type: 'scatter',
+		mode: 'markers',
+		marker: {
+			color: 'rgb(7,110,180)',
+			opacity: 0.5,
+			size: 6
+		},
+		text: texts,
+		opacity: 1,
+		showlegend: false
+	};
+	const data = [backgroundTrace, dataPointTrace];
+
+	const imageSource = map.mapSource;
+
+	// for a detailed description of layout attributes: https://plotly.com/javascript/reference/#layout
+	const layout: any = {
+		width: 1000,
+		height: 1000,
+		xaxis: {
+			visible: false, // changes all visibility settings including showgrid, zeroline, showticklabels and hiding ticks
+			range: [0, 500] // range of displayed graph
+		},
+		yaxis: {
+			visible: false,
+			range: [0, 500],
+			scaleanchor: 'x'
+		},
+		images: [{
+			layer: 'below',
+			source: imageSource,
+			xref: 'x',
+			yref: 'y',
+			x: 0,
+			y: 0,
+			sizex: 500,
+			sizey: 500,
+			xanchor: 'left',
+			yanchor: 'bottom',
+			sizing: 'contain',
+			opacity: 1
+		}]
+	};
+
+	return <Plot
+		data={data as PlotData[]}
+		layout={layout}
+		config={{
+			// makes locales available for use
+			locales: Locales,
+			locale: currentLanguange
+		}}
+		onClick={(event: PlotMouseEvent) => {
+			event.event.preventDefault();
+			dispatch(localEditsSlice.actions.updateCurrentCartesian(event));
+		}}
+	/>;
+}
+
+/**
+ * use a transparent heatmap to capture which point the user clicked on the map
+ * @param imageDimensions Normalized dimensions of the image
+ * @param settings Settings for calibration displays
+ * @returns point and data
+ */
+function createBackgroundTrace(imageDimensions: Dimensions, settings: CalibrationSettings) {
+	// define the grid of heatmap
+	const x: number[] = [];
+	const y: number[] = [];
+	// bound the grid to image dimensions to avoid clicking outside of the map
+	for (let i = 0; i <= Math.ceil(imageDimensions.width); i = i + 1) {
+		x.push(i);
+	}
+	for (let j = 0; j <= Math.ceil(imageDimensions.height); j = j + 1) {
+		y.push(j);
+	}
+	// define the actual points of the graph, numbers in the array are used to designate different colors;
+	const z: number[][] = [];
+	for (let ind1 = 0; ind1 < y.length; ++ind1) {
+		const temp = [];
+		for (let ind2 = 0; ind2 < x.length; ++ind2) {
+			temp.push(0);
+		}
+		z.push(temp);
+	}
+	const trace = {
+		x,
+		y,
+		z,
+		type: 'heatmap',
+		colorscale: [['0.5', 'rgba(6,86,157,0)']], // set colors to be fully transparent
+		xgap: 1,
+		ygap: 1,
+		hoverinfo: 'x+y',
+		opacity: (settings.showGrid) ? '0.5' : '0', // controls whether the grids will be displayed
+		showscale: false
+	};
+	return trace;
+}
\ No newline at end of file
diff --git a/src/client/app/components/maps/MapCalibrationComponent.tsx b/src/client/app/components/maps/MapCalibrationComponent.tsx
index cfdbbd97c..9b71cb86e 100644
--- a/src/client/app/components/maps/MapCalibrationComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationComponent.tsx
@@ -9,6 +9,13 @@ import MapCalibrationInitiateContainer from '../../containers/maps/MapCalibratio
 //import MapsDetailContainer from '../../containers/maps/MapsDetailContainer';
 import { CalibrationModeTypes } from '../../types/redux/map';
 import MapsDetailComponent from './MapsDetailComponent';
+import { Navigate } from 'react-router-dom';
+import { useAppSelector } from '../../redux/reduxHooks';
+import MapCalibrationInfoDisplayComponent from './MapCalibrationInfoDisplayComponent';
+import MapCalibrationInitiateComponent from './MapCalibrationInitiateComponent';
+import { localEditsSlice } from '../../redux/slices/localEditsSlice';
+import { mapsAdapter } from '../../redux/api/mapsApi';
+import MapCalibrationChartDisplayComponent from './MapCalibrationChartDisplayComponent';
 
 interface MapCalibrationProps {
 	mode: CalibrationModeTypes;
@@ -54,3 +61,32 @@ export default class MapCalibrationComponent extends React.Component<MapCalibrat
 		}
 	}
 }
+/**
+ * @returns Calibration Component corresponding to current step invloved
+ */
+export const MapCalibrationComponent2 = () => {
+	const mapToCalibrate = useAppSelector(localEditsSlice.selectors.selectCalibrationMapId);
+	const calibrationMode = useAppSelector(state => {
+		const data = mapsAdapter.getSelectors().selectById(state.localEdits.mapEdits, mapToCalibrate);
+		return data?.calibrationMode ?? CalibrationModeTypes.unavailable;
+	});
+	if (calibrationMode === CalibrationModeTypes.initiate) {
+		return (
+			<div className='container-fluid'>
+				{/* <MapCalibrationInitiateContainer /> */}
+				<MapCalibrationInitiateComponent />
+			</div >
+		);
+	} else if (calibrationMode === CalibrationModeTypes.calibrate) {
+		return (
+			<div className='container-fluid'>
+				<div id={'MapCalibrationContainer'}>
+					<MapCalibrationChartDisplayComponent />
+					<MapCalibrationInfoDisplayComponent />
+				</div>
+			</div>
+		);
+	} else {
+		return <Navigate to='/maps' replace />;
+	}
+};
diff --git a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx
index 7e27aeede..5e091af28 100644
--- a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx
@@ -3,116 +3,110 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import * as React from 'react';
-import {GPSPoint, isValidGPSInput} from '../../utils/calibration';
-import {ChangeEvent, FormEvent} from 'react';
-import {FormattedMessage} from 'react-intl';
+import { ChangeEvent, FormEvent } from 'react';
+import { FormattedMessage } from 'react-intl';
+import { logsApi } from '../../redux/api/logApi';
+import { mapsAdapter, mapsApi } from '../../redux/api/mapsApi';
+import { useTranslate } from '../../redux/componentHooks';
+import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
+import { localEditsSlice } from '../../redux/slices/localEditsSlice';
+import { GPSPoint, isValidGPSInput } from '../../utils/calibration';
 
-interface InfoDisplayProps {
-	showGrid: boolean;
-	currentCartesianDisplay: string;
-	resultDisplay: string;
-	changeGridDisplay(): any;
-	updateGPSCoordinates(gpsCoordinate: GPSPoint): any;
-	submitCalibratingMap(): any;
-	dropCurrentCalibration(): any;
-	log(level: string, message: string): any;
-}
-
-interface InfoDisplayState {
-	value: string;
-}
-
-export default class MapCalibrationInfoDisplayComponent extends React.Component<InfoDisplayProps, InfoDisplayState> {
-	constructor(props: InfoDisplayProps) {
-		super(props);
-		this.state = {
-			value: ''
-		};
-		this.handleGridDisplay = this.handleGridDisplay.bind(this);
-		this.handleGPSInput = this.handleGPSInput.bind(this);
-		this.resetInputField = this.resetInputField.bind(this);
-		this.handleSubmit = this.handleSubmit.bind(this);
-		this.handleChanges = this.handleChanges.bind(this);
-		this.dropCurrentCalibration = this.dropCurrentCalibration.bind(this);
-	}
-	public render() {
-		const calibrationDisplay = `${this.props.resultDisplay}`;
-		return (
-			<div>
-				<div className='checkbox'>
-					<label><input type='checkbox' onChange={this.handleGridDisplay} checked={this.props.showGrid} />
-						<FormattedMessage id='show.grid' />
-					</label>
-				</div>
-				<div id='UserInput'>
-					<form onSubmit={this.handleSubmit}>
-						<label>
-							<FormattedMessage id='input.gps.coords.first'/> {this.props.currentCartesianDisplay}
-							<br/>
-							<FormattedMessage id='input.gps.coords.second'/>
-							<br/>
-							<textarea id={'text'} cols={50} value={this.state.value} onChange={this.handleGPSInput}/>
-						</label>
-						<br/>
-						<FormattedMessage id='calibration.submit.button'>
-							{intlSubmitText => <input type={'submit'} value={intlSubmitText.toString()}/>}
-						</FormattedMessage>
-					</form>
-					<FormattedMessage id='calibration.reset.button'>
-						{intlResetButton => <button onClick={this.dropCurrentCalibration}>{intlResetButton.toString()}</button>}
-					</FormattedMessage>
-					<FormattedMessage id='calibration.save.database'>
-						{intlSaveChanges => <button onClick={this.handleChanges}>{intlSaveChanges.toString()}</button>}
-					</FormattedMessage>
-					<FormattedMessage id='calibration.display'>
-						{intlResult => <p>{intlResult.toString()}{calibrationDisplay}</p>}
-					</FormattedMessage>
-				</div>
-			</div>
-		);
-	}
+/**
+ * @returns TODO DO ME
+ */
+export default function MapCalibrationInfoDisplayComponent() {
+	const dispatch = useAppDispatch();
+	const [createNewMap] = mapsApi.useCreateMapMutation();
+	const [editMap] = mapsApi.useEditMapMutation();
+	const translate = useTranslate();
+	const [logToServer] = logsApi.useLogToServerMutation();
+	const [value, setValue] = React.useState<string>('');
+	const showGrid = useAppSelector(state => state.localEdits.calibrationSettings.showGrid);
+	const mapData = useAppSelector(state => mapsAdapter.getSelectors().selectById(state.localEdits.mapEdits, state.localEdits.calibratingMap));
+	const resultDisplay = (mapData.calibrationResult)
+		? `x: ${mapData.calibrationResult.maxError.x}%, y: ${mapData.calibrationResult.maxError.y}%`
+		: translate('need.more.points');
+	const cartesianDisplay = (mapData.currentPoint)
+		? `x: ${mapData.currentPoint.cartesian.x}, y: ${mapData.currentPoint.cartesian.y}`
+		: translate('undefined');
 
-	private handleGridDisplay() {
-		this.props.changeGridDisplay();
-	}
+	const handleGridDisplay = () => { dispatch(localEditsSlice.actions.toggleMapShowGrid()); };
 
-	private resetInputField() {
-		this.setState({
-			value: ''
-		});
-	}
+	const resetInputField = () => setValue('');
 
-	private handleSubmit = (event: FormEvent) => {
+	const handleSubmit = (event: FormEvent) => {
 		event.preventDefault();
 		const latitudeIndex = 0;
 		const longitudeIndex = 1;
-		if (this.props.currentCartesianDisplay === 'x: undefined, y: undefined') { return; }
-		const input = this.state.value;
+		if (cartesianDisplay === 'x: undefined, y: undefined') {
+			return;
+		}
+		const input = value;
 		if (isValidGPSInput(input)) {
 			const array = input.split(',').map((value: string) => parseFloat(value));
 			const gps: GPSPoint = {
 				longitude: array[longitudeIndex],
 				latitude: array[latitudeIndex]
 			};
-			this.props.updateGPSCoordinates(gps);
-			this.resetInputField();
+			console.log('Verify: this.props.updateGPSCoordinates(gps); ', gps);
+
+			dispatch(localEditsSlice.actions.offerCurrentGPS(gps));
+			resetInputField();
 		} else {
-			this.props.log('info', `refused data point with invalid input: ${input}`);
+			logToServer({ level: 'info', message: `refused data point with invalid input: ${input}` });
 		}
 	};
 
-	private handleGPSInput(event: ChangeEvent<HTMLTextAreaElement>) {
-		this.setState({
-			value: event.target.value
-		});
-	}
+	const handleGPSInput = (event: ChangeEvent<HTMLTextAreaElement>) => setValue(event.target.value);
+
+	const dropCurrentCalibration = () => {
+		console.log('Verfiy  this.props.dropCurrentCalibration();');
+		dispatch(localEditsSlice.actions.resetCalibration(mapData.id));
+	};
 
-	private dropCurrentCalibration() {
-		this.props.dropCurrentCalibration();
-	}
+	const handleChanges = () => {
+		console.log('Verfiy: // this.props.submitCalibratingMap();');
+		if (mapData.id < 0) {
+			createNewMap(mapData);
+		} else {
+			editMap(mapData);
+		}
+	};
+	const calibrationDisplay = `${resultDisplay}`;
+	return (
+		<div>
+			<div className='checkbox'>
+				<label><input type='checkbox' onChange={handleGridDisplay} checked={showGrid} />
+					<FormattedMessage id='show.grid' />
+				</label>
+			</div>
+			<div id='UserInput'>
+				<form onSubmit={handleSubmit}>
+					<label>
+						<FormattedMessage id='input.gps.coords.first' /> {cartesianDisplay}
+						<br />
+						<FormattedMessage id='input.gps.coords.second' />
+						<br />
+						<textarea id={'text'} cols={50} value={value} onChange={handleGPSInput} />
+					</label>
+					<br />
+					<FormattedMessage id='calibration.submit.button'>
+						{intlSubmitText => <input type={'submit'} value={intlSubmitText.toString()} />}
+					</FormattedMessage>
+				</form>
+				<FormattedMessage id='calibration.reset.button'>
+					{intlResetButton => <button onClick={dropCurrentCalibration}>{intlResetButton.toString()}</button>}
+				</FormattedMessage>
+				<FormattedMessage id='calibration.save.database'>
+					{intlSaveChanges => <button onClick={handleChanges}>{intlSaveChanges.toString()}</button>}
+				</FormattedMessage>
+				<FormattedMessage id='calibration.display'>
+					{intlResult => <p>{intlResult.toString()}{calibrationDisplay}</p>}
+				</FormattedMessage>
+			</div>
+		</div>
+	);
 
-	private handleChanges() {
-		this.props.submitCalibratingMap();
-	}
 }
 
diff --git a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
index 1a7cc4d06..dd30dd9f0 100644
--- a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
@@ -10,6 +10,7 @@ import { logsApi } from '../../redux/api/logApi';
 import { selectMapById } from '../../redux/api/mapsApi';
 import { useTranslate } from '../../redux/componentHooks';
 import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
+import { selectSelectedMap } from '../../redux/slices/graphSlice';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
 import { showErrorNotification } from '../../utils/notifications';
 
@@ -44,12 +45,7 @@ export default function MapCalibrationInitiateComponent() {
 	const [mapName, setMapName] = React.useState<string>('');
 	const [angle, setAngle] = React.useState<string>('');
 	const fileRef = React.useRef<HTMLInputElement>(null);
-	const mapData = useAppSelector(state => selectMapById(state, state.maps.selectedMap));
-	// const [mapData] = useAppSelector(state => selectEntityDisplayData(state, {
-	// 	type: EntityType.MAP,
-	// 	id: state.localEdits.mapCalibration.calibratingMap
-	// }));
-
+	const mapData = useAppSelector(state => selectMapById(state, selectSelectedMap(state)));
 
 	const notify = (key: 'map.bad.number' | 'map.bad.digita' | 'map.bad.digitb' | 'map.bad.load' | 'map.bad.name') => {
 		showErrorNotification(translate(key));
diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index 937f65430..e35c321cf 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -7,10 +7,10 @@ import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
 import { LocaleDataKey } from 'translations/data';
 import { useAppSelector } from '../../redux/reduxHooks';
-import { selectMapById } from '../../redux/selectors/maps';
 import '../../styles/card-page.css';
 import translate from '../../utils/translate';
 import EditMapModalComponent from './EditMapModalComponent';
+import { selectMapById } from '../../redux/api/mapsApi';
 interface MapViewProps {
 	mapID: number;
 }
@@ -18,7 +18,7 @@ interface MapViewProps {
 //TODO: Migrate to RTK
 const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 
-	const map = useAppSelector(selectMapById(mapID));
+	const map = useAppSelector(state => selectMapById(state, mapID));
 
 	// Helper function checks map to see if it's calibrated
 	const getCalibrationStatus = () => {
diff --git a/src/client/app/redux/actions/map.ts b/src/client/app/redux/actions/map.ts
index 688183839..06882af69 100644
--- a/src/client/app/redux/actions/map.ts
+++ b/src/client/app/redux/actions/map.ts
@@ -4,17 +4,17 @@
 
 // TODO: Migrate to RTK
 
+import * as moment from 'moment';
 import { ActionType, Dispatch, GetState, Thunk } from '../../types/redux/actions';
 import * as t from '../../types/redux/map';
 import { CalibrationModeTypes, MapData, MapMetadata } from '../../types/redux/map';
-import { calibrate, CalibratedPoint, CalibrationResult, CartesianPoint, Dimensions, GPSPoint } from '../../utils/calibration';
 import { State } from '../../types/redux/state';
-import MapsApi from '../../utils/api/MapsApi';
 import ApiBackend from '../../utils/api/ApiBackend';
+import MapsApi from '../../utils/api/MapsApi';
+import { calibrate, CalibratedPoint, CalibrationResult, CartesianPoint, GPSPoint } from '../../utils/calibration';
+import { browserHistory } from '../../utils/history';
 import { showErrorNotification, showSuccessNotification } from '../../utils/notifications';
 import translate from '../../utils/translate';
-import * as moment from 'moment';
-import { browserHistory } from '../../utils/history';
 import { logToServer } from './logs';
 
 const mapsApi = new MapsApi(new ApiBackend());
@@ -160,7 +160,7 @@ export function offerCurrentGPS(currentGPS: GPSPoint): Thunk {
 	};
 }
 
-function hasCartesian(point: CalibratedPoint) {
+export function hasCartesian(point: CalibratedPoint) {
 	return point.cartesian.x !== -1 && point.cartesian.y !== -1;
 }
 
@@ -189,13 +189,9 @@ function isReadyForCalculation(state: State): boolean {
 function prepareDataToCalculation(state: State): CalibrationResult {
 	const mapID = state.maps.calibratingMap;
 	const mp = state.maps.editedMaps[mapID];
-	const imageDimensions: Dimensions = {
-		width: mp.imgWidth,
-		height: mp.imgHeight
-	};
 	// Since mp is defined above, calibrationSet is defined.
 	/* eslint-disable @typescript-eslint/no-non-null-assertion */
-	const result = calibrate(mp.calibrationSet!, imageDimensions, mp.northAngle);
+	const result = calibrate(mp);
 	return result;
 	/* eslint-enable @typescript-eslint/no-non-null-assertion */
 }
diff --git a/src/client/app/redux/api/baseApi.ts b/src/client/app/redux/api/baseApi.ts
index 3750dfe3f..9262d4707 100644
--- a/src/client/app/redux/api/baseApi.ts
+++ b/src/client/app/redux/api/baseApi.ts
@@ -4,13 +4,10 @@
 
 import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
 import { RootState } from '../../store';
-// TODO Should be env variable?
-const baseHref = (document.getElementsByTagName('base')[0] || {}).href;
 
 export const baseApi = createApi({
 	reducerPath: 'api',
 	baseQuery: fetchBaseQuery({
-		baseUrl: baseHref,
 		prepareHeaders: (headers, { getState }) => {
 			const state = getState() as RootState;
 			// For each api call attempt to set the JWT token in the request header
@@ -21,7 +18,6 @@ export const baseApi = createApi({
 		},
 		// Default Behavior assumes all responses are json
 		// use content type because API responses are varied
-		// TODO Validate Behavior against all endpoints
 		responseHandler: 'content-type'
 	}),
 	// The types of tags that any injected endpoint may, provide, or invalidate.
diff --git a/src/client/app/redux/api/mapsApi.ts b/src/client/app/redux/api/mapsApi.ts
index 9f1afcd21..7a8f7addd 100644
--- a/src/client/app/redux/api/mapsApi.ts
+++ b/src/client/app/redux/api/mapsApi.ts
@@ -1,6 +1,7 @@
 import { createEntityAdapter, EntityState } from '@reduxjs/toolkit';
 import { pick } from 'lodash';
 import * as moment from 'moment';
+import { createAppSelector } from '../../redux/selectors/selectors';
 import { RootState } from '../../store';
 import { MapData, MapMetadata } from '../../types/redux/map';
 import { showErrorNotification, showSuccessNotification } from '../../utils/notifications';
@@ -115,7 +116,6 @@ export const mapsApi = baseApi.injectEndpoints({
 							showSuccessNotification(translate('updated.map.without.calibration'));
 						}
 						// Cleanup LocalEditsSLice
-						// TODO Centralize localEditCleanup. Should be same as others.
 						// api.dispatch(localEditsSlice.actions.removeOneEdit({ type: EntityType.MAP, id: map.id }));
 					}).catch(() => {
 						showErrorNotification(translate('failed.to.edit.map'));
@@ -145,3 +145,11 @@ export const {
 	selectEntities: selectMapDataById,
 	selectTotal: selectTotalMaps
 } = mapsAdapter.getSelectors(selectMapApiData);
+
+export const selectMapSelectOptions = createAppSelector(
+	[selectAllMaps],
+	allMaps => allMaps.map(map => (
+		{ value: map.id, label: map.name, isDisabled: !(map.origin && map.opposite) }
+	)));
+
+
diff --git a/src/client/app/redux/rootReducer.ts b/src/client/app/redux/rootReducer.ts
index a6d29340c..9e5f33a1c 100644
--- a/src/client/app/redux/rootReducer.ts
+++ b/src/client/app/redux/rootReducer.ts
@@ -8,14 +8,16 @@ import { adminSlice } from './slices/adminSlice';
 import { appStateSlice } from './slices/appStateSlice';
 import { currentUserSlice } from './slices/currentUserSlice';
 import { graphSlice } from './slices/graphSlice';
-import maps from './reducers/maps';
+// import maps from './reducers/maps';
+import { localEditsSlice } from './slices/localEditsSlice';
 
 export const rootReducer = combineReducers({
 	appState: appStateSlice.reducer,
 	graph: graphSlice.reducer,
 	admin: adminSlice.reducer,
 	currentUser: currentUserSlice.reducer,
+	localEdits: localEditsSlice.reducer,
 	// RTK Query's Derived Reducers
-	[baseApi.reducerPath]: baseApi.reducer,
-	maps
+	[baseApi.reducerPath]: baseApi.reducer
+	// maps
 });
\ No newline at end of file
diff --git a/src/client/app/redux/selectors/chartQuerySelectors.ts b/src/client/app/redux/selectors/chartQuerySelectors.ts
index b0253fcc4..7df10b84d 100644
--- a/src/client/app/redux/selectors/chartQuerySelectors.ts
+++ b/src/client/app/redux/selectors/chartQuerySelectors.ts
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import { createSelector } from '@reduxjs/toolkit';
-import { RootState } from 'store';
+import { omit } from 'lodash';
 import { MeterOrGroup, ReadingInterval } from '../../types/redux/graph';
 import { calculateCompareShift } from '../../utils/calculateCompare';
 import { roundTimeIntervalForFetch } from '../../utils/dateRangeCompatibility';
 import {
 	selectBarWidthDays, selectComparePeriod,
 	selectCompareTimeInterval, selectMapBarWidthDays, selectQueryTimeInterval,
-	selectSelectedGroups, selectSelectedMeters,
+	selectSelectedGroups, selectSelectedMap, selectSelectedMeters,
 	selectSelectedUnit, selectThreeDState
 } from '../slices/graphSlice';
-import { omit } from 'lodash';
+import { createAppSelector } from './selectors';
 
 // query args that 'most' graphs share
 export interface commonQueryArgs {
@@ -137,11 +137,11 @@ export const selectCompareChartQueryArgs = createSelector(
 	}
 );
 
-export const selectMapChartQueryArgs = createSelector(
+export const selectMapChartQueryArgs = createAppSelector(
 	selectBarChartQueryArgs,
 	selectMapBarWidthDays,
-	(state: RootState) => state.maps,
-	(barChartArgs, barWidthDays, maps) => {
+	selectSelectedMap,
+	(barChartArgs, barWidthDays, selectedMap) => {
 		const durationDays = Math.round(barWidthDays.asDays());
 
 		const meterArgs: MapReadingApiArgs = {
@@ -155,8 +155,8 @@ export const selectMapChartQueryArgs = createSelector(
 			barWidthDays: durationDays
 
 		};
-		const meterShouldSkip = barChartArgs.meterShouldSkip || maps.selectedMap === 0;
-		const groupShouldSkip = barChartArgs.groupShouldSkip || maps.selectedMap === 0;
+		const meterShouldSkip = barChartArgs.meterShouldSkip || selectedMap === 0;
+		const groupShouldSkip = barChartArgs.groupShouldSkip || selectedMap === 0;
 		return { meterArgs, groupArgs, meterShouldSkip, groupShouldSkip };
 	}
 
diff --git a/src/client/app/redux/selectors/maps.ts b/src/client/app/redux/selectors/maps.ts
deleted file mode 100644
index fe068ee73..000000000
--- a/src/client/app/redux/selectors/maps.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/* 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/. */
-
-// TODO: Migrate to RTK
-
-import { RootState } from 'store';
-import { MapState } from 'types/redux/map';
-import { createAppSelector } from './selectors';
-
-export const selectMapState = (state: RootState) => state.maps;
-export const selectMaps = createAppSelector([selectMapState], maps =>
-	Object.keys(maps.byMapID)
-		.map(key => parseInt(key))
-		.filter(key => !isNaN(key))
-);
-
-export const selectMapById = (id: number) =>
-	createAppSelector([selectMapState], (maps: MapState) => maps.byMapID[id]);
diff --git a/src/client/app/redux/selectors/uiSelectors.ts b/src/client/app/redux/selectors/uiSelectors.ts
index b80edd585..26190d805 100644
--- a/src/client/app/redux/selectors/uiSelectors.ts
+++ b/src/client/app/redux/selectors/uiSelectors.ts
@@ -4,6 +4,7 @@
 
 import { sortBy } from 'lodash';
 import { selectGroupDataById } from '../../redux/api/groupsApi';
+import { selectMapById } from '../../redux/api/mapsApi';
 import { selectMeterDataById } from '../../redux/api/metersApi';
 import { selectUnitDataById } from '../../redux/api/unitsApi';
 import { selectChartLinkHideOptions } from '../../redux/slices/appStateSlice';
@@ -19,10 +20,9 @@ import {
 } from '../../utils/calibration';
 import { metersInGroup, unitsCompatibleWithMeters } from '../../utils/determineCompatibleUnits';
 import { AreaUnitType } from '../../utils/getAreaUnitConversion';
-import { selectMapState } from '../selectors/maps';
 import {
 	selectChartToRender, selectGraphAreaNormalization, selectGraphState,
-	selectSelectedGroups, selectSelectedMeters, selectSelectedUnit, selectSliderRangeInterval
+	selectSelectedGroups, selectSelectedMap, selectSelectedMeters, selectSelectedUnit, selectSliderRangeInterval
 } from '../slices/graphSlice';
 import { selectVisibleMetersAndGroups, selectVisibleUnitOrSuffixState } from './authVisibilitySelectors';
 import { selectDefaultGraphicUnitFromEntity, selectMeterOrGroupFromEntity, selectNameFromEntity } from './entitySelectors';
@@ -131,11 +131,12 @@ export const selectChartTypeCompatibility = createAppSelector(
 	[
 		selectCurrentAreaCompatibility,
 		selectChartToRender,
+		selectSelectedMap,
 		selectMeterDataById,
 		selectGroupDataById,
-		selectMapState
+		state => selectMapById(state, selectSelectedMap(state))
 	],
-	(areaCompat, chartToRender, meterDataById, groupDataById, mapState) => {
+	(areaCompat, chartToRender, selectedMap, meterDataById, groupDataById, selectedMapMetadata) => {
 		// Deep Copy previous selector's values, and update as needed based on current ChartType(s)
 		const compatibleMeters = new Set<number>(Array.from(areaCompat.compatibleMeters));
 		const incompatibleMeters = new Set<number>(Array.from(areaCompat.incompatibleMeters));
@@ -144,13 +145,12 @@ export const selectChartTypeCompatibility = createAppSelector(
 		const incompatibleGroups = new Set<number>(Array.from(areaCompat.incompatibleGroups));
 
 		// ony run this check if we are displaying a map chart
-		if (chartToRender === ChartTypes.map && mapState.selectedMap !== 0) {
-			const mp = mapState.byMapID[mapState.selectedMap];
+		if (chartToRender === ChartTypes.map && selectedMap !== 0) {
 			// filter meters;
 			// The size of the original map loaded into OED.
 			const imageDimensions: Dimensions = {
-				width: mp.imgWidth,
-				height: mp.imgHeight
+				width: selectedMapMetadata.imgWidth,
+				height: selectedMapMetadata.imgHeight
 			};
 			// Determine the dimensions so within the Plotly coordinates on the user map.
 			const imageDimensionNormalized = normalizeImageDimensions(imageDimensions);
@@ -167,19 +167,19 @@ export const selectChartTypeCompatibility = createAppSelector(
 			// and upper, right corners of the user map.
 			// The gps value can be null from the database. Note using gps !== null to check for both null and undefined
 			// causes TS to complain about the unknown case so not used.
-			const origin = mp.origin;
-			const opposite = mp.opposite;
+			const origin = selectedMapMetadata.origin;
+			const opposite = selectedMapMetadata.opposite;
 			compatibleMeters.forEach(meterID => {
 				// This meter's GPS value.
 				const gps = meterDataById[meterID].gps;
-				if (origin !== undefined && opposite !== undefined && gps !== undefined && gps !== null) {
+				if (origin && opposite && gps) {
 					// Get the GPS degrees per unit of Plotly grid for x and y. By knowing the two corners
 					// (or really any two distinct points) you can calculate this by the change in GPS over the
 					// change in x or y which is the map's width & height in this case.
-					const scaleOfMap = calculateScaleFromEndpoints(origin, opposite, imageDimensionNormalized, mp.northAngle);
+					const scaleOfMap = calculateScaleFromEndpoints(origin, opposite, imageDimensionNormalized, selectedMapMetadata.northAngle);
 					// Convert GPS of meter to grid on user map. See calibration.ts for more info on this.
-					const meterGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, mp.northAngle);
-					if (!(itemMapInfoOk(meterID, DataType.Meter, mp, gps) &&
+					const meterGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, selectedMapMetadata.northAngle);
+					if (!(itemMapInfoOk(meterID, DataType.Meter, selectedMapMetadata, gps) &&
 						itemDisplayableOnMap(imageDimensionNormalized, meterGPSInUserGrid))) {
 						incompatibleMeters.add(meterID);
 					}
@@ -192,10 +192,10 @@ export const selectChartTypeCompatibility = createAppSelector(
 			// The below code follows the logic for meters shown above. See comments above for clarification on the below code.
 			compatibleGroups.forEach(groupID => {
 				const gps = groupDataById[groupID].gps;
-				if (origin !== undefined && opposite !== undefined && gps !== undefined && gps !== null) {
-					const scaleOfMap = calculateScaleFromEndpoints(origin, opposite, imageDimensionNormalized, mp.northAngle);
-					const groupGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, mp.northAngle);
-					if (!(itemMapInfoOk(groupID, DataType.Group, mp, gps) &&
+				if (origin && opposite && gps) {
+					const scaleOfMap = calculateScaleFromEndpoints(origin, opposite, imageDimensionNormalized, selectedMapMetadata.northAngle);
+					const groupGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, selectedMapMetadata.northAngle);
+					if (!(itemMapInfoOk(groupID, DataType.Group, selectedMapMetadata, gps) &&
 						itemDisplayableOnMap(imageDimensionNormalized, groupGPSInUserGrid))) {
 						incompatibleGroups.add(groupID);
 					}
@@ -435,7 +435,7 @@ export const selectChartLink = createAppSelector(
 		selectGraphState,
 		selectChartLinkHideOptions,
 		selectSliderRangeInterval,
-		state => state.maps.selectedMap
+		selectSelectedMap
 	],
 	(current, chartLinkHideOptions, rangeSliderInterval, selectedMap) => {
 		// Determine the beginning of the URL to add arguments to.
diff --git a/src/client/app/redux/slices/graphSlice.ts b/src/client/app/redux/slices/graphSlice.ts
index 1114225e4..a5ea57bd1 100644
--- a/src/client/app/redux/slices/graphSlice.ts
+++ b/src/client/app/redux/slices/graphSlice.ts
@@ -17,12 +17,15 @@ import { ChartTypes, GraphState, LineGraphRate, MeterOrGroup, ReadingInterval }
 import { ComparePeriod, SortingOrder, calculateCompareTimeInterval, validateComparePeriod, validateSortingOrder } from '../../utils/calculateCompare';
 import { AreaUnitType } from '../../utils/getAreaUnitConversion';
 import { preferencesApi } from '../api/preferencesApi';
+import { mapsApi } from '../../redux/api/mapsApi';
 
 const defaultState: GraphState = {
 	selectedMeters: [],
 	selectedGroups: [],
 	selectedUnit: -99,
 	selectedAreaUnit: AreaUnitType.none,
+	// TODO appropriate default value?
+	selectedMap: 0,
 	queryTimeInterval: TimeInterval.unbounded(),
 	rangeSliderInterval: TimeInterval.unbounded(),
 	barDuration: moment.duration(4, 'weeks'),
@@ -58,6 +61,9 @@ export const graphSlice = createSlice({
 	name: 'graph',
 	initialState: initialState,
 	reducers: {
+		updateSelectedMaps: (state, action: PayloadAction<number>) => {
+			state.current.selectedMap = action.payload;
+		},
 		updateSelectedMeters: (state, action: PayloadAction<number[]>) => {
 			state.current.selectedMeters = action.payload;
 		},
@@ -355,6 +361,16 @@ export const graphSlice = createSlice({
 					});
 				}
 			)
+			.addMatcher(
+				mapsApi.endpoints.getMapDetails.matchFulfilled,
+				({ current }, action) => {
+					// On Fetch fulfilled
+					// If there is only one map, selectedMap is the id of the only map. ie; display map automatically if only 1 map
+					if (!current.hotlinked && action.payload.ids.length === 1) {
+						current.selectedMap = action.payload.ids[0];
+					}
+				}
+			)
 			.addMatcher(preferencesApi.endpoints.getPreferences.matchFulfilled, ({ current }, action) => {
 				if (!current.hotlinked) {
 					const { defaultAreaUnit, defaultChartToRender, defaultBarStacking, defaultAreaNormalization } = action.payload;
@@ -374,6 +390,7 @@ export const graphSlice = createSlice({
 		selectBarStacking: state => state.current.barStacking,
 		selectBarWidthDays: state => state.current.barDuration,
 		selectMapBarWidthDays: state => state.current.mapsBarDuration,
+		selectSelectedMap: state => state.current.selectedMap,
 		selectAreaUnit: state => state.current.selectedAreaUnit,
 		selectSelectedUnit: state => state.current.selectedUnit,
 		selectChartToRender: state => state.current.chartToRender,
@@ -411,7 +428,7 @@ export const {
 	selectGraphAreaNormalization, selectSliderRangeInterval,
 	selectDefaultGraphState, selectHistoryIsDirty,
 	selectPlotlySliderMax, selectPlotlySliderMin,
-	selectMapBarWidthDays
+	selectMapBarWidthDays, selectSelectedMap
 } = graphSlice.selectors;
 
 // actionCreators exports
@@ -428,6 +445,7 @@ export const {
 	toggleAreaNormalization, updateThreeDMeterOrGroup,
 	changeCompareSortingOrder, updateThreeDMeterOrGroupID,
 	updateThreeDReadingInterval, updateThreeDMeterOrGroupInfo,
-	updateSelectedMetersOrGroups, updateMapsBarDuration
+	updateSelectedMetersOrGroups, updateMapsBarDuration,
+	updateSelectedMaps
 } = graphSlice.actions;
 
diff --git a/src/client/app/redux/slices/localEditsSlice.ts b/src/client/app/redux/slices/localEditsSlice.ts
new file mode 100644
index 000000000..7845a1811
--- /dev/null
+++ b/src/client/app/redux/slices/localEditsSlice.ts
@@ -0,0 +1,122 @@
+import { createEntityAdapter } from '@reduxjs/toolkit';
+import { hasCartesian } from '../../redux/actions/map';
+import { createThunkSlice } from '../../redux/sliceCreators';
+import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
+import { calibrate, CalibratedPoint, CartesianPoint, GPSPoint } from '../../utils/calibration';
+import { mapsAdapter } from '../../redux/api/mapsApi';
+import { PlotMouseEvent } from 'plotly.js';
+
+const localEditAdapter = createEntityAdapter<MapMetadata>();
+export const localEditsSlice = createThunkSlice({
+	name: 'localEdits',
+	initialState: {
+		// Maps
+		mapEdits: mapsAdapter.getInitialState(),
+		calibratingMap: 0,
+		newMapIdCounter: 0,
+		calibrationSettings: {
+			calibrationThreshold: 3,
+			showGrid: false
+
+		}
+
+	},
+	reducers: create => ({
+		incrementCounter: create.reducer<void>(state => {
+			state.newMapIdCounter++;
+		}),
+		toggleMapShowGrid: create.reducer<void>(state => {
+			state.calibrationSettings.showGrid;
+		}),
+		createNewMap: create.reducer(state => {
+			state.newMapIdCounter++;
+			const temporaryID = state.newMapIdCounter * -1;
+			localEditAdapter.setOne(state.mapEdits, { ...emptyMetadata, id: temporaryID });
+			state.calibratingMap = temporaryID;
+		}),
+		offerCurrentGPS: create.reducer<GPSPoint>((state, { payload }) => {
+			// Stripped offerCurrentGPS thunk into a single reducer for simplicity. The only missing functionality are the serverlogs
+			// Current axios approach doesn't require dispatch, however if moved to rtk will. thunks for this adds complexity
+			// For simplicity, these logs can instead be tabulated in a middleware.(probably.)
+			const map = localEditAdapter.getSelectors().selectById(state.mapEdits, state.calibratingMap);
+			const point = map.currentPoint;
+			if (point && hasCartesian(point)) {
+				point.gps = payload;
+				map.calibrationSet.push(point);
+				if (map.calibrationSet.length >= state.calibrationSettings.calibrationThreshold) {
+					// Since mp is defined above, calibrationSet is defined.
+					const result = calibrate(map);
+					map.calibrationResult = result;
+				}
+			}
+		}),
+		updateCurrentCartesian: create.reducer<PlotMouseEvent>((state, { payload }) => {
+			// repourposed getClickedCoordinate Events from previous maps implementatinon moved to reducer
+			// trace 0 keeps a transparent trace of closely positioned points used for calibration(backgroundTrace),
+			// trace 1 keeps the data points used for calibration are automatically added to the same trace(dataPointTrace),
+			// event.points will include all points near a mouse click, including those in the backgroundTrace and the dataPointTrace,
+			// so the algorithm only looks at trace 0 since points from trace 1 are already put into the data set used for calibration.
+			const eligiblePoints = [];
+			for (const point of payload.points) {
+				const traceNumber = point.curveNumber;
+				if (traceNumber === 0) {
+					eligiblePoints.push(point);
+				}
+			}
+			const xValue = eligiblePoints[0].x as number;
+			const yValue = eligiblePoints[0].y as number;
+			const clickedPoint: CartesianPoint = {
+				x: Number(xValue.toFixed(6)),
+				y: Number(yValue.toFixed(6))
+			};
+
+			// update calibrating map with new datapoint
+			const currentPoint: CalibratedPoint = {
+				cartesian: clickedPoint,
+				gps: { longitude: -1, latitude: -1 }
+			};
+
+			localEditAdapter.updateOne(state.mapEdits, {
+				id: state.calibratingMap,
+				changes: { currentPoint }
+			});
+
+
+		}),
+		resetCalibration: create.reducer<number>((state, { payload }) => {
+			localEditAdapter.updateOne(state.mapEdits, {
+				id: payload,
+				changes: {
+					currentPoint: undefined,
+					calibrationResult: undefined,
+					calibrationSet: []
+				}
+			});
+		})
+	}),
+
+	selectors: {
+		selectCalibrationMapId: state => state.calibratingMap
+	}
+});
+
+// MAP Stuff TODO RELOCATE
+const emptyMetadata: MapMetadata = {
+	id: 0,
+	name: '',
+	displayable: false,
+	note: undefined,
+	filename: '',
+	modifiedDate: '',
+	origin: undefined,
+	opposite: undefined,
+	mapSource: '',
+	imgHeight: 0,
+	imgWidth: 0,
+	calibrationMode: CalibrationModeTypes.initiate,
+	currentPoint: undefined,
+	calibrationSet: [],
+	calibrationResult: undefined,
+	northAngle: 0,
+	circleSize: 0
+};
diff --git a/src/client/app/types/redux/graph.ts b/src/client/app/types/redux/graph.ts
index e649dd143..8640761ac 100644
--- a/src/client/app/types/redux/graph.ts
+++ b/src/client/app/types/redux/graph.ts
@@ -60,6 +60,7 @@ export interface GraphState {
 	selectedMeters: number[];
 	selectedGroups: number[];
 	selectedUnit: number;
+	selectedMap: number;
 	selectedAreaUnit: AreaUnitType;
 	rangeSliderInterval: TimeInterval;
 	barDuration: moment.Duration;
diff --git a/src/client/app/types/redux/state.ts b/src/client/app/types/redux/state.ts
deleted file mode 100644
index 1d97657ef..000000000
--- a/src/client/app/types/redux/state.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/* 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/. */
-
-import { BarReadingsState } from './barReadings';
-import { LineReadingsState } from './lineReadings';
-import { GraphState } from './graph';
-import { GroupsState } from './groups';
-import { MetersState } from './meters';
-import { AdminState } from './admin';
-import { CompareReadingsState } from './compareReadings';
-import { VersionState } from './version';
-import { MapState } from './map';
-import { CurrentUserState } from './currentUser';
-import { UnsavedWarningState } from './unsavedWarning';
-import { UnitsState } from './units';
-import { ConversionsState } from './conversions';
-import { AppState } from 'redux/slices/appStateSlice';
-
-export interface State {
-	appState: AppState;
-	meters: MetersState;
-	readings: {
-		line: LineReadingsState;
-		bar: BarReadingsState;
-		compare: CompareReadingsState;
-	};
-	graph: GraphState;
-	maps: MapState;
-	groups: GroupsState;
-	admin: AdminState;
-	version: VersionState;
-	currentUser: CurrentUserState;
-	unsavedWarning: UnsavedWarningState;
-	units: UnitsState;
-	conversions: ConversionsState;
-}
diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts
index 7ec057a4b..63e2ca8a5 100644
--- a/src/client/app/utils/calibration.ts
+++ b/src/client/app/utils/calibration.ts
@@ -177,12 +177,19 @@ export function gpsToUserGrid(size: Dimensions, gps: GPSPoint, originGPS: GPSPoi
  * origin and opposite points. It also calculates the relative error for this
  * scale by finding the maximum difference between the calculated scale and the
  * one from each pair of calibration points. It returns all three of these values.
- * @param calibrationSet All the points clicked by the user for calibration.
- * @param imageDimensions The dimensions of the original map to use from the user.
- * @param northAngle The angle between true north and straight up on the map image.
+ * @param map TODO
+ * calibrationSet All the points clicked by the user for calibration.
+ * imageDimensions The dimensions of the original map to use from the user.
+ * northAngle The angle between true north and straight up on the map image.
  * @returns The error and the origin & opposite point in GPS to use for mapping.
  */
-export function calibrate(calibrationSet: CalibratedPoint[], imageDimensions: Dimensions, northAngle: number): CalibrationResult {
+export function calibrate(map: MapMetadata): CalibrationResult {
+	const { calibrationSet, northAngle } = map;
+	const imageDimensions: Dimensions = {
+		width: map.imgWidth,
+		height: map.imgHeight
+	};
+	// calibrationSet: CalibratedPoint[], imageDimensions: Dimensions, northAngle: number
 	// Normalize dimensions to grid used in Plotly
 	const normalizedDimensions = normalizeImageDimensions(imageDimensions);
 	// Array to hold the map scale for each pair of points.

From 17c851e7efcdff0414aec819aed73986c460be27 Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Sun, 11 Aug 2024 13:55:04 -0700
Subject: [PATCH 39/50] Comments

---
 src/client/app/redux/slices/appStateSlice.ts    | 8 ++++----
 src/client/app/redux/slices/currentUserSlice.ts | 3 ++-
 src/client/app/redux/slices/graphSlice.ts       | 4 ++++
 3 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/src/client/app/redux/slices/appStateSlice.ts b/src/client/app/redux/slices/appStateSlice.ts
index 8099e5def..a96db219b 100644
--- a/src/client/app/redux/slices/appStateSlice.ts
+++ b/src/client/app/redux/slices/appStateSlice.ts
@@ -6,8 +6,7 @@ import * as moment from 'moment';
 import { processGraphLink } from '../../redux/actions/extraActions';
 import { mapsApi } from '../../redux/api/mapsApi';
 import { LanguageTypes } from '../../types/redux/i18n';
-import { deleteToken, getToken, hasToken } from '../../utils/token';
-import { fetchMapsDetails } from '../actions/map';
+import { getToken, hasToken } from '../../utils/token';
 import { authApi } from '../api/authApi';
 import { conversionsApi } from '../api/conversionsApi';
 import { groupsApi } from '../api/groupsApi';
@@ -61,6 +60,7 @@ export const appStateSlice = createThunkSlice({
 			async (_: void, { dispatch }) => {
 				// These queries will trigger a api request, and add a subscription to the store.
 				// Typically they return an unsubscribe method, however we always want to be subscribed to any cache changes for these endpoints.
+				// Unlike QueryHooks used in other components, these Queries will remain indefinitely subscribed;
 				dispatch(preferencesApi.endpoints.getPreferences.initiate());
 				dispatch(versionApi.endpoints.getVersion.initiate());
 				dispatch(unitsApi.endpoints.getUnitsDetails.initiate());
@@ -68,7 +68,7 @@ export const appStateSlice = createThunkSlice({
 				dispatch(conversionsApi.endpoints.getCikDetails.initiate());
 
 				// Older style thunk fetch cycle for maps until migration
-				dispatch(fetchMapsDetails());
+				// dispatch(fetchMapsDetails());
 
 				// If user is an admin, they receive additional meter details.
 				// To avoid sending duplicate requests upon startup, verify user then fetch
@@ -89,7 +89,6 @@ export const appStateSlice = createThunkSlice({
 					} catch {
 						// User had a token that isn't valid or getUserDetails threw an error.
 						// Assume token is invalid. Delete if any
-						deleteToken();
 						dispatch(currentUserSlice.actions.clearCurrentUser());
 					}
 
@@ -101,6 +100,7 @@ export const appStateSlice = createThunkSlice({
 
 			},
 			{
+				// Callback triggers when thunk completes, on either success OR failure.
 				settled: state => {
 					state.initComplete = true;
 				}
diff --git a/src/client/app/redux/slices/currentUserSlice.ts b/src/client/app/redux/slices/currentUserSlice.ts
index 3bb734a4c..85d655a41 100644
--- a/src/client/app/redux/slices/currentUserSlice.ts
+++ b/src/client/app/redux/slices/currentUserSlice.ts
@@ -6,7 +6,7 @@ import type { PayloadAction } from '@reduxjs/toolkit';
 import { createSlice } from '@reduxjs/toolkit';
 import { UserRole } from '../../types/items';
 import { CurrentUserState } from '../../types/redux/currentUser';
-import { setToken } from '../../utils/token';
+import { deleteToken, setToken } from '../../utils/token';
 import { authApi } from '../api/authApi';
 import { userApi } from '../api/userApi';
 
@@ -25,6 +25,7 @@ export const currentUserSlice = createSlice({
 		clearCurrentUser: state => {
 			state.profile = null;
 			state.token = null;
+			deleteToken();
 		},
 		setUserToken: (state, action: PayloadAction<string | null>) => {
 			state.token = action.payload;
diff --git a/src/client/app/redux/slices/graphSlice.ts b/src/client/app/redux/slices/graphSlice.ts
index a5ea57bd1..8f83cacc2 100644
--- a/src/client/app/redux/slices/graphSlice.ts
+++ b/src/client/app/redux/slices/graphSlice.ts
@@ -60,6 +60,8 @@ const initialState: History<GraphState> = {
 export const graphSlice = createSlice({
 	name: 'graph',
 	initialState: initialState,
+	// Current History Implementation tracks ANY action defined in 'reducers' using IsAnyOf(...graphslice.actions)
+	// To update the current graphState without causing a history entry to be created, utilize the 'Extra Reducers' property
 	reducers: {
 		updateSelectedMaps: (state, action: PayloadAction<number>) => {
 			state.current.selectedMap = action.payload;
@@ -247,6 +249,8 @@ export const graphSlice = createSlice({
 
 	},
 	extraReducers: builder => {
+		// Current History Implementation tracks ANY action defined in 'reducers'
+		// To update graphState without causing a history entry to be created, utilize the 'Extra Reducers' property
 		builder
 			.addCase(
 				updateHistory,

From 5c8dbc16c1438fb5754c5f4cb1b4caa4a3d9e660 Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Mon, 12 Aug 2024 17:03:16 -0700
Subject: [PATCH 40/50] More Maps Changes

---
 src/client/app/components/RouteComponent.tsx  |   4 +-
 .../conversion/ConversionsDetailComponent.tsx |   3 +-
 .../components/maps/EditMapModalComponent.tsx |  95 +++--
 .../MapCalibrationChartDisplayComponent.tsx   |   4 +-
 .../maps/MapCalibrationComponent.tsx          |  61 +--
 .../MapCalibrationInfoDisplayComponent.tsx    |   7 +-
 .../maps/MapCalibrationInitiateComponent.tsx  |   8 +-
 .../app/components/maps/MapViewComponent.tsx  |  28 +-
 .../components/maps/MapsDetailComponent.tsx   |   9 +-
 .../app/containers/MapChartContainer.ts       | 353 ------------------
 .../MapCalibrationChartDisplayContainer.ts    | 173 ---------
 .../maps/MapCalibrationContainer.ts           |  19 -
 .../MapCalibrationInfoDisplayContainer.ts     |  38 --
 .../maps/MapCalibrationInitiateContainer.ts   |  25 --
 .../app/containers/maps/MapViewContainer.tsx  |  32 --
 .../containers/maps/MapsDetailContainer.tsx   |  28 --
 src/client/app/redux/actions/map.ts           |  22 +-
 src/client/app/redux/api/groupsApi.ts         |   8 +-
 src/client/app/redux/api/mapsApi.ts           |  31 +-
 src/client/app/redux/api/metersApi.ts         |   8 +-
 src/client/app/redux/api/unitsApi.ts          |   7 +-
 src/client/app/redux/devToolConfig.ts         |   3 +-
 src/client/app/redux/entityAdapters.ts        |  45 +++
 src/client/app/redux/slices/appStateSlice.ts  |   8 +-
 .../app/redux/slices/localEditsSlice.ts       |  42 ++-
 src/client/app/types/redux/actions.ts         |   8 +-
 26 files changed, 209 insertions(+), 860 deletions(-)
 delete mode 100644 src/client/app/containers/MapChartContainer.ts
 delete mode 100644 src/client/app/containers/maps/MapCalibrationChartDisplayContainer.ts
 delete mode 100644 src/client/app/containers/maps/MapCalibrationContainer.ts
 delete mode 100644 src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts
 delete mode 100644 src/client/app/containers/maps/MapCalibrationInitiateContainer.ts
 delete mode 100644 src/client/app/containers/maps/MapViewContainer.tsx
 delete mode 100644 src/client/app/containers/maps/MapsDetailContainer.tsx
 create mode 100644 src/client/app/redux/entityAdapters.ts

diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx
index 827645741..e2cb41e8a 100644
--- a/src/client/app/components/RouteComponent.tsx
+++ b/src/client/app/components/RouteComponent.tsx
@@ -16,7 +16,7 @@ import AdminComponent from './admin/AdminComponent';
 import UsersDetailComponent from './admin/users/UsersDetailComponent';
 import ConversionsDetailComponent from './conversion/ConversionsDetailComponent';
 import GroupsDetailComponent from './groups/GroupsDetailComponent';
-import { MapCalibrationComponent2 } from './maps/MapCalibrationComponent';
+import { MapCalibrationComponent } from './maps/MapCalibrationComponent';
 import MapsDetailComponent from './maps/MapsDetailComponent';
 import MetersDetailComponent from './meters/MetersDetailComponent';
 import AdminOutlet from './router/AdminOutlet';
@@ -54,7 +54,7 @@ const router = createBrowserRouter([
 				element: <AdminOutlet />,
 				children: [
 					{ path: 'admin', element: <AdminComponent /> },
-					{ path: 'calibration', element: <MapCalibrationComponent2 /> },
+					{ path: 'calibration', element: <MapCalibrationComponent /> },
 					{ path: 'maps', element: <MapsDetailComponent /> },
 					{ path: 'units', element: <UnitsDetailComponent /> },
 					{ path: 'conversions', element: <ConversionsDetailComponent /> },
diff --git a/src/client/app/components/conversion/ConversionsDetailComponent.tsx b/src/client/app/components/conversion/ConversionsDetailComponent.tsx
index 41654ed53..fc4a48528 100644
--- a/src/client/app/components/conversion/ConversionsDetailComponent.tsx
+++ b/src/client/app/components/conversion/ConversionsDetailComponent.tsx
@@ -7,11 +7,12 @@ import { FormattedMessage } from 'react-intl';
 import SpinnerComponent from '../SpinnerComponent';
 import TooltipHelpComponent from '../TooltipHelpComponent';
 import { conversionsApi, stableEmptyConversions } from '../../redux/api/conversionsApi';
-import { stableEmptyUnitDataById, unitsAdapter, unitsApi } from '../../redux/api/unitsApi';
+import { stableEmptyUnitDataById, unitsApi } from '../../redux/api/unitsApi';
 import { ConversionData } from '../../types/redux/conversions';
 import TooltipMarkerComponent from '../TooltipMarkerComponent';
 import ConversionViewComponent from './ConversionViewComponent';
 import CreateConversionModalComponent from './CreateConversionModalComponent';
+import { unitsAdapter } from '../../redux/entityAdapters';
 
 /**
  * Defines the conversions page card view
diff --git a/src/client/app/components/maps/EditMapModalComponent.tsx b/src/client/app/components/maps/EditMapModalComponent.tsx
index 588f4bfd3..f02cb2656 100644
--- a/src/client/app/components/maps/EditMapModalComponent.tsx
+++ b/src/client/app/components/maps/EditMapModalComponent.tsx
@@ -2,15 +2,17 @@
 * 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 { debounce, isEqual } from 'lodash';
 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 { Button, Form, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
+import { mapsApi, selectMapById } from '../../redux/api/mapsApi';
+import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
+import { localEditsSlice } from '../../redux/slices/localEditsSlice';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
-import { editMapDetails, submitEditedMap, removeMap, setCalibration } from '../../redux/actions/map';
 import { showErrorNotification } from '../../utils/notifications';
-import { useAppDispatch } from '../../redux/reduxHooks';
-import { AppDispatch } from 'store';
 
 interface EditMapModalProps {
 	map: MapMetadata;
@@ -19,47 +21,65 @@ interface EditMapModalProps {
 // TODO: Migrate to RTK
 const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 	const [showModal, setShowModal] = useState(false);
-	const handleShow = () => setShowModal(true);
-	const handleClose = () => setShowModal(false);
-	const dispatch: AppDispatch = useAppDispatch();
+	const dispatch = useAppDispatch();
 	const [nameInput, setNameInput] = useState(map.name);
 	const [noteInput, setNoteInput] = useState(map.note || '');
-	const [circleInput, setCircleInput] = useState(map.circleSize.toString());
+	const [circleInput, setCircleInput] = useState(map.circleSize);
 	const [displayable, setDisplayable] = useState(map.displayable);
-
+	const [submitEdit] = mapsApi.useEditMapMutation();
+	const [deleteMap] = mapsApi.useDeleteMapMutation();
+	// Only used to track stable reference changes to reset form.
+	const apiMapCache = useAppSelector(state => selectMapById(state, map.id));
 	const intl = useIntl();
 
+
+	const handleShow = () => setShowModal(true);
+	const handleClose = () => setShowModal(false);
+	const updatedMap = (): MapMetadata => ({
+		...map,
+		name: nameInput,
+		note: noteInput,
+		circleSize: circleInput,
+		displayable: displayable
+	});
+	const debouncedLocalUpdate = React.useMemo(() => debounce(
+		(map: MapMetadata) => !isEqual(map, updatedMap()) && dispatch(localEditsSlice.actions.setOneEdit(map)),
+		1000
+	), []);
+	React.useEffect(() => { debouncedLocalUpdate(updatedMap()); }, [nameInput, noteInput, circleInput, displayable]);
+
+	// Sync with API Cache changes, if any.
+	React.useEffect(() => {
+		setNameInput(map.name);
+		setNoteInput(map.note || '');
+		setCircleInput(map.circleSize);
+		setDisplayable(map.displayable);
+	}, [apiMapCache]);
+
 	const handleSave = () => {
-		const updatedMap = {
-			...map,
-			name: nameInput,
-			note: noteInput,
-			circleSize: parseFloat(circleInput),
-			displayable: displayable
-		};
-		dispatch(editMapDetails(updatedMap));
-		dispatch(submitEditedMap(updatedMap.id));
+		submitEdit(updatedMap());
 		handleClose();
 	};
 
 	const handleDelete = () => {
 		const consent = window.confirm(intl.formatMessage({ id: 'map.confirm.remove' }, { name: map.name }));
 		if (consent) {
-			dispatch(removeMap(map.id));
+			deleteMap(map.id);
 			handleClose();
 		}
 	};
 
 	const handleCalibrationSetting = (mode: CalibrationModeTypes) => {
-		dispatch(setCalibration(mode, map.id));
+		// Add/update entry to localEdits Slice
+		dispatch(localEditsSlice.actions.setOneEdit(updatedMap()));
+		// Update Calibration Mode
+		dispatch(localEditsSlice.actions.updateMapCalibrationMode({ mode, id: map.id }));
 		handleClose();
 	};
 
+	const circIsValid = circleInput > 0.0 && circleInput <= 2.0;
 	const toggleCircleEdit = () => {
-		const regtest = /^\d+(\.\d+)?$/;
-		if (regtest.test(circleInput) && parseFloat(circleInput) <= 2.0) {
-			setCircleInput(circleInput);
-		} else {
+		if (!circIsValid) {
 			showErrorNotification(intl.formatMessage({ id: 'invalid.number' }));
 		}
 	};
@@ -102,9 +122,10 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 							<Input
 								id="mapCircleSize"
 								type='number'
-								value={circleInput}
-								onChange={e => setCircleInput(e.target.value)}
-								invalid={parseFloat(circleInput) < 0}
+								value={String(circleInput)}
+								onChange={e => setCircleInput(parseFloat(e.target.value))}
+								invalid={!circIsValid}
+								step={0.1}
 								onBlur={toggleCircleEdit}
 							/>
 						</FormGroup>
@@ -114,7 +135,7 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 								id="mapNote"
 								type="textarea"
 								value={noteInput}
-								onChange={e => setNoteInput(e.target.value.slice(0, 30))}
+								onChange={e => setNoteInput(e.target.value)}
 							/>
 						</FormGroup>
 					</Form>
@@ -127,18 +148,22 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 							defaultValue={map.filename}
 							disabled>
 						</Input>
-						<Button color='primary' onClick={() => handleCalibrationSetting(CalibrationModeTypes.initiate)}>
-							<FormattedMessage id='map.upload.new.file' />
-						</Button>
+						<Link to='/calibration' onClick={() => handleCalibrationSetting(CalibrationModeTypes.initiate)}>
+							<Button color='primary' >
+								<FormattedMessage id='map.upload.new.file' />
+							</Button>
+						</Link>
 					</div>
 					<div>
 						<Label><FormattedMessage id="map.calibration" /></Label>
 						<p>
 							<FormattedMessage id={map.origin && map.opposite ? 'map.is.calibrated' : 'map.is.not.calibrated'} />
 						</p>
-						<Button color='primary' onClick={() => handleCalibrationSetting(CalibrationModeTypes.calibrate)}>
-							<FormattedMessage id='map.calibrate' />
-						</Button>
+						<Link to='/calibration' onClick={() => handleCalibrationSetting(CalibrationModeTypes.calibrate)}>
+							<Button color='primary' >
+								<FormattedMessage id='map.calibrate' />
+							</Button>
+						</Link>
 					</div>
 				</ModalBody>
 				<ModalFooter>
@@ -148,7 +173,7 @@ const EditMapModalComponent: React.FC<EditMapModalProps> = ({ map }) => {
 					<Button color="secondary" onClick={handleClose}>
 						<FormattedMessage id="cancel" />
 					</Button>
-					<Button color="primary" onClick={handleSave}>
+					<Button color="primary" onClick={handleSave} disabled={!circIsValid}>
 						<FormattedMessage id="save.all" />
 					</Button>
 				</ModalFooter>
diff --git a/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
index a2f0e071a..fa415b16d 100644
--- a/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
@@ -11,7 +11,7 @@ import { localEditsSlice } from '../../redux/slices/localEditsSlice';
 import Locales from '../../types/locales';
 import { CalibrationSettings } from '../../types/redux/map';
 import { Dimensions, normalizeImageDimensions } from '../../utils/calibration';
-import { mapsAdapter } from '../../redux/api/mapsApi';
+import { selectMapById } from '../../redux/api/mapsApi';
 
 /**
  * @returns TODO DO ME
@@ -22,7 +22,7 @@ export default function MapCalibrationChartDisplayContainer() {
 	const y: number[] = [];
 	const texts: string[] = [];
 	const currentLanguange = useAppSelector(selectSelectedLanguage);
-	const map = useAppSelector(state => mapsAdapter.getSelectors().selectById(state.localEdits.mapEdits, state.localEdits.calibratingMap));
+	const map = useAppSelector(state => selectMapById(state, state.localEdits.calibratingMap));
 
 	const settings = useAppSelector(state => state.localEdits.calibrationSettings);
 	const points = map.calibrationSet;
diff --git a/src/client/app/components/maps/MapCalibrationComponent.tsx b/src/client/app/components/maps/MapCalibrationComponent.tsx
index 9b71cb86e..17c6adf6a 100644
--- a/src/client/app/components/maps/MapCalibrationComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationComponent.tsx
@@ -3,71 +3,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import * as React from 'react';
-import MapCalibrationChartDisplayContainer from '../../containers/maps/MapCalibrationChartDisplayContainer';
-import MapCalibrationInfoDisplayContainer from '../../containers/maps/MapCalibrationInfoDisplayContainer';
-import MapCalibrationInitiateContainer from '../../containers/maps/MapCalibrationInitiateContainer';
-//import MapsDetailContainer from '../../containers/maps/MapsDetailContainer';
-import { CalibrationModeTypes } from '../../types/redux/map';
-import MapsDetailComponent from './MapsDetailComponent';
 import { Navigate } from 'react-router-dom';
 import { useAppSelector } from '../../redux/reduxHooks';
-import MapCalibrationInfoDisplayComponent from './MapCalibrationInfoDisplayComponent';
-import MapCalibrationInitiateComponent from './MapCalibrationInitiateComponent';
 import { localEditsSlice } from '../../redux/slices/localEditsSlice';
-import { mapsAdapter } from '../../redux/api/mapsApi';
+import { CalibrationModeTypes } from '../../types/redux/map';
 import MapCalibrationChartDisplayComponent from './MapCalibrationChartDisplayComponent';
+import MapCalibrationInfoDisplayComponent from './MapCalibrationInfoDisplayComponent';
+import MapCalibrationInitiateComponent from './MapCalibrationInitiateComponent';
+import { selectMapById } from '../../redux/api/mapsApi';
 
-interface MapCalibrationProps {
-	mode: CalibrationModeTypes;
-	isLoading: boolean;
-	mapID: number;
-}
-
-export default class MapCalibrationComponent extends React.Component<MapCalibrationProps> {
-	constructor(props: any) {
-		super(props);
-	}
-
-	public render() {
-		if (this.props.mode === CalibrationModeTypes.initiate) {
-			return (
-				<div className='container-fluid'>
-					{/* <UnsavedWarningContainer /> */}
-					<MapCalibrationInitiateContainer />
-				</div>
-			);
-		} else if (this.props.mode === CalibrationModeTypes.calibrate) {
-			return (
-				<div className='container-fluid'>
-					{/* <UnsavedWarningContainer /> */}
-					<div id={'MapCalibrationContainer'}>
-						{/* TODO These types of plotly containers expect a lot of passed
-						values and it gives a TS error. Given we plan to  replace this
-						with the react hooks version and it does not seem to cause any
-						issues, this TS error is being suppressed for now.
-						eslint-disable-next-line @typescript-eslint/ban-ts-comment
-						@ts-ignore */}
-						<MapCalibrationChartDisplayContainer />
-						<MapCalibrationInfoDisplayContainer />
-					</div>
-				</div>
-			);
-		} else { // preview mode containers
-			return (
-				<div className='container-fluid'>
-					<MapsDetailComponent />
-				</div>
-			);
-		}
-	}
-}
 /**
  * @returns Calibration Component corresponding to current step invloved
  */
-export const MapCalibrationComponent2 = () => {
+export const MapCalibrationComponent = () => {
 	const mapToCalibrate = useAppSelector(localEditsSlice.selectors.selectCalibrationMapId);
 	const calibrationMode = useAppSelector(state => {
-		const data = mapsAdapter.getSelectors().selectById(state.localEdits.mapEdits, mapToCalibrate);
+		const data = selectMapById(state, mapToCalibrate);
 		return data?.calibrationMode ?? CalibrationModeTypes.unavailable;
 	});
 	if (calibrationMode === CalibrationModeTypes.initiate) {
diff --git a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx
index 5e091af28..359d96459 100644
--- a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
 import { ChangeEvent, FormEvent } from 'react';
 import { FormattedMessage } from 'react-intl';
 import { logsApi } from '../../redux/api/logApi';
-import { mapsAdapter, mapsApi } from '../../redux/api/mapsApi';
+import { mapsApi, selectMapById } from '../../redux/api/mapsApi';
 import { useTranslate } from '../../redux/componentHooks';
 import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
 import { localEditsSlice } from '../../redux/slices/localEditsSlice';
@@ -23,7 +23,7 @@ export default function MapCalibrationInfoDisplayComponent() {
 	const [logToServer] = logsApi.useLogToServerMutation();
 	const [value, setValue] = React.useState<string>('');
 	const showGrid = useAppSelector(state => state.localEdits.calibrationSettings.showGrid);
-	const mapData = useAppSelector(state => mapsAdapter.getSelectors().selectById(state.localEdits.mapEdits, state.localEdits.calibratingMap));
+	const mapData = useAppSelector(state => selectMapById(state, state.localEdits.calibratingMap));
 	const resultDisplay = (mapData.calibrationResult)
 		? `x: ${mapData.calibrationResult.maxError.x}%, y: ${mapData.calibrationResult.maxError.y}%`
 		: translate('need.more.points');
@@ -49,7 +49,6 @@ export default function MapCalibrationInfoDisplayComponent() {
 				longitude: array[longitudeIndex],
 				latitude: array[latitudeIndex]
 			};
-			console.log('Verify: this.props.updateGPSCoordinates(gps); ', gps);
 
 			dispatch(localEditsSlice.actions.offerCurrentGPS(gps));
 			resetInputField();
@@ -61,12 +60,10 @@ export default function MapCalibrationInfoDisplayComponent() {
 	const handleGPSInput = (event: ChangeEvent<HTMLTextAreaElement>) => setValue(event.target.value);
 
 	const dropCurrentCalibration = () => {
-		console.log('Verfiy  this.props.dropCurrentCalibration();');
 		dispatch(localEditsSlice.actions.resetCalibration(mapData.id));
 	};
 
 	const handleChanges = () => {
-		console.log('Verfiy: // this.props.submitCalibratingMap();');
 		if (mapData.id < 0) {
 			createNewMap(mapData);
 		} else {
diff --git a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
index dd30dd9f0..8c99655cf 100644
--- a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import { ChangeEvent } from 'react';
 import { FormattedMessage } from 'react-intl';
-import { updateMapMode, updateMapSource } from '../../redux/actions/map';
+import { localEditsSlice } from '../../redux/slices/localEditsSlice';
 import { logsApi } from '../../redux/api/logApi';
 import { selectMapById } from '../../redux/api/mapsApi';
 import { useTranslate } from '../../redux/componentHooks';
@@ -92,8 +92,8 @@ export default function MapCalibrationInitiateComponent() {
 		event.preventDefault();
 		try {
 			const mapMetaData = await processImgMapMetaData();
-			dispatch(updateMapSource(mapMetaData));
-			dispatch(updateMapMode(CalibrationModeTypes.calibrate));
+			dispatch(localEditsSlice.actions.setOneEdit(mapMetaData));
+			dispatch(localEditsSlice.actions.updateMapCalibrationMode({ id: mapData.id, mode: CalibrationModeTypes.calibrate }));
 		} catch (err) {
 			logToServer({ level: 'error', message: `Error, map source image uploading: ${err}` });
 		}
@@ -129,7 +129,7 @@ export default function MapCalibrationInitiateComponent() {
 				// Fire when image load complete.
 				img.onload = () => {
 					// resolve mapMetadata from image.
-					// Not storing image in state, instead extract relevang values
+					// Not storing image in state, instead extract relevant values
 					resolve({
 						...mapData,
 						imgWidth: img.width,
diff --git a/src/client/app/components/maps/MapViewComponent.tsx b/src/client/app/components/maps/MapViewComponent.tsx
index e35c321cf..f8e9d6650 100644
--- a/src/client/app/components/maps/MapViewComponent.tsx
+++ b/src/client/app/components/maps/MapViewComponent.tsx
@@ -5,12 +5,12 @@
 import { parseZone } from 'moment';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { LocaleDataKey } from 'translations/data';
+import { selectMapById } from '../../redux/api/mapsApi';
 import { useAppSelector } from '../../redux/reduxHooks';
+import { localEditsSlice } from '../../redux/slices/localEditsSlice';
 import '../../styles/card-page.css';
 import translate from '../../utils/translate';
 import EditMapModalComponent from './EditMapModalComponent';
-import { selectMapById } from '../../redux/api/mapsApi';
 interface MapViewProps {
 	mapID: number;
 }
@@ -18,11 +18,15 @@ interface MapViewProps {
 //TODO: Migrate to RTK
 const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 
-	const map = useAppSelector(state => selectMapById(state, mapID));
+	const apiMap = useAppSelector(state => selectMapById(state, mapID));
+	const localEditMap = useAppSelector(state => localEditsSlice.selectors.selectLocalEdit(state, mapID));
+
+	// Use local data first, if any
+	const mapToDisplay = localEditMap ?? apiMap;
 
 	// Helper function checks map to see if it's calibrated
 	const getCalibrationStatus = () => {
-		const isCalibrated = map.origin && map.opposite;
+		const isCalibrated = mapToDisplay.origin && mapToDisplay.opposite;
 		return {
 			color: isCalibrated ? 'black' : 'gray',
 			messageId: isCalibrated ? 'map.is.calibrated' : 'map.is.not.calibrated'
@@ -33,24 +37,24 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 	return (
 		<div className="card">
 			<div className="identifier-container">
-				{map.name}
+				{`${mapToDisplay.name}:${localEditMap ? ' (Unsaved Edits)' : ''}`}
 			</div>
-			<div className={map.displayable.toString()}>
-				<b><FormattedMessage id="map.displayable" /></b> {translate(`TrueFalseType.${map.displayable.toString()}` as LocaleDataKey)}
+			<div className={mapToDisplay.displayable.toString()}>
+				<b><FormattedMessage id="map.displayable" /></b> {translate(`TrueFalseType.${mapToDisplay.displayable.toString()}`)}
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="map.circle.size" /></b> {map.circleSize}
+				<b><FormattedMessage id="map.circle.size" /></b> {mapToDisplay.circleSize}
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="note" /></b> {map.note ? map.note.slice(0, 29) : ''}
+				<b><FormattedMessage id="note" /></b> {mapToDisplay.note ? mapToDisplay.note.slice(0, 29) + ' ...' : ''}
 			</div>
 			<div className="item-container">
-				<b><FormattedMessage id="map.filename" /></b> {map.filename}
+				<b><FormattedMessage id="map.filename" /></b> {mapToDisplay.filename}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.modified.date" /></b>
 				{/* TODO I don't think this will properly internationalize. */}
-				{parseZone(map.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
+				{parseZone(apiMap.modifiedDate, undefined, true).format('dddd, MMM DD, YYYY hh:mm a')}
 			</div>
 			<div className="item-container">
 				<b><FormattedMessage id="map.calibration" /></b>
@@ -59,7 +63,7 @@ const MapViewComponent: React.FC<MapViewProps> = ({ mapID }) => {
 				</span>
 			</div>
 			<EditMapModalComponent
-				map={map}
+				map={mapToDisplay}
 			/>
 		</div>
 	);
diff --git a/src/client/app/components/maps/MapsDetailComponent.tsx b/src/client/app/components/maps/MapsDetailComponent.tsx
index c5a34ef17..95b5e1c7c 100644
--- a/src/client/app/components/maps/MapsDetailComponent.tsx
+++ b/src/client/app/components/maps/MapsDetailComponent.tsx
@@ -8,11 +8,11 @@ import { Link } from 'react-router-dom';
 import { Button } from 'reactstrap';
 import { selectMapIds } from '../../redux/api/mapsApi';
 import TooltipHelpComponent from '../../components/TooltipHelpComponent';
-import { setNewMap } from '../../redux/actions/map';
 import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
 import '../../styles/card-page.css';
 import TooltipMarkerComponent from '../TooltipMarkerComponent';
 import MapViewComponent from './MapViewComponent';
+import { localEditsSlice } from '../../redux/slices/localEditsSlice';
 
 /**
  * Defines the maps page card view
@@ -22,8 +22,7 @@ import MapViewComponent from './MapViewComponent';
 export default function MapsDetailComponent() {
 	const dispatch = useAppDispatch();
 	// Load map IDs from state and store in number array
-	const maps = useAppSelector(state => selectMapIds(state));
-
+	const mapIds = useAppSelector(state => selectMapIds(state));
 	return (
 		<div className='flexGrowOne'>
 			<TooltipHelpComponent page='maps' />
@@ -36,14 +35,14 @@ export default function MapsDetailComponent() {
 				</h2>
 				{ /* TODO: Change Link to <CreateMapModalComponent /> when it is completed */}
 				<div className="edit-btn">
-					<Link to='/calibration' onClick={() => dispatch(setNewMap())}>
+					<Link to='/calibration' onClick={() => dispatch(localEditsSlice.actions.createNewMap())}>
 						<Button color='primary'>
 							<FormattedMessage id='create.map' />
 						</Button>
 					</Link>
 				</div>
 				<div className="card-container">
-					{maps.map(mapID => (
+					{mapIds.map(mapID => (
 						<MapViewComponent key={mapID} mapID={mapID} />
 					))}
 				</div>
diff --git a/src/client/app/containers/MapChartContainer.ts b/src/client/app/containers/MapChartContainer.ts
deleted file mode 100644
index 28320c022..000000000
--- a/src/client/app/containers/MapChartContainer.ts
+++ /dev/null
@@ -1,353 +0,0 @@
-/* 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/. */
-
-
-
-import { orderBy } from 'lodash';
-import * as moment from 'moment';
-import Plot, { PlotParams } from 'react-plotly.js';
-import { connect } from 'react-redux';
-import { DataType } from '../types/Datasources';
-import { State } from '../types/redux/state';
-import { UnitRepresentType } from '../types/redux/units';
-import {
-	CartesianPoint,
-	Dimensions,
-	calculateScaleFromEndpoints,
-	gpsToUserGrid,
-	itemDisplayableOnMap,
-	itemMapInfoOk,
-	normalizeImageDimensions
-} from '../utils/calibration';
-import { AreaUnitType, getAreaUnitConversion } from '../utils/getAreaUnitConversion';
-import getGraphColor from '../utils/getGraphColor';
-import translate from '../utils/translate';
-
-function mapStateToProps(state: State) {
-	const unitID = state.graph.selectedUnit;
-	// Map to use.
-	let map;
-	// Holds Plotly mapping info.
-	const data = [];
-	// Holds the image to use.
-	if (state.maps.selectedMap !== 0) {
-		const mapID = state.maps.selectedMap;
-		if (state.maps.byMapID[mapID]) {
-			map = state.maps.byMapID[mapID];
-			if (state.maps.editedMaps[mapID]) {
-				map = state.maps.editedMaps[mapID];
-			}
-		}
-		// Holds the hover text for each point for Plotly
-		const hoverText: string[] = [];
-		// Holds the size of each circle for Plotly.
-		const size: number[] = [];
-		// Holds the color of each circle for Plotly.
-		const colors: string[] = [];
-		// If there is no map then use a new, empty image as the map. I believe this avoids errors
-		// and gives the blank screen.
-		// Arrays to hold the Plotly grid location (x, y) for circles to place on map.
-		const x: number[] = [];
-		const y: number[] = [];
-
-		// Figure out what time interval the bar is using since user bar data for now.
-		const timeInterval = state.graph.queryTimeInterval;
-		const barDuration = state.graph.barDuration;
-		// Make sure there is a map with values so avoid issues.
-		if (map && map.origin && map.opposite) {
-			// The size of the original map loaded into OED.
-			const imageDimensions: Dimensions = {
-				width: map.imgWidth,
-				height: map.imgHeight
-			};
-			// Determine the dimensions so within the Plotly coordinates on the user map.
-			const imageDimensionNormalized = normalizeImageDimensions(imageDimensions);
-			// This is the origin & opposite from the calibration. It is the lower, left
-			// and upper, right corners of the user map.
-			const origin = map.origin;
-			const opposite = map.opposite;
-			// Get the GPS degrees per unit of Plotly grid for x and y. By knowing the two corners
-			// (or really any two distinct points) you can calculate this by the change in GPS over the
-			// change in x or y which is the map's width & height in this case.
-			const scaleOfMap = calculateScaleFromEndpoints(origin, opposite, imageDimensionNormalized, map.northAngle);
-			// Loop over all selected meters. Maps only work for meters at this time.
-			// The y-axis label depends on the unit which is in selectUnit state.
-			const graphingUnit = state.graph.selectedUnit;
-			let unitLabel: string = '';
-			// If graphingUnit is -99 then none selected and nothing to graph so label is empty.
-			// This will probably happen when the page is first loaded.
-			if (graphingUnit !== -99) {
-				const selectUnitState = state.units.units[state.graph.selectedUnit];
-				if (selectUnitState !== undefined) {
-					// Quantity and flow units have different unit labels.
-					// Look up the type of unit if it is for quantity/flow (should not be raw) and decide what to do.
-					// Bar graphics are always quantities.
-					if (selectUnitState.unitRepresent === UnitRepresentType.quantity) {
-						// If it is a quantity unit then that is the unit you are graphing but it is normalized to per day.
-						unitLabel = selectUnitState.identifier + ' / day';
-					} else if (selectUnitState.unitRepresent === UnitRepresentType.flow) {
-						// If it is a flow meter then you need to multiply by time to get the quantity unit then show as per day.
-						// The quantity/time for flow has varying time so label by multiplying by time.
-						// To make sure it is clear, also indicate it is a quantity.
-						// Note this should not be used for raw data.
-						// It might not be usual to take a flow and make it into a quantity so this label is a little different to
-						// catch people's attention. If sites/users don't like OED doing this then we can eliminate flow for these types
-						// of graphics as we are doing for rate.
-						unitLabel = selectUnitState.identifier + ' * time / day ≡ quantity / day';
-					}
-					if (state.graph.areaNormalization) {
-						unitLabel += ' / ' + translate(`AreaUnitType.${state.graph.selectedAreaUnit}`);
-					}
-				}
-			}
-
-			for (const meterID of state.graph.selectedMeters) {
-				// Get meter id number.
-				const byMeterID = state.readings.bar.byMeterID[meterID];
-				// Get meter GPS value.
-				const gps = state.meters.byMeterID[meterID].gps;
-				// filter meters with actual gps coordinates.
-				if (gps !== undefined && gps !== null && byMeterID !== undefined) {
-					let meterArea = state.meters.byMeterID[meterID].area;
-					// we either don't care about area, or we do in which case there needs to be a nonzero area
-					if (!state.graph.areaNormalization || (meterArea > 0 && state.meters.byMeterID[meterID].areaUnit != AreaUnitType.none)) {
-						if (state.graph.areaNormalization) {
-							// convert the meter area into the proper unit, if needed
-							meterArea *= getAreaUnitConversion(state.meters.byMeterID[meterID].areaUnit, state.graph.selectedAreaUnit);
-						}
-						// Convert the gps value to the equivalent Plotly grid coordinates on user map.
-						// First, convert from GPS to grid units. Since we are doing a GPS calculation, this happens on the true north map.
-						// It must be on true north map since only there are the GPS axis parallel to the map axis.
-						// To start, calculate the user grid coordinates (Plotly) from the GPS value. This involves calculating
-						// it coordinates on the true north map and then rotating/shifting to the user map.
-						const meterGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, map.northAngle);
-						// Only display items within valid info and within map.
-						if (itemMapInfoOk(meterID, DataType.Meter, map, gps) && itemDisplayableOnMap(imageDimensionNormalized, meterGPSInUserGrid)) {
-							// The x, y value for Plotly to use that are on the user map.
-							x.push(meterGPSInUserGrid.x);
-							y.push(meterGPSInUserGrid.y);
-							// Make sure the bar reading data is available. The timeInterval should be fine (but checked) but the barDuration might have changed
-							// and be fetching. The unit could change from that menu so also need to check.
-							if (byMeterID[timeInterval.toString()] !== undefined && byMeterID[timeInterval.toString()][barDuration.toISOString()] !== undefined) {
-								// Get the bar data to use for the map circle.
-								const readingsData = byMeterID[timeInterval.toString()][barDuration.toISOString()][unitID];
-								// This protects against there being no readings or that the data is being updated.
-								if (readingsData !== undefined && !readingsData.isFetching) {
-									// Meter name to include in hover on graph.
-									const label = state.meters.byMeterID[meterID].identifier;
-									// The usual color for this meter.
-									colors.push(getGraphColor(meterID, DataType.Meter));
-									if (readingsData.readings === undefined) {
-										throw new Error('Unacceptable condition: readingsData.readings is undefined.');
-									}
-									// Use the most recent time reading for the circle on the map.
-									// This has the limitations of the bar value where the last one can include ranges without
-									// data (GitHub issue on this).
-									// TODO: It might be better to do this similarly to compare. (See GitHub issue)
-									const readings = orderBy(readingsData.readings, ['startTimestamp'], ['desc']);
-									const mapReading = readings[0];
-									let timeReading: string;
-									let averagedReading = 0;
-									if (readings.length === 0) {
-										// No data. The next lines causes an issue so set specially.
-										// There may be a better overall fix for no data.
-										timeReading = 'no data to display';
-										size.push(0);
-									} else {
-										// only display a range of dates for the hover text if there is more than one day in the range
-										// Shift to UTC since want database time not local/browser time which is what moment does.
-										timeReading = `${moment.utc(mapReading.startTimestamp).format('ll')}`;
-										if (barDuration.asDays() != 1) {
-											// subtracting one extra day caused by day ending at midnight of the next day.
-											// Going from DB unit timestamp that is UTC so force UTC with moment, as usual.
-											timeReading += ` - ${moment.utc(mapReading.endTimestamp).subtract(1, 'days').format('ll')}`;
-										}
-										// The value for the circle is the average daily usage.
-										averagedReading = mapReading.reading / barDuration.asDays();
-										if (state.graph.areaNormalization) {
-											averagedReading /= meterArea;
-										}
-										// The size is the reading value. It will be scaled later.
-										size.push(averagedReading);
-									}
-									// The hover text.
-									hoverText.push(`<b> ${timeReading} </b> <br> ${label}: ${averagedReading.toPrecision(6)} ${unitLabel}`);
-								}
-							}
-						}
-					}
-				}
-			}
-
-			for (const groupID of state.graph.selectedGroups) {
-				// Get group id number.
-				const byGroupID = state.readings.bar.byGroupID[groupID];
-				// Get group GPS value.
-				const gps = state.groups.byGroupID[groupID].gps;
-				// Filter groups with actual gps coordinates.
-				if (gps !== undefined && gps !== null && byGroupID !== undefined) {
-					let groupArea = state.groups.byGroupID[groupID].area;
-					if (!state.graph.areaNormalization || (groupArea > 0 && state.groups.byGroupID[groupID].areaUnit != AreaUnitType.none)) {
-						if (state.graph.areaNormalization) {
-							// convert the meter area into the proper unit, if needed
-							groupArea *= getAreaUnitConversion(state.groups.byGroupID[groupID].areaUnit, state.graph.selectedAreaUnit);
-						}
-						// Convert the gps value to the equivalent Plotly grid coordinates on user map.
-						// First, convert from GPS to grid units. Since we are doing a GPS calculation, this happens on the true north map.
-						// It must be on true north map since only there are the GPS axis parallel to the map axis.
-						// To start, calculate the user grid coordinates (Plotly) from the GPS value. This involves calculating
-						// it coordinates on the true north map and then rotating/shifting to the user map.
-						const groupGPSInUserGrid: CartesianPoint = gpsToUserGrid(imageDimensionNormalized, gps, origin, scaleOfMap, map.northAngle);
-						// Only display items within valid info and within map.
-						if (itemMapInfoOk(groupID, DataType.Group, map, gps) && itemDisplayableOnMap(imageDimensionNormalized, groupGPSInUserGrid)) {
-							// The x, y value for Plotly to use that are on the user map.
-							x.push(groupGPSInUserGrid.x);
-							y.push(groupGPSInUserGrid.y);
-							// Make sure the bar reading data is available. The timeInterval should be fine (but checked) but the barDuration might have changed
-							// and be fetching. The unit could change from that menu so also need to check.
-							if (byGroupID[timeInterval.toString()] !== undefined && byGroupID[timeInterval.toString()][barDuration.toISOString()] !== undefined) {
-								// Get the bar data to use for the map circle.
-								const readingsData = byGroupID[timeInterval.toString()][barDuration.toISOString()][unitID];
-								// This protects against there being no readings or that the data is being updated.
-								if (readingsData !== undefined && !readingsData.isFetching) {
-									// Group name to include in hover on graph.
-									const label = state.groups.byGroupID[groupID].name;
-									// The usual color for this group.
-									colors.push(getGraphColor(groupID, DataType.Group));
-									if (readingsData.readings === undefined) {
-										throw new Error('Unacceptable condition: readingsData.readings is undefined.');
-									}
-									// Use the most recent time reading for the circle on the map.
-									// This has the limitations of the bar value where the last one can include ranges without
-									// data (GitHub issue on this).
-									// TODO: It might be better to do this similarly to compare. (See GitHub issue)
-									const readings = orderBy(readingsData.readings, ['startTimestamp'], ['desc']);
-									const mapReading = readings[0];
-									let timeReading: string;
-									let averagedReading = 0;
-									if (readings.length === 0) {
-										// No data. The next lines causes an issue so set specially.
-										// There may be a better overall fix for no data.
-										timeReading = 'no data to display';
-										size.push(0);
-									} else {
-										// only display a range of dates for the hover text if there is more than one day in the range
-										timeReading = `${moment.utc(mapReading.startTimestamp).format('ll')}`;
-										if (barDuration.asDays() != 1) {
-											// subtracting one extra day caused by day ending at midnight of the next day.
-											// Going from DB unit timestamp that is UTC so force UTC with moment, as usual.
-											timeReading += ` - ${moment.utc(mapReading.endTimestamp).subtract(1, 'days').format('ll')}`;
-										}
-										// The value for the circle is the average daily usage.
-										averagedReading = mapReading.reading / barDuration.asDays();
-										if (state.graph.areaNormalization) {
-											averagedReading /= groupArea;
-										}
-										// The size is the reading value. It will be scaled later.
-										size.push(averagedReading);
-									}
-									// The hover text.
-									hoverText.push(`<b> ${timeReading} </b> <br> ${label}: ${averagedReading.toPrecision(6)} ${unitLabel}`);
-								}
-							}
-						}
-					}
-				}
-			}
-			// TODO Using the following seems to have no impact on the code. It has been noticed that this function is called
-			// many times for each change. Someone should look at why that is happening and why some have no items in the arrays.
-			// if (size.length > 0) {
-			// TODO The max circle diameter should come from admin/DB.
-			const maxFeatureFraction = map.circleSize;
-			// Find the smaller of width and height. This is used since it means the circle size will be
-			// scaled to that dimension and smaller relative to the other coordinate.
-			const minDimension = Math.min(imageDimensionNormalized.width, imageDimensionNormalized.height);
-			// The circle size is set to area below. Thus, we need to convert from wanting a max
-			// diameter of minDimension * maxFeatureFraction to an area.
-			const maxCircleSize = Math.PI * Math.pow(minDimension * maxFeatureFraction / 2, 2);
-			// Find the largest circle which is usage.
-			const largestCircleSize = Math.max(...size);
-			// Scale largest circle to the max size and others will be scaled to be smaller.
-			// Not that < 1 => a larger circle.
-			const scaling = largestCircleSize / maxCircleSize;
-
-			// Per https://plotly.com/javascript/reference/scatter/:
-			// The opacity of 0.5 makes it possible to see the map even when there is a circle but the hover
-			// opacity is 1 so it is easy to see.
-			// Set the sizemode to area not diameter.
-			// Set the sizemin so a circle cannot get so small that it might disappear. Unsure the best size.
-			// Set the sizeref to scale each point to the desired area.
-			// Note all sizes are in px so have to estimate the actual size. This could be an issue but maps are currently
-			// a fixed size so not too much of an issue.
-			// Also note that the circle can go off the edge of the map. At some point it would be nice to have a border
-			// around the map to avoid this.
-			const traceOne = {
-				x,
-				y,
-				type: 'scatter',
-				mode: 'markers',
-				marker: {
-					color: colors,
-					opacity: 0.5,
-					size,
-					sizemin: 6,
-					sizeref: scaling,
-					sizemode: 'area'
-				},
-				text: hoverText,
-				hoverinfo: 'text',
-				opacity: 1,
-				showlegend: false
-			};
-			data.push(traceOne);
-		}
-	}
-
-	// set map background image
-	const layout: any = {
-		// Either the actual map name or text to say it is not available.
-		title: {
-			text: (map) ? map.name : translate('map.unavailable')
-		},
-		width: 1000,
-		height: 1000,
-		xaxis: {
-			visible: false, // changes all visibility settings including showgrid, zeroline, showticklabels and hiding ticks
-			range: [0, 500] // range of displayed graph
-		},
-		yaxis: {
-			visible: false,
-			range: [0, 500],
-			scaleanchor: 'x'
-		},
-		images: [{
-			layer: 'below',
-			source: map?.mapSource,
-			xref: 'x',
-			yref: 'y',
-			x: 0,
-			y: 0,
-			sizex: 500,
-			sizey: 500,
-			xanchor: 'left',
-			yanchor: 'bottom',
-			sizing: 'contain',
-			opacity: 1
-		}]
-	};
-
-	/***
-	 * Usage:
-	 *  <Plot data={toJS(this.model_data)}
-	 *               layout={layout}
-	 *               onClick={({points, event}) => console.log(points, event)}>
-	 */
-	const props = {
-		data,
-		layout
-	} as PlotParams;
-	return props;
-}
-
-export default connect(mapStateToProps)(Plot);
diff --git a/src/client/app/containers/maps/MapCalibrationChartDisplayContainer.ts b/src/client/app/containers/maps/MapCalibrationChartDisplayContainer.ts
deleted file mode 100644
index 64208c7fe..000000000
--- a/src/client/app/containers/maps/MapCalibrationChartDisplayContainer.ts
+++ /dev/null
@@ -1,173 +0,0 @@
-/* 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/. */
-
-import { connect } from 'react-redux';
-import Plot from 'react-plotly.js';
-import { State } from '../../types/redux/state';
-import * as plotly from 'plotly.js';
-import { CartesianPoint, Dimensions, normalizeImageDimensions } from '../../utils/calibration';
-import { updateCurrentCartesian } from '../../redux/actions/map';
-import { store }  from '../../store';
-import { CalibrationSettings } from '../../types/redux/map';
-import Locales from '../../types/locales';
-
-function mapStateToProps(state: State) {
-	const x: number[] = [];
-	const y: number[] = [];
-	const texts: string[] = [];
-
-	const mapID = state.maps.calibratingMap;
-	const map = state.maps.editedMaps[mapID];
-	const points = map.calibrationSet;
-	if (points) {
-		for (const point of points) {
-			x.push(point.cartesian.x);
-			y.push(point.cartesian.y);
-			texts.push(`latitude: ${point.gps.latitude}, longitude: ${point.gps.longitude}`);
-		}
-	}
-	const imageDimensions: Dimensions = normalizeImageDimensions({
-		width: map.imgWidth,
-		height: map.imgHeight
-	});
-	const settings = state.maps.calibrationSettings;
-	const backgroundTrace = createBackgroundTrace(imageDimensions, settings);
-	const dataPointTrace = {
-		x,
-		y,
-		type: 'scatter',
-		mode: 'markers',
-		marker: {
-			color: 'rgb(7,110,180)',
-			opacity: 0.5,
-			size: 6
-		},
-		text: texts,
-		opacity: 1,
-		showlegend: false
-	};
-	const data = [backgroundTrace, dataPointTrace];
-
-	const imageSource = map.mapSource;
-
-	// for a detailed description of layout attributes: https://plotly.com/javascript/reference/#layout
-	const layout: any = {
-		width: 1000,
-		height: 1000,
-		xaxis: {
-			visible: false, // changes all visibility settings including showgrid, zeroline, showticklabels and hiding ticks
-			range: [0, 500] // range of displayed graph
-		},
-		yaxis: {
-			visible: false,
-			range: [0, 500],
-			scaleanchor: 'x'
-		},
-		images: [{
-			layer: 'below',
-			source: imageSource,
-			xref: 'x',
-			yref: 'y',
-			x: 0,
-			y: 0,
-			sizex: 500,
-			sizey: 500,
-			xanchor: 'left',
-			yanchor: 'bottom',
-			sizing: 'contain',
-			opacity: 1
-		}]
-	};
-
-	/***
-	 * Usage:
-	 *  <Plot data={toJS(this.model_data)}
-	 *               layout={layout}
-	 *               onClick={({points, event}) => console.log(points, event)}>
-	 * Plotly no longer has IPlotlyChartProps so we will use any for now.
-	 */
-	const props: any = {
-		data,
-		layout,
-		onClick: (event: plotly.PlotMouseEvent) => handlePointClick(event),
-		config: {
-			locales: Locales // makes locales available for use
-		}
-	};
-	props.config.locale = state.appState.selectedLanguage;
-	return props;
-}
-
-/**
- * use a transparent heatmap to capture which point the user clicked on the map
- * @param imageDimensions Normalized dimensions of the image
- * @param settings Settings for calibration displays
- * @returns point and data
- */
-function createBackgroundTrace(imageDimensions: Dimensions, settings: CalibrationSettings) {
-	// define the grid of heatmap
-	const x: number[] = [];
-	const y: number[] = [];
-	// bound the grid to image dimensions to avoid clicking outside of the map
-	for (let i = 0; i <= Math.ceil(imageDimensions.width); i = i + 1) {
-		x.push(i);
-	}
-	for (let j = 0; j <= Math.ceil(imageDimensions.height); j = j + 1) {
-		y.push(j);
-	}
-	// define the actual points of the graph, numbers in the array are used to designate different colors;
-	const z: number[][] = [];
-	for (let ind1 = 0; ind1 < y.length; ++ind1) {
-		const temp = [];
-		for (let ind2 = 0; ind2 < x.length; ++ind2) {
-			temp.push(0);
-		}
-		z.push(temp);
-	}
-	const trace = {
-		x,
-		y,
-		z,
-		type: 'heatmap',
-		colorscale: [['0.5', 'rgba(6,86,157,0)']], // set colors to be fully transparent
-		xgap: 1,
-		ygap: 1,
-		hoverinfo: 'x+y',
-		opacity: (settings.showGrid) ? '0.5' : '0', // controls whether the grids will be displayed
-		showscale: false
-	};
-	return trace;
-}
-
-function handlePointClick(event: plotly.PlotMouseEvent) {
-	event.event.preventDefault();
-	const currentPoint: CartesianPoint = getClickedCoordinates(event);
-	store.dispatch(updateCurrentCartesian(currentPoint));
-}
-
-function getClickedCoordinates(event: plotly.PlotMouseEvent) {
-	event.event.preventDefault();
-	/*
-	 *  trace 0 keeps a transparent trace of closely positioned points used for calibration(backgroundTrace),
-	 *  trace 1 keeps the data points used for calibration are automatically added to the same trace(dataPointTrace),
-	 *  event.points will include all points near a mouse click, including those in the backgroundTrace and the dataPointTrace,
-	 *  so the algorithm only looks at trace 0 since points from trace 1 are already put into the data set used for calibration.
-	   */
-	const eligiblePoints = [];
-	for (const point of event.points) {
-		const traceNumber = point.curveNumber;
-		if (traceNumber === 0) {
-			eligiblePoints.push(point);
-		}
-	}
-	const xValue = eligiblePoints[0].x as number;
-	const yValue = eligiblePoints[0].y as number;
-	const clickedPoint: CartesianPoint = {
-		x: Number(xValue.toFixed(6)),
-		y: Number(yValue.toFixed(6))
-	};
-	return clickedPoint;
-}
-
-export default connect(mapStateToProps)(Plot);
diff --git a/src/client/app/containers/maps/MapCalibrationContainer.ts b/src/client/app/containers/maps/MapCalibrationContainer.ts
deleted file mode 100644
index fd927bd35..000000000
--- a/src/client/app/containers/maps/MapCalibrationContainer.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-/* 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/. */
-
-import {connect} from 'react-redux';
-import {State} from '../../types/redux/state';
-import MapCalibrationComponent from '../../components/maps/MapCalibrationComponent';
-import {CalibrationModeTypes} from '../../types/redux/map';
-
-function mapStateToProps(state: State) {
-	const mapID = state.maps.calibratingMap;
-	return {
-		mode: (state.maps.editedMaps[mapID]) ? state.maps.editedMaps[mapID].calibrationMode : CalibrationModeTypes.unavailable,
-		isLoading: false,
-		mapID
-	};
-}
-
-export default connect(mapStateToProps)(MapCalibrationComponent);
diff --git a/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts b/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts
deleted file mode 100644
index 94e27983c..000000000
--- a/src/client/app/containers/maps/MapCalibrationInfoDisplayContainer.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/* 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/. */
-
-import {connect} from 'react-redux';
-import { State } from '../../types/redux/state';
-import { Dispatch } from '../../types/redux/actions';
-import MapCalibrationInfoDisplayComponent from '../../components/maps/MapCalibrationInfoDisplayComponent';
-import {changeGridDisplay, dropCalibration, offerCurrentGPS, submitCalibratingMap} from '../../redux/actions/map';
-import {GPSPoint} from '../../utils/calibration';
-import {logToServer} from '../../redux/actions/logs';
-import translate from '../../utils/translate';
-
-function mapStateToProps(state: State) {
-	const mapID = state.maps.calibratingMap;
-	const map = state.maps.editedMaps[mapID];
-	const resultDisplay = (map.calibrationResult) ?
-		`x: ${map.calibrationResult.maxError.x}%, y: ${map.calibrationResult.maxError.y}%`
-		: translate('need.more.points');
-	const currentCartesianDisplay = (map.currentPoint) ?
-		`x: ${map.currentPoint.cartesian.x}, y: ${map.currentPoint.cartesian.y}` : translate('undefined');
-	return {
-		showGrid: state.maps.calibrationSettings.showGrid,
-		currentCartesianDisplay,
-		resultDisplay
-	};
-}
-
-function mapDispatchToProps(dispatch: Dispatch) {
-	return {
-		updateGPSCoordinates: (gpsCoordinate: GPSPoint) => dispatch(offerCurrentGPS(gpsCoordinate)),
-		submitCalibratingMap: () => dispatch(submitCalibratingMap()),
-		dropCurrentCalibration: () => dispatch(dropCalibration()),
-		log: (level: string, message: string) => dispatch(logToServer(level, message)),
-		changeGridDisplay: () => dispatch(changeGridDisplay())
-	};
-}
-export default connect(mapStateToProps, mapDispatchToProps)(MapCalibrationInfoDisplayComponent);
diff --git a/src/client/app/containers/maps/MapCalibrationInitiateContainer.ts b/src/client/app/containers/maps/MapCalibrationInitiateContainer.ts
deleted file mode 100644
index 647f2a6df..000000000
--- a/src/client/app/containers/maps/MapCalibrationInitiateContainer.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-/* 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/. */
-
-import MapCalibrationInitiateComponent from '../../components/maps/MapCalibrationInitiateComponent';
-import { connect } from 'react-redux';
-import { Dispatch } from '../../types/redux/actions';
-import {updateMapMode, updateMapSource} from '../../redux/actions/map';
-import {CalibrationModeTypes, MapMetadata} from '../../types/redux/map';
-import {State} from '../../types/redux/state';
-
-function mapStateToProps(state: State) {
-	return {
-		map: state.maps.editedMaps[state.maps.calibratingMap]
-	};
-}
-
-function mapDispatchToProps(dispatch: Dispatch) {
-	return {
-		updateMapMode: (nextMode: CalibrationModeTypes) => dispatch(updateMapMode(nextMode)),
-		onSourceChange: (data: MapMetadata) => dispatch(updateMapSource(data))
-	};
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(MapCalibrationInitiateComponent);
diff --git a/src/client/app/containers/maps/MapViewContainer.tsx b/src/client/app/containers/maps/MapViewContainer.tsx
deleted file mode 100644
index 7fdecbac8..000000000
--- a/src/client/app/containers/maps/MapViewContainer.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-/* 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/. */
-
-import { connect } from 'react-redux';
-import MapViewComponent from '../../components/maps/MapViewComponent';
-import { Dispatch } from '../../types/redux/actions';
-import { State } from '../../types/redux/state';
-import {CalibrationModeTypes, MapMetadata} from '../../types/redux/map';
-import {editMapDetails, removeMap, setCalibration} from '../../redux/actions/map';
-
-function mapStateToProps(state: State, ownProps: {id: number}) {
-	let map = state.maps.byMapID[ownProps.id];
-	if (state.maps.editedMaps[ownProps.id]) {
-		map = state.maps.editedMaps[ownProps.id];
-	}
-	return {
-		map,
-		isEdited: state.maps.editedMaps[ownProps.id] !== undefined,
-		isSubmitting: state.maps.submitting.indexOf(ownProps.id) !== -1
-	};
-}
-
-function mapDispatchToProps(dispatch: Dispatch) {
-	return {
-		editMapDetails: (map: MapMetadata) => dispatch(editMapDetails(map)),
-		setCalibration: (mode: CalibrationModeTypes, mapID: number) => dispatch(setCalibration(mode, mapID)),
-		removeMap: (id: number) => dispatch(removeMap(id))
-	};
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(MapViewComponent);
diff --git a/src/client/app/containers/maps/MapsDetailContainer.tsx b/src/client/app/containers/maps/MapsDetailContainer.tsx
deleted file mode 100644
index 011a82e59..000000000
--- a/src/client/app/containers/maps/MapsDetailContainer.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-/* 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/. */
-
-import { connect } from 'react-redux';
-import { State } from '../../types/redux/state';
-import {Dispatch} from '../../types/redux/actions';
-import {fetchMapsDetails, setNewMap, submitEditedMaps} from '../../redux/actions/map';
-import MapsDetailComponent from '../../components/maps/MapsDetailComponent';
-
-function mapStateToProps(state: State) {
-	return {
-		maps: Object.keys(state.maps.byMapID)
-			.map(key => parseInt(key))
-			.filter(key => !isNaN(key)),
-		unsavedChanges: Object.keys(state.maps.editedMaps).length > 0
-	};
-}
-
-function mapDispatchToProps(dispatch: Dispatch) {
-	return {
-		fetchMapsDetails: () => dispatch(fetchMapsDetails()),
-		submitEditedMaps: () => dispatch(submitEditedMaps()),
-		createNewMap: () => dispatch(setNewMap())
-	};
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(MapsDetailComponent);
diff --git a/src/client/app/redux/actions/map.ts b/src/client/app/redux/actions/map.ts
index 06882af69..ae5e0a5d3 100644
--- a/src/client/app/redux/actions/map.ts
+++ b/src/client/app/redux/actions/map.ts
@@ -1,3 +1,6 @@
+/* eslint-disable @typescript-eslint/ban-ts-comment */
+// @ts-ignore
+// @ts-nocheck
 /* 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/. */
@@ -8,7 +11,6 @@ import * as moment from 'moment';
 import { ActionType, Dispatch, GetState, Thunk } from '../../types/redux/actions';
 import * as t from '../../types/redux/map';
 import { CalibrationModeTypes, MapData, MapMetadata } from '../../types/redux/map';
-import { State } from '../../types/redux/state';
 import ApiBackend from '../../utils/api/ApiBackend';
 import MapsApi from '../../utils/api/MapsApi';
 import { calibrate, CalibratedPoint, CalibrationResult, CartesianPoint, GPSPoint } from '../../utils/calibration';
@@ -16,6 +18,7 @@ import { browserHistory } from '../../utils/history';
 import { showErrorNotification, showSuccessNotification } from '../../utils/notifications';
 import translate from '../../utils/translate';
 import { logToServer } from './logs';
+import { RootState } from 'store';
 
 const mapsApi = new MapsApi(new ApiBackend());
 
@@ -135,8 +138,8 @@ export function updateCurrentCartesian(currentCartesian: CartesianPoint): t.Upda
  */
 export function offerCurrentGPS(currentGPS: GPSPoint): Thunk {
 	return (dispatch: Dispatch, getState: GetState) => {
-		const mapID = getState().maps.calibratingMap;
-		const point = getState().maps.editedMaps[mapID].currentPoint;
+		const mapID = getState().localEdits.calibratingMap;
+		const point = getState().localEdits.mapEdits.entities[mapID].currentPoint;
 		if (point && hasCartesian(point)) {
 			point.gps = currentGPS;
 			dispatch(updateCalibrationSet(point));
@@ -173,12 +176,10 @@ function updateCalibrationSet(calibratedPoint: CalibratedPoint): t.AppendCalibra
  * @param state The redux state
  * @returns Result of safety check
  */
-function isReadyForCalculation(state: State): boolean {
+function isReadyForCalculation(state: RootState): boolean {
 	const calibrationThreshold = 3;
 	// assume calibrationSet is defined, as offerCurrentGPS indicates through point that the map is defined.
-	/* eslint-disable @typescript-eslint/no-non-null-assertion */
-	return state.maps.editedMaps[state.maps.calibratingMap].calibrationSet!.length >= calibrationThreshold;
-	/* eslint-enable @typescript-eslint/no-non-null-assertion */
+	return state.localEdits.mapEdits.entities[state.localEdits.calibratingMap].calibrationSet.length >= calibrationThreshold;
 }
 
 /**
@@ -186,9 +187,10 @@ function isReadyForCalculation(state: State): boolean {
  * @param state The redux state
  * @returns Result of map calibration
  */
-function prepareDataToCalculation(state: State): CalibrationResult {
-	const mapID = state.maps.calibratingMap;
-	const mp = state.maps.editedMaps[mapID];
+function prepareDataToCalculation(state: RootState): CalibrationResult {
+	// TODO FIX BEFORE PR
+	const mapID = state.localEdits.calibratingMap;
+	const mp = state.localEdits.mapEdits.entities[mapID];
 	// Since mp is defined above, calibrationSet is defined.
 	/* eslint-disable @typescript-eslint/no-non-null-assertion */
 	const result = calibrate(mp);
diff --git a/src/client/app/redux/api/groupsApi.ts b/src/client/app/redux/api/groupsApi.ts
index cc73eae5f..37bdebcad 100644
--- a/src/client/app/redux/api/groupsApi.ts
+++ b/src/client/app/redux/api/groupsApi.ts
@@ -2,19 +2,15 @@
  * 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 { EntityState, Update, createEntityAdapter } from '@reduxjs/toolkit';
+import { Update } from '@reduxjs/toolkit';
 import { omit } from 'lodash';
+import { GroupDataState, groupsAdapter, groupsInitialState } from '../../redux/entityAdapters';
 import { RootState } from '../../store';
 import { GroupChildren, GroupData } from '../../types/redux/groups';
 import { showErrorNotification } from '../../utils/notifications';
 import { selectIsAdmin } from '../slices/currentUserSlice';
 import { baseApi } from './baseApi';
 
-export const groupsAdapter = createEntityAdapter<GroupData>({
-	sortComparer: (groupA, groupB) => groupA.name?.localeCompare(groupB.name, undefined, { sensitivity: 'accent' })
-});
-export const groupsInitialState = groupsAdapter.getInitialState();
-export type GroupDataState = EntityState<GroupData, number>;
 
 export const groupsApi = baseApi.injectEndpoints({
 	endpoints: builder => ({
diff --git a/src/client/app/redux/api/mapsApi.ts b/src/client/app/redux/api/mapsApi.ts
index 7a8f7addd..93a82d1eb 100644
--- a/src/client/app/redux/api/mapsApi.ts
+++ b/src/client/app/redux/api/mapsApi.ts
@@ -1,13 +1,13 @@
-import { createEntityAdapter, EntityState } from '@reduxjs/toolkit';
 import { pick } from 'lodash';
 import * as moment from 'moment';
+import { MapDataState, mapsAdapter, mapsInitialState } from '../../redux/entityAdapters';
 import { createAppSelector } from '../../redux/selectors/selectors';
+import { emtpyMapMetadata, localEditsSlice } from '../../redux/slices/localEditsSlice';
 import { RootState } from '../../store';
 import { MapData, MapMetadata } from '../../types/redux/map';
 import { showErrorNotification, showSuccessNotification } from '../../utils/notifications';
 import translate from '../../utils/translate';
 import { baseApi } from './baseApi';
-// import { logToServer } from '../../redux/actions/logs';
 
 // Helper function to extract image dimensions from the mapSource
 const mapResponseImgSrcToDimensions = (response: MapMetadata[]) => Promise.all(
@@ -15,11 +15,10 @@ const mapResponseImgSrcToDimensions = (response: MapMetadata[]) => Promise.all(
 		new Promise<MapMetadata>(resolve => {
 			const img = new Image();
 			img.onload = () => {
-				resolve({ ...mapData, imgWidth: img.width, imgHeight: img.height });
+				resolve({ ...emtpyMapMetadata, ...mapData, imgWidth: img.width, imgHeight: img.height });
 			};
 			img.onerror = () => {
-				// TODO default to falsy value, 0, on error.
-				resolve({ ...mapData, imgWidth: 0, imgHeight: 0 });
+				resolve({ ...emtpyMapMetadata, ...mapData });
 			};
 			img.src = mapData.mapSource;
 		})
@@ -27,13 +26,6 @@ const mapResponseImgSrcToDimensions = (response: MapMetadata[]) => Promise.all(
 );
 
 
-export const mapsAdapter = createEntityAdapter<MapMetadata>({
-	sortComparer: (meterA, meterB) => meterA.name?.localeCompare(meterB.name, undefined, { sensitivity: 'accent' })
-
-});
-export const mapsInitialState = mapsAdapter.getInitialState();
-export type MapDataState = EntityState<MapMetadata, number>;
-
 
 export const mapsApi = baseApi.injectEndpoints({
 	endpoints: build => ({
@@ -116,7 +108,7 @@ export const mapsApi = baseApi.injectEndpoints({
 							showSuccessNotification(translate('updated.map.without.calibration'));
 						}
 						// Cleanup LocalEditsSLice
-						// api.dispatch(localEditsSlice.actions.removeOneEdit({ type: EntityType.MAP, id: map.id }));
+						api.dispatch(localEditsSlice.actions.removeOneEdit(map.id));
 					}).catch(() => {
 						showErrorNotification(translate('failed.to.edit.map'));
 					});
@@ -128,7 +120,16 @@ export const mapsApi = baseApi.injectEndpoints({
 				url: 'api/maps/delete',
 				method: 'POST',
 				body: { id }
-			})
+			}),
+			onQueryStarted: (arg, api) => {
+				api.queryFulfilled
+					//Cleanup Local Edits if any for deleted entity
+					.then(() => {
+						api.dispatch(localEditsSlice.actions.removeOneEdit(arg));
+					})
+					.catch();
+			},
+			invalidatesTags: ['MapsData']
 		}),
 		getMapById: build.query<MapData, number>({
 			query: id => `api/maps/${id}`
@@ -151,5 +152,3 @@ export const selectMapSelectOptions = createAppSelector(
 	allMaps => allMaps.map(map => (
 		{ value: map.id, label: map.name, isDisabled: !(map.origin && map.opposite) }
 	)));
-
-
diff --git a/src/client/app/redux/api/metersApi.ts b/src/client/app/redux/api/metersApi.ts
index 5e1a55519..8bb7c263f 100644
--- a/src/client/app/redux/api/metersApi.ts
+++ b/src/client/app/redux/api/metersApi.ts
@@ -2,7 +2,6 @@
  * 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 { EntityState, createEntityAdapter } from '@reduxjs/toolkit';
 import { NamedIDItem } from 'types/items';
 import { RawReadings } from 'types/readings';
 import { TimeInterval } from '../../../../common/TimeInterval';
@@ -11,13 +10,8 @@ import { MeterData } from '../../types/redux/meters';
 import { durationFormat } from '../../utils/durationFormat';
 import { baseApi } from './baseApi';
 import { conversionsApi } from './conversionsApi';
+import { MeterDataState, meterAdapter, metersInitialState } from '../../redux/entityAdapters';
 
-export const meterAdapter = createEntityAdapter<MeterData>({
-	sortComparer: (meterA, meterB) => meterA.identifier?.localeCompare(meterB.identifier, undefined, { sensitivity: 'accent' })
-
-});
-export const metersInitialState = meterAdapter.getInitialState();
-export type MeterDataState = EntityState<MeterData, number>;
 
 export const metersApi = baseApi.injectEndpoints({
 	endpoints: builder => ({
diff --git a/src/client/app/redux/api/unitsApi.ts b/src/client/app/redux/api/unitsApi.ts
index eae58d7a4..ae662c909 100644
--- a/src/client/app/redux/api/unitsApi.ts
+++ b/src/client/app/redux/api/unitsApi.ts
@@ -2,16 +2,11 @@
  * 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 { EntityState, createEntityAdapter } from '@reduxjs/toolkit';
 import { RootState } from 'store';
 import { UnitData, UnitDataById } from '../../types/redux/units';
 import { baseApi } from './baseApi';
 import { conversionsApi } from './conversionsApi';
-export const unitsAdapter = createEntityAdapter<UnitData>({
-	sortComparer: (unitA, unitB) => unitA.identifier?.localeCompare(unitB.identifier, undefined, { sensitivity: 'accent' })
-});
-export const unitsInitialState = unitsAdapter.getInitialState();
-export type UnitDataState = EntityState<UnitData, number>;
+import { UnitDataState, unitsAdapter, unitsInitialState } from '../../redux/entityAdapters';
 
 export const unitsApi = baseApi.injectEndpoints({
 	endpoints: builder => ({
diff --git a/src/client/app/redux/devToolConfig.ts b/src/client/app/redux/devToolConfig.ts
index 70d5a31b0..02fe511bc 100644
--- a/src/client/app/redux/devToolConfig.ts
+++ b/src/client/app/redux/devToolConfig.ts
@@ -1,5 +1,6 @@
 import { DevToolsEnhancerOptions } from '@reduxjs/toolkit';
-import { mapsAdapter, mapsApi, mapsInitialState } from './api/mapsApi';
+import { mapsApi } from './api/mapsApi';
+import { mapsAdapter, mapsInitialState } from './entityAdapters';
 
 export const devToolsConfig: DevToolsEnhancerOptions = {
 	actionSanitizer: action => {
diff --git a/src/client/app/redux/entityAdapters.ts b/src/client/app/redux/entityAdapters.ts
new file mode 100644
index 000000000..92402a2ee
--- /dev/null
+++ b/src/client/app/redux/entityAdapters.ts
@@ -0,0 +1,45 @@
+import { EntityState, createEntityAdapter } from '@reduxjs/toolkit';
+import { ConversionData } from '../types/redux/conversions';
+import { GroupData } from '../types/redux/groups';
+import { MapMetadata } from '../types/redux/map';
+import { MeterData } from '../types/redux/meters';
+import { UnitData } from '../types/redux/units';
+const sortByIdentifierProperty = (a: any, b: any) => a.identifier?.localeCompare(b.identifier, undefined, { sensitivity: 'accent' });
+const sortByNameProperty = (a: any, b: any) => a.name?.localeCompare(b.name, undefined, { sensitivity: 'accent' });
+
+// Adapters re-homed for compatability with localEditsSlice.ts/ prevents circular dependency issues.
+// Meters
+export const meterAdapter = createEntityAdapter<MeterData>({ sortComparer: sortByIdentifierProperty });
+export const metersInitialState = meterAdapter.getInitialState();
+export type MeterDataState = EntityState<MeterData, number>;
+
+
+// Units
+export const unitsAdapter = createEntityAdapter<UnitData>({ sortComparer: sortByIdentifierProperty });
+export const unitsInitialState = unitsAdapter.getInitialState();
+export type UnitDataState = EntityState<UnitData, number>;
+
+// Groups
+export const groupsAdapter = createEntityAdapter<GroupData>({ sortComparer: sortByNameProperty });
+export const groupsInitialState = groupsAdapter.getInitialState();
+export type GroupDataState = EntityState<GroupData, number>;
+
+
+// Maps
+export const mapsAdapter = createEntityAdapter<MapMetadata>({ sortComparer: sortByNameProperty });
+export const mapsInitialState = mapsAdapter.getInitialState();
+export type MapDataState = EntityState<MapMetadata, number>;
+
+// Conversions
+// Extending conversion data to add an id number
+// Conversions are stored in the database as a composite key of source/destination. EntityAdapter requires a unique ID,
+export type ConversionDataWithIds = ConversionData & { id: number };
+// This is exclusively for the front end to take advantage of the entity adapter and its derived selectors.
+// So Adding the id property as the response's array index
+// Will not impact backend/server
+// Conversions sorts using unitData values, which is not possible with entity adapters so sort by synthetic id
+// Will have to sort by 'id' (default, no sort comparer)
+export const conversionsAdapter = createEntityAdapter<ConversionDataWithIds>();
+export const conversionsInitialState = conversionsAdapter.getInitialState();
+export type ConversionDataState = EntityState<ConversionDataWithIds, number>;
+
diff --git a/src/client/app/redux/slices/appStateSlice.ts b/src/client/app/redux/slices/appStateSlice.ts
index a96db219b..547249212 100644
--- a/src/client/app/redux/slices/appStateSlice.ts
+++ b/src/client/app/redux/slices/appStateSlice.ts
@@ -75,16 +75,12 @@ export const appStateSlice = createThunkSlice({
 				if (hasToken()) {
 					// User has a session token verify before requesting meter/group details
 					try {
-						await dispatch(authApi.endpoints.verifyToken.initiate(getToken()))
-							.unwrap()
-							.catch(e => { throw e; });
+						await dispatch(authApi.endpoints.verifyToken.initiate(getToken())).unwrap();
 						// Token is valid if not errored out by this point,
 						// Apis will now use the token in headers via baseAPI's Prepare Headers
 						dispatch(currentUserSlice.actions.setUserToken(getToken()));
 						//  Get userDetails with verified token in headers
-						await dispatch(userApi.endpoints.getUserDetails.initiate(undefined, { subscribe: false }))
-							.unwrap()
-							.catch(e => { throw e; });
+						await dispatch(userApi.endpoints.getUserDetails.initiate(undefined, { subscribe: false })).unwrap();
 
 					} catch {
 						// User had a token that isn't valid or getUserDetails threw an error.
diff --git a/src/client/app/redux/slices/localEditsSlice.ts b/src/client/app/redux/slices/localEditsSlice.ts
index 7845a1811..c9e9002c1 100644
--- a/src/client/app/redux/slices/localEditsSlice.ts
+++ b/src/client/app/redux/slices/localEditsSlice.ts
@@ -1,12 +1,13 @@
 import { createEntityAdapter } from '@reduxjs/toolkit';
+import { PlotMouseEvent } from 'plotly.js';
 import { hasCartesian } from '../../redux/actions/map';
 import { createThunkSlice } from '../../redux/sliceCreators';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
 import { calibrate, CalibratedPoint, CartesianPoint, GPSPoint } from '../../utils/calibration';
-import { mapsAdapter } from '../../redux/api/mapsApi';
-import { PlotMouseEvent } from 'plotly.js';
+import { mapsAdapter } from '../../redux/entityAdapters';
 
 const localEditAdapter = createEntityAdapter<MapMetadata>();
+const localSelectors = localEditAdapter.getSelectors();
 export const localEditsSlice = createThunkSlice({
 	name: 'localEdits',
 	initialState: {
@@ -17,9 +18,7 @@ export const localEditsSlice = createThunkSlice({
 		calibrationSettings: {
 			calibrationThreshold: 3,
 			showGrid: false
-
 		}
-
 	},
 	reducers: create => ({
 		incrementCounter: create.reducer<void>(state => {
@@ -28,17 +27,32 @@ export const localEditsSlice = createThunkSlice({
 		toggleMapShowGrid: create.reducer<void>(state => {
 			state.calibrationSettings.showGrid;
 		}),
+		setOneEdit: create.reducer<MapMetadata>((state, { payload }) => {
+			localEditAdapter.setOne(state.mapEdits, payload);
+		}),
+		removeOneEdit: create.reducer<number>((state, { payload }) => {
+			localEditAdapter.removeOne(state.mapEdits, payload);
+		}),
+		updateMapCalibrationMode: create.reducer<{ id: number, mode: CalibrationModeTypes }>((state, { payload }) => {
+			state.calibratingMap = payload.id;
+			localEditAdapter.updateOne(state.mapEdits, {
+				id: payload.id,
+				changes: { calibrationMode: payload.mode }
+			});
+		}),
 		createNewMap: create.reducer(state => {
 			state.newMapIdCounter++;
 			const temporaryID = state.newMapIdCounter * -1;
-			localEditAdapter.setOne(state.mapEdits, { ...emptyMetadata, id: temporaryID });
+			localEditAdapter.setOne(state.mapEdits, { ...emtpyMapMetadata, id: temporaryID });
 			state.calibratingMap = temporaryID;
 		}),
 		offerCurrentGPS: create.reducer<GPSPoint>((state, { payload }) => {
 			// Stripped offerCurrentGPS thunk into a single reducer for simplicity. The only missing functionality are the serverlogs
 			// Current axios approach doesn't require dispatch, however if moved to rtk will. thunks for this adds complexity
 			// For simplicity, these logs can instead be tabulated in a middleware.(probably.)
-			const map = localEditAdapter.getSelectors().selectById(state.mapEdits, state.calibratingMap);
+			// const map = localEditAdapter.getSelectors().selectById(state.mapEdits, state.calibratingMap);
+			const map = state.mapEdits.entities[state.calibratingMap];
+
 			const point = map.currentPoint;
 			if (point && hasCartesian(point)) {
 				point.gps = payload;
@@ -63,8 +77,9 @@ export const localEditsSlice = createThunkSlice({
 					eligiblePoints.push(point);
 				}
 			}
-			const xValue = eligiblePoints[0].x as number;
-			const yValue = eligiblePoints[0].y as number;
+			// TODO VERIFY
+			const xValue = eligiblePoints[0]?.x as number;
+			const yValue = eligiblePoints[0]?.y as number;
 			const clickedPoint: CartesianPoint = {
 				x: Number(xValue.toFixed(6)),
 				y: Number(yValue.toFixed(6))
@@ -80,8 +95,6 @@ export const localEditsSlice = createThunkSlice({
 				id: state.calibratingMap,
 				changes: { currentPoint }
 			});
-
-
 		}),
 		resetCalibration: create.reducer<number>((state, { payload }) => {
 			localEditAdapter.updateOne(state.mapEdits, {
@@ -96,12 +109,11 @@ export const localEditsSlice = createThunkSlice({
 	}),
 
 	selectors: {
-		selectCalibrationMapId: state => state.calibratingMap
+		selectCalibrationMapId: state => state.calibratingMap,
+		selectLocalEdit: (state, id: number) => localSelectors.selectById(state.mapEdits, id)
 	}
 });
-
-// MAP Stuff TODO RELOCATE
-const emptyMetadata: MapMetadata = {
+export const emtpyMapMetadata: MapMetadata = {
 	id: 0,
 	name: '',
 	displayable: false,
@@ -119,4 +131,4 @@ const emptyMetadata: MapMetadata = {
 	calibrationResult: undefined,
 	northAngle: 0,
 	circleSize: 0
-};
+};
\ No newline at end of file
diff --git a/src/client/app/types/redux/actions.ts b/src/client/app/types/redux/actions.ts
index 5ab356b7d..d72af3afc 100644
--- a/src/client/app/types/redux/actions.ts
+++ b/src/client/app/types/redux/actions.ts
@@ -4,7 +4,7 @@
 
 import { Action } from 'redux';
 import { ThunkAction, ThunkDispatch } from 'redux-thunk';
-import { State } from './state';
+import { RootState } from 'store';
 
 export enum ActionType {
 
@@ -35,15 +35,15 @@ export enum ActionType {
  * The type of the redux-thunk dispatch function.
  * Uses the overloaded version from Redux-Thunk.
  */
-export type Dispatch = ThunkDispatch<State, void, Action<any>>;
+export type Dispatch = ThunkDispatch<RootState, void, Action<any>>;
 
 /**
  * The type of the redux-thunk getState function.
  */
-export type GetState = () => State;
+export type GetState = () => RootState;
 
 /**
  * The type of promissory actions used in the project.
  * Returns a promise, no extra argument, uses the global state.
  */
-export type Thunk = ThunkAction<Promise<any>, State, void, Action>;
+export type Thunk = ThunkAction<Promise<any>, RootState, void, Action>;

From b7a3e5613b5167c7aea247ca47d2f9e142e80ed0 Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Mon, 12 Aug 2024 17:49:58 -0700
Subject: [PATCH 41/50] ReEnable Immutable check

---
 src/client/app/store.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/client/app/store.ts b/src/client/app/store.ts
index 3a1cce927..10d7f9a2d 100644
--- a/src/client/app/store.ts
+++ b/src/client/app/store.ts
@@ -13,7 +13,7 @@ import { Dispatch } from './types/redux/actions';
 export const store = configureStore({
 	reducer: rootReducer,
 	middleware: getDefaultMiddleware => getDefaultMiddleware({
-		immutableCheck: false,
+		// immutableCheck: false,
 		serializableCheck: false
 	}).prepend(listenerMiddleware.middleware)
 		.concat(baseApi.middleware),

From ae28cdab5480d51fce37c0b5dad7678f9e997209 Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Mon, 12 Aug 2024 20:37:26 -0700
Subject: [PATCH 42/50] Bug Fix Revert  Change.

---
 src/client/app/components/maps/MapCalibrationComponent.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/client/app/components/maps/MapCalibrationComponent.tsx b/src/client/app/components/maps/MapCalibrationComponent.tsx
index 17c6adf6a..b6d2d42f2 100644
--- a/src/client/app/components/maps/MapCalibrationComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationComponent.tsx
@@ -10,7 +10,6 @@ import { CalibrationModeTypes } from '../../types/redux/map';
 import MapCalibrationChartDisplayComponent from './MapCalibrationChartDisplayComponent';
 import MapCalibrationInfoDisplayComponent from './MapCalibrationInfoDisplayComponent';
 import MapCalibrationInitiateComponent from './MapCalibrationInitiateComponent';
-import { selectMapById } from '../../redux/api/mapsApi';
 
 /**
  * @returns Calibration Component corresponding to current step invloved
@@ -18,9 +17,10 @@ import { selectMapById } from '../../redux/api/mapsApi';
 export const MapCalibrationComponent = () => {
 	const mapToCalibrate = useAppSelector(localEditsSlice.selectors.selectCalibrationMapId);
 	const calibrationMode = useAppSelector(state => {
-		const data = selectMapById(state, mapToCalibrate);
+		const data = localEditsSlice.selectors.selectLocalEdit(state, mapToCalibrate);
 		return data?.calibrationMode ?? CalibrationModeTypes.unavailable;
 	});
+	console.log(calibrationMode);
 	if (calibrationMode === CalibrationModeTypes.initiate) {
 		return (
 			<div className='container-fluid'>

From 67d578c1f2ff55d6ade58f71d7391590e98a7d8b Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Mon, 12 Aug 2024 21:55:16 -0700
Subject: [PATCH 43/50] Hot Fix Changes.

---
 .../maps/MapCalibrationChartDisplayComponent.tsx       |  3 +--
 .../maps/MapCalibrationInfoDisplayComponent.tsx        |  6 ++++--
 .../maps/MapCalibrationInitiateComponent.tsx           |  3 ++-
 src/client/app/redux/api/authApi.ts                    |  2 +-
 src/client/app/redux/api/mapsApi.ts                    |  5 +++--
 src/client/app/redux/slices/localEditsSlice.ts         | 10 +++++-----
 6 files changed, 16 insertions(+), 13 deletions(-)

diff --git a/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
index fa415b16d..201a21459 100644
--- a/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
@@ -11,7 +11,6 @@ import { localEditsSlice } from '../../redux/slices/localEditsSlice';
 import Locales from '../../types/locales';
 import { CalibrationSettings } from '../../types/redux/map';
 import { Dimensions, normalizeImageDimensions } from '../../utils/calibration';
-import { selectMapById } from '../../redux/api/mapsApi';
 
 /**
  * @returns TODO DO ME
@@ -22,7 +21,7 @@ export default function MapCalibrationChartDisplayContainer() {
 	const y: number[] = [];
 	const texts: string[] = [];
 	const currentLanguange = useAppSelector(selectSelectedLanguage);
-	const map = useAppSelector(state => selectMapById(state, state.localEdits.calibratingMap));
+	const map = useAppSelector(state => localEditsSlice.selectors.selectLocalEdit(state, localEditsSlice.selectors.selectCalibrationMapId(state)));
 
 	const settings = useAppSelector(state => state.localEdits.calibrationSettings);
 	const points = map.calibrationSet;
diff --git a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx
index 359d96459..bff7993d6 100644
--- a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx
@@ -6,7 +6,7 @@ import * as React from 'react';
 import { ChangeEvent, FormEvent } from 'react';
 import { FormattedMessage } from 'react-intl';
 import { logsApi } from '../../redux/api/logApi';
-import { mapsApi, selectMapById } from '../../redux/api/mapsApi';
+import { mapsApi } from '../../redux/api/mapsApi';
 import { useTranslate } from '../../redux/componentHooks';
 import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
 import { localEditsSlice } from '../../redux/slices/localEditsSlice';
@@ -23,7 +23,7 @@ export default function MapCalibrationInfoDisplayComponent() {
 	const [logToServer] = logsApi.useLogToServerMutation();
 	const [value, setValue] = React.useState<string>('');
 	const showGrid = useAppSelector(state => state.localEdits.calibrationSettings.showGrid);
-	const mapData = useAppSelector(state => selectMapById(state, state.localEdits.calibratingMap));
+	const mapData = useAppSelector(state => localEditsSlice.selectors.selectLocalEdit(state, state.localEdits.calibratingMap));
 	const resultDisplay = (mapData.calibrationResult)
 		? `x: ${mapData.calibrationResult.maxError.x}%, y: ${mapData.calibrationResult.maxError.y}%`
 		: translate('need.more.points');
@@ -64,6 +64,8 @@ export default function MapCalibrationInfoDisplayComponent() {
 	};
 
 	const handleChanges = () => {
+		console.log('MapID: ', mapData.id);
+
 		if (mapData.id < 0) {
 			createNewMap(mapData);
 		} else {
diff --git a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
index 8c99655cf..c0e26acd8 100644
--- a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
@@ -45,7 +45,8 @@ export default function MapCalibrationInitiateComponent() {
 	const [mapName, setMapName] = React.useState<string>('');
 	const [angle, setAngle] = React.useState<string>('');
 	const fileRef = React.useRef<HTMLInputElement>(null);
-	const mapData = useAppSelector(state => selectMapById(state, selectSelectedMap(state)));
+	const mapData = useAppSelector(state => localEditsSlice.selectors.selectLocalEdit(state, localEditsSlice.selectors.selectCalibrationMapId(state)));
+	console.log('EmpyMapData>: ', mapData);
 
 	const notify = (key: 'map.bad.number' | 'map.bad.digita' | 'map.bad.digitb' | 'map.bad.load' | 'map.bad.name') => {
 		showErrorNotification(translate(key));
diff --git a/src/client/app/redux/api/authApi.ts b/src/client/app/redux/api/authApi.ts
index 9bff1db07..d9186f41a 100644
--- a/src/client/app/redux/api/authApi.ts
+++ b/src/client/app/redux/api/authApi.ts
@@ -55,4 +55,4 @@ export const authApi = baseApi.injectEndpoints({
 });
 
 // Poll interval in milliseconds (1 minute)
-export const authPollInterval = 60000;
\ No newline at end of file
+export const authPollInterval = 1000 * 60 * 60;
\ No newline at end of file
diff --git a/src/client/app/redux/api/mapsApi.ts b/src/client/app/redux/api/mapsApi.ts
index 93a82d1eb..068142dcc 100644
--- a/src/client/app/redux/api/mapsApi.ts
+++ b/src/client/app/redux/api/mapsApi.ts
@@ -55,12 +55,13 @@ export const mapsApi = baseApi.injectEndpoints({
 					opposite: (map.calibrationResult) ? map.calibrationResult.opposite : undefined
 				}
 			}),
-			onQueryStarted: (map, api) => {
+			onQueryStarted: async (map, api) => {
 				api.queryFulfilled
 					// TODO Serverlogs migrate to rtk Query to drop axios?
 					// Requires dispatch so inconvenient
-					.then(() => {
+					.then(e => {
 						if (map.calibrationResult) {
+							const { data } = e;
 							// logToServer('info', 'New calibrated map uploaded to database');
 							showSuccessNotification(translate('upload.new.map.with.calibration'));
 						} else {
diff --git a/src/client/app/redux/slices/localEditsSlice.ts b/src/client/app/redux/slices/localEditsSlice.ts
index c9e9002c1..78023c57b 100644
--- a/src/client/app/redux/slices/localEditsSlice.ts
+++ b/src/client/app/redux/slices/localEditsSlice.ts
@@ -25,7 +25,7 @@ export const localEditsSlice = createThunkSlice({
 			state.newMapIdCounter++;
 		}),
 		toggleMapShowGrid: create.reducer<void>(state => {
-			state.calibrationSettings.showGrid;
+			state.calibrationSettings.showGrid = !state.calibrationSettings.showGrid;
 		}),
 		setOneEdit: create.reducer<MapMetadata>((state, { payload }) => {
 			localEditAdapter.setOne(state.mapEdits, payload);
@@ -41,10 +41,10 @@ export const localEditsSlice = createThunkSlice({
 			});
 		}),
 		createNewMap: create.reducer(state => {
-			state.newMapIdCounter++;
+			state.newMapIdCounter = state.newMapIdCounter + 1;
 			const temporaryID = state.newMapIdCounter * -1;
-			localEditAdapter.setOne(state.mapEdits, { ...emtpyMapMetadata, id: temporaryID });
 			state.calibratingMap = temporaryID;
+			localEditAdapter.setOne(state.mapEdits, { ...emtpyMapMetadata, id: temporaryID });
 		}),
 		offerCurrentGPS: create.reducer<GPSPoint>((state, { payload }) => {
 			// Stripped offerCurrentGPS thunk into a single reducer for simplicity. The only missing functionality are the serverlogs
@@ -78,8 +78,8 @@ export const localEditsSlice = createThunkSlice({
 				}
 			}
 			// TODO VERIFY
-			const xValue = eligiblePoints[0]?.x as number;
-			const yValue = eligiblePoints[0]?.y as number;
+			const xValue = eligiblePoints[0].x as number;
+			const yValue = eligiblePoints[0].y as number;
 			const clickedPoint: CartesianPoint = {
 				x: Number(xValue.toFixed(6)),
 				y: Number(yValue.toFixed(6))

From 66ea48d526f46b91d1eb93120193f6c305db00cd Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Mon, 12 Aug 2024 21:58:46 -0700
Subject: [PATCH 44/50] Fix Imports

---
 .../app/components/maps/MapCalibrationInitiateComponent.tsx   | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
index c0e26acd8..dd6f0ba41 100644
--- a/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationInitiateComponent.tsx
@@ -5,12 +5,10 @@
 import * as React from 'react';
 import { ChangeEvent } from 'react';
 import { FormattedMessage } from 'react-intl';
-import { localEditsSlice } from '../../redux/slices/localEditsSlice';
 import { logsApi } from '../../redux/api/logApi';
-import { selectMapById } from '../../redux/api/mapsApi';
 import { useTranslate } from '../../redux/componentHooks';
 import { useAppDispatch, useAppSelector } from '../../redux/reduxHooks';
-import { selectSelectedMap } from '../../redux/slices/graphSlice';
+import { localEditsSlice } from '../../redux/slices/localEditsSlice';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
 import { showErrorNotification } from '../../utils/notifications';
 

From 095def8ebbfd70c33bbda724af36aa7319d558a9 Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Tue, 13 Aug 2024 09:08:52 -0700
Subject: [PATCH 45/50] MapChartSelect Crash Fix

---
 src/client/app/components/MapChartSelectComponent.tsx | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/src/client/app/components/MapChartSelectComponent.tsx b/src/client/app/components/MapChartSelectComponent.tsx
index 4504c1847..9c1aa0b47 100644
--- a/src/client/app/components/MapChartSelectComponent.tsx
+++ b/src/client/app/components/MapChartSelectComponent.tsx
@@ -34,11 +34,6 @@ export default function MapChartSelectComponent() {
 	const selectedMapData = useAppSelector(state => selectMapById(state, selectSelectedMap(state)));
 
 
-	const selectedMap = {
-		label: selectedMapData.name,
-		value: selectedMapData.id
-	};
-
 	//useIntl instead of injectIntl and WrappedComponentProps
 	const intl = useIntl();
 
@@ -51,7 +46,7 @@ export default function MapChartSelectComponent() {
 			<div style={divBottomPadding}>
 				<SingleSelectComponent
 					options={sortedMaps}
-					selectedOption={(selectedMap.value === 0) ? undefined : selectedMap}
+					selectedOption={selectedMapData ? { label: selectedMapData.name, value: selectedMapData.id } : undefined}
 					placeholder={intl.formatMessage(messages.selectMap)}
 					onValueChange={selected => dispatch(updateSelectedMaps(selected.value))}
 				/>

From 8a535056d7b22f0511a9896d8307c516b124ead5 Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Tue, 13 Aug 2024 09:09:18 -0700
Subject: [PATCH 46/50] Login Invalidation updated.

---
 src/client/app/redux/api/authApi.ts | 4 ++--
 src/client/app/redux/api/mapsApi.ts | 3 +--
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/client/app/redux/api/authApi.ts b/src/client/app/redux/api/authApi.ts
index d9186f41a..5d6ba6792 100644
--- a/src/client/app/redux/api/authApi.ts
+++ b/src/client/app/redux/api/authApi.ts
@@ -23,7 +23,7 @@ export const authApi = baseApi.injectEndpoints({
 			// next time the corresponding endpoint is queried, cache will be ignored and overwritten by a fresh query.
 			// in this case, a user logged in which means that some info for ADMIN meters groups etc.
 			// invalidate forces a refetch to any subscribed components or the next query.
-			invalidatesTags: ['MeterData', 'GroupData']
+			invalidatesTags: ['MeterData', 'GroupData', 'MapsData']
 		}),
 		verifyToken: builder.mutation<{ success: boolean }, string>({
 			query: token => ({
@@ -49,7 +49,7 @@ export const authApi = baseApi.injectEndpoints({
 				dispatch(currentUserSlice.actions.clearCurrentUser());
 				return { data: null };
 			},
-			invalidatesTags: ['MeterData', 'GroupData']
+			invalidatesTags: ['MeterData', 'GroupData', 'MapsData']
 		})
 	})
 });
diff --git a/src/client/app/redux/api/mapsApi.ts b/src/client/app/redux/api/mapsApi.ts
index 068142dcc..62e5325fb 100644
--- a/src/client/app/redux/api/mapsApi.ts
+++ b/src/client/app/redux/api/mapsApi.ts
@@ -59,9 +59,8 @@ export const mapsApi = baseApi.injectEndpoints({
 				api.queryFulfilled
 					// TODO Serverlogs migrate to rtk Query to drop axios?
 					// Requires dispatch so inconvenient
-					.then(e => {
+					.then(() => {
 						if (map.calibrationResult) {
-							const { data } = e;
 							// logToServer('info', 'New calibrated map uploaded to database');
 							showSuccessNotification(translate('upload.new.map.with.calibration'));
 						} else {

From b71527ade60c38845b7da7d48d776960aa0d6289 Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Tue, 13 Aug 2024 12:40:25 -0700
Subject: [PATCH 47/50] Cleanup Legacy Redux where applicable  - Legacy
 Actions/Creators deleted,  - unused actions deleted  - unused types deleted 
 - maps files purged.  - comments.

---
 .../app/components/BarChartComponent.tsx      |   2 +-
 .../app/components/DateRangeComponent.tsx     |   7 +-
 .../app/components/HeaderButtonsComponent.tsx |   2 +-
 .../app/components/HistoryComponent.tsx       |   2 +-
 .../app/components/LineChartComponent.tsx     |   2 +-
 .../app/components/PlotNavComponent.tsx       |   2 +-
 .../components/router/GraphLinkComponent.tsx  |   2 +-
 src/client/app/redux/actions/conversions.ts   | 135 -------
 src/client/app/redux/actions/extraActions.ts  |  14 -
 src/client/app/redux/actions/logs.ts          |   2 +
 src/client/app/redux/actions/map.ts           | 338 ------------------
 src/client/app/redux/listenerMiddleware.ts    |   2 +-
 .../middleware/graphHistoryMiddleware.ts      |   3 +-
 src/client/app/redux/reducers/maps.ts         | 246 -------------
 src/client/app/redux/slices/appStateSlice.ts  |   2 +-
 src/client/app/redux/slices/graphSlice.ts     |  18 +-
 .../app/redux/slices/localEditsSlice.ts       |   5 +-
 src/client/app/store.ts                       |  11 +-
 src/client/app/types/redux/actions.ts         |  49 ---
 src/client/app/types/redux/map.ts             | 102 +-----
 src/client/app/utils/api/LogsApi.ts           |   2 +
 src/client/app/utils/api/MapsApi.ts           |  40 ---
 src/client/app/utils/api/index.ts             |   3 -
 src/client/app/utils/calibration.ts           |   3 +-
 24 files changed, 42 insertions(+), 952 deletions(-)
 delete mode 100644 src/client/app/redux/actions/conversions.ts
 delete mode 100644 src/client/app/redux/actions/extraActions.ts
 delete mode 100644 src/client/app/redux/actions/map.ts
 delete mode 100644 src/client/app/redux/reducers/maps.ts
 delete mode 100644 src/client/app/types/redux/actions.ts
 delete mode 100644 src/client/app/utils/api/MapsApi.ts

diff --git a/src/client/app/components/BarChartComponent.tsx b/src/client/app/components/BarChartComponent.tsx
index 755509308..02379d0f4 100644
--- a/src/client/app/components/BarChartComponent.tsx
+++ b/src/client/app/components/BarChartComponent.tsx
@@ -8,7 +8,7 @@ import { PlotRelayoutEvent } from 'plotly.js';
 import * as React from 'react';
 import Plot from 'react-plotly.js';
 import { TimeInterval } from '../../../common/TimeInterval';
-import { updateSliderRange } from '../redux/actions/extraActions';
+import { updateSliderRange } from '../redux/slices/graphSlice';
 import { readingsApi, stableEmptyBarReadings } from '../redux/api/readingsApi';
 import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
 import { selectPlotlyBarDataFromResult, selectPlotlyBarDeps } from '../redux/selectors/barChartSelectors';
diff --git a/src/client/app/components/DateRangeComponent.tsx b/src/client/app/components/DateRangeComponent.tsx
index 22f7846ed..e4727abaf 100644
--- a/src/client/app/components/DateRangeComponent.tsx
+++ b/src/client/app/components/DateRangeComponent.tsx
@@ -9,9 +9,8 @@ import * as React from 'react';
 import 'react-calendar/dist/Calendar.css';
 import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
 import { selectSelectedLanguage } from '../redux/slices/appStateSlice';
-import { changeSliderRange, selectQueryTimeInterval, updateTimeInterval, selectChartToRender} from '../redux/slices/graphSlice';
+import { changeSliderRange, selectQueryTimeInterval, updateTimeInterval, selectChartToRender } from '../redux/slices/graphSlice';
 import '../styles/DateRangeCustom.css';
-import { Dispatch } from '../types/redux/actions';
 import { dateRangeToTimeInterval, timeIntervalToDateRange } from '../utils/dateRangeCompatibility';
 import translate from '../utils/translate';
 import TooltipMarkerComponent from './TooltipMarkerComponent';
@@ -22,11 +21,11 @@ import { ChartTypes } from '../types/redux/graph';
  * @returns Date Range Calendar Picker
  */
 export default function DateRangeComponent() {
-	const dispatch: Dispatch = useAppDispatch();
+	const dispatch = useAppDispatch();
 	const queryTimeInterval = useAppSelector(selectQueryTimeInterval);
 	const locale = useAppSelector(selectSelectedLanguage);
 	const chartType = useAppSelector(selectChartToRender);
-	const datePickerVisible =  chartType !== ChartTypes.compare;
+	const datePickerVisible = chartType !== ChartTypes.compare;
 
 	const handleChange = (value: Value) => {
 		dispatch(updateTimeInterval(dateRangeToTimeInterval(value)));
diff --git a/src/client/app/components/HeaderButtonsComponent.tsx b/src/client/app/components/HeaderButtonsComponent.tsx
index 39fda6430..03fe81fe7 100644
--- a/src/client/app/components/HeaderButtonsComponent.tsx
+++ b/src/client/app/components/HeaderButtonsComponent.tsx
@@ -8,7 +8,6 @@ import { FormattedMessage } from 'react-intl';
 import { Link, useLocation } from 'react-router-dom';
 import { DropdownItem, DropdownMenu, DropdownToggle, Nav, NavLink, Navbar, UncontrolledDropdown } from 'reactstrap';
 import TooltipHelpComponent from '../components/TooltipHelpComponent';
-import { clearGraphHistory } from '../redux/actions/extraActions';
 import { authApi } from '../redux/api/authApi';
 import { selectOEDVersion } from '../redux/api/versionApi';
 import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
@@ -19,6 +18,7 @@ import { UserRole } from '../types/items';
 import translate from '../utils/translate';
 import LanguageSelectorComponent from './LanguageSelectorComponent';
 import TooltipMarkerComponent from './TooltipMarkerComponent';
+import { clearGraphHistory } from '../redux/slices/graphSlice';
 
 /**
  * React Component that defines the header buttons at the top of a page
diff --git a/src/client/app/components/HistoryComponent.tsx b/src/client/app/components/HistoryComponent.tsx
index 3af439a63..7534ca6f2 100644
--- a/src/client/app/components/HistoryComponent.tsx
+++ b/src/client/app/components/HistoryComponent.tsx
@@ -5,7 +5,7 @@
 import * as React from 'react';
 import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
 import { selectForwardHistory, selectPrevHistory } from '../redux/slices/graphSlice';
-import { historyStepBack, historyStepForward } from '../redux/actions/extraActions';
+import { historyStepBack, historyStepForward } from '../redux/slices/graphSlice';
 import TooltipMarkerComponent from './TooltipMarkerComponent';
 
 /**
diff --git a/src/client/app/components/LineChartComponent.tsx b/src/client/app/components/LineChartComponent.tsx
index f71058629..ad9a9fc47 100644
--- a/src/client/app/components/LineChartComponent.tsx
+++ b/src/client/app/components/LineChartComponent.tsx
@@ -8,7 +8,7 @@ import { PlotRelayoutEvent } from 'plotly.js';
 import * as React from 'react';
 import Plot from 'react-plotly.js';
 import { TimeInterval } from '../../../common/TimeInterval';
-import { updateSliderRange } from '../redux/actions/extraActions';
+import { updateSliderRange } from '../redux/slices/graphSlice';
 import { readingsApi, stableEmptyLineReadings } from '../redux/api/readingsApi';
 import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
 import { selectLineChartQueryArgs } from '../redux/selectors/chartQuerySelectors';
diff --git a/src/client/app/components/PlotNavComponent.tsx b/src/client/app/components/PlotNavComponent.tsx
index 21e28cd60..bd7a07ddd 100644
--- a/src/client/app/components/PlotNavComponent.tsx
+++ b/src/client/app/components/PlotNavComponent.tsx
@@ -4,7 +4,7 @@
 
 import * as React from 'react';
 import { TimeInterval } from '../../../common/TimeInterval';
-import { clearGraphHistory } from '../redux/actions/extraActions';
+import { clearGraphHistory } from '../redux/slices/graphSlice';
 import { useAppDispatch, useAppSelector } from '../redux/reduxHooks';
 import { selectAnythingFetching } from '../redux/selectors/apiSelectors';
 import {
diff --git a/src/client/app/components/router/GraphLinkComponent.tsx b/src/client/app/components/router/GraphLinkComponent.tsx
index d60ccb312..4efa2811d 100644
--- a/src/client/app/components/router/GraphLinkComponent.tsx
+++ b/src/client/app/components/router/GraphLinkComponent.tsx
@@ -6,8 +6,8 @@ import * as React from 'react';
 import { Navigate, useSearchParams } from 'react-router-dom';
 import { useWaitForInit } from '../../redux/componentHooks';
 import { useAppDispatch } from '../../redux/reduxHooks';
-import { processGraphLink } from '../../redux/actions/extraActions';
 import InitializingComponent from '../router/InitializingComponent';
+import { processGraphLink } from '../../redux/slices/graphSlice';
 
 export const GraphLink = () => {
 	const dispatch = useAppDispatch();
diff --git a/src/client/app/redux/actions/conversions.ts b/src/client/app/redux/actions/conversions.ts
deleted file mode 100644
index 782cb48f6..000000000
--- a/src/client/app/redux/actions/conversions.ts
+++ /dev/null
@@ -1,135 +0,0 @@
-/* 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/. */
-
-/* eslint-disable jsdoc/check-param-names */
-/* eslint-disable @typescript-eslint/ban-ts-comment */
-/* eslint-disable @typescript-eslint/no-unused-vars */
-// @ts-nocheck
-/* eslint-disable jsdoc/require-param */
-
-import { Thunk, Dispatch, GetState } from '../../types/redux/actions';
-import { showSuccessNotification, showErrorNotification } from '../../utils/notifications';
-import translate from '../../utils/translate';
-import * as t from '../../types/redux/conversions';
-import { conversionsApi } from '../../utils/api';
-import { updateCikAndDBViewsIfNeeded } from './admin';
-import { conversionsSlice } from '../reducers/conversions';
-
-
-export function fetchConversionsDetails(): Thunk {
-	return async (dispatch: Dispatch, getState: GetState) => {
-		// ensure a fetch is not currently happening
-		if (!getState().conversions.isFetching) {
-			// set isFetching to true
-			dispatch(conversionsSlice.actions.requestConversionsDetails());
-			// attempt to retrieve conversions details from database
-			const conversions = await conversionsApi.getConversionsDetails();
-			// update the state with the conversions details and set isFetching to false
-			dispatch(conversionsSlice.actions.receiveConversionsDetails(conversions));
-			// If this is the first fetch, inform the store that the first fetch has been made
-			if (!getState().conversions.hasBeenFetchedOnce) {
-				dispatch(conversionsSlice.actions.conversionsFetchedOnce());
-			}
-		}
-	};
-}
-
-
-/**
- * Fetch the conversions details from the database if they have not already been fetched once
- */
-export function fetchConversionsDetailsIfNeeded(): Thunk {
-	return (dispatch: Dispatch, getState: GetState) => {
-		// If conversions have not been fetched once, return the fetchConversionDetails function
-		if (!getState().conversions.hasBeenFetchedOnce) {
-			return dispatch(fetchConversionsDetails());
-		}
-		// If conversions have already been fetched, return a resolved promise
-		return Promise.resolve();
-	};
-}
-
-export function submitEditedConversion(editedConversion: t.ConversionData, shouldRedoCik: boolean): Thunk {
-	return async (dispatch: Dispatch, getState: GetState) => {
-		// check if conversionData is already submitting (indexOf returns -1 if item does not exist in array)
-
-		// Search the array of ConversionData in submitting for an object with source/destination ids matching that editedConversion
-		const conversionDataIndex = getState().conversions.submitting.findIndex(conversionData => ((
-			conversionData.sourceId === editedConversion.sourceId) &&
-			conversionData.destinationId === editedConversion.destinationId));
-
-		// If the editedConversion is not already being submitted
-		if (conversionDataIndex === -1) {
-			// Inform the store we are about to edit the passed in conversion
-			// Pushes edited conversionData to submit onto the submitting state array
-			dispatch(conversionsSlice.actions.submitEditedConversion(editedConversion));
-			// Attempt to edit the conversion in the database
-			try {
-				// posts the edited conversionData to the conversions API
-				await conversionsApi.edit(editedConversion);
-				dispatch(updateCikAndDBViewsIfNeeded(shouldRedoCik, false));
-				// Update the store with our new edits
-				dispatch(conversionsSlice.actions.confirmEditedConversion(editedConversion));
-				// Success!
-				showSuccessNotification(translate('conversion.successfully.edited.conversion'));
-			} catch (err) {
-				// Failure! ):
-				showErrorNotification(translate('conversion.failed.to.edit.conversion') + ' "' + err.response.data as string + '"');
-			}
-			// Clear conversionData object from submitting state array
-			dispatch(conversionsSlice.actions.deleteSubmittedConversion(editedConversion));
-		}
-	};
-}
-
-// Add conversion to database
-export function addConversion(conversion: t.ConversionData): Dispatch {
-	return async (dispatch: Dispatch) => {
-		try {
-			// Attempt to add conversion to database
-			await conversionsApi.addConversion(conversion);
-			// Adding a new conversion only affects the Cik table
-			dispatch(updateCikAndDBViewsIfNeeded(true, false));
-			// Update the conversions state from the database on a successful call
-			// In the future, getting rid of this database fetch and updating the store on a successful API call would make the page faster
-			// However, since the database currently assigns the id to the ConversionData we fetch from DB.
-			dispatch(fetchConversionsDetails());
-			showSuccessNotification(translate('conversion.successfully.create.conversion'));
-		} catch (err) {
-			showErrorNotification(translate('conversion.failed.to.create.conversion') + ' "' + err.response.data as string + '"');
-		}
-	};
-}
-
-// Delete conversion from database
-export function deleteConversion(conversion: t.ConversionData): (dispatch: Dispatch, getState: GetState) => Promise<void> {
-	return async (dispatch: Dispatch, getState: GetState) => {
-		// Ensure the conversion is not already being worked on
-		// Search the array of ConversionData in submitting for an object with source/destination ids matching that conversion
-		const conversionDataIndex = getState().conversions.submitting.findIndex(conversionData => ((
-			conversionData.sourceId === conversion.sourceId) &&
-			conversionData.destinationId === conversion.destinationId));
-
-		// If the conversion is not already being worked on
-		if (conversionDataIndex === -1) {
-			// Inform the store we are about to work on this conversion
-			// Update the submitting state array
-			dispatch(conversionsSlice.actions.submitEditedConversion(conversion));
-			try {
-				// Attempt to delete the conversion from the database
-				await conversionsApi.delete(conversion);
-				// Deleting a conversion only affects the Cik table
-				dispatch(updateCikAndDBViewsIfNeeded(true, false));
-				// Delete was successful
-				// Update the store to match
-				dispatch(conversionsSlice.actions.confirmEditedConversion(conversion));
-				showSuccessNotification(translate('conversion.successfully.delete.conversion'));
-			} catch (err) {
-				showErrorNotification(translate('conversion.failed.to.delete.conversion') + ' "' + err.response.data as string + '"');
-			}
-			// Inform the store we are done working with the conversion
-			dispatch(conversionsSlice.actions.deleteSubmittedConversion(conversion));
-		}
-	};
-}
diff --git a/src/client/app/redux/actions/extraActions.ts b/src/client/app/redux/actions/extraActions.ts
deleted file mode 100644
index 5bac9de82..000000000
--- a/src/client/app/redux/actions/extraActions.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/* 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/. */
-
-import { createAction } from '@reduxjs/toolkit';
-import { GraphState } from 'types/redux/graph';
-import { TimeInterval } from '../../../../common/TimeInterval';
-
-export const historyStepBack = createAction('graph/historyStepBack');
-export const historyStepForward = createAction('graph/historyStepForward');
-export const updateHistory = createAction<GraphState>('graph/updateHistory');
-export const processGraphLink = createAction<URLSearchParams>('graph/graphLink');
-export const clearGraphHistory = createAction('graph/clearHistory');
-export const updateSliderRange = createAction<TimeInterval>('graph/UpdateSliderRange');
diff --git a/src/client/app/redux/actions/logs.ts b/src/client/app/redux/actions/logs.ts
index 318becdbe..4c366f29a 100644
--- a/src/client/app/redux/actions/logs.ts
+++ b/src/client/app/redux/actions/logs.ts
@@ -11,6 +11,8 @@ import { logsApi } from '../../utils/api';
  * @param error An optional error object to provide a stacktrace
  * @param skipMail Don't e-mail this message even if we would normally emit an e-mail for this level.
  * @returns logs to server based on level
+ * TODO migrate to using RTKQuery for logging.
+ * This will require logging to be initiated via dispatch, which differs, and conflicts with current implementation and usage.
  */
 export function logToServer(level: string, message: string, error?: Error, skipMail?: boolean) {
 	const log: LogData = {
diff --git a/src/client/app/redux/actions/map.ts b/src/client/app/redux/actions/map.ts
deleted file mode 100644
index ae5e0a5d3..000000000
--- a/src/client/app/redux/actions/map.ts
+++ /dev/null
@@ -1,338 +0,0 @@
-/* eslint-disable @typescript-eslint/ban-ts-comment */
-// @ts-ignore
-// @ts-nocheck
-/* 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/. */
-
-// TODO: Migrate to RTK
-
-import * as moment from 'moment';
-import { ActionType, Dispatch, GetState, Thunk } from '../../types/redux/actions';
-import * as t from '../../types/redux/map';
-import { CalibrationModeTypes, MapData, MapMetadata } from '../../types/redux/map';
-import ApiBackend from '../../utils/api/ApiBackend';
-import MapsApi from '../../utils/api/MapsApi';
-import { calibrate, CalibratedPoint, CalibrationResult, CartesianPoint, GPSPoint } from '../../utils/calibration';
-import { browserHistory } from '../../utils/history';
-import { showErrorNotification, showSuccessNotification } from '../../utils/notifications';
-import translate from '../../utils/translate';
-import { logToServer } from './logs';
-import { RootState } from 'store';
-
-const mapsApi = new MapsApi(new ApiBackend());
-
-function requestMapsDetails(): t.RequestMapsDetailsAction {
-	return { type: ActionType.RequestMapsDetails };
-}
-
-function receiveMapsDetails(data: MapData[]): t.ReceiveMapsDetailsAction {
-	return { type: ActionType.ReceiveMapsDetails, data };
-}
-
-function submitMapEdits(mapID: number): t.SubmitEditedMapAction {
-	return { type: ActionType.SubmitEditedMap, mapID };
-}
-
-function confirmMapEdits(mapID: number): t.ConfirmEditedMapAction {
-	return { type: ActionType.ConfirmEditedMap, mapID };
-}
-
-export function fetchMapsDetails(): Thunk {
-	return async (dispatch: Dispatch) => {
-		dispatch(requestMapsDetails());
-		try {
-			const mapsDetails = await mapsApi.details();
-			dispatch(receiveMapsDetails(mapsDetails));
-		} catch (error) {
-			showErrorNotification(translate('failed.to.fetch.maps'));
-		}
-	};
-}
-
-export function editMapDetails(map: MapMetadata): t.EditMapDetailsAction {
-	return { type: ActionType.EditMapDetails, map };
-}
-
-function incrementCounter(): t.IncrementCounterAction {
-	return { type: ActionType.IncrementCounter };
-}
-
-export function setNewMap(): Thunk {
-	return async (dispatch: Dispatch) => {
-		dispatch(incrementCounter());
-		dispatch((dispatch2: Dispatch, getState2: GetState) => {
-			const temporaryID = getState2().maps.newMapCounter * -1;
-			dispatch2(logToServer('info', `Set up new map, id = ${temporaryID}`));
-			dispatch2(setCalibration(CalibrationModeTypes.initiate, temporaryID));
-		});
-	};
-}
-
-/**
- * start a new calibration session
- * @param mode calibration modes
- * @param mapID id of map being calibrated
- */
-export function setCalibration(mode: CalibrationModeTypes, mapID: number): Thunk {
-	return async (dispatch: Dispatch) => {
-		dispatch(prepareCalibration(mode, mapID));
-		dispatch((dispatch2: Dispatch) => {
-			dispatch2(logToServer('info', `Start Calibration for map, id=${mapID}, mode:${mode}`));
-		});
-	};
-}
-
-export function prepareCalibration(mode: CalibrationModeTypes, mapID: number): t.SetCalibrationAction {
-	return { type: ActionType.SetCalibration, mode, mapID };
-}
-
-/**
- * toggle display of grids during calibration
- */
-export function changeGridDisplay(): t.ChangeGridDisplayAction {
-	return { type: ActionType.ChangeGridDisplay };
-}
-
-/**
- * drop present calibration session in a traceable manner
- */
-export function dropCalibration(): Thunk {
-	return async (dispatch: Dispatch, getState: GetState) => {
-		const mapToReset = getState().maps.calibratingMap;
-		dispatch(resetCalibration(mapToReset));
-		dispatch((dispatch2: Dispatch) => {
-			dispatch2(logToServer('info', `reset calibration for map, id: ${mapToReset}.`));
-		});
-	};
-}
-
-function resetCalibration(mapToReset: number): t.ResetCalibrationAction {
-	return { type: ActionType.ResetCalibration, mapID: mapToReset };
-}
-
-export function updateMapSource(data: MapMetadata): t.UpdateMapSourceAction {
-	return { type: ActionType.UpdateMapSource, data };
-}
-
-export function updateMapMode(nextMode: CalibrationModeTypes): t.ChangeMapModeAction {
-	return { type: ActionType.UpdateCalibrationMode, nextMode };
-}
-
-/**
- * Changes the selected map ID
- * @param newSelectedMapID new map ID
- */
-export function changeSelectedMap(newSelectedMapID: number): t.UpdateSelectedMapAction {
-	return { type: ActionType.UpdateSelectedMap, mapID: newSelectedMapID };
-}
-
-export function updateCurrentCartesian(currentCartesian: CartesianPoint): t.UpdateCurrentCartesianAction {
-	return { type: ActionType.UpdateCurrentCartesian, currentCartesian };
-}
-
-/**
- * pair collected GPS coordinate with cartesian coordinate to form a complete data point,
- * append to calibration set and trigger calibration if needed
- * @param currentGPS GPS data, from user input
- */
-export function offerCurrentGPS(currentGPS: GPSPoint): Thunk {
-	return (dispatch: Dispatch, getState: GetState) => {
-		const mapID = getState().localEdits.calibratingMap;
-		const point = getState().localEdits.mapEdits.entities[mapID].currentPoint;
-		if (point && hasCartesian(point)) {
-			point.gps = currentGPS;
-			dispatch(updateCalibrationSet(point));
-			dispatch(resetCurrentPoint());
-			// Nesting dispatches to preserve that updateCalibrationSet() is called before calibration
-			dispatch(async (dispatch2: Dispatch) => {
-				dispatch2(logToServer('info', `gps input (lat:${currentGPS.latitude},long:${currentGPS.longitude})
-				provided for cartesian point:${point.cartesian.x},${point.cartesian.y}
-				and added to data point`));
-				if (isReadyForCalculation(getState())) {
-					const result = prepareDataToCalculation(getState());
-					dispatch2(updateResult(result));
-					dispatch2(logToServer('info', `calculation complete, maxError: x:${result.maxError.x},y:${result.maxError.y},
-					origin:${result.origin.latitude},${result.origin.longitude}, opposite:${result.opposite.latitude},${result.opposite.longitude}`));
-				} else {
-					dispatch2(logToServer('info', 'threshold not met, didn\'t trigger calibration'));
-				}
-			});
-		}
-		return Promise.resolve();
-	};
-}
-
-export function hasCartesian(point: CalibratedPoint) {
-	return point.cartesian.x !== -1 && point.cartesian.y !== -1;
-}
-
-function updateCalibrationSet(calibratedPoint: CalibratedPoint): t.AppendCalibrationSetAction {
-	return { type: ActionType.AppendCalibrationSet, calibratedPoint };
-}
-
-/**
- * use a default number as the threshold in determining if it's safe to call the calibration function
- * @param state The redux state
- * @returns Result of safety check
- */
-function isReadyForCalculation(state: RootState): boolean {
-	const calibrationThreshold = 3;
-	// assume calibrationSet is defined, as offerCurrentGPS indicates through point that the map is defined.
-	return state.localEdits.mapEdits.entities[state.localEdits.calibratingMap].calibrationSet.length >= calibrationThreshold;
-}
-
-/**
- *  prepare data to required formats to pass it to function calculating mapScales
- * @param state The redux state
- * @returns Result of map calibration
- */
-function prepareDataToCalculation(state: RootState): CalibrationResult {
-	// TODO FIX BEFORE PR
-	const mapID = state.localEdits.calibratingMap;
-	const mp = state.localEdits.mapEdits.entities[mapID];
-	// Since mp is defined above, calibrationSet is defined.
-	/* eslint-disable @typescript-eslint/no-non-null-assertion */
-	const result = calibrate(mp);
-	return result;
-	/* eslint-enable @typescript-eslint/no-non-null-assertion */
-}
-
-function updateResult(result: CalibrationResult): t.UpdateCalibrationResultAction {
-	return { type: ActionType.UpdateCalibrationResults, result };
-}
-
-export function resetCurrentPoint(): t.ResetCurrentPointAction {
-	return { type: ActionType.ResetCurrentPoint };
-}
-
-export function submitEditedMaps(): Thunk {
-	return async (dispatch: Dispatch, getState: GetState) => {
-		Object.keys(getState().maps.editedMaps).forEach(mapID2Submit => {
-			const mapID = parseInt(mapID2Submit);
-			if (getState().maps.submitting.indexOf(mapID) === -1) {
-				dispatch(submitEditedMap(mapID));
-			}
-		});
-	};
-}
-
-export function submitCalibratingMap(): Thunk {
-	return async (dispatch: Dispatch, getState: GetState) => {
-		const mapID = getState().maps.calibratingMap;
-		if (mapID < 0) {
-			dispatch(submitNewMap());
-		} else {
-			dispatch(submitEditedMap(mapID));
-		}
-	};
-}
-
-/**
- * submit a new map to database at the end of a calibration session
- */
-export function submitNewMap(): Thunk {
-	return async (dispatch: Dispatch, getState: GetState) => {
-		const mapID = getState().maps.calibratingMap;
-		const map = getState().maps.editedMaps[mapID];
-		try {
-			const acceptableMap: MapData = {
-				...map,
-				mapSource: map.mapSource,
-				displayable: false,
-				modifiedDate: moment().toISOString(),
-				origin: map.calibrationResult?.origin,
-				opposite: map.calibrationResult?.opposite
-			};
-			await mapsApi.create(acceptableMap);
-			if (map.calibrationResult) {
-				dispatch(logToServer('info', 'New calibrated map uploaded to database'));
-				showSuccessNotification(translate('upload.new.map.with.calibration'));
-			} else {
-				dispatch(logToServer('info', 'New map uploaded to database(without calibration)'));
-				showSuccessNotification(translate('upload.new.map.without.calibration'));
-			}
-			dispatch(confirmMapEdits(mapID));
-			browserHistory.push('/maps');
-		} catch (e) {
-			showErrorNotification(translate('failed.to.create.map'));
-			dispatch(logToServer('error', `failed to create map, ${e}`));
-		}
-	};
-}
-
-/**
- * submit changes of an existing map to database at the end of a calibration session
- * @param mapID the edited map being updated at database
- */
-export function submitEditedMap(mapID: number): Thunk {
-	return async (dispatch: Dispatch, getState: GetState) => {
-		const map = getState().maps.editedMaps[mapID];
-		dispatch(submitMapEdits(mapID));
-		try {
-			const acceptableMap: MapData = {
-				...map,
-				mapSource: map.mapSource,
-				// As in other place, this take the time, in this case the current time, grabs the
-				// date and time without timezone and then set it to UTC. This allows the software
-				// to recreate it with the same date/time as it is on this web browser when it is
-				// displayed later (without the timezone shown).
-				// It might be better to use the server time but this is good enough.
-				modifiedDate: moment().format('YYYY-MM-DD HH:mm:ss') + '+00:00',
-				origin: map.calibrationResult?.origin,
-				opposite: map.calibrationResult?.opposite,
-				circleSize: map.circleSize
-			};
-			await mapsApi.edit(acceptableMap);
-			if (map.calibrationResult) {
-				dispatch(logToServer('info', 'Edited map uploaded to database(newly calibrated)'));
-				showSuccessNotification(translate('updated.map.with.calibration'));
-			} else if (map.origin && map.opposite) {
-				dispatch(logToServer('info', 'Edited map uploaded to database(calibration not updated)'));
-				showSuccessNotification(translate('updated.map.without.new.calibration'));
-			} else {
-				dispatch(logToServer('info', 'Edited map uploaded to database(without calibration)'));
-				showSuccessNotification(translate('updated.map.without.calibration'));
-			}
-			dispatch(confirmMapEdits(mapID));
-		} catch (err) {
-			showErrorNotification(translate('failed.to.edit.map'));
-			dispatch(logToServer('error', `failed to edit map, ${err}`));
-		}
-	};
-}
-
-/**
- * permanently remove a map
- * @param mapID map to be removed
- */
-export function removeMap(mapID: number): Thunk {
-	return async (dispatch: Dispatch) => {
-		try {
-			await mapsApi.delete(mapID);
-			dispatch(deleteMap(mapID));
-			dispatch(logToServer('info', `Deleted map, id = ${mapID}`));
-			showSuccessNotification(translate('map.is.deleted'));
-			browserHistory.push('/maps');
-		} catch (err) {
-			showErrorNotification(translate('failed.to.delete.map'));
-			dispatch(logToServer('error', `Failed to delete map, id = ${mapID}, ${err}`));
-		}
-	};
-}
-
-function deleteMap(mapID: number): t.DeleteMapAction {
-	return { type: ActionType.DeleteMap, mapID };
-}
-
-/**
- * Remove all the maps in editing without submitting them
- */
-export function confirmEditedMaps() {
-	return async (dispatch: Dispatch, getState: GetState) => {
-		Object.keys(getState().maps.editedMaps).forEach(mapID2Submit => {
-			const mapID = parseInt(mapID2Submit);
-			dispatch(confirmMapEdits(mapID));
-		});
-	};
-}
diff --git a/src/client/app/redux/listenerMiddleware.ts b/src/client/app/redux/listenerMiddleware.ts
index 1f2b65643..3e9bdafc0 100644
--- a/src/client/app/redux/listenerMiddleware.ts
+++ b/src/client/app/redux/listenerMiddleware.ts
@@ -11,7 +11,7 @@ import { unauthorizedRequestListener } from './middleware/unauthorizedAccesMiddl
 
 export const listenerMiddleware = createListenerMiddleware();
 
-export const startAppListening = listenerMiddleware.startListening.withTypes< RootState, AppDispatch>();
+export const startAppListening = listenerMiddleware.startListening.withTypes<RootState, AppDispatch>();
 export const addAppListener = addListener.withTypes<RootState, AppDispatch>();
 export type AppListener = typeof startAppListening;
 
diff --git a/src/client/app/redux/middleware/graphHistoryMiddleware.ts b/src/client/app/redux/middleware/graphHistoryMiddleware.ts
index 36ad789bf..639a89975 100644
--- a/src/client/app/redux/middleware/graphHistoryMiddleware.ts
+++ b/src/client/app/redux/middleware/graphHistoryMiddleware.ts
@@ -4,8 +4,7 @@
 
 import { isAnyOf } from '@reduxjs/toolkit';
 import { AppListener } from '../listenerMiddleware';
-import { graphSlice } from '../slices/graphSlice';
-import { updateHistory } from '../../redux/actions/extraActions';
+import { graphSlice, updateHistory } from '../slices/graphSlice';
 
 export const graphHistoryListener = (startListening: AppListener) => {
 	startListening({
diff --git a/src/client/app/redux/reducers/maps.ts b/src/client/app/redux/reducers/maps.ts
deleted file mode 100644
index 3cd683941..000000000
--- a/src/client/app/redux/reducers/maps.ts
+++ /dev/null
@@ -1,246 +0,0 @@
-/* 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/. */
-
-// TODO: Migrate to RTK
-
-import { MapMetadata, MapsAction, MapState } from '../../types/redux/map';
-import { ActionType } from '../../types/redux/actions';
-import { keyBy } from 'lodash';
-import { CalibratedPoint } from '../../utils/calibration';
-
-const defaultState: MapState = {
-	isLoading: false,
-	byMapID: {},
-	selectedMap: 0,
-	calibratingMap: 0,
-	editedMaps: {},
-	submitting: [],
-	newMapCounter: 0,
-	calibrationSettings: { showGrid: false }
-};
-
-// eslint-disable-next-line jsdoc/require-jsdoc
-export default function maps(state = defaultState, action: MapsAction) {
-	let submitting;
-	let editedMaps;
-	let byMapID;
-	const calibrated = state.calibratingMap;
-	switch (action.type) {
-		case ActionType.UpdateCalibrationMode:
-			return {
-				...state,
-				editedMaps: {
-					...state.editedMaps,
-					[calibrated]: {
-						...state.editedMaps[calibrated],
-						calibrationMode: action.nextMode
-					}
-				}
-			};
-		case ActionType.UpdateSelectedMap:
-			return {
-				...state,
-				selectedMap: action.mapID
-			};
-		case ActionType.RequestMapsDetails:
-			return {
-				...state,
-				isLoading: true
-			};
-		case ActionType.ReceiveMapsDetails: {
-			const data: MapMetadata[] = action.data.map(mapData => {
-				// parse JSON format to MapMetadata object
-				const parsedData = JSON.parse(JSON.stringify(mapData));
-				parsedData.image = new Image();
-				parsedData.image.src = parsedData.mapSource;
-				return parsedData;
-			});
-			return {
-				...state,
-				isLoading: false,
-				byMapID: keyBy(data, map => map.id)
-			};
-		}
-		case ActionType.IncrementCounter: {
-			const counter = state.newMapCounter;
-			return {
-				...state,
-				newMapCounter: counter + 1
-			};
-		}
-		case ActionType.SetCalibration:
-			byMapID = state.byMapID;
-			// if the map is freshly created, just add a new instance into editedMaps
-			if (action.mapID < 0) {
-				return {
-					...state,
-					calibratingMap: action.mapID,
-					editedMaps: {
-						...state.editedMaps,
-						[action.mapID]: {
-							id: action.mapID,
-							calibrationMode: action.mode
-						}
-					}
-				};
-			} else if (state.editedMaps[action.mapID] === undefined) {
-				return {
-					...state,
-					calibratingMap: action.mapID,
-					editedMaps: {
-						...state.editedMaps,
-						[action.mapID]: {
-							// copy map from byMapID to editedMaps if there is not already a dirty map(with unsaved changes) in editedMaps
-							...state.byMapID[action.mapID],
-							calibrationMode: action.mode
-						}
-					}
-				};
-			} else {
-				return {
-					...state,
-					calibratingMap: action.mapID,
-					editedMaps: {
-						...state.editedMaps,
-						[action.mapID]: {
-							...state.editedMaps[action.mapID],
-							calibrationMode: action.mode
-						}
-					}
-				};
-			}
-		case ActionType.ChangeGridDisplay:
-			return {
-				...state,
-				calibrationSettings: {
-					...state.calibrationSettings,
-					showGrid: !state.calibrationSettings.showGrid
-				}
-			};
-		case ActionType.ResetCalibration: {
-			editedMaps = state.editedMaps;
-			const mapToReset = { ...editedMaps[action.mapID] };
-			delete mapToReset.currentPoint;
-			delete mapToReset.calibrationResult;
-			// TODO FIX mapsDataFetch
-			// delete mapToReset.calibrationSet;
-			return {
-				...state,
-				editedMaps: {
-					...state.editedMaps,
-					[calibrated]: mapToReset
-				}
-			};
-		}
-		case ActionType.UpdateMapSource:
-			return {
-				...state,
-				editedMaps: {
-					...state.editedMaps,
-					[action.data.id]: {
-						...action.data
-					}
-				}
-			};
-		case ActionType.EditMapDetails:
-			editedMaps = state.editedMaps;
-			editedMaps[action.map.id] = action.map;
-			return {
-				...state,
-				editedMaps
-			};
-		case ActionType.SubmitEditedMap:
-			submitting = state.submitting;
-			submitting.push(action.mapID);
-			return {
-				...state,
-				submitting
-			};
-		case ActionType.ConfirmEditedMap:
-			submitting = state.submitting;
-			submitting.splice(submitting.indexOf(action.mapID), 1);
-			byMapID = state.byMapID;
-			editedMaps = state.editedMaps;
-			if (action.mapID > 0) {
-				byMapID[action.mapID] = { ...editedMaps[action.mapID] };
-			}
-			delete editedMaps[action.mapID];
-			return {
-				...state,
-				calibratingMap: 0,
-				submitting,
-				editedMaps,
-				byMapID
-			};
-		case ActionType.DeleteMap:
-			editedMaps = state.editedMaps;
-			delete editedMaps[action.mapID];
-			byMapID = state.byMapID;
-			delete byMapID[action.mapID];
-			return {
-				...state,
-				editedMaps,
-				byMapID
-			};
-		case ActionType.UpdateCurrentCartesian: {
-			const newDataPoint: CalibratedPoint = {
-				cartesian: action.currentCartesian,
-				gps: { longitude: -1, latitude: -1 }
-			};
-			return {
-				...state,
-				editedMaps: {
-					...state.editedMaps,
-					[calibrated]: {
-						...state.editedMaps[calibrated],
-						currentPoint: newDataPoint
-					}
-				}
-			};
-		}
-		case ActionType.ResetCurrentPoint:
-			return {
-				...state,
-				editedMaps: {
-					...state.editedMaps,
-					[calibrated]: {
-						...state.editedMaps[calibrated],
-						currentPoint: undefined
-					}
-				}
-			};
-		case ActionType.AppendCalibrationSet: {
-			const originalSet = state.editedMaps[calibrated].calibrationSet;
-			let copiedSet;
-			if (originalSet) {
-				copiedSet = originalSet.map(point => point);
-				copiedSet.push(action.calibratedPoint);
-			} else {
-				copiedSet = [action.calibratedPoint];
-			}
-			return {
-				...state,
-				editedMaps: {
-					...state.editedMaps,
-					[calibrated]: {
-						...state.editedMaps[calibrated],
-						calibrationSet: copiedSet
-					}
-				}
-			};
-		}
-		case ActionType.UpdateCalibrationResults:
-			return {
-				...state,
-				editedMaps: {
-					[calibrated]: {
-						...state.editedMaps[calibrated],
-						calibrationResult: action.result
-					}
-				}
-			};
-		default:
-			return state;
-	}
-}
diff --git a/src/client/app/redux/slices/appStateSlice.ts b/src/client/app/redux/slices/appStateSlice.ts
index 547249212..62aa982d2 100644
--- a/src/client/app/redux/slices/appStateSlice.ts
+++ b/src/client/app/redux/slices/appStateSlice.ts
@@ -3,7 +3,7 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import * as moment from 'moment';
-import { processGraphLink } from '../../redux/actions/extraActions';
+import { processGraphLink } from '../../redux/slices/graphSlice';
 import { mapsApi } from '../../redux/api/mapsApi';
 import { LanguageTypes } from '../../types/redux/i18n';
 import { getToken, hasToken } from '../../utils/token';
diff --git a/src/client/app/redux/slices/graphSlice.ts b/src/client/app/redux/slices/graphSlice.ts
index 8f83cacc2..f03ff599d 100644
--- a/src/client/app/redux/slices/graphSlice.ts
+++ b/src/client/app/redux/slices/graphSlice.ts
@@ -2,16 +2,11 @@
  * 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 { PayloadAction, createSlice } from '@reduxjs/toolkit';
+import { PayloadAction, createAction, createSlice } from '@reduxjs/toolkit';
 import { cloneDeep } from 'lodash';
 import * as moment from 'moment';
 import { ActionMeta } from 'react-select';
 import { TimeInterval } from '../../../../common/TimeInterval';
-import {
-	clearGraphHistory, historyStepBack,
-	historyStepForward, processGraphLink,
-	updateHistory, updateSliderRange
-} from '../../redux/actions/extraActions';
 import { SelectOption } from '../../types/items';
 import { ChartTypes, GraphState, LineGraphRate, MeterOrGroup, ReadingInterval } from '../../types/redux/graph';
 import { ComparePeriod, SortingOrder, calculateCompareTimeInterval, validateComparePeriod, validateSortingOrder } from '../../utils/calculateCompare';
@@ -453,3 +448,14 @@ export const {
 	updateSelectedMaps
 } = graphSlice.actions;
 
+
+// Defined as External Reducers for middleware history implementation.
+// Extenrally defined actions to be acted upon in 'graphSlice.extraReducers'
+export const historyStepBack = createAction('graph/historyStepBack');
+export const historyStepForward = createAction('graph/historyStepForward');
+export const updateHistory = createAction<GraphState>('graph/updateHistory');
+export const processGraphLink = createAction<URLSearchParams>('graph/graphLink');
+export const clearGraphHistory = createAction('graph/clearHistory');
+export const updateSliderRange = createAction<TimeInterval>('graph/UpdateSliderRange');
+
+
diff --git a/src/client/app/redux/slices/localEditsSlice.ts b/src/client/app/redux/slices/localEditsSlice.ts
index 78023c57b..e57c667b8 100644
--- a/src/client/app/redux/slices/localEditsSlice.ts
+++ b/src/client/app/redux/slices/localEditsSlice.ts
@@ -1,6 +1,5 @@
 import { createEntityAdapter } from '@reduxjs/toolkit';
 import { PlotMouseEvent } from 'plotly.js';
-import { hasCartesian } from '../../redux/actions/map';
 import { createThunkSlice } from '../../redux/sliceCreators';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
 import { calibrate, CalibratedPoint, CartesianPoint, GPSPoint } from '../../utils/calibration';
@@ -131,4 +130,8 @@ export const emtpyMapMetadata: MapMetadata = {
 	calibrationResult: undefined,
 	northAngle: 0,
 	circleSize: 0
+};
+
+const hasCartesian = (point: CalibratedPoint) => {
+	return point.cartesian.x !== -1 && point.cartesian.y !== -1;
 };
\ No newline at end of file
diff --git a/src/client/app/store.ts b/src/client/app/store.ts
index 10d7f9a2d..c59cf4828 100644
--- a/src/client/app/store.ts
+++ b/src/client/app/store.ts
@@ -8,7 +8,6 @@ import { baseApi } from './redux/api/baseApi';
 import { devToolsConfig } from './redux/devToolConfig';
 import { listenerMiddleware } from './redux/listenerMiddleware';
 import { rootReducer } from './redux/rootReducer';
-import { Dispatch } from './types/redux/actions';
 
 export const store = configureStore({
 	reducer: rootReducer,
@@ -28,6 +27,10 @@ setGlobalDevModeChecks({ inputStabilityCheck: 'always', identityFunctionCheck: '
 // https://react-redux.js.org/using-react-redux/usage-with-typescript#define-root-state-and-dispatch-types
 export type RootState = ReturnType<typeof store.getState>
 export type AppDispatch = typeof store.dispatch
-	// Adding old dispatch definition for backwards compatibility with useAppDispatch and older style thunks
-	// TODO eventually move away and delete Dispatch Type entirely
-	& Dispatch
\ No newline at end of file
+
+
+/**
+ * The type of the redux-thunk getState function.
+ * TODO verify if applicable to RTK (should be? for getState in RTKQ lifecycle thunks, and CreateAsyncThunk?)
+ */
+export type GetState = () => RootState;
diff --git a/src/client/app/types/redux/actions.ts b/src/client/app/types/redux/actions.ts
deleted file mode 100644
index d72af3afc..000000000
--- a/src/client/app/types/redux/actions.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/* 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/. */
-
-import { Action } from 'redux';
-import { ThunkAction, ThunkDispatch } from 'redux-thunk';
-import { RootState } from 'store';
-
-export enum ActionType {
-
-	UpdateUnsavedChanges = 'UPDATE_UNSAVED_CHANGES',
-	RemoveUnsavedChanges = 'REMOVE_UNSAVED_CHANGES',
-	FlipLogOutState = 'FLIP_LOG_OUT_STATE',
-
-	UpdateCalibrationMode = 'UPDATE_MAP_MODE',
-	UpdateSelectedMap = 'UPDATE_SELECTED_MAPS',
-	UpdateMapSource = 'UPDATE_MAP_IMAGE',
-	ChangeGridDisplay = 'CHANGE_GRID_DISPLAY',
-	UpdateCurrentCartesian = 'UPDATE_CURRENT_CARTESIAN',
-	ResetCurrentPoint = 'RESET_CURRENT_POINT',
-	AppendCalibrationSet = 'APPEND_CALIBRATION_SET',
-	UpdateCalibrationResults = 'UPDATE_CALIBRATION_RESULTS',
-	RequestMapsDetails = 'REQUEST_MAP_DETAILS',
-	ReceiveMapsDetails = 'RECEIVE_MAP_DETAILS',
-	DeleteMap = 'DELETE_MAP',
-	EditMapDetails = 'EDIT_MAP_DETAILS',
-	SubmitEditedMap = 'SUBMIT_EDITED_MAP',
-	ConfirmEditedMap = 'CONFIRM_EDITED_MAP',
-	SetCalibration = 'SET_CALIBRATION',
-	ResetCalibration = 'RESET_CALIBRATION',
-	IncrementCounter = 'INCREMENT_COUNTER',
-}
-
-/**
- * The type of the redux-thunk dispatch function.
- * Uses the overloaded version from Redux-Thunk.
- */
-export type Dispatch = ThunkDispatch<RootState, void, Action<any>>;
-
-/**
- * The type of the redux-thunk getState function.
- */
-export type GetState = () => RootState;
-
-/**
- * The type of promissory actions used in the project.
- * Returns a promise, no extra argument, uses the global state.
- */
-export type Thunk = ThunkAction<Promise<any>, RootState, void, Action>;
diff --git a/src/client/app/types/redux/map.ts b/src/client/app/types/redux/map.ts
index 5a07976bb..d6a89cd26 100644
--- a/src/client/app/types/redux/map.ts
+++ b/src/client/app/types/redux/map.ts
@@ -2,8 +2,7 @@
  * 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 { ActionType } from './actions';
-import { CalibratedPoint, CalibrationResult, CartesianPoint, GPSPoint } from '../../utils/calibration';
+import { CalibratedPoint, CalibrationResult, GPSPoint } from '../../utils/calibration';
 
 /**
  * 'initiate', 'calibrate' or 'unavailable'
@@ -14,106 +13,7 @@ export enum CalibrationModeTypes {
 	unavailable = 'unavailable'
 }
 
-export interface ChangeMapModeAction {
-	type: ActionType.UpdateCalibrationMode;
-	nextMode: CalibrationModeTypes;
-}
-
-export interface RequestMapsDetailsAction {
-	type: ActionType.RequestMapsDetails;
-}
-
-export interface ReceiveMapsDetailsAction {
-	type: ActionType.ReceiveMapsDetails;
-	data: MapData[];
-}
-
-export interface UpdateMapSourceAction {
-	type: ActionType.UpdateMapSource;
-	data: MapMetadata;
-}
-
-export interface ChangeGridDisplayAction {
-	type: ActionType.ChangeGridDisplay;
-}
-
-export interface UpdateSelectedMapAction {
-	type: ActionType.UpdateSelectedMap;
-	mapID: number;
-}
-
-export interface UpdateCurrentCartesianAction {
-	type: ActionType.UpdateCurrentCartesian;
-	currentCartesian: CartesianPoint;
-}
-
-export interface ResetCurrentPointAction {
-	type: ActionType.ResetCurrentPoint;
-}
-
-export interface AppendCalibrationSetAction {
-	type: ActionType.AppendCalibrationSet;
-	calibratedPoint: CalibratedPoint;
-}
-
-export interface UpdateCalibrationResultAction {
-	type: ActionType.UpdateCalibrationResults;
-	result: CalibrationResult;
-}
-
-export interface DeleteMapAction {
-	type: ActionType.DeleteMap;
-	mapID: number;
-}
-
-export interface EditMapDetailsAction {
-	type: ActionType.EditMapDetails;
-	map: MapMetadata;
-}
-
-export interface SubmitEditedMapAction {
-	type: ActionType.SubmitEditedMap;
-	mapID: number;
-}
-
-export interface ConfirmEditedMapAction {
-	type: ActionType.ConfirmEditedMap;
-	mapID: number;
-}
-
-export interface SetCalibrationAction {
-	type: ActionType.SetCalibration;
-	mapID: number;
-	mode: CalibrationModeTypes;
-}
-
-export interface IncrementCounterAction {
-	type: ActionType.IncrementCounter;
-}
-
-export interface ResetCalibrationAction {
-	type: ActionType.ResetCalibration;
-	mapID: number;
-}
 
-export type MapsAction =
-	| ChangeMapModeAction
-	| UpdateSelectedMapAction
-	| RequestMapsDetailsAction
-	| ReceiveMapsDetailsAction
-	| UpdateMapSourceAction
-	| ChangeGridDisplayAction
-	| EditMapDetailsAction
-	| SubmitEditedMapAction
-	| ConfirmEditedMapAction
-	| UpdateCurrentCartesianAction
-	| ResetCurrentPointAction
-	| AppendCalibrationSetAction
-	| UpdateCalibrationResultAction
-	| SetCalibrationAction
-	| ResetCalibrationAction
-	| IncrementCounterAction
-	| DeleteMapAction;
 
 /**
  * data format stored in the database
diff --git a/src/client/app/utils/api/LogsApi.ts b/src/client/app/utils/api/LogsApi.ts
index d1f2a4211..36a8239b1 100644
--- a/src/client/app/utils/api/LogsApi.ts
+++ b/src/client/app/utils/api/LogsApi.ts
@@ -7,6 +7,8 @@
 import ApiBackend from './ApiBackend';
 import {LogData} from '../../types/redux/logs';
 
+//TODO migrate to using RTKQuery for logging.
+// This will require logging to be initiated via dispatch, which differs, and conflicts with current implementation and usage.
 export default class LogsApi {
 	private readonly backend: ApiBackend;
 
diff --git a/src/client/app/utils/api/MapsApi.ts b/src/client/app/utils/api/MapsApi.ts
deleted file mode 100644
index 778f0c80a..000000000
--- a/src/client/app/utils/api/MapsApi.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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/.
- */
-
-import ApiBackend from './ApiBackend';
-import { MapData } from '../../types/redux/map';
-
-export default class MapsApi {
-	private readonly backend: ApiBackend;
-
-	constructor(backend: ApiBackend) {
-		this.backend = backend;
-	}
-
-	public async details(): Promise<MapData[]> {
-		return await this.backend.doGetRequest<MapData[]>('/api/maps/');
-	}
-
-	public async create(mapData: MapData): Promise<void> {
-		return await this.backend.doPostRequest<void>('/api/maps/create', mapData);
-	}
-
-	public async edit(mapData: MapData): Promise<MapData> {
-		return await this.backend.doPostRequest<MapData>('/api/maps/edit', mapData);
-	}
-
-	public async delete(id: number): Promise<void> {
-		return await this.backend.doPostRequest<void>('/api/maps/delete', { id });
-	}
-
-	public async getMapById(id: number): Promise<MapData> {
-		return await this.backend.doGetRequest<MapData>(`/api/maps/${id}`);
-	}
-
-	public async getMapByName(name: string): Promise<MapData> {
-		return await this.backend.doGetRequest<MapData>('/api/maps/getByName', { 'name': name });
-	}
-}
diff --git a/src/client/app/utils/api/index.ts b/src/client/app/utils/api/index.ts
index 436a1e970..41eb6be24 100644
--- a/src/client/app/utils/api/index.ts
+++ b/src/client/app/utils/api/index.ts
@@ -6,19 +6,16 @@
 
 import ApiBackend from './ApiBackend';
 import UploadCSVApi from './UploadCSVApi';
-import MapsApi from './MapsApi';
 import LogsApi from './LogsApi';
 
 const apiBackend = new ApiBackend();
 
 // All specific backends share the same ApiBackend
 const uploadCSVApi = new UploadCSVApi(apiBackend);
-const mapsApi = new MapsApi(apiBackend);
 const logsApi = new LogsApi(apiBackend);
 
 
 export {
-	mapsApi,
 	logsApi,
 	uploadCSVApi
 };
diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts
index 63e2ca8a5..7d65d6e41 100644
--- a/src/client/app/utils/calibration.ts
+++ b/src/client/app/utils/calibration.ts
@@ -3,10 +3,10 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import { showErrorNotification } from './notifications';
-import { logToServer } from '../redux/actions/logs';
 import { DataType } from '../types/Datasources';
 import { MapMetadata } from '../types/redux/map';
 import translate from './translate';
+import { logToServer } from '../redux/actions/logs';
 
 /**
  * Defines a Cartesian Point with x & y
@@ -79,6 +79,7 @@ export function itemMapInfoOk(itemID: number, type: DataType, map: MapMetadata,
 	if (map === undefined) { return false; }
 	if ((gps === null || gps === undefined) || map.origin === undefined || map.opposite === undefined) { return false; }
 	if (!isValidGPSInput(`${gps.latitude},${gps.longitude}`)) {
+		// Find way to migrate to RTKQuery logs since dispatch is required, thunks are most likely the
 		logToServer('error', `Found invalid ${type === DataType.Meter ? 'meter' : 'group'} gps stored in database, id = ${itemID}`)();
 		return false;
 	}

From 18b8b6ec45c71db345ad9e18be45842c6e6e19e1 Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Tue, 13 Aug 2024 12:59:30 -0700
Subject: [PATCH 48/50] Fix Merge Issues / Missing MPL headers

---
 src/client/app/components/RouteComponent.tsx   | 3 ++-
 src/client/app/redux/api/logApi.ts             | 3 +++
 src/client/app/redux/api/mapsApi.ts            | 3 +++
 src/client/app/redux/devToolConfig.ts          | 3 +++
 src/client/app/redux/entityAdapters.ts         | 3 +++
 src/client/app/redux/slices/localEditsSlice.ts | 4 +++-
 src/client/app/utils/api/index.ts              | 6 +-----
 7 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/src/client/app/components/RouteComponent.tsx b/src/client/app/components/RouteComponent.tsx
index c25395c63..16d93065b 100644
--- a/src/client/app/components/RouteComponent.tsx
+++ b/src/client/app/components/RouteComponent.tsx
@@ -4,7 +4,6 @@
 import * as React from 'react';
 import { IntlProvider } from 'react-intl';
 import { RouterProvider, createBrowserRouter } from 'react-router-dom';
-import UploadCSVContainer from '../containers/csv/UploadCSVContainer';
 import { useAppSelector } from '../redux/reduxHooks';
 import { selectSelectedLanguage } from '../redux/slices/appStateSlice';
 import LocaleTranslationData from '../translations/data';
@@ -25,6 +24,8 @@ import { GraphLink } from './router/GraphLinkComponent';
 import NotFound from './router/NotFoundOutlet';
 import RoleOutlet from './router/RoleOutlet';
 import UnitsDetailComponent from './unit/UnitsDetailComponent';
+import MetersCSVUploadComponent from './csv/MetersCSVUploadComponent';
+import ReadingsCSVUploadComponent from './csv/ReadingsCSVUploadComponent';
 
 /**
  * @returns the router component Responsible for client side routing.
diff --git a/src/client/app/redux/api/logApi.ts b/src/client/app/redux/api/logApi.ts
index f601e469a..9c913ae8a 100644
--- a/src/client/app/redux/api/logApi.ts
+++ b/src/client/app/redux/api/logApi.ts
@@ -1,3 +1,6 @@
+/* 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/. */
 import { LogData } from 'types/redux/logs';
 import { baseApi } from './baseApi';
 
diff --git a/src/client/app/redux/api/mapsApi.ts b/src/client/app/redux/api/mapsApi.ts
index 62e5325fb..36eb5171b 100644
--- a/src/client/app/redux/api/mapsApi.ts
+++ b/src/client/app/redux/api/mapsApi.ts
@@ -1,3 +1,6 @@
+/* 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/. */
 import { pick } from 'lodash';
 import * as moment from 'moment';
 import { MapDataState, mapsAdapter, mapsInitialState } from '../../redux/entityAdapters';
diff --git a/src/client/app/redux/devToolConfig.ts b/src/client/app/redux/devToolConfig.ts
index 02fe511bc..6a5d862fb 100644
--- a/src/client/app/redux/devToolConfig.ts
+++ b/src/client/app/redux/devToolConfig.ts
@@ -1,3 +1,6 @@
+/* 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/. */
 import { DevToolsEnhancerOptions } from '@reduxjs/toolkit';
 import { mapsApi } from './api/mapsApi';
 import { mapsAdapter, mapsInitialState } from './entityAdapters';
diff --git a/src/client/app/redux/entityAdapters.ts b/src/client/app/redux/entityAdapters.ts
index 92402a2ee..582b540e5 100644
--- a/src/client/app/redux/entityAdapters.ts
+++ b/src/client/app/redux/entityAdapters.ts
@@ -1,3 +1,6 @@
+/* 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/. */
 import { EntityState, createEntityAdapter } from '@reduxjs/toolkit';
 import { ConversionData } from '../types/redux/conversions';
 import { GroupData } from '../types/redux/groups';
diff --git a/src/client/app/redux/slices/localEditsSlice.ts b/src/client/app/redux/slices/localEditsSlice.ts
index e57c667b8..bfb4ae48b 100644
--- a/src/client/app/redux/slices/localEditsSlice.ts
+++ b/src/client/app/redux/slices/localEditsSlice.ts
@@ -1,3 +1,6 @@
+/* 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/. */
 import { createEntityAdapter } from '@reduxjs/toolkit';
 import { PlotMouseEvent } from 'plotly.js';
 import { createThunkSlice } from '../../redux/sliceCreators';
@@ -49,7 +52,6 @@ export const localEditsSlice = createThunkSlice({
 			// Stripped offerCurrentGPS thunk into a single reducer for simplicity. The only missing functionality are the serverlogs
 			// Current axios approach doesn't require dispatch, however if moved to rtk will. thunks for this adds complexity
 			// For simplicity, these logs can instead be tabulated in a middleware.(probably.)
-			// const map = localEditAdapter.getSelectors().selectById(state.mapEdits, state.calibratingMap);
 			const map = state.mapEdits.entities[state.calibratingMap];
 
 			const point = map.currentPoint;
diff --git a/src/client/app/utils/api/index.ts b/src/client/app/utils/api/index.ts
index 41eb6be24..583c15999 100644
--- a/src/client/app/utils/api/index.ts
+++ b/src/client/app/utils/api/index.ts
@@ -5,17 +5,13 @@
  */
 
 import ApiBackend from './ApiBackend';
-import UploadCSVApi from './UploadCSVApi';
 import LogsApi from './LogsApi';
 
 const apiBackend = new ApiBackend();
 
 // All specific backends share the same ApiBackend
-const uploadCSVApi = new UploadCSVApi(apiBackend);
 const logsApi = new LogsApi(apiBackend);
 
-
 export {
-	logsApi,
-	uploadCSVApi
+	logsApi
 };

From a8237ba46c1c4ea7f85705444e594994268cd17b Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Tue, 13 Aug 2024 18:28:10 -0700
Subject: [PATCH 49/50] Update Root Reducer, Sanitize Devtools

---
 src/client/app/redux/devToolConfig.ts         | 53 ++++++++++++++++++-
 .../middleware/graphHistoryMiddleware.ts      |  3 +-
 src/client/app/redux/rootReducer.ts           | 23 ++++----
 src/client/app/store.ts                       |  2 +-
 4 files changed, 66 insertions(+), 15 deletions(-)

diff --git a/src/client/app/redux/devToolConfig.ts b/src/client/app/redux/devToolConfig.ts
index 6a5d862fb..a9ed53eed 100644
--- a/src/client/app/redux/devToolConfig.ts
+++ b/src/client/app/redux/devToolConfig.ts
@@ -2,9 +2,11 @@
  * 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 { DevToolsEnhancerOptions } from '@reduxjs/toolkit';
-import { mapsApi } from './api/mapsApi';
+import { RootState } from 'store';
+import { mapsApi, selectAllMaps, selectMapIds } from './api/mapsApi';
 import { mapsAdapter, mapsInitialState } from './entityAdapters';
 
+const devToolSanitizedDataMessage = 'Omitted From Devtools - Refer to devToolConfig.ts for Details';
 export const devToolsConfig: DevToolsEnhancerOptions = {
 	actionSanitizer: action => {
 		switch (true) {
@@ -12,7 +14,7 @@ export const devToolsConfig: DevToolsEnhancerOptions = {
 			case mapsApi.endpoints.getMapDetails.matchFulfilled(action): {
 				// omitMapSource from metaData
 				const sanitizedMapMetadata = Object.values(action.payload.entities)
-					.map(data => ({ ...data, mapSource: 'Omitted From Devtools Serialization' }));
+					.map(data => ({ ...data, mapSource: devToolSanitizedDataMessage }));
 
 				// sanitized devtool Action
 				return { ...action, payload: { ...mapsAdapter.setAll(mapsInitialState, sanitizedMapMetadata) } };
@@ -20,6 +22,53 @@ export const devToolsConfig: DevToolsEnhancerOptions = {
 			default:
 				return action;
 		}
+	},
+	stateSanitizer: state => {
+		const sanitizedState = sanitizeState(state as RootState);
+		return sanitizedState as typeof state;
+	}
+};
+export const sanitizeState = (state: RootState) => {
+	let s: RootState = state;
+	// if there are map entries in state, sanitize their map source.
+	if (selectMapIds(s).length) {
+		const sanitizedEntities = selectAllMaps(s).map(mapMetaData => ({ ...mapMetaData, mapSource: devToolSanitizedDataMessage }));
+		// recreate Sanitized Cache
+		const sanitizedCache = mapsAdapter.setAll(mapsAdapter.getInitialState(), sanitizedEntities);
 
+		s = {
+			...s,
+			api: {
+				...s.api,
+				queries: {
+					...s.api.queries,
+					'getMapDetails(undefined)': {
+						...s.api.queries['getMapDetails(undefined)'],
+						data: sanitizedCache
+					}
+				}
+			}
+		} as RootState;
+	}
+	// Sanitize localEditsDevtools mapSource
+	if (s.localEdits.mapEdits.ids.length) {
+		const sanitizedEntities = mapsAdapter
+			.getSelectors()
+			.selectAll(s.localEdits.mapEdits)
+			.map(mapMetaData => ({ ...mapMetaData, mapSource: devToolSanitizedDataMessage }));
+		// recreate Sanitized Cache
+		const sanitizedCache = mapsAdapter.setAll(mapsAdapter.getInitialState(), sanitizedEntities);
+		s = {
+			...s,
+			localEdits: {
+				...s.localEdits,
+				mapEdits: sanitizedCache
+			}
+		} as RootState;
 	}
+	// Sanitize some more ...
+
+
+	// return sanitized state, for devtools
+	return s;
 };
\ No newline at end of file
diff --git a/src/client/app/redux/middleware/graphHistoryMiddleware.ts b/src/client/app/redux/middleware/graphHistoryMiddleware.ts
index 639a89975..86e18e149 100644
--- a/src/client/app/redux/middleware/graphHistoryMiddleware.ts
+++ b/src/client/app/redux/middleware/graphHistoryMiddleware.ts
@@ -20,5 +20,6 @@ export const graphHistoryListener = (startListening: AppListener) => {
 	});
 };
 
-// listen to all graphSlice actions
+// listen to all graphSlice actions defined in graphSlice.reducers.
+// Updating state via graphsSlice.extraReducers will not trigger history middleware
 const isHistoryTrigger = isAnyOf(...Object.values(graphSlice.actions));
diff --git a/src/client/app/redux/rootReducer.ts b/src/client/app/redux/rootReducer.ts
index 9e5f33a1c..623a37a72 100644
--- a/src/client/app/redux/rootReducer.ts
+++ b/src/client/app/redux/rootReducer.ts
@@ -2,22 +2,23 @@
  * 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 { combineReducers } from 'redux';
 import { baseApi } from './api/baseApi';
 import { adminSlice } from './slices/adminSlice';
 import { appStateSlice } from './slices/appStateSlice';
 import { currentUserSlice } from './slices/currentUserSlice';
 import { graphSlice } from './slices/graphSlice';
 // import maps from './reducers/maps';
+import { combineSlices } from '@reduxjs/toolkit';
 import { localEditsSlice } from './slices/localEditsSlice';
 
-export const rootReducer = combineReducers({
-	appState: appStateSlice.reducer,
-	graph: graphSlice.reducer,
-	admin: adminSlice.reducer,
-	currentUser: currentUserSlice.reducer,
-	localEdits: localEditsSlice.reducer,
-	// RTK Query's Derived Reducers
-	[baseApi.reducerPath]: baseApi.reducer
-	// maps
-});
\ No newline at end of file
+// export const rootReducer = combineReducers({
+// 	appState: appStateSlice.reducer,
+// 	graph: graphSlice.reducer,
+// 	admin: adminSlice.reducer,
+// 	currentUser: currentUserSlice.reducer,
+// 	localEdits: localEditsSlice.reducer,
+// 	// RTK Query's Derived Reducers
+// 	[baseApi.reducerPath]: baseApi.reducer
+// 	// maps
+// });
+export const rootReducer = combineSlices(appStateSlice, graphSlice, adminSlice, currentUserSlice, localEditsSlice, baseApi);
\ No newline at end of file
diff --git a/src/client/app/store.ts b/src/client/app/store.ts
index c59cf4828..239a0733d 100644
--- a/src/client/app/store.ts
+++ b/src/client/app/store.ts
@@ -13,7 +13,7 @@ export const store = configureStore({
 	reducer: rootReducer,
 	middleware: getDefaultMiddleware => getDefaultMiddleware({
 		// immutableCheck: false,
-		serializableCheck: false
+		// serializableCheck: false
 	}).prepend(listenerMiddleware.middleware)
 		.concat(baseApi.middleware),
 	devTools: devToolsConfig

From 04ba6a5e753e4354a6365ef9605f7981b3143807 Mon Sep 17 00:00:00 2001
From: ChrisMart21 <ChrisMart21@github.com>
Date: Tue, 13 Aug 2024 19:49:49 -0700
Subject: [PATCH 50/50] Address Unserializable Values In State/Actions.  -
 queryTimeIntervalString  - rangeSliderIntervalString  - barDuration  -
 mapsBarDuration  - compareTimeIntervalString  - Re-Enable DevModeChecks

---
 .../app/components/BarChartComponent.tsx      |  5 +-
 .../app/components/DateRangeComponent.tsx     |  4 +-
 .../app/components/LineChartComponent.tsx     |  5 +-
 .../app/components/PlotNavComponent.tsx       |  4 +-
 src/client/app/components/PlotOED.tsx         |  5 +-
 src/client/app/components/ThreeDComponent.tsx |  5 +-
 .../MapCalibrationChartDisplayComponent.tsx   | 22 ++++-
 src/client/app/redux/api/mapsApi.ts           | 10 ++
 src/client/app/redux/selectors/uiSelectors.ts |  5 +-
 src/client/app/redux/slices/graphSlice.ts     | 96 +++++++++++++------
 .../app/redux/slices/localEditsSlice.ts       | 27 +-----
 src/client/app/types/redux/graph.ts           | 15 +--
 12 files changed, 126 insertions(+), 77 deletions(-)

diff --git a/src/client/app/components/BarChartComponent.tsx b/src/client/app/components/BarChartComponent.tsx
index 02379d0f4..bcccceb67 100644
--- a/src/client/app/components/BarChartComponent.tsx
+++ b/src/client/app/components/BarChartComponent.tsx
@@ -115,14 +115,15 @@ export default function BarChartComponent() {
 							const startTS = utc(e['xaxis.range[0]']);
 							const endTS = utc(e['xaxis.range[1]']);
 							const workingTimeInterval = new TimeInterval(startTS, endTS);
-							dispatch(updateSliderRange(workingTimeInterval));
+							dispatch(updateSliderRange(workingTimeInterval.toString()));
 						}
 						else if (e['xaxis.range']) {
 							// this case is when the slider knobs are dragged.
 							const range = e['xaxis.range']!;
 							const startTS = range && range[0];
 							const endTS = range && range[1];
-							dispatch(updateSliderRange(new TimeInterval(utc(startTS), utc(endTS))));
+							const interval = new TimeInterval(utc(startTS), utc(endTS)).toString();
+							dispatch(updateSliderRange(interval));
 
 						}
 					}, 500, { leading: false, trailing: true })}
diff --git a/src/client/app/components/DateRangeComponent.tsx b/src/client/app/components/DateRangeComponent.tsx
index e4727abaf..ceffc9e14 100644
--- a/src/client/app/components/DateRangeComponent.tsx
+++ b/src/client/app/components/DateRangeComponent.tsx
@@ -28,8 +28,8 @@ export default function DateRangeComponent() {
 	const datePickerVisible = chartType !== ChartTypes.compare;
 
 	const handleChange = (value: Value) => {
-		dispatch(updateTimeInterval(dateRangeToTimeInterval(value)));
-		dispatch(changeSliderRange(dateRangeToTimeInterval(value)));
+		dispatch(updateTimeInterval(dateRangeToTimeInterval(value).toString()));
+		dispatch(changeSliderRange(dateRangeToTimeInterval(value).toString()));
 	};
 
 
diff --git a/src/client/app/components/LineChartComponent.tsx b/src/client/app/components/LineChartComponent.tsx
index ad9a9fc47..2c909609e 100644
--- a/src/client/app/components/LineChartComponent.tsx
+++ b/src/client/app/components/LineChartComponent.tsx
@@ -101,14 +101,15 @@ export default function LineChartComponent() {
 							const startTS = utc(e['xaxis.range[0]']);
 							const endTS = utc(e['xaxis.range[1]']);
 							const workingTimeInterval = new TimeInterval(startTS, endTS);
-							dispatch(updateSliderRange(workingTimeInterval));
+							dispatch(updateSliderRange(workingTimeInterval.toString()));
 						}
 						else if (e['xaxis.range']) {
 							// this case is when the slider knobs are dragged.
 							const range = e['xaxis.range']!;
 							const startTS = range && range[0];
 							const endTS = range && range[1];
-							dispatch(updateSliderRange(new TimeInterval(utc(startTS), utc(endTS))));
+							const interval = new TimeInterval(utc(startTS), utc(endTS));
+							dispatch(updateSliderRange(interval.toString()));
 
 						}
 					}, 500, { leading: false, trailing: true })
diff --git a/src/client/app/components/PlotNavComponent.tsx b/src/client/app/components/PlotNavComponent.tsx
index bd7a07ddd..0729c53b5 100644
--- a/src/client/app/components/PlotNavComponent.tsx
+++ b/src/client/app/components/PlotNavComponent.tsx
@@ -40,7 +40,7 @@ export const ExpandComponent = () => {
 	const dispatch = useAppDispatch();
 	return (
 		<img src='./expand.png' style={{ height: '25px' }}
-			onClick={() => { dispatch(changeSliderRange(TimeInterval.unbounded())); }}
+			onClick={() => { dispatch(changeSliderRange(TimeInterval.unbounded().toString())); }}
 		/>
 	);
 };
@@ -70,7 +70,7 @@ export const RefreshGraphComponent = () => {
 		<img
 			src='./refresh.png'
 			style={{ height: '25px', transform: `rotate(${time}deg)`, visibility: iconVisible ? 'visible' : 'hidden' }}
-			onClick={() => { !somethingFetching && dispatch(updateTimeInterval(sliderInterval)); }}
+			onClick={() => { !somethingFetching && dispatch(updateTimeInterval(sliderInterval.toString())); }}
 		/>
 	);
 };
diff --git a/src/client/app/components/PlotOED.tsx b/src/client/app/components/PlotOED.tsx
index b460cf79a..0adbe7fdf 100644
--- a/src/client/app/components/PlotOED.tsx
+++ b/src/client/app/components/PlotOED.tsx
@@ -44,14 +44,15 @@ export const PlotOED = (props: OEDPlotProps) => {
 				const startTS = moment.utc(e['xaxis.range[0]']);
 				const endTS = moment.utc(e['xaxis.range[1]']);
 				const workingTimeInterval = new TimeInterval(startTS, endTS);
-				dispatch(changeSliderRange(workingTimeInterval));
+				dispatch(changeSliderRange(workingTimeInterval.toString()));
 			}
 			else if (e['xaxis.range']) {
 				// this case is when the slider knobs are dragged.
 				const range = figure.current.layout?.xaxis?.range;
 				const startTS = range && range[0];
 				const endTS = range && range[1];
-				dispatch(changeSliderRange(new TimeInterval(startTS, endTS)));
+				const interval = new TimeInterval(startTS, endTS).toString();
+				dispatch(changeSliderRange(interval));
 
 			}
 		}, 500, { leading: false, trailing: true });
diff --git a/src/client/app/components/ThreeDComponent.tsx b/src/client/app/components/ThreeDComponent.tsx
index 658df7415..37305d073 100644
--- a/src/client/app/components/ThreeDComponent.tsx
+++ b/src/client/app/components/ThreeDComponent.tsx
@@ -11,7 +11,7 @@ import { selectUnitDataById } from '../redux/api/unitsApi';
 import { useAppSelector } from '../redux/reduxHooks';
 import { selectThreeDQueryArgs } from '../redux/selectors/chartQuerySelectors';
 import { selectThreeDComponentInfo } from '../redux/selectors/threeDSelectors';
-import { selectGraphState } from '../redux/slices/graphSlice';
+import { selectGraphState, selectQueryTimeInterval } from '../redux/slices/graphSlice';
 import { ThreeDReading } from '../types/readings';
 import { GraphState, MeterOrGroup } from '../types/redux/graph';
 import { GroupDataByID } from '../types/redux/groups';
@@ -38,6 +38,7 @@ export default function ThreeDComponent() {
 	const groupDataById = useAppSelector(selectGroupDataById);
 	const unitDataById = useAppSelector(selectUnitDataById);
 	const graphState = useAppSelector(selectGraphState);
+	const queryTimeInterval = useAppSelector(selectQueryTimeInterval);
 	const locale = useAppSelector(selectSelectedLanguage);
 	const { meterOrGroupID, meterOrGroupName, isAreaCompatible } = useAppSelector(selectThreeDComponentInfo);
 
@@ -53,7 +54,7 @@ export default function ThreeDComponent() {
 		layout = setHelpLayout(translate('select.meter.group'));
 	} else if (graphState.areaNormalization && !isAreaCompatible) {
 		layout = setHelpLayout(`${meterOrGroupName}${translate('threeD.area.incompatible')}`);
-	} else if (!isValidThreeDInterval(roundTimeIntervalForFetch(graphState.queryTimeInterval))) {
+	} else if (!isValidThreeDInterval(roundTimeIntervalForFetch(queryTimeInterval))) {
 		// Not a valid time interval. ThreeD can only support up to 1 year of readings
 		layout = setHelpLayout(translate('threeD.date.range.too.long'));
 	} else if (!threeDData) {
diff --git a/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
index 201a21459..089bc66cd 100644
--- a/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
+++ b/src/client/app/components/maps/MapCalibrationChartDisplayComponent.tsx
@@ -10,7 +10,7 @@ import { selectSelectedLanguage } from '../../redux/slices/appStateSlice';
 import { localEditsSlice } from '../../redux/slices/localEditsSlice';
 import Locales from '../../types/locales';
 import { CalibrationSettings } from '../../types/redux/map';
-import { Dimensions, normalizeImageDimensions } from '../../utils/calibration';
+import { CartesianPoint, Dimensions, normalizeImageDimensions } from '../../utils/calibration';
 
 /**
  * @returns TODO DO ME
@@ -93,8 +93,26 @@ export default function MapCalibrationChartDisplayContainer() {
 			locale: currentLanguange
 		}}
 		onClick={(event: PlotMouseEvent) => {
+			// trace 0 keeps a transparent trace of closely positioned points used for calibration(backgroundTrace),
+			// trace 1 keeps the data points used for calibration are automatically added to the same trace(dataPointTrace),
+			// event.points will include all points near a mouse click, including those in the backgroundTrace and the dataPointTrace,
+			// so the algorithm only looks at trace 0 since points from trace 1 are already put into the data set used for calibration.
 			event.event.preventDefault();
-			dispatch(localEditsSlice.actions.updateCurrentCartesian(event));
+			const eligiblePoints = [];
+			for (const point of event.points) {
+				const traceNumber = point.curveNumber;
+				if (traceNumber === 0) {
+					eligiblePoints.push(point);
+				}
+			}
+			// TODO VERIFY
+			const xValue = eligiblePoints[0].x as number;
+			const yValue = eligiblePoints[0].y as number;
+			const clickedPoint: CartesianPoint = {
+				x: Number(xValue.toFixed(6)),
+				y: Number(yValue.toFixed(6))
+			};
+			dispatch(localEditsSlice.actions.updateCurrentCartesian(clickedPoint));
 		}}
 	/>;
 }
diff --git a/src/client/app/redux/api/mapsApi.ts b/src/client/app/redux/api/mapsApi.ts
index 36eb5171b..aa7411c9b 100644
--- a/src/client/app/redux/api/mapsApi.ts
+++ b/src/client/app/redux/api/mapsApi.ts
@@ -5,6 +5,7 @@ import { pick } from 'lodash';
 import * as moment from 'moment';
 import { MapDataState, mapsAdapter, mapsInitialState } from '../../redux/entityAdapters';
 import { createAppSelector } from '../../redux/selectors/selectors';
+import { setGraphSliceState } from '../../redux/slices/graphSlice';
 import { emtpyMapMetadata, localEditsSlice } from '../../redux/slices/localEditsSlice';
 import { RootState } from '../../store';
 import { MapData, MapMetadata } from '../../types/redux/map';
@@ -125,12 +126,21 @@ export const mapsApi = baseApi.injectEndpoints({
 				body: { id }
 			}),
 			onQueryStarted: (arg, api) => {
+				const s = api.getState() as RootState;
 				api.queryFulfilled
 					//Cleanup Local Edits if any for deleted entity
 					.then(() => {
+						// set current to 0 if current selected is arg
+						const updatedCurrent = s.graph.current.selectedMap === arg ? { ...s.graph.current, selectedMap: 0 } : s.graph.current;
+						// filter entries with this id
+						const filteredPrev = s.graph.prev.filter(graphState => graphState.selectedMap === arg);
+						const filteredNext = s.graph.next.filter(graphState => graphState.selectedMap === arg);
+						api.dispatch(setGraphSliceState({ prev: filteredPrev, current: updatedCurrent, next: filteredNext }));
 						api.dispatch(localEditsSlice.actions.removeOneEdit(arg));
 					})
 					.catch();
+
+
 			},
 			invalidatesTags: ['MapsData']
 		}),
diff --git a/src/client/app/redux/selectors/uiSelectors.ts b/src/client/app/redux/selectors/uiSelectors.ts
index 26190d805..387a6fd4a 100644
--- a/src/client/app/redux/selectors/uiSelectors.ts
+++ b/src/client/app/redux/selectors/uiSelectors.ts
@@ -27,6 +27,7 @@ import {
 import { selectVisibleMetersAndGroups, selectVisibleUnitOrSuffixState } from './authVisibilitySelectors';
 import { selectDefaultGraphicUnitFromEntity, selectMeterOrGroupFromEntity, selectNameFromEntity } from './entitySelectors';
 import { createAppSelector } from './selectors';
+import moment from 'moment';
 
 export const selectCurrentUnitCompatibility = createAppSelector(
 	[
@@ -459,10 +460,10 @@ export const selectChartLink = createAppSelector(
 		}
 		linkText += `chartType=${current.chartToRender}`;
 		// weeklyLink = linkText + '&serverRange=7dfp'; // dfp: days from present;
-		linkText += `&serverRange=${current.queryTimeInterval.toString()}`;
+		linkText += `&serverRange=${current.queryTimeIntervalString.toString()}`;
 		switch (current.chartToRender) {
 			case ChartTypes.bar:
-				linkText += `&barDuration=${current.barDuration.asDays()}`;
+				linkText += `&barDuration=${moment.duration(current.barDuration).asDays()}`;
 				linkText += `&barStacking=${current.barStacking}`;
 				break;
 			case ChartTypes.line:
diff --git a/src/client/app/redux/slices/graphSlice.ts b/src/client/app/redux/slices/graphSlice.ts
index f03ff599d..973b68aa2 100644
--- a/src/client/app/redux/slices/graphSlice.ts
+++ b/src/client/app/redux/slices/graphSlice.ts
@@ -2,7 +2,7 @@
  * 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 { PayloadAction, createAction, createSlice } from '@reduxjs/toolkit';
+import { PayloadAction, createAction, createSelector, createSlice } from '@reduxjs/toolkit';
 import { cloneDeep } from 'lodash';
 import * as moment from 'moment';
 import { ActionMeta } from 'react-select';
@@ -21,12 +21,12 @@ const defaultState: GraphState = {
 	selectedAreaUnit: AreaUnitType.none,
 	// TODO appropriate default value?
 	selectedMap: 0,
-	queryTimeInterval: TimeInterval.unbounded(),
-	rangeSliderInterval: TimeInterval.unbounded(),
-	barDuration: moment.duration(4, 'weeks'),
-	mapsBarDuration: moment.duration(4, 'weeks'),
+	queryTimeIntervalString: TimeInterval.unbounded().toString(),
+	rangeSliderIntervalString: TimeInterval.unbounded().toString(),
+	barDuration: moment.duration(4, 'weeks').toISOString(),
+	mapsBarDuration: moment.duration(4, 'weeks').toISOString(),
+	compareTimeIntervalString: calculateCompareTimeInterval(ComparePeriod.Week, moment()).toString(),
 	comparePeriod: ComparePeriod.Week,
-	compareTimeInterval: calculateCompareTimeInterval(ComparePeriod.Week, moment()),
 	compareSortingOrder: SortingOrder.Descending,
 	chartToRender: ChartTypes.line,
 	barStacking: false,
@@ -83,29 +83,29 @@ export const graphSlice = createSlice({
 			state.current.selectedAreaUnit = action.payload;
 		},
 		updateBarDuration: (state, action: PayloadAction<moment.Duration>) => {
-			state.current.barDuration = action.payload;
+			state.current.barDuration = action.payload.toISOString();
 		},
 		updateMapsBarDuration: (state, action: PayloadAction<moment.Duration>) => {
-			state.current.mapsBarDuration = action.payload;
+			state.current.mapsBarDuration = action.payload.toISOString();
 		},
-		updateTimeInterval: (state, action: PayloadAction<TimeInterval>) => {
+		updateTimeInterval: (state, action: PayloadAction<string>) => {
 			// always update if action is bounded, else only set unbounded if current isn't already unbounded.
 			// clearing when already unbounded should be a no-op
-			if (action.payload.getIsBounded() || state.current.queryTimeInterval.getIsBounded()) {
-				state.current.queryTimeInterval = action.payload;
+			if (TimeInterval.fromString(action.payload).getIsBounded() || TimeInterval.fromString(state.current.queryTimeIntervalString).getIsBounded()) {
+				state.current.queryTimeIntervalString = action.payload.toString();
 			}
 		},
-		changeSliderRange: (state, action: PayloadAction<TimeInterval>) => {
-			if (action.payload.getIsBounded() || state.current.rangeSliderInterval.getIsBounded()) {
-				state.current.rangeSliderInterval = action.payload;
+		changeSliderRange: (state, action: PayloadAction<string>) => {
+			if (TimeInterval.fromString(action.payload).getIsBounded() || TimeInterval.fromString(state.current.rangeSliderIntervalString).getIsBounded()) {
+				state.current.rangeSliderIntervalString = action.payload.toString();
 			}
 		},
 		resetRangeSliderStack: state => {
-			state.current.rangeSliderInterval = TimeInterval.unbounded();
+			state.current.rangeSliderIntervalString = TimeInterval.unbounded().toString();
 		},
 		updateComparePeriod: (state, action: PayloadAction<{ comparePeriod: ComparePeriod, currentTime: moment.Moment }>) => {
 			state.current.comparePeriod = action.payload.comparePeriod;
-			state.current.compareTimeInterval = calculateCompareTimeInterval(action.payload.comparePeriod, action.payload.currentTime);
+			state.current.compareTimeIntervalString = calculateCompareTimeInterval(action.payload.comparePeriod, action.payload.currentTime).toString();
 		},
 		changeChartToRender: (state, action: PayloadAction<ChartTypes>) => {
 			state.current.chartToRender = action.payload;
@@ -234,8 +234,8 @@ export const graphSlice = createSlice({
 			}
 		},
 		resetTimeInterval: state => {
-			if (!state.current.queryTimeInterval.equals(TimeInterval.unbounded())) {
-				state.current.queryTimeInterval = TimeInterval.unbounded();
+			if (!TimeInterval.fromString(state.current.queryTimeIntervalString).equals(TimeInterval.unbounded())) {
+				state.current.queryTimeIntervalString = TimeInterval.unbounded().toString();
 			}
 		},
 		setGraphState: (state, action: PayloadAction<GraphState>) => {
@@ -247,6 +247,10 @@ export const graphSlice = createSlice({
 		// Current History Implementation tracks ANY action defined in 'reducers'
 		// To update graphState without causing a history entry to be created, utilize the 'Extra Reducers' property
 		builder
+			.addCase(
+				setGraphSliceState,
+				(_state, action) => action.payload
+			)
 			.addCase(
 				updateHistory,
 				(state, action) => {
@@ -285,7 +289,7 @@ export const graphSlice = createSlice({
 			.addCase(
 				updateSliderRange,
 				(state, { payload }) => {
-					state.current.rangeSliderInterval = payload;
+					state.current.rangeSliderIntervalString = payload;
 				}
 			)
 			.addCase(
@@ -304,7 +308,7 @@ export const graphSlice = createSlice({
 								current.selectedAreaUnit = value as AreaUnitType;
 								break;
 							case 'barDuration':
-								current.barDuration = moment.duration(parseInt(value), 'days');
+								current.barDuration = moment.duration(parseInt(value), 'days').toISOString();
 								break;
 							case 'barStacking':
 								current.barStacking = value === 'true';
@@ -315,7 +319,7 @@ export const graphSlice = createSlice({
 							case 'comparePeriod':
 								{
 									current.comparePeriod = validateComparePeriod(value);
-									current.compareTimeInterval = calculateCompareTimeInterval(validateComparePeriod(value), moment());
+									current.compareTimeIntervalString = calculateCompareTimeInterval(validateComparePeriod(value), moment()).toString();
 								}
 								break;
 							case 'compareSortingOrder':
@@ -347,7 +351,7 @@ export const graphSlice = createSlice({
 								current.threeD.readingInterval = parseInt(value);
 								break;
 							case 'serverRange':
-								current.queryTimeInterval = TimeInterval.fromString(value);
+								current.queryTimeIntervalString = value;
 								break;
 							case 'sliderRange':
 								// TODO omitted for now re-implement later.
@@ -387,8 +391,6 @@ export const graphSlice = createSlice({
 		selectThreeDState: state => state.current.threeD,
 		selectShowMinMax: state => state.current.showMinMax,
 		selectBarStacking: state => state.current.barStacking,
-		selectBarWidthDays: state => state.current.barDuration,
-		selectMapBarWidthDays: state => state.current.mapsBarDuration,
 		selectSelectedMap: state => state.current.selectedMap,
 		selectAreaUnit: state => state.current.selectedAreaUnit,
 		selectSelectedUnit: state => state.current.selectedUnit,
@@ -398,17 +400,50 @@ export const graphSlice = createSlice({
 		selectSelectedMeters: state => state.current.selectedMeters,
 		selectSelectedGroups: state => state.current.selectedGroups,
 		selectSortingOrder: state => state.current.compareSortingOrder,
-		selectQueryTimeInterval: state => state.current.queryTimeInterval,
 		selectThreeDMeterOrGroup: state => state.current.threeD.meterOrGroup,
-		selectCompareTimeInterval: state => state.current.compareTimeInterval,
 		selectGraphAreaNormalization: state => state.current.areaNormalization,
 		selectThreeDMeterOrGroupID: state => state.current.threeD.meterOrGroupID,
 		selectThreeDReadingInterval: state => state.current.threeD.readingInterval,
 		selectDefaultGraphState: () => defaultState,
 		selectHistoryIsDirty: state => state.prev.length > 0 || state.next.length > 0,
-		selectSliderRangeInterval: state => state.current.rangeSliderInterval,
-		selectPlotlySliderMin: state => state.current.rangeSliderInterval.getStartTimestamp()?.utc().toDate().toISOString(),
-		selectPlotlySliderMax: state => state.current.rangeSliderInterval.getEndTimestamp()?.utc().toDate().toISOString()
+		selectPlotlySliderMin: state => TimeInterval.fromString(state.current.rangeSliderIntervalString).getStartTimestamp()?.utc().toDate().toISOString(),
+		selectPlotlySliderMax: state => TimeInterval.fromString(state.current.rangeSliderIntervalString).getEndTimestamp()?.utc().toDate().toISOString(),
+		selectQueryTimeIntervalString: state => state.current.queryTimeIntervalString,
+		selectCompareTimeIntervalString: state => state.current.compareTimeIntervalString,
+		selectSliderRangeIntervalString: state => state.current.rangeSliderIntervalString,
+
+		// Memoized selector(s) becuase creating new TimeInterval.fromString(), each execution leads to unnecessary re-renders
+		// Avoids Saving Un-serializable objects (TimeIntervals) in store.
+		selectQueryTimeInterval: createSelector(
+			(sliceState: History<GraphState>) => sliceState.current.queryTimeIntervalString,
+			timeIntervalString => {
+				return TimeInterval.fromString(timeIntervalString);
+			}
+		),
+		selectSliderRangeInterval: createSelector(
+			(sliceState: History<GraphState>) => sliceState.current.rangeSliderIntervalString,
+			timeIntervalString => {
+				return TimeInterval.fromString(timeIntervalString);
+			}
+		),
+		selectCompareTimeInterval: createSelector(
+			(sliceState: History<GraphState>) => sliceState.current.compareTimeIntervalString,
+			timeIntervalString => {
+				return TimeInterval.fromString(timeIntervalString);
+			}
+		),
+		selectBarWidthDays: createSelector(
+			(sliceState: History<GraphState>) => sliceState.current.barDuration,
+			durationString => {
+				return moment.duration(durationString);
+			}
+		),
+		selectMapBarWidthDays: createSelector(
+			(sliceState: History<GraphState>) => sliceState.current.mapsBarDuration,
+			durationString => {
+				return moment.duration(durationString);
+			}
+		)
 	}
 });
 
@@ -456,6 +491,7 @@ export const historyStepForward = createAction('graph/historyStepForward');
 export const updateHistory = createAction<GraphState>('graph/updateHistory');
 export const processGraphLink = createAction<URLSearchParams>('graph/graphLink');
 export const clearGraphHistory = createAction('graph/clearHistory');
-export const updateSliderRange = createAction<TimeInterval>('graph/UpdateSliderRange');
+export const updateSliderRange = createAction<string>('graph/UpdateSliderRange');
+export const setGraphSliceState = createAction<History<GraphState>>('graph/SetGraphSliceState');
 
 
diff --git a/src/client/app/redux/slices/localEditsSlice.ts b/src/client/app/redux/slices/localEditsSlice.ts
index bfb4ae48b..2696eac44 100644
--- a/src/client/app/redux/slices/localEditsSlice.ts
+++ b/src/client/app/redux/slices/localEditsSlice.ts
@@ -2,11 +2,10 @@
  * 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 { createEntityAdapter } from '@reduxjs/toolkit';
-import { PlotMouseEvent } from 'plotly.js';
+import { mapsAdapter } from '../../redux/entityAdapters';
 import { createThunkSlice } from '../../redux/sliceCreators';
 import { CalibrationModeTypes, MapMetadata } from '../../types/redux/map';
 import { calibrate, CalibratedPoint, CartesianPoint, GPSPoint } from '../../utils/calibration';
-import { mapsAdapter } from '../../redux/entityAdapters';
 
 const localEditAdapter = createEntityAdapter<MapMetadata>();
 const localSelectors = localEditAdapter.getSelectors();
@@ -65,30 +64,10 @@ export const localEditsSlice = createThunkSlice({
 				}
 			}
 		}),
-		updateCurrentCartesian: create.reducer<PlotMouseEvent>((state, { payload }) => {
-			// repourposed getClickedCoordinate Events from previous maps implementatinon moved to reducer
-			// trace 0 keeps a transparent trace of closely positioned points used for calibration(backgroundTrace),
-			// trace 1 keeps the data points used for calibration are automatically added to the same trace(dataPointTrace),
-			// event.points will include all points near a mouse click, including those in the backgroundTrace and the dataPointTrace,
-			// so the algorithm only looks at trace 0 since points from trace 1 are already put into the data set used for calibration.
-			const eligiblePoints = [];
-			for (const point of payload.points) {
-				const traceNumber = point.curveNumber;
-				if (traceNumber === 0) {
-					eligiblePoints.push(point);
-				}
-			}
-			// TODO VERIFY
-			const xValue = eligiblePoints[0].x as number;
-			const yValue = eligiblePoints[0].y as number;
-			const clickedPoint: CartesianPoint = {
-				x: Number(xValue.toFixed(6)),
-				y: Number(yValue.toFixed(6))
-			};
-
+		updateCurrentCartesian: create.reducer<CartesianPoint>((state, { payload }) => {
 			// update calibrating map with new datapoint
 			const currentPoint: CalibratedPoint = {
-				cartesian: clickedPoint,
+				cartesian: payload,
 				gps: { longitude: -1, latitude: -1 }
 			};
 
diff --git a/src/client/app/types/redux/graph.ts b/src/client/app/types/redux/graph.ts
index 8640761ac..83cc9f307 100644
--- a/src/client/app/types/redux/graph.ts
+++ b/src/client/app/types/redux/graph.ts
@@ -2,8 +2,6 @@
  * 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 { TimeInterval } from '../../../../common/TimeInterval';
 import { ComparePeriod, SortingOrder } from '../../utils/calculateCompare';
 import { AreaUnitType } from '../../utils/getAreaUnitConversion';
 
@@ -62,17 +60,20 @@ export interface GraphState {
 	selectedUnit: number;
 	selectedMap: number;
 	selectedAreaUnit: AreaUnitType;
-	rangeSliderInterval: TimeInterval;
-	barDuration: moment.Duration;
-	mapsBarDuration: moment.Duration;
 	comparePeriod: ComparePeriod;
-	compareTimeInterval: TimeInterval;
 	compareSortingOrder: SortingOrder;
 	chartToRender: ChartTypes;
 	barStacking: boolean;
 	lineGraphRate: LineGraphRate;
 	showMinMax: boolean;
 	threeD: ThreeDState;
-	queryTimeInterval: TimeInterval;
 	hotlinked: boolean;
+	// save time intervals as strings.
+	// convert to TimeInterval w/ TimeInterval.fromString()
+	rangeSliderIntervalString: string;
+	compareTimeIntervalString: string;
+	queryTimeIntervalString: string;
+	// save duration as string
+	barDuration: string;
+	mapsBarDuration: string;
 }