diff --git a/meshapp/src/pages/Profiles/Page.tsx b/meshapp/src/pages/Profiles/Page.tsx index 18cc50f2..096f491f 100644 --- a/meshapp/src/pages/Profiles/Page.tsx +++ b/meshapp/src/pages/Profiles/Page.tsx @@ -294,6 +294,68 @@ const ProfileEducation = (props: { education: Education }) => { `${inputName} cannot have whitespace at beginning or end.`; const charLimit = (inputName: string) => (val: string) => val.length < 100 || `${inputName} cannot be longer than 100 characters.`; + + //state for education array + //for now id will just be index + const [educationData, setEducationData] = useState( + props.education.map((currentEd, index) => { + return { + accordionId: index, + comboOneVal: currentEd.degree, + comboTwoVal: currentEd.school, + descText: currentEd.description, + }; + }) + ); + + //Edit and Delete education accordion handlers, + // takes in id number and returns a function that edits/deletes an education accordion with that id + + //The accordions already handle editing in terms of local changes without saving, + //so this edit handler is only for saving on the backend + + const editEducationHandler = (accordionId: number) => { + return () => { + //Insert API actions here + }; + }; + + //This delete handler should handle deletion on on the backend and locally + const deleteEducationHandler = (accordionId: number) => { + return () => { + //Insert API actions here + + //local deletion + setEducationData((prevEduData) => + prevEduData.filter((edu) => edu.accordionId !== accordionId) + ); + }; + }; + + //handler for adding new accordion + //should handle adding on both backend and locally + + const AddEducationHandler = ( + degree: string, + school: string, + desc: string + ) => { + //insert API actions here + + //local addition + //ideally we should get getting a new ID from the backend for this education to insert + //for now we'll just use current array length + + setEducationData((prevEduData) => [ + ...prevEduData, + { + accordionId: educationData.length, + comboOneVal: degree, + comboTwoVal: school, + descText: desc, + }, + ]); + }; return ( @@ -307,13 +369,10 @@ const ProfileEducation = (props: { education: Education }) => { Education { - return { - comboOneVal: currentEd.degree, - comboTwoVal: currentEd.school, - descText: currentEd.description, - }; - })} + groupAccordState={educationData} + setGroupAccordState={setEducationData} + editAccordHandler={editEducationHandler} + deleteAccordHandler={deleteEducationHandler} comboOneValPlaceholder="Level of Education" comboTwoValPlaceholder="School" comboOneValOptions={[ @@ -332,6 +391,7 @@ const ProfileEducation = (props: { education: Education }) => { whiteSpace("Description"), charLimit("Description"), ]} + addAccordHandler={AddEducationHandler} /> diff --git a/meshapp/src/pages/Profiles/ProfileAccordion.tsx b/meshapp/src/pages/Profiles/ProfileAccordion.tsx index 13cadbb2..bbe45e50 100644 --- a/meshapp/src/pages/Profiles/ProfileAccordion.tsx +++ b/meshapp/src/pages/Profiles/ProfileAccordion.tsx @@ -9,10 +9,19 @@ import { groupAccordionState, setGroupAccordionState, } from "./ProfileGroupAccordion"; -import { Box, Grid } from "@mui/material"; +import { + Box, + Container, + Grid, + Modal, + Stack, + Button, + Typography, +} from "@mui/material"; + import EditIcon from "@mui/icons-material/Edit"; import SaveIcon from "@mui/icons-material/Save"; - +import { Delete } from "@mui/icons-material"; /** * A React Component that returns an accordion with two comboboxes and a description textfield. * Provides editing capabilities for these inputs. @@ -20,11 +29,11 @@ import SaveIcon from "@mui/icons-material/Save"; * Used in Profile Group Accordion (src/profile/ProfileGroupAccordion.tsx) * * @param props - Properties of the component + * @param {string} props.accordionId - number representing ID of this accordion * @param {string} props.comboOneVal - value for the first combobox within the accordion, passed down from group state array data * @param {string} props.comboTwoVal - value for the second combobox within the accordion, passed down from group state array data * @param {string} props.descPlaceholder - placeholder for description textfield * @param {string} props.descText - value for description textfield, passed down from group state array data - * @param {number} props.accordionIndex - index of this accordion within the group state array * @param {string} props.comboOneValPlaceholder - placeholder for comboOneVal combobox * @param {string} props.comboTwoValPlaceholder - placeholder for comboTwoVal combobox * @param {Array} props.comboOneValOptions - list of options to be provided to comboOneVal combobox @@ -32,16 +41,21 @@ import SaveIcon from "@mui/icons-material/Save"; * @param {Array} props.comboOneValErrValidations - an array of functions to evaluate the first combo box value for errors (takes in the string value as a parameter, returns True if there was no error or the error message if there is) * @param {Array} props.comboTwoValErrValidations - an array of functions to evaluate the second combobox value for errors (takes in the string value as a parameter, returns True if there was no error or the error message if there is) * @param {Array} props.descErrValidations - an array of functions to evaluate the description text value for errors (takes in the string value as a parameter, returns True if there was no error or the error message if there is) - * @param {groupState} props.groupState - the group accordion state data for all accordions + * @param {Function} props.editTaskHandler = handler function that takes in an accordion's id and returns a function for editing on backend + * @param {Function} props.deleteTaskHandler - handler function that takes in an accordion's id and returns a function that deletes on both backend and locally + * @param {groupState} props.groupState - the group accordion state data for all accordions * @param {setGroupState} props.setGroupState - the group accordion state setter method * @param {boolean} props.alwaysOpen - if this is set true, then the accordion will be permanently open and the expand icon will disappear - */ + * @param {Function} props.editHandler - function for saving edit on backend (is optional since this component can be used for adding or editing/deleting accordion) + * @param {Function} props.deleteHandler - function for deleting on backend (is optional since this component can be used for adding or editing/deleting accordion) + +*/ export default function ProfileAccordion(props: { comboOneVal: string; comboTwoVal: string; descPlaceholder: string; descText: string; - accordionIndex: number; + accordionId: number; comboOneValPlaceholder: string; comboTwoValPlaceholder: string; comboOneValOptions: Array; @@ -52,6 +66,8 @@ export default function ProfileAccordion(props: { groupState: groupAccordionState; setGroupState: setGroupAccordionState; alwaysOpen?: boolean; + editHandler?: Function; + deleteHandler?: Function; }) { //controls whether accordion is expanded to show description or not const [expanded, setExpanded] = React.useState(false); @@ -62,8 +78,32 @@ export default function ProfileAccordion(props: { const handleEditClick = () => { setEditMode(true); }; + + //also handles saving edit on backend, if the handler exists const handleSaveClick = () => { setEditMode(false); + if (props.editHandler) { + props.editHandler(); + } + }; + + //Delete Confirmation + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + + const openDeleteConfirmModal = () => setDeleteConfirmOpen(true); + const closeDeleteConfirmModal = () => setDeleteConfirmOpen(false); + + const deleteIconClickHandler = () => { + //pops up delete confirmation modal + openDeleteConfirmModal(); + }; + + //when user confirms they want to delete + const confirmDeleteHandler = () => { + if (props.deleteHandler) { + props.deleteHandler(); + } + closeDeleteConfirmModal(); }; const groupState = props.groupState; @@ -85,15 +125,17 @@ export default function ProfileAccordion(props: { } //edit the first combo box value for the specific accordion, copy everything else setGroupState( - groupState.map((profileAccordion, index) => { - if (index === props.accordionIndex) { + groupState.map((profileAccordion) => { + if (profileAccordion.accordionId === props.accordionId) { return { + accordionId: props.accordionId, comboOneVal: newValue ? newValue : "", comboTwoVal: profileAccordion.comboTwoVal, descText: profileAccordion.descText, }; } return { + accordionId: profileAccordion.accordionId, comboOneVal: profileAccordion.comboOneVal, comboTwoVal: profileAccordion.comboTwoVal, descText: profileAccordion.descText, @@ -118,15 +160,17 @@ export default function ProfileAccordion(props: { } //edit the second combobox value for the specific accordion, copy everything else setGroupState( - groupState.map((profileAccordion, index) => { - if (index === props.accordionIndex) { + groupState.map((profileAccordion) => { + if (profileAccordion.accordionId === props.accordionId) { return { + accordionId: props.accordionId, comboOneVal: profileAccordion.comboOneVal, comboTwoVal: newValue ? newValue : "", descText: profileAccordion.descText, }; } return { + accordionId: profileAccordion.accordionId, comboOneVal: profileAccordion.comboOneVal, comboTwoVal: profileAccordion.comboTwoVal, descText: profileAccordion.descText, @@ -138,15 +182,17 @@ export default function ProfileAccordion(props: { //onChange handler for description const descOnChange = (newValue: string) => { setGroupState( - groupState.map((profileAccordion, index) => { - if (index === props.accordionIndex) { + groupState.map((profileAccordion) => { + if (profileAccordion.accordionId === props.accordionId) { return { + accordionId: props.accordionId, comboOneVal: profileAccordion.comboOneVal, comboTwoVal: profileAccordion.comboTwoVal, descText: newValue, }; } return { + accordionId: profileAccordion.accordionId, comboOneVal: profileAccordion.comboOneVal, comboTwoVal: profileAccordion.comboTwoVal, descText: profileAccordion.descText, @@ -156,92 +202,150 @@ export default function ProfileAccordion(props: { }; return ( - - - } + <> + {/*Modal for confirming deletion*/} + + + + Are you sure you want to delete this? + + + + + + + + {/*Accordion*/} + - {/*First combobox */} - + + ) + } + > + {/*First combobox */} + + + + {/*Second combobox */} - - {/*Second combobox */} - - - { - //conditional rendering for edit/save icons based on edit mode - editMode ? ( - { - event.stopPropagation(); - handleSaveClick(); - }} - sx={{ - "&:hover": { - color: "#0A6B57", - }, - cursor: "pointer", - transition: "color 0.15s ease-in-out", - }} - /> - ) : ( - { - event.stopPropagation(); - handleEditClick(); - }} + + { + //conditional rendering for edit/save icons based on edit mode + editMode ? ( + { + event.stopPropagation(); + handleSaveClick(); + }} + sx={{ + "&:hover": { + color: "#0A6B57", + }, + cursor: "pointer", + transition: "color 0.15s ease-in-out", + }} + /> + ) : ( + { + event.stopPropagation(); + handleEditClick(); + }} + sx={{ + "&:hover": { + color: "#0b7d66", + }, + cursor: "pointer", + transition: "color 0.15s ease-in-out", + }} + /> + ) + } + + {props.deleteHandler ? ( + - ) - } - - - - {/*Description Text Field */} - - - + ) : null} + + + + {/*Description Text Field */} + + + + ); } diff --git a/meshapp/src/pages/Profiles/ProfileAccordionTextfield.tsx b/meshapp/src/pages/Profiles/ProfileAccordionTextfield.tsx index 60918075..7ce4fc6e 100644 --- a/meshapp/src/pages/Profiles/ProfileAccordionTextfield.tsx +++ b/meshapp/src/pages/Profiles/ProfileAccordionTextfield.tsx @@ -15,17 +15,18 @@ import ErrorIcon from "@mui/icons-material/Error"; * @param {string} props.label - The label * @param {string} props.placeholder - The placeholder text * @param {string} props.text - The state text value from the group accordion state for the corresponding description - * @param {number} props.accordionIndex - The index of the current Profile Accordion * @param {function} props.onChange - function that takes in a new text value to change the corresponding state entry * @param {Array} props.errValidations - an array of functions to evaluate the current value for errors (takes in the string value as a parameter, returns True if there was no error or the error message if there is) - */ + * @param {Function} props.editHandler - function for saving edit on backend (is optional since this component can be used for adding or editing/deleting accordion) + +*/ const ProfileAccordionTextField = (props: { label: string; placeholder: string; text: string; - accordionIndex: number; onChange: (newValue: string) => void; errValidations: Array<(value: string) => boolean | string>; + editHandler?: Function; }) => { //used to set edit mode const [editMode, setEditMode] = useState(false); @@ -63,11 +64,15 @@ const ProfileAccordionTextField = (props: { }; //for toggling edit mode + //also handles saving edit on backend, if the handler exists const handleEditClick = () => { setEditMode(true); }; const handleSaveClick = () => { setEditMode(false); + if (props.editHandler) { + props.editHandler(); + } }; //for toggling whether to display error or not diff --git a/meshapp/src/pages/Profiles/ProfileGroupAccordion.tsx b/meshapp/src/pages/Profiles/ProfileGroupAccordion.tsx index 2ebbf851..eabeb518 100644 --- a/meshapp/src/pages/Profiles/ProfileGroupAccordion.tsx +++ b/meshapp/src/pages/Profiles/ProfileGroupAccordion.tsx @@ -13,6 +13,7 @@ import ControlPointIcon from "@mui/icons-material/ControlPoint"; //type of group accordion state export type groupAccordionState = Array<{ + accordionId: number; comboOneVal: string; comboTwoVal: string; descText: string; @@ -28,7 +29,11 @@ export type setGroupAccordionState = (newState: groupAccordionState) => void; * Used in the Profile Page (src/profile/profile-page.tsx). * * @param props - Properties of the component - * @param {groupAccordionState} props.groupAccordArgs - The array containing data for each accordion + * @param {groupAccordionState} props.groupAccordState - the state array that contains the group accordion data + * @param {setGroupAccordionState} props.setGroupAccordState - the state mutator method for the group accordion data + * @param {Function} props.editAccordHandler = handler function that takes in an accordion's id and returns a function for editing on backend + * @param {Function} props.deleteAccordHandler - handler function that takes in an accordion's id and returns a function that deletes on both backend and locally + * @param {Function} props.addAccordHandler - handler function for adding an accord on the backend and locally * @param {string} props.descPlaceholder - the placeholder for the description textfield * @param {string} props.comboOneValPlaceholder - the placeholder for the first combobox combobox in each accordion * @param {string} props.comboTwoValPlaceholder - the placeholder for the second combobox combobox in each accordion @@ -39,7 +44,10 @@ export type setGroupAccordionState = (newState: groupAccordionState) => void; * @param {Array} props.descErrValidations - an array of functions to evaluate the description text value for each accordion for errors (takes in the string value as a parameter, returns True if there was no error or the error message if there is) */ export function ProfileGroupAccordion(props: { - groupAccordArgs: groupAccordionState; + groupAccordState: groupAccordionState; + setGroupAccordState: setGroupAccordionState; + editAccordHandler: Function; + deleteAccordHandler: Function; descPlaceholder: string; comboOneValPlaceholder: string; comboTwoValPlaceholder: string; @@ -48,26 +56,16 @@ export function ProfileGroupAccordion(props: { comboOneValErrValidations: Array<(value: string) => boolean | string>; comboTwoValErrValidations: Array<(value: string) => boolean | string>; descErrValidations: Array<(value: string) => boolean | string>; + addAccordHandler: Function; }) { - //group accordion data state with set state method - const [GroupAccordState, setGroupAccordState] = useState( - props.groupAccordArgs.map((accord) => { - return { - comboOneVal: accord.comboOneVal, - comboTwoVal: accord.comboTwoVal, - descText: accord.descText, - }; - }) - ); - //state to control whether new accordion modal is open or not const [addOpen, setAddOpen] = useState(false); const showModal = () => setAddOpen(true); const hideModal = () => setAddOpen(false); - //state to store data for the new accordion + //state to store data for the new accordion, id doesn't matter since it won't really be used const [newAccordionData, setNewAccordionData] = useState([ - { comboOneVal: "", comboTwoVal: "", descText: "" }, + { comboOneVal: "", comboTwoVal: "", descText: "", accordionId: 0 }, ]); return ( @@ -76,13 +74,14 @@ export function ProfileGroupAccordion(props: { container flexDirection={"column"} alignItems={"center"} - onClick={() => console.log(GroupAccordState)} + // onClick={() => console.log(props.groupAccordState)} > - {GroupAccordState.map((accordArgs, accordIndex) => { + {props.groupAccordState.map((accordArgs) => { return ( ); })} @@ -112,8 +113,8 @@ export function ProfileGroupAccordion(props: { showModal={showModal} newAccordData={newAccordionData} setAccordData={setNewAccordionData} - groupState={GroupAccordState} - setGroupState={setGroupAccordState} + groupState={props.groupAccordState} + setGroupState={props.setGroupAccordState} comboOneValErrValidations={props.comboOneValErrValidations} comboTwoValErrValidations={props.comboTwoValErrValidations} descErrValidations={props.descErrValidations} @@ -122,6 +123,7 @@ export function ProfileGroupAccordion(props: { comboTwoValPlaceholder={props.comboTwoValPlaceholder} comboOneValOptions={props.comboOneValOptions} comboTwoValOptions={props.comboTwoValOptions} + addAccordHandler={props.addAccordHandler} /> ); @@ -146,6 +148,7 @@ export function ProfileGroupAccordion(props: { * @param {string} props.comboTwoValPlaceholder - placeholder for second combobox * @param {Array} props.comboOneValOptions - list of options to be provided for first combobox * @param {Array} props.comboTwoValOptions -list of options to be provided for second combobox + * @param {Function} props.addAccordHandler - handler function for adding an accord on the backend and locally */ function NewAccordionModal(props: { addOpen: boolean; @@ -163,12 +166,13 @@ function NewAccordionModal(props: { comboTwoValPlaceholder: string; comboOneValOptions: Array; comboTwoValOptions: Array; + addAccordHandler: Function; }) { //grab the data for the current new accordion let newAccordData = props.newAccordData[0]; - let comboOneValVal = newAccordData.comboOneVal; - let comboTwoValVal = newAccordData.comboTwoVal; - let descVal = newAccordData.descText; + let newComboOneVal = newAccordData.comboOneVal; + let newComboTwoVal = newAccordData.comboTwoVal; + let newDescVal = newAccordData.descText; //controls whether snackbar is open or not const [snackbar, setSnackbar] = useState(false); @@ -199,11 +203,11 @@ function NewAccordionModal(props: { > console.log(newAccordData)}> { @@ -266,15 +270,10 @@ function NewAccordionModal(props: { //if there are no errors, hide the modal, add the new accordion data to real group accord data, and erase the modal accordion data if (errResults.every((err) => typeof err === "boolean")) { props.hideModal(); - props.setGroupState([ - ...props.groupState, - { - comboOneVal: comboOneValVal, - comboTwoVal: comboTwoValVal, - descText: descVal, - }, + props.addAccordHandler(newComboOneVal, newComboTwoVal, newDescVal); + props.setAccordData([ + { accordionId: 0, comboOneVal: "", comboTwoVal: "", descText: "" }, ]); - props.setAccordData([{ comboOneVal: "", comboTwoVal: "", descText: "" }]); } //otherwise, display the error, and don't erase modal accordion data else {