From f9a191c3e9ebd3cf666e14012d99427f3262417f Mon Sep 17 00:00:00 2001 From: Ryan Spencer Date: Sun, 10 Aug 2025 16:09:59 -0700 Subject: [PATCH 1/3] updated modal to include back button and updated Cancel button to return to location details --- app/screens/FindMachine.js | 76 ++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/app/screens/FindMachine.js b/app/screens/FindMachine.js index 74510d6b..af43becc 100644 --- a/app/screens/FindMachine.js +++ b/app/screens/FindMachine.js @@ -10,6 +10,7 @@ import { StyleSheet, TextInput, View, + TouchableOpacity, } from "react-native"; import { ThemeContext } from "../theme-context"; import { KeyboardAwareScrollView } from "react-native-keyboard-controller"; @@ -30,8 +31,8 @@ import { WarningButton, } from "../components"; import Checkbox from "expo-checkbox"; - import { alphaSortNameObj } from "../utils/utilityFunctions"; +import { FontAwesome6 } from "@expo/vector-icons"; let deviceWidth = Dimensions.get("window").width; @@ -57,7 +58,6 @@ class MultiSelectRow extends React.PureComponent { const { index, machine, selected } = this.props; const theme = this.context.theme; const backgroundColor = index % 2 === 0 ? theme.base1 : theme.base2; - return ( { + returnToMachineSelection = () => { this.setState({ showModal: false, machine: {}, @@ -238,6 +238,17 @@ class FindMachine extends React.PureComponent { }); }; + returnToLocationDetails = () => { + this.setState({ + showModal: false, + machine: {}, + condition: "", + }); + this.props.navigation.navigate("LocationDetails", { + id: this.props.location.location.id, + }); + }; + renderRow = ({ item, index }) => { const theme = this.context.theme; const backgroundColor = index % 2 === 0 ? theme.base1 : theme.base2; @@ -353,16 +364,37 @@ class FindMachine extends React.PureComponent { keyboardShouldPersistTaps="handled" > - - Add{" "} - - {this.state.machine.name} - {" "} - to{" "} - - {this.props.location.location.name} + + this.returnToMachineSelection()} + style={s.backButton} + activeOpacity={0.5} + > + + + + Add{" "} + + {this.state.machine.name} + {" "} + to{" "} + + {this.props.location.location.name} + - + {!!opdb_img && ( @@ -529,13 +561,24 @@ const getStyles = (theme) => fontSize: 18, fontFamily: "Nunito-Regular", }, - + headerContainer: { + flexDirection: "row", + alignItems: "center", + position: "relative", + justifyContent: "center", + }, + backButton: { + position: "absolute", + left: 10, + top: 0, + bottom: 0, + justifyContent: "center", + }, textInput: { backgroundColor: theme.white, borderColor: theme.theme == "dark" ? theme.base4 : theme.indigo4, borderWidth: 1, marginHorizontal: 30, - marginTop: 10, marginBottom: 10, borderRadius: 10, fontFamily: "Nunito-Regular", @@ -545,7 +588,7 @@ const getStyles = (theme) => verticalAlign: { flexDirection: "column", justifyContent: "top", - marginTop: 80, + marginTop: 60, marginBottom: 40, }, multiSelect: { @@ -604,6 +647,7 @@ const getStyles = (theme) => modalTitle: { textAlign: "center", marginHorizontal: 40, + marginVertical: 20, fontSize: 18, fontFamily: "Nunito-Regular", }, From 879e65897713bfd527e4109409a41eb4f5cd7a54 Mon Sep 17 00:00:00 2001 From: Ryan Spencer Date: Sun, 10 Aug 2025 16:13:39 -0700 Subject: [PATCH 2/3] updated modal to include back button and updated Cancel button to return to location details --- app/screens/FindMachine.js | 77 +++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/app/screens/FindMachine.js b/app/screens/FindMachine.js index 43ecda55..af43becc 100644 --- a/app/screens/FindMachine.js +++ b/app/screens/FindMachine.js @@ -10,6 +10,7 @@ import { StyleSheet, TextInput, View, + TouchableOpacity, } from "react-native"; import { ThemeContext } from "../theme-context"; import { KeyboardAwareScrollView } from "react-native-keyboard-controller"; @@ -30,8 +31,8 @@ import { WarningButton, } from "../components"; import Checkbox from "expo-checkbox"; - import { alphaSortNameObj } from "../utils/utilityFunctions"; +import { FontAwesome6 } from "@expo/vector-icons"; let deviceWidth = Dimensions.get("window").width; @@ -57,7 +58,6 @@ class MultiSelectRow extends React.PureComponent { const { index, machine, selected } = this.props; const theme = this.context.theme; const backgroundColor = index % 2 === 0 ? theme.base1 : theme.base2; - return ( { + returnToMachineSelection = () => { this.setState({ showModal: false, machine: {}, @@ -238,6 +238,17 @@ class FindMachine extends React.PureComponent { }); }; + returnToLocationDetails = () => { + this.setState({ + showModal: false, + machine: {}, + condition: "", + }); + this.props.navigation.navigate("LocationDetails", { + id: this.props.location.location.id, + }); + }; + renderRow = ({ item, index }) => { const theme = this.context.theme; const backgroundColor = index % 2 === 0 ? theme.base1 : theme.base2; @@ -353,16 +364,37 @@ class FindMachine extends React.PureComponent { keyboardShouldPersistTaps="handled" > - - Add{" "} - - {this.state.machine.name} - {" "} - to{" "} - - {this.props.location.location.name} + + this.returnToMachineSelection()} + style={s.backButton} + activeOpacity={0.5} + > + + + + Add{" "} + + {this.state.machine.name} + {" "} + to{" "} + + {this.props.location.location.name} + - + {!!opdb_img && ( @@ -529,13 +561,24 @@ const getStyles = (theme) => fontSize: 18, fontFamily: "Nunito-Regular", }, - + headerContainer: { + flexDirection: "row", + alignItems: "center", + position: "relative", + justifyContent: "center", + }, + backButton: { + position: "absolute", + left: 10, + top: 0, + bottom: 0, + justifyContent: "center", + }, textInput: { backgroundColor: theme.white, borderColor: theme.theme == "dark" ? theme.base4 : theme.indigo4, borderWidth: 1, marginHorizontal: 30, - marginTop: 5, marginBottom: 10, borderRadius: 10, fontFamily: "Nunito-Regular", @@ -545,7 +588,7 @@ const getStyles = (theme) => verticalAlign: { flexDirection: "column", justifyContent: "top", - marginTop: 80, + marginTop: 60, marginBottom: 40, }, multiSelect: { @@ -604,7 +647,7 @@ const getStyles = (theme) => modalTitle: { textAlign: "center", marginHorizontal: 40, - marginBottom: 15, + marginVertical: 20, fontSize: 18, fontFamily: "Nunito-Regular", }, From 2cd8e39741b7b9398afd7991ac16c59214531c4d Mon Sep 17 00:00:00 2001 From: Ryan Spencer Date: Fri, 29 Aug 2025 19:41:17 -0700 Subject: [PATCH 3/3] converted class component to functional component and implement useSafeAreaInstes() --- app/screens/FindMachine.js | 825 ++++++++++++++++--------------------- 1 file changed, 347 insertions(+), 478 deletions(-) diff --git a/app/screens/FindMachine.js b/app/screens/FindMachine.js index efb610ee..5f181a43 100644 --- a/app/screens/FindMachine.js +++ b/app/screens/FindMachine.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useContext, useEffect, useState } from "react"; import PropTypes from "prop-types"; import { connect } from "react-redux"; import { @@ -11,13 +11,13 @@ import { TextInput, View, TouchableOpacity, - TouchableOpacity, } from "react-native"; +import { useNavigation, useRoute } from "@react-navigation/native"; import { ThemeContext } from "../theme-context"; import { KeyboardAwareScrollView } from "react-native-keyboard-controller"; import MaterialIcons from "@expo/vector-icons/MaterialIcons"; import { MaterialCommunityIcons } from "@expo/vector-icons"; -import { FlashList } from "@shopify/flash-list"; +import { LegendList } from "@legendapp/list"; import { addMachineToLocation, addMachineToList, @@ -34,7 +34,7 @@ import { import Checkbox from "expo-checkbox"; import { alphaSortNameObj } from "../utils/utilityFunctions"; import { FontAwesome6 } from "@expo/vector-icons"; -import { FontAwesome6 } from "@expo/vector-icons"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; let deviceWidth = Dimensions.get("window").width; @@ -49,88 +49,83 @@ const getDisplayText = (machine, theme) => ( ); -class MultiSelectRow extends React.PureComponent { - static contextType = ThemeContext; - - _onPress = () => { - this.props.onPressItem(this.props.machine); - }; - - render() { - const { index, machine, selected } = this.props; - const theme = this.context.theme; - const backgroundColor = index % 2 === 0 ? theme.base1 : theme.base2; - return ( - [ - { - display: "flex", - flexDirection: "row", - alignItems: "center", - padding: 8, - justifyContent: "space-between", - }, - pressed - ? { backgroundColor: theme.base4, opacity: 0.8 } - : { backgroundColor, opacity: 1 }, - ]} - > - - {getDisplayText(machine, theme)} - - {selected ? ( - - ) : null} - - ); - } -} - -MultiSelectRow.propTypes = { - onPressItem: PropTypes.func, - machine: PropTypes.object, - selected: PropTypes.bool, - index: PropTypes.number, +const MultiSelectRow = ({ index, machine, selected, onPressItem }) => { + const { theme } = useContext(ThemeContext); + const backgroundColor = index % 2 === 0 ? theme.base1 : theme.base2; + + const _onPress = () => onPressItem(machine); + + return ( + [ + { + display: "flex", + flexDirection: "row", + alignItems: "center", + padding: 8, + justifyContent: "space-between", + }, + pressed + ? { backgroundColor: theme.base4, opacity: 0.8 } + : { backgroundColor, opacity: 1 }, + ]} + > + + {getDisplayText(machine, theme)} + + {selected ? ( + + ) : null} + + ); }; -class FindMachine extends React.PureComponent { - constructor(props) { - super(props); - const sortedMachines = alphaSortNameObj(props.machines.machines); - - this.state = { - machines: sortedMachines, - allMachines: sortedMachines, - query: "", - showModal: false, - machine: {}, - condition: "", - machineList: props.location.machineList, - machinesInView: false, - ic_enabled: undefined, - }; - } - - componentDidMount() { - this.props.navigation.setOptions({ - title: this.props.route.params?.machineFilter +function FindMachine(props) { + const { + location, + machines, + mapLocations, + addMachineToLocation, + setMachineFilter, + } = props; + + const navigation = useNavigation(); + const route = useRoute(); + const { theme } = useContext(ThemeContext); + const insets = useSafeAreaInsets(); + + const sortedMachines = alphaSortNameObj(machines.machines); + const [list, setList] = useState(sortedMachines); + const [allMachines] = useState(sortedMachines); + const [query, setQuery] = useState(""); + const [showModal, setShowModal] = useState(false); + const [machine, setMachine] = useState({}); + const [condition, setCondition] = useState(""); + const [machineList, setMachineList] = useState(location.machineList || []); + const [machinesInView, setMachinesInView] = useState(false); + const [ic_enabled, setIcEnabled] = useState(undefined); + const [refresh, setRefresh] = useState(false); + + useEffect(() => { + setMachineList(location.machineList || []); + }, [location.machineList]); + + useEffect(() => { + navigation.setOptions({ + title: route.params?.machineFilter ? "Select Machine to Filter" - : `Select Machine${this.props.route.params?.multiSelect ? "s" : ""}`, + : `Select Machine${route.params?.multiSelect ? "s" : ""}`, headerRight: () => - this.props.route.params?.showDone ? ( - this.props.navigation.goBack(null)}> + route.params?.showDone ? ( + navigation.goBack(null)}> {({ pressed }) => ( - + ) : null, }); - } - - componentDidUpdate() { - this.props.navigation.setOptions({ - headerRight: () => - this.props.route.params?.showDone ? ( - this.props.navigation.goBack(null)}> - {({ pressed }) => ( - - - - )} - - ) : null, - }); - } - - static contextType = ThemeContext; - - handleSearch = (query, machinesInView) => { - const formattedQuery = query.toLowerCase(); - - if (machinesInView) { - const machinesInView = this.props.mapLocations.reduce((machines, loc) => { - loc.machine_ids && - loc.machine_ids.map((machineId) => { - if (machines.indexOf(machineId) === -1) machines.push(machineId); - }); - - return machines; + }, [ + navigation, + route.params?.machineFilter, + route.params?.multiSelect, + route.params?.showDone, + ]); + + useEffect(() => { + navigation.setParams?.({ showDone: (machineList?.length || 0) > 0 }); + }, [machineList, navigation]); + + const toArray = (maybeArrayOrObject) => + Array.isArray(maybeArrayOrObject) + ? maybeArrayOrObject + : Object.values(maybeArrayOrObject || {}); + + const handleSearch = (q, wantInView) => { + const formatted = q.toLowerCase(); + + if (wantInView) { + const idsInView = toArray(mapLocations).reduce((ids, loc) => { + (loc?.machine_ids || []).forEach((id) => { + if (!ids.includes(id)) ids.push(id); + }); + return ids; }, []); - const curMachines = this.state.allMachines.filter( - (mach) => machinesInView.indexOf(mach.id) > -1, - ); - const machines = curMachines.filter((m) => - m.name.toLowerCase().includes(formattedQuery), + const cur = allMachines.filter((m) => idsInView.includes(m.id)); + const filtered = cur.filter((m) => + m.name.toLowerCase().includes(formatted), ); - this.setState({ query, machines }); + setQuery(q); + setList(filtered); } else { - const machines = this.state.allMachines.filter((m) => - m.name.toLowerCase().includes(formattedQuery), + const filtered = allMachines.filter((m) => + m.name.toLowerCase().includes(formatted), ); - this.setState({ query, machines }); + setQuery(q); + setList(filtered); } }; - handleClear = () => { - this.setState({ query: "" }); + const handleClear = () => { + setQuery(""); + setList(allMachines); }; - toggleViewMachinesInMapArea = (idx) => { - if (idx === 0 && !!this.state.machinesInView) { - this.handleSearch(this.state.query, false); - this.setState({ machinesInView: false }); - } else if (idx === 1 && !!!this.state.machinesInView) { - this.handleSearch(this.state.query, true); - this.setState({ machinesInView: true }); + const toggleViewMachinesInMapArea = (idx) => { + if (idx === 0 && machinesInView) { + handleSearch(query, false); + setMachinesInView(false); + } else if (idx === 1 && !machinesInView) { + handleSearch(query, true); + setMachinesInView(true); } }; - setSelected = (machine) => { - if (this.props.route.params?.machineFilter) { - this.props.setMachineFilter(machine); - this.props.navigation.goBack(); + const setSelected = (m) => { + if (route.params?.machineFilter) { + setMachineFilter(m); + navigation.goBack(); } else { - this.setState({ - showModal: true, - machine, - }); + setShowModal(true); + setMachine(m); } }; - addMachine = () => { - this.props.addMachineToLocation( - this.state.machine, - this.state.condition, - this.state.ic_enabled, - ); - this.setState({ showModal: false }); - this.props.navigation.goBack(); + const addMachineAndClose = () => { + addMachineToLocation(machine, condition, ic_enabled); + setShowModal(false); + navigation.goBack(); }; - returnToMachineSelection = () => { - returnToMachineSelection = () => { - this.setState({ - showModal: false, - machine: {}, - condition: "", - }); + const returnToMachineSelection = () => { + setShowModal(false); + setMachine({}); + setCondition(""); }; - returnToLocationDetails = () => { - this.setState({ - showModal: false, - machine: {}, - condition: "", - }); - this.props.navigation.navigate("LocationDetails", { - id: this.props.location.location.id, - }); + const returnToLocationDetails = () => { + setShowModal(false); + setMachine({}); + setCondition(""); + navigation.goBack(); }; - returnToLocationDetails = () => { - this.setState({ - showModal: false, - machine: {}, - condition: "", - }); - this.props.navigation.navigate("LocationDetails", { - id: this.props.location.location.id, - }); + const onPressMultiSelect = (m) => { + const selected = machineList.find((x) => x.id === m.id); + if (selected) { + removeMachineFromList(m); + } else { + addMachineToList(m); + } + setRefresh((r) => !r); }; - renderRow = ({ item, index }) => { - const theme = this.context.theme; + const keyExtractor = (m) => `${m.id}`; + + const onIcEnabledPressed = (val) => { + if (ic_enabled === val) setIcEnabled(undefined); + else setIcEnabled(val); + }; + + const multiSelect = !!route.params?.multiSelect; + const isFiltering = !!route.params?.machineFilter; + const selectedIdx = machinesInView ? 1 : 0; + const s = getStyles(theme); + const keyboardDismissProp = + Platform.OS === "ios" + ? { keyboardDismissMode: "on-drag" } + : { onScrollBeginDrag: Keyboard.dismiss }; + + const { opdb_img, opdb_img_height, opdb_img_width } = machine; + const opdb_resized = (opdb_img_width || 0) - (deviceWidth - 48); + const opdb_img_height_calc = + (deviceWidth - 48) * ((opdb_img_height || 0) / (opdb_img_width || 1)); + const opdbImgHeight = + opdb_resized > 0 ? opdb_img_height_calc : opdb_img_height; + const opdbImgWidth = opdb_resized > 0 ? deviceWidth - 48 : opdb_img_width; + + const renderRow = ({ item, index }) => { const backgroundColor = index % 2 === 0 ? theme.base1 : theme.base2; return ( - this.setSelected(item)}> + setSelected(item)}> {({ pressed }) => ( ( + const renderMultiSelectRow = ({ item, index }) => ( m.id === item.id)} + onPressItem={onPressMultiSelect} + selected={!!(location.machineList || []).find((m) => m.id === item.id)} index={index} /> ); - onPressMultiSelect = (machine) => { - const selected = !!this.props.location.machineList.find( - (m) => m.id === machine.id, - ); - - if (selected) { - this.props.removeMachineFromList(machine); - this.setState({ - refresh: !this.state.refresh, - }); - } else { - this.props.addMachineToList(machine); - this.setState({ - refresh: !this.state.refresh, - }); - } - }; - - keyExtractor = (machine) => `${machine.id}`; - - onIcEnabledPressed = (ic_enabled) => { - const prevState = this.state.ic_enabled; - // uncheck if pressing the currently checked box - if ( - (!!prevState && !!ic_enabled) || - (prevState === false && ic_enabled === false) - ) { - return this.setState({ ic_enabled: undefined }); - } - - this.setState({ ic_enabled }); - }; + return ( + <> + {}} + transparent={false} + statusBarTranslucent + navigationBarTranslucent + > + + + + + returnToMachineSelection()} + style={s.backButton} + activeOpacity={0.5} + > + + + + Add {machine.name} to{" "} + {location.name} + + - UNSAFE_componentWillReceiveProps(props) { - if ( - this.props.location.machineList.length === 0 && - props.location.machineList.length > 0 - ) - this.props.navigation.setParams({ showDone: true }); - - if ( - this.props.location.machineList.length > 0 && - props.location.machineList.length === 0 - ) - this.props.navigation.setParams({ showDone: false }); - } - - render() { - const { machineList = [] } = this.props.location; - const multiSelect = - (this.props.route.params && this.props.route.params["multiSelect"]) || - false; - const isFiltering = this.props.route.params?.machineFilter; - const selectedIdx = this.state.machinesInView ? 1 : 0; - const theme = this.context.theme; - const s = getStyles(theme); - const keyboardDismissProp = - Platform.OS === "ios" - ? { keyboardDismissMode: "on-drag" } - : { onScrollBeginDrag: Keyboard.dismiss }; - const { opdb_img, opdb_img_height, opdb_img_width } = this.state.machine; - const opdb_resized = opdb_img_width - (deviceWidth - 48); - const opdb_img_height_calc = - (deviceWidth - 48) * (opdb_img_height / opdb_img_width); - const opdbImgHeight = - opdb_resized > 0 ? opdb_img_height_calc : opdb_img_height; - const opdbImgWidth = opdb_resized > 0 ? deviceWidth - 48 : opdb_img_width; + {!!opdb_img && ( + + )} + + - return ( - <> - {}} - transparent={false} - statusBarTranslucent={true} - navigationBarTranslucent={true} - > - - - - - this.returnToMachineSelection()} - style={s.backButton} - activeOpacity={0.5} - > - - - - Add{" "} - - {this.state.machine.name} - {" "} - to{" "} - - {this.props.location.location.name} - + {machine.ic_eligible && ( + + + Does machine have Stern Insider Connected enabled? - - - this.returnToMachineSelection()} - style={s.backButton} - activeOpacity={0.5} - > - + onIcEnabledPressed(true)} + color={theme.purple} + style={s.checkStyle} /> - - - Add{" "} - - {this.state.machine.name} - {" "} - to{" "} - - {this.props.location.location.name} - - - - {!!opdb_img && ( - - )} - this.setState({ condition })} - textAlignVertical="top" - underlineColorAndroid="transparent" - /> - {this.state.machine.ic_eligible && ( - - - Does machine have Stern Insider Connected enabled? - - - this.onIcEnabledPressed(true)} - color={theme.purple} - style={s.checkStyle} - /> - - Yes - - this.onIcEnabledPressed(false)} - color={theme.purple} - style={s.checkStyle} - /> - No - + Yes + onIcEnabledPressed(false)} + color={theme.purple} + style={s.checkStyle} + /> + No - )} - - - - - - - - - - - this.handleSearch(query, this.state.machinesInView) - } - value={this.state.query} - style={s.inputStyle} - autoCorrect={false} + + )} + + + + + + + + + + + + handleSearch(q, machinesInView)} + value={query} + style={s.inputStyle} + autoCorrect={false} + /> + + {query.length > 0 && ( + + - - {this.state.query.length > 0 && ( - - - + + )} + + + {isFiltering ? ( + + + + ) : null} + + {multiSelect ? ( + + {machineList.length === 0 ? ( + 0 machines selected + ) : ( + + + {`${machineList.length} machine${machineList.length > 1 ? "s" : ""} selected`} + + )} - {isFiltering ? ( - - - - ) : null} - {multiSelect ? ( - - {machineList.length === 0 ? ( - 0 machines selected - ) : ( - - {`${ - machineList.length - } machine${machineList.length > 1 ? "s" : ""} selected`} - - )} - - ) : null} - - - ); - } + ) : null} + + + + ); } const getStyles = (theme) => @@ -615,20 +496,7 @@ const getStyles = (theme) => }, backButton: { position: "absolute", - left: 10, - top: 0, - bottom: 0, - justifyContent: "center", - }, - headerContainer: { - flexDirection: "row", - alignItems: "center", - position: "relative", - justifyContent: "center", - }, - backButton: { - position: "absolute", - left: 10, + left: 0, top: 0, bottom: 0, justifyContent: "center", @@ -638,6 +506,7 @@ const getStyles = (theme) => borderColor: theme.theme == "dark" ? theme.base4 : theme.indigo4, borderWidth: 1, marginHorizontal: 30, + marginTop: 5, marginBottom: 10, borderRadius: 10, fontFamily: "Nunito-Regular", @@ -648,7 +517,6 @@ const getStyles = (theme) => flexDirection: "column", justifyContent: "top", marginTop: 60, - marginTop: 60, marginBottom: 40, }, multiSelect: { @@ -708,7 +576,6 @@ const getStyles = (theme) => textAlign: "center", marginHorizontal: 40, marginVertical: 20, - marginVertical: 20, fontSize: 18, fontFamily: "Nunito-Regular", }, @@ -751,6 +618,7 @@ const mapStateToProps = ({ location, machines, locations }) => ({ machines, mapLocations: locations.mapLocations || {}, }); + const mapDispatchToProps = (dispatch) => ({ addMachineToLocation: (machine, condition, ic_enabled) => dispatch(addMachineToLocation(machine, condition, ic_enabled)), @@ -758,4 +626,5 @@ const mapDispatchToProps = (dispatch) => ({ removeMachineFromList: (machine) => dispatch(removeMachineFromList(machine)), setMachineFilter: (machine) => dispatch(setMachineFilter(machine)), }); + export default connect(mapStateToProps, mapDispatchToProps)(FindMachine);