From 5b83fc19e7c4e4f39fa4b2eabeddc99906f33dd1 Mon Sep 17 00:00:00 2001 From: Kaifur Rahman Date: Tue, 2 Jul 2024 18:46:08 +0530 Subject: [PATCH] signup approval table implemented --- .../Homepage/Announcements/Announcements.jsx | 2 +- .../SignupApproval/SignupApproval.jsx | 177 ++++++++ .../SignupApproval/TableColumnHeadings.jsx | 92 ++++ .../components/SignupApproval/TableData.jsx | 86 ++++ .../SignupApproval/TableDescription.jsx | 36 ++ .../SignupApproval/TableToolbar.jsx | 107 +++++ .../SignupApproval/columnHeadings.js | 197 +++++++++ client/src/constants/Data/signupApproavl.js | 415 ++++++++++++++++++ client/src/index.css | 4 + client/src/pages/Template.jsx | 4 +- client/src/routes/router.js | 17 + 11 files changed, 1135 insertions(+), 2 deletions(-) create mode 100644 client/src/components/SignupApproval/SignupApproval.jsx create mode 100644 client/src/components/SignupApproval/TableColumnHeadings.jsx create mode 100644 client/src/components/SignupApproval/TableData.jsx create mode 100644 client/src/components/SignupApproval/TableDescription.jsx create mode 100644 client/src/components/SignupApproval/TableToolbar.jsx create mode 100644 client/src/components/SignupApproval/columnHeadings.js create mode 100644 client/src/constants/Data/signupApproavl.js diff --git a/client/src/components/Homepage/Announcements/Announcements.jsx b/client/src/components/Homepage/Announcements/Announcements.jsx index baade5a..8cbf926 100644 --- a/client/src/components/Homepage/Announcements/Announcements.jsx +++ b/client/src/components/Homepage/Announcements/Announcements.jsx @@ -14,7 +14,7 @@ import "swiper/css/pagination"; function Announcements() { return ( <> - + a[orderBy]) { + return 1; + } + return 0; +} + +function getComparator(order, orderBy) { + return order === "desc" + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); +} + +function stableSort(array, comparator) { + const stabilizedThis = array.map((el, index) => [el, index]); + stabilizedThis.sort((a, b) => { + const order = comparator(a[0], b[0]); + if (order !== 0) { + return order; + } + return a[1] - b[1]; + }); + return stabilizedThis.map((el) => el[0]); +} + +// main table +export default function ApprovalTable({ signupRole }) { + const [order, setOrder] = React.useState("asc"); + const [orderBy, setOrderBy] = React.useState("calories"); + const [selected, setSelected] = React.useState([]); + const [page, setPage] = React.useState(0); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + + const handleRequestSort = (event, property) => { + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); + setOrderBy(property); + }; + + const handleSelectAllClick = (event) => { + if (event.target.checked) { + const newSelected = signupData[signupRole].map((n) => n.id); + setSelected(newSelected); + return; + } + setSelected([]); + }; + + //function to handle individual row select + const handleClick = (event, id) => { + const selectedIndex = selected.indexOf(id); + //saves the id of selected row + let newSelected = []; + if (selectedIndex === -1) { + newSelected = newSelected.concat(selected, id); + } else if (selectedIndex === 0) { + newSelected = newSelected.concat(selected.slice(1)); + } else if (selectedIndex === selected.length - 1) { + newSelected = newSelected.concat(selected.slice(0, -1)); + } else if (selectedIndex > 0) { + newSelected = newSelected.concat( + selected.slice(0, selectedIndex), + selected.slice(selectedIndex + 1) + ); + } + setSelected(newSelected); + }; + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + const isSelected = (id) => selected.indexOf(id) !== -1; + + // Avoid a layout jump when reaching the last page with empty rows. + const emptyRows = + page > 0 + ? Math.max(0, (1 + page) * rowsPerPage - signupData[signupRole].length) + : 0; + + const visibleRows = React.useMemo( + () => + stableSort(signupData[signupRole], getComparator(order, orderBy)).slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ), + [order, orderBy, page, rowsPerPage, signupRole] + ); + + return ( + <> + + + {/* table description */} + + + {/* table toolbar */} + + + + {/* table column headings */} + + + {/* table rows */} + +
+
+ +
+
+
+ + ); +} + +ApprovalTable.propTypes = { + signupRole: PropTypes.string.isRequired, +}; diff --git a/client/src/components/SignupApproval/TableColumnHeadings.jsx b/client/src/components/SignupApproval/TableColumnHeadings.jsx new file mode 100644 index 0000000..9450e74 --- /dev/null +++ b/client/src/components/SignupApproval/TableColumnHeadings.jsx @@ -0,0 +1,92 @@ +import Box from "@mui/material/Box"; +import PropTypes from "prop-types"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import TableCell from "@mui/material/TableCell"; +import Checkbox from "@mui/material/Checkbox"; +import TableSortLabel from "@mui/material/TableSortLabel"; +import { visuallyHidden } from "@mui/utils"; +import { colorScheme } from "../../constants/colorScheme"; +import { headCells, additionDetailsCells } from "./columnHeadings"; + +function TableColumnHeadings(props) { + const { + onSelectAllClick, + order, + orderBy, + numSelected, + rowCount, + onRequestSort, + signupRole, + } = props; + + const createSortHandler = (property) => (event) => { + onRequestSort(event, property); + }; + + const columnHeadings = additionDetailsCells[signupRole] + ? [...headCells, ...additionDetailsCells[signupRole]] + : headCells; + + return ( + + + {/* bulk selection check box */} + + 0 && numSelected < rowCount} + checked={rowCount > 0 && numSelected === rowCount} + onChange={onSelectAllClick} + inputProps={{ + "aria-label": "select all desserts", + }} + sx={{ + color: colorScheme.secondaryGrey, + "&.Mui-checked": { + color: colorScheme.primaryOrange, + }, + "&.MuiCheckbox-indeterminate": { + color: colorScheme.secondaryGrey, + }, + }} + /> + + {/* columns mapping */} + {columnHeadings.map((headCell) => ( + + + {headCell.label} + {orderBy === headCell.id ? ( + + {order === "desc" ? "sorted descending" : "sorted ascending"} + + ) : null} + + + ))} + + + ); +} + +TableColumnHeadings.propTypes = { + numSelected: PropTypes.number.isRequired, + onRequestSort: PropTypes.func.isRequired, + onSelectAllClick: PropTypes.func.isRequired, + order: PropTypes.oneOf(["asc", "desc"]).isRequired, + orderBy: PropTypes.string.isRequired, + rowCount: PropTypes.number.isRequired, + signupRole: PropTypes.string.isRequired, +}; + +export default TableColumnHeadings; diff --git a/client/src/components/SignupApproval/TableData.jsx b/client/src/components/SignupApproval/TableData.jsx new file mode 100644 index 0000000..afaebb6 --- /dev/null +++ b/client/src/components/SignupApproval/TableData.jsx @@ -0,0 +1,86 @@ +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableRow from "@mui/material/TableRow"; +import Checkbox from "@mui/material/Checkbox"; +import PropTypes from "prop-types"; +import { colorScheme } from "../../constants/colorScheme"; +import { headCells, additionDetailsCells } from "./columnHeadings"; + +function TableData({ + signupRole, + visibleRows, + isSelected, + handleClick, + emptyRows, +}) { + const columnHeadings = additionDetailsCells[signupRole] + ? [...headCells, ...additionDetailsCells[signupRole]] + : headCells; + + return ( + + {visibleRows.map((row, index) => { + const isItemSelected = isSelected(row.id); + const labelId = `enhanced-table-checkbox-${index}`; + return ( + handleClick(event, row.id)} + role="checkbox" + aria-checked={isItemSelected} + tabIndex={-1} + key={row.id} + selected={isItemSelected} + sx={{ + cursor: "pointer", + "&.Mui-selected": { + backgroundColor: colorScheme.primaryOrangeLight, + "&:hover": { + backgroundColor: "#FFEAD6", + }, + }, + }} + > + + + + {/* row cell names mapping */} + {columnHeadings.map((cell) => ( + + {row[cell.id]} + + ))} + + ); + })} + {emptyRows > 0 && ( + + + + )} + + ); +} +TableData.propTypes = { + signupRole: PropTypes.string.isRequired, + handleClick: PropTypes.func.isRequired, + visibleRows: PropTypes.array.isRequired, + isSelected: PropTypes.func.isRequired, + emptyRows: PropTypes.number.isRequired, +}; +export default TableData; diff --git a/client/src/components/SignupApproval/TableDescription.jsx b/client/src/components/SignupApproval/TableDescription.jsx new file mode 100644 index 0000000..b822e79 --- /dev/null +++ b/client/src/components/SignupApproval/TableDescription.jsx @@ -0,0 +1,36 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Typography } from "@mui/material"; +import { colorScheme } from "../../constants/colorScheme"; + +function TableDescription({ signupRole }) { + return ( + <> + + Pending Signups + + + {` Overview of all ${signupRole} signups awaiting your approval.`} + + + ); +} + +TableDescription.propTypes = { + signupRole: PropTypes.string.isRequired, +}; +export default TableDescription; diff --git a/client/src/components/SignupApproval/TableToolbar.jsx b/client/src/components/SignupApproval/TableToolbar.jsx new file mode 100644 index 0000000..d88903a --- /dev/null +++ b/client/src/components/SignupApproval/TableToolbar.jsx @@ -0,0 +1,107 @@ +import Toolbar from "@mui/material/Toolbar"; +import Box from "@mui/material/Box"; +import Typography from "@mui/material/Typography"; +import IconButton from "@mui/material/IconButton"; +import Tooltip from "@mui/material/Tooltip"; +import PropTypes from "prop-types"; +import ClearIcon from "@mui/icons-material/Clear"; +import Button from "@mui/material/Button"; +import DoneIcon from "@mui/icons-material/Done"; +import { colorScheme } from "../../constants/colorScheme"; + +function TableToolbar(props) { + const { numSelected, signupRole } = props; + + return ( + + {numSelected > 0 ? ( + + {numSelected} Selected + + ) : ( + + {`${signupRole} Registration Details`} + + )} + + {numSelected > 0 ? ( + <> + + + + + + ) : null} + + ); +} + +TableToolbar.propTypes = { + numSelected: PropTypes.number.isRequired, + signupRole: PropTypes.string.isRequired, +}; + +export default TableToolbar; diff --git a/client/src/components/SignupApproval/columnHeadings.js b/client/src/components/SignupApproval/columnHeadings.js new file mode 100644 index 0000000..bf02268 --- /dev/null +++ b/client/src/components/SignupApproval/columnHeadings.js @@ -0,0 +1,197 @@ +//common column headings +const headCells = [ + { + id: "firstName", + numeric: false, + disablePadding: true, + label: "First Name", + }, + { + id: "lastName", + numeric: false, + disablePadding: false, + label: "Last Name", + }, + { + id: "email", + numeric: false, + disablePadding: false, + label: "Email", + }, + { + id: "userName", + numeric: false, + disablePadding: false, + label: "User Name", + }, + { + id: "phone", + numeric: false, + disablePadding: false, + label: "Phone", + }, +]; + +//role speicific column headings +const additionDetailsCells = { + Organization: [ + { + id: "dob", + numeric: false, + disablePadding: false, + label: "DoB", + }, + { + id: "organizationName", + numeric: false, + disablePadding: false, + label: "Organization Name", + }, + { + id: "dateOfEstablishment", + numeric: false, + disablePadding: false, + label: "Date of Establishment", + }, + { + id: "type", + numeric: false, + disablePadding: false, + label: "Type", + }, + { + id: "parentOrganization", + numeric: false, + disablePadding: false, + label: "Parent Organization", + }, + { + id: "state", + numeric: false, + disablePadding: false, + label: "State", + }, + { + id: "city", + numeric: false, + disablePadding: false, + label: "City", + }, + { + id: "district", + numeric: false, + disablePadding: false, + label: "District", + }, + { + id: "pincode", + numeric: false, + disablePadding: false, + label: "Pincode", + }, + { + id: "address", + numeric: false, + disablePadding: false, + label: "Address", + }, + ], + School: [ + { + id: "dob", + numeric: false, + disablePadding: false, + label: "DoB", + }, + { + id: "schoolName", + numeric: false, + disablePadding: false, + label: "School Name", + }, + { + id: "dateOfEstablishment", + numeric: false, + disablePadding: false, + label: "Date of Establishment", + }, + { + id: "type", + numeric: false, + disablePadding: false, + label: "Type", + }, + { + id: "parentOrganization", + numeric: false, + disablePadding: false, + label: "Parent Organization", + }, + { + id: "state", + numeric: false, + disablePadding: false, + label: "State", + }, + { + id: "city", + numeric: false, + disablePadding: false, + label: "City", + }, + { + id: "district", + numeric: false, + disablePadding: false, + label: "District", + }, + { + id: "pincode", + numeric: false, + disablePadding: false, + label: "Pincode", + }, + { + id: "address", + numeric: false, + disablePadding: false, + label: "Address", + }, + ], + User: [ + { + id: "dob", + numeric: false, + disablePadding: false, + label: "DoB", + }, + { + id: "organization", + numeric: false, + disablePadding: false, + label: "Organization", + }, + { + id: "school", + numeric: false, + disablePadding: false, + label: "School", + }, + { + id: "role", + numeric: false, + disablePadding: false, + label: "Role", + }, + ], + "Training Team": [ + { + id: "type", + numeric: false, + disablePadding: false, + label: "Type", + }, + ], +}; + +export { headCells, additionDetailsCells }; diff --git a/client/src/constants/Data/signupApproavl.js b/client/src/constants/Data/signupApproavl.js new file mode 100644 index 0000000..5dda1dd --- /dev/null +++ b/client/src/constants/Data/signupApproavl.js @@ -0,0 +1,415 @@ +//temp dict data later speicific api +const signupData = { + Organization: [ + { + id: 1, + firstName: "John", + lastName: "Doe", + email: "john.doe@organization.com", + userName: "johndoe", + phone: "123-456-7890", + organizationName: "Tech Corp", + dateOfEstablishment: "2001-01-01", + type: "Private", + parentOrganization: "Global Tech", + state: "California", + city: "San Francisco", + district: "SF District", + pincode: "94101", + address: "123 Tech Street", + }, + { + id: 2, + firstName: "Sarah", + lastName: "Connor", + email: "sarah.connor@organization.com", + userName: "sconnor", + phone: "321-654-0987", + organizationName: "Innova LLC", + dateOfEstablishment: "1999-05-20", + type: "Private", + parentOrganization: "Global Innovators", + state: "Texas", + city: "Austin", + district: "Austin District", + pincode: "73301", + address: "456 Innovation Drive", + }, + { + id: 3, + firstName: "John", + lastName: "Doe", + email: "john.doe@organization.com", + userName: "johndoe", + phone: "123-456-7890", + organizationName: "Tech Corp", + dateOfEstablishment: "2001-01-01", + type: "Private", + parentOrganization: "Global Tech", + state: "California", + city: "San Francisco", + district: "SF District", + pincode: "94101", + address: "123 Tech Street", + }, + { + id: 4, + firstName: "Sarah", + lastName: "Connor", + email: "sarah.connor@organization.com", + userName: "sconnor", + phone: "321-654-0987", + organizationName: "Innova LLC", + dateOfEstablishment: "1999-05-20", + type: "Private", + parentOrganization: "Global Innovators", + state: "Texas", + city: "Austin", + district: "Austin District", + pincode: "73301", + address: "456 Innovation Drive", + }, + { + id: 5, + firstName: "John", + lastName: "Doe", + email: "john.doe@organization.com", + userName: "johndoe", + phone: "123-456-7890", + organizationName: "Tech Corp", + dateOfEstablishment: "2001-01-01", + type: "Private", + parentOrganization: "Global Tech", + state: "California", + city: "San Francisco", + district: "SF District", + pincode: "94101", + address: "123 Tech Street", + }, + { + id: 6, + firstName: "Sarah", + lastName: "Connor", + email: "sarah.connor@organization.com", + userName: "sconnor", + phone: "321-654-0987", + organizationName: "Innova LLC", + dateOfEstablishment: "1999-05-20", + type: "Private", + parentOrganization: "Global Innovators", + state: "Texas", + city: "Austin", + district: "Austin District", + pincode: "73301", + address: "456 Innovation Drive", + }, + { + id: 7, + firstName: "John", + lastName: "Doe", + email: "john.doe@organization.com", + userName: "johndoe", + phone: "123-456-7890", + organizationName: "Tech Corp", + dateOfEstablishment: "2001-01-01", + type: "Private", + parentOrganization: "Global Tech", + state: "California", + city: "San Francisco", + district: "SF District", + pincode: "94101", + address: "123 Tech Street", + }, + { + id: 8, + firstName: "Sarah", + lastName: "Connor", + email: "sarah.connor@organization.com", + userName: "sconnor", + phone: "321-654-0987", + organizationName: "Innova LLC", + dateOfEstablishment: "1999-05-20", + type: "Private", + parentOrganization: "Global Innovators", + state: "Texas", + city: "Austin", + district: "Austin District", + pincode: "73301", + address: "456 Innovation Drive", + }, + { + id: 9, + firstName: "John", + lastName: "Doe", + email: "john.doe@organization.com", + userName: "johndoe", + phone: "123-456-7890", + organizationName: "Tech Corp", + dateOfEstablishment: "2001-01-01", + type: "Private", + parentOrganization: "Global Tech", + state: "California", + city: "San Francisco", + district: "SF District", + pincode: "94101", + address: "123 Tech Street", + }, + { + id: 10, + firstName: "Sarah", + lastName: "Connor", + email: "sarah.connor@organization.com", + userName: "sconnor", + phone: "321-654-0987", + organizationName: "Innova LLC", + dateOfEstablishment: "1999-05-20", + type: "Private", + parentOrganization: "Global Innovators", + state: "Texas", + city: "Austin", + district: "Austin District", + pincode: "73301", + address: "456 Innovation Drive", + }, + { + id: 11, + firstName: "John", + lastName: "Doe", + email: "john.doe@organization.com", + userName: "johndoe", + phone: "123-456-7890", + organizationName: "Tech Corp", + dateOfEstablishment: "2001-01-01", + type: "Private", + parentOrganization: "Global Tech", + state: "California", + city: "San Francisco", + district: "SF District", + pincode: "94101", + address: "123 Tech Street", + }, + { + id: 12, + firstName: "Sarah", + lastName: "Connor", + email: "sarah.connor@organization.com", + userName: "sconnor", + phone: "321-654-0987", + organizationName: "Innova LLC", + dateOfEstablishment: "1999-05-20", + type: "Private", + parentOrganization: "Global Innovators", + state: "Texas", + city: "Austin", + district: "Austin District", + pincode: "73301", + address: "456 Innovation Drive", + }, + ], + School: [ + { + id: 1, + firstName: "Jane", + lastName: "Smith", + email: "jane.smith@school.com", + userName: "janesmith", + phone: "987-654-3210", + schoolName: "Greenwood High", + dateOfEstablishment: "1995-08-15", + type: "Public", + parentOrganization: "City Education Board", + state: "New York", + city: "New York City", + district: "Manhattan", + pincode: "10001", + address: "456 School Lane", + }, + { + id: 2, + firstName: "Mike", + lastName: "Johnson", + email: "mike.johnson@school.com", + userName: "mjohnson", + phone: "555-987-6543", + schoolName: "Riverdale School", + dateOfEstablishment: "1980-11-30", + type: "Private", + parentOrganization: "Riverdale Education", + state: "California", + city: "Los Angeles", + district: "LA District", + pincode: "90001", + address: "789 School Street", + }, + { + id: 3, + firstName: "Jane", + lastName: "Smith", + email: "jane.smith@school.com", + userName: "janesmith", + phone: "987-654-3210", + schoolName: "Greenwood High", + dateOfEstablishment: "1995-08-15", + type: "Public", + parentOrganization: "City Education Board", + state: "New York", + city: "New York City", + district: "Manhattan", + pincode: "10001", + address: "456 School Lane", + }, + { + id: 4, + firstName: "Mike", + lastName: "Johnson", + email: "mike.johnson@school.com", + userName: "mjohnson", + phone: "555-987-6543", + schoolName: "Riverdale School", + dateOfEstablishment: "1980-11-30", + type: "Private", + parentOrganization: "Riverdale Education", + state: "California", + city: "Los Angeles", + district: "LA District", + pincode: "90001", + address: "789 School Street", + }, + { + id: 5, + firstName: "Jane", + lastName: "Smith", + email: "jane.smith@school.com", + userName: "janesmith", + phone: "987-654-3210", + schoolName: "Greenwood High", + dateOfEstablishment: "1995-08-15", + type: "Public", + parentOrganization: "City Education Board", + state: "New York", + city: "New York City", + district: "Manhattan", + pincode: "10001", + address: "456 School Lane", + }, + { + id: 6, + firstName: "Mike", + lastName: "Johnson", + email: "mike.johnson@school.com", + userName: "mjohnson", + phone: "555-987-6543", + schoolName: "Riverdale School", + dateOfEstablishment: "1980-11-30", + type: "Private", + parentOrganization: "Riverdale Education", + state: "California", + city: "Los Angeles", + district: "LA District", + pincode: "90001", + address: "789 School Street", + }, + { + id: 7, + firstName: "Jane", + lastName: "Smith", + email: "jane.smith@school.com", + userName: "janesmith", + phone: "987-654-3210", + schoolName: "Greenwood High", + dateOfEstablishment: "1995-08-15", + type: "Public", + parentOrganization: "City Education Board", + state: "New York", + city: "New York City", + district: "Manhattan", + pincode: "10001", + address: "456 School Lane", + }, + { + id: 8, + firstName: "Mike", + lastName: "Johnson", + email: "mike.johnson@school.com", + userName: "mjohnson", + phone: "555-987-6543", + schoolName: "Riverdale School", + dateOfEstablishment: "1980-11-30", + type: "Private", + parentOrganization: "Riverdale Education", + state: "California", + city: "Los Angeles", + district: "LA District", + pincode: "90001", + address: "789 School Street", + }, + ], + User: [ + { + id: 1, + firstName: "Alice", + lastName: "Johnson", + email: "alice.johnson@user.com", + userName: "alicejohnson", + phone: "555-123-4567", + organization: "Tech Corp", + school: "Greenwood High", + role: "Student", + }, + { + id: 2, + firstName: "Bob", + lastName: "Smith", + email: "bob.smith@user.com", + userName: "bobsmith", + phone: "444-555-6666", + organization: "Innova LLC", + school: "Riverdale School", + role: "Teacher", + }, + { + id: 3, + firstName: "Alice", + lastName: "Johnson", + email: "alice.johnson@user.com", + userName: "alicejohnson", + phone: "555-123-4567", + organization: "Tech Corp", + school: "Greenwood High", + role: "Student", + }, + { + id: 4, + firstName: "Bob", + lastName: "Smith", + email: "bob.smith@user.com", + userName: "bobsmith", + phone: "444-555-6666", + organization: "Innova LLC", + school: "Riverdale School", + role: "Teacher", + }, + ], + "Training Team": [ + { + id: 1, + firstName: "Bob", + lastName: "Brown", + email: "bob.brown@trainingteam.com", + userName: "bobbrown", + phone: "444-555-6666", + type: "Technical", + }, + { + id: 2, + firstName: "Eve", + lastName: "White", + email: "eve.white@trainingteam.com", + userName: "evewhite", + phone: "333-444-5555", + type: "Managerial", + }, + ], +}; + +export { signupData }; diff --git a/client/src/index.css b/client/src/index.css index 3860b69..a6e360f 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -6,3 +6,7 @@ .swiper-pagination-bullet-active { background-color: #f89021 !important; } + +.MuiToolbar-root { + min-height: 3rem !important; +} diff --git a/client/src/pages/Template.jsx b/client/src/pages/Template.jsx index 3e938e0..89ec56a 100644 --- a/client/src/pages/Template.jsx +++ b/client/src/pages/Template.jsx @@ -60,7 +60,9 @@ function Template() { {showNavList ? ( ) : ( - + <> + + )}