diff --git a/package-lock.json b/package-lock.json index a154dc27..bba91755 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17822,6 +17822,11 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz", + "integrity": "sha512-oIixUO+OamkUkwjhAVE18rAMfRJNsNe/Stid/gwHSOfHrOtw9EhAY2AHvdKZ/k/MggcYELFCJz/Sn2pL8b8JMw==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", diff --git a/package.json b/package.json index abf1df66..d807ef6c 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "leaflet-routing-machine": "^3.2.12", "lodash": "^4.17.15", "material-ui-phone-number": "^2.2.6", + "moment": "^2.26.0", "path": "^0.12.7", "querystring-browser": "^1.0.4", "react": "^16.13.1", diff --git a/src/app/component/AddressInput/AddressInput.jsx b/src/app/component/AddressInput/AddressInput.jsx index b1f7bf2c..c0243045 100644 --- a/src/app/component/AddressInput/AddressInput.jsx +++ b/src/app/component/AddressInput/AddressInput.jsx @@ -22,7 +22,17 @@ const useStyles = makeStyles((theme) => ({ const AddressInput = (props) => { const classes = useStyles(); - const { disabled, error, location, onClear, placeholder, setLocation, showMap, value } = props; + const { + disabled, + error, + id, + location, + onClear, + placeholder, + setLocation, + showMap, + value, + } = props; const [zoom, setZoom] = useState(0); const [position, setPosition] = useState([0, 0]); @@ -107,6 +117,7 @@ const AddressInput = (props) => { ) : ( { - const handleChange = (field, newValue) => { - onChange({ - ...value, - [field]: newValue, - }); - }; - - const datePickerContainerProps = { - id: "datetime-input-date", - label: "Date", - margin: "normal", - ...dateInputProps, - }; - - const timePickerProps = { - id: "datetime-input-time", - label: "Time", - margin: "normal", - ...timeInputProps, - }; - - return ( - - handleChange("date", val)} - required={required} - value={value.date} - margin="normal" - format="MM/dd/yyyy" - KeyboardButtonProps={{ - "aria-label": "change date", - }} - {...datePickerContainerProps} - /> - handleChange("time", val)} - KeyboardButtonProps={{ "aria-label": "change time" }} - {...timePickerProps} - /> - - ); -}; - -DateTimeInput.propTypes = { - dateInputProps: PropTypes.object, - timeInputProps: PropTypes.object, - onChange: PropTypes.func.isRequired, - value: PropTypes.shape({ - time: PropTypes.object, - date: PropTypes.object, - }).isRequired, // Strict shape not enforced, object can contain other properties (like 'location') -}; - -export default DateTimeInput; diff --git a/src/app/component/DateTimeInput/DateTimeInput.tsx b/src/app/component/DateTimeInput/DateTimeInput.tsx new file mode 100644 index 00000000..63715202 --- /dev/null +++ b/src/app/component/DateTimeInput/DateTimeInput.tsx @@ -0,0 +1,67 @@ +import MomentUtils from "@date-io/date-fns"; +import { Box } from "@material-ui/core"; +import { + DatePicker as MuiDatePicker, + TimePicker as MuiTimePicker, + MuiPickersUtilsProvider, +} from "@material-ui/pickers"; +import React from "react"; +import styled from "styled-components"; + +const DatePicker = styled(MuiDatePicker)` + flex-grow: 1; + margin-right: 30px; +`; +const TimePicker = styled(MuiTimePicker)` + flex-grow: 1; +`; + +/** + * Wrapper component for KeybardDatePickerContainer and KeyboadTimePicker + * which accepts a value object that has 'time' and 'date' props and will + * update that object via the onChange handler + * @function + * @param {string} value + * @param {func} onChange + */ +const DateTimeInput = ({ onChange, value }: { value: string | null; onChange: Function }) => { + function getDateTime(time: string | null) { + if (!time) { + return null; + } + return new Date(time); + } + const time = getDateTime(value); + + const handleChange = (date: any) => { + if (date === null) { + onChange(""); + } else { + onChange(date.toISOString()); + } + }; + + return ( + + + + + + + ); +}; + +export default DateTimeInput; diff --git a/src/app/component/UsersAutocomplete.jsx b/src/app/component/UsersAutocomplete.jsx index 8915da98..4fdf9a4e 100644 --- a/src/app/component/UsersAutocomplete.jsx +++ b/src/app/component/UsersAutocomplete.jsx @@ -2,6 +2,7 @@ import { Avatar, Box, TextField } from "@material-ui/core"; import { makeStyles } from "@material-ui/core/styles"; import Autocomplete from "@material-ui/lab/Autocomplete"; import React, { Fragment } from "react"; +import { H6 } from "./"; const useStyles = makeStyles((theme) => ({ root: { @@ -31,8 +32,11 @@ const SearchUsers = ({ editable, handleChange, selected, users }) => { }; const options = users?.reduce(reducer, []) || []; const optionSelected = options.find((option) => option && selected && option.id === selected.id); - const defaultValue = editable ? optionSelected : ""; - const value = !editable ? optionSelected : ""; + const defaultValue = editable ? optionSelected : null; + + if (!editable) { + return
{selected.displayName + " " + selected.phoneNumber}
; + } return ( { classes={{ root: classes.root }} getOptionLabel={(user) => user.searchString} onChange={(event, newValue) => handleChange(newValue)} - value={value} defaultValue={defaultValue} renderInput={(params) => ( ({ - root: { - textAlign: "left", - height: "100%", - overflow: "auto", - padding: `0px ${theme.spacing(1)}px`, - }, - image: { - height: 0, - paddingTop: "56.25%", // 16:9 - }, - missionTypeText: { - paddingTop: theme.spacing(0.5), - }, - rowLabel: { - fontWeight: 600, - marginTop: theme.spacing(2), - }, - deliveryDetails: { - marginTop: theme.spacing(0.5), - }, - - missionImage: { - margin: theme.spacing(1), - width: "100%", - maxHeight: "200px", - }, - foodBoxDetailContainer: { - border: "1px solid lightgrey", - borderRadius: "4px", - fontSize: "18px", - color: "black", - fontWeight: "bold", - marginBottom: theme.spacing(1), - }, - foodBoxDetailQuantity: { - padding: theme.spacing(1), - borderRight: "1px solid lightgrey", - }, - foodBoxDetailName: { - padding: theme.spacing(1), - }, - buttonBox: { - spacing: theme.spacing(1), - display: "flex", - justifyContent: "center", - }, -})); - -/**=====BASE COMPONENTs======**/ - -const Label = ({ children, classes }) => { - if (!children) return null; - return ( - - {children} - - ); -}; - -const Row = ({ children, Icon }) => { - if (!children) return null; - return ( - - - {Icon && } - - {children} - - ); -}; - -const Card = ({ children, classes, label }) => { - if (!children) return null; - return ( - <> - - {children} - - ); -}; - -/**=====ROW COMPONENTS=======*/ - -const MissionImage = ({ classes, mission }) => { - const imageUrl = mission?.image; - if (!imageUrl) return null; - - return ( - - details - - ); -}; - -const MissionTypeRow = ({ classes, mission }) => { - let missionTypeText; - switch (mission?.type) { - case Mission.Type.resource: - missionTypeText = "Foodbox"; - break; - case Mission.Type.errand: - missionTypeText = "General Errand"; - break; - default: - missionTypeText = "Mission Name"; - } - return ( - -

{missionTypeText}

-
- ); -}; - -const VolunteerRow = ({ mission }) => { - const { tentativeVolunteerDisplayName, volunteerDisplayName } = mission; - let assigned = ""; - if (volunteerDisplayName) { - assigned = volunteerDisplayName + " - accepted"; - } else if (tentativeVolunteerDisplayName) { - assigned = tentativeVolunteerDisplayName + " - tentative"; - } else { - assigned = "Looking for volunteer"; - } - return {assigned}; -}; - -const MissionFundedStatusRow = ({ classes, mission }) => { - let missionFundedStatusText; - switch (mission?.fundedStatus) { - case Mission.FundedStatus.fundedbydonation: - missionFundedStatusText = "Funded By Donation"; - break; - case Mission.FundedStatus.fundedbyrecipient: - missionFundedStatusText = "Funded By Recipient"; - break; - case Mission.FundedStatus.fundingnotneeded: - missionFundedStatusText = "Funding Not Needed"; - break; - case Mission.FundedStatus.notfunded: - missionFundedStatusText = "Not Yet Funded"; - break; - default: - throw Error("mission funded status not exist", mission.fundedStatus); - } - return ( - - {missionFundedStatusText} - - ); -}; - -const FoodBoxDetailsRow = ({ details }) => { - return ( - - {details?.map((box) => ( - - {box?.quantity} x {box?.displayName} - - ))} - - ); -}; - -const MissionDetailsRow = ({ mission }) => { - let type = mission?.type; - let details = mission?.details; - if (type === "resource") { - return ; - } - return null; -}; - -/** - * Component for displaying mission details as a card - * @component - */ -const MissionDetailsCard = ({ mission, toEditView, toListView }) => { - const classes = useStyles(); - const recipientPhoneNumber = _.get(mission, "recipientPhoneNumber"); - - const props = { classes: classes, mission: mission }; - return ( - - - - - - {isLoaded(mission) && !isEmpty(mission) && ( - - - - - - - - - - - {mission?.pickUpLocation?.address} - - - {mission?.pickUpWindow?.startTime} - - - - - - {mission?.deliveryLocation?.address} - - - {mission?.deliveryWindow?.startTime} - - - {mission?.recipientName} - - - {recipientPhoneNumber && ( - {recipientPhoneNumber} - )} - - - - - {mission?.deliveryNotes || "No additional informations"} - - - - - - - - - )} - - - ); -}; - -export default MissionDetailsCard; diff --git a/src/app/dashboard/Missions/ListView.jsx b/src/app/dashboard/Missions/ListView.jsx index 2218e3ec..41f5ad8b 100644 --- a/src/app/dashboard/Missions/ListView.jsx +++ b/src/app/dashboard/Missions/ListView.jsx @@ -4,7 +4,7 @@ import { makeStyles } from "@material-ui/core/styles"; import React, { useEffect, useState } from "react"; import GroupWorkIcon from "@material-ui/icons/GroupWork"; import Divider from "@material-ui/core/Divider"; -import EditView from "./MissionEditView"; +import EditView from "./MissionDetailsEdit"; import MuiExpansionPanel from "@material-ui/core/ExpansionPanel"; import MuiExpansionPanelSummary from "@material-ui/core/ExpansionPanelSummary"; @@ -12,7 +12,6 @@ import MuiExpansionPanelDetails from "@material-ui/core/ExpansionPanelDetails"; import Card from "@material-ui/core/Card"; import Paper from "@material-ui/core/Paper"; -import DetailsView from "./DetailsView"; import ListItem from "./ListItem"; import { Mission } from "../../model"; import _ from "../../utils/lodash"; @@ -131,9 +130,6 @@ const MissionsListView = ({ if (!group?.missions) return null; const color = _.randomColor(group.groupDisplayName); - /** - * Will be add another icon later, remove this for now - */ return ( @@ -174,20 +170,12 @@ const MissionsListView = ({ return ( -
- - setSelected(newValue)} - renderInput={(params) => ( - - )} - getOptionLabel={(group) => - group.tmpDisplayName ? group.tmpDisplayName : group.groupDisplayName - } - renderOption={(group) => ( - <> - {" "} - {group.tmpDisplayName ? group.tmpDisplayName : group.groupDisplayName} - - )} - filterOptions={(groups, params) => { - const filtered = filter(groups, params); - - // Suggest the creation of a new value - if (params.inputValue !== "") { - filtered.push({ - inputValue: params.inputValue, - groupDisplayName: params.inputValue, - tmpDisplayName: `Create and add to "${params.inputValue}"`, - }); - } - - return filtered; - }} - /> - +