Changeset bc20307 for sources/client/src
- Timestamp:
- 02/14/22 01:41:41 (3 years ago)
- Branches:
- master
- Children:
- 747e0ab
- Parents:
- e8b1076
- Location:
- sources/client/src
- Files:
-
- 88 added
- 5 deleted
- 34 edited
- 2 moved
Legend:
- Unmodified
- Added
- Removed
-
sources/client/src/App.js
re8b1076 rbc20307 1 import './App.css'; 2 import { Route, Switch, Redirect } from 'react-router-dom'; 1 import AdminEmployeeHomeScreen from './screens/AdminHomeScreen'; 2 import UserAndNotAuthScreen from './screens/UserAndNotAuthScreen'; 3 import Alert from './components/Alert'; 4 import BackgropLoader from './components/Loaders/BackdropLoader'; 5 import { roles } from './config/enums'; 6 import { UserContext } from './context/UserContext'; 7 import { AccessoriesContext } from './context/AccessoriesContext'; 8 import useIsMobile from './hooks/useIsMobile'; 9 import { useState } from 'react'; 3 10 4 import DestinationComponent from './utils/DestinationComponent'; 5 6 import AdminHomeScreen from './screens/AdminHomeScreen'; 7 import LoginScreenImported from './screens/LoginScreen'; 8 9 import { roles } from './config/enums'; 10 11 const LoginScreen = new DestinationComponent('/', LoginScreenImported, true); 12 const AdminEmployeeHomeScreen = new DestinationComponent('/', AdminHomeScreen); 13 // const UserHomeScreen = new DestinationComponent('/', UserHomeScreen); TODO 14 15 const publicRoutes = [LoginScreen]; 16 17 const userRoutes = [ 18 // UserHomeScreen 19 ]; 20 21 const adminAndEmployeeRoutes = [AdminEmployeeHomeScreen]; 11 import useFindUser from './hooks/useFindUser'; 12 import Sidedrawer from './components/Sidedrawer'; 22 13 23 14 function App(props) { 24 const user = { 25 role: 'ROLE_ADMIN', 26 }; 15 const [alertData, setAlertData] = useState({ 16 type: 'error', 17 msg: 'Не Сте Логирани!', 18 }); 19 const setAlert = ({ type, msg }) => { 20 setAlertData({ type, msg }); 21 setIsAlertOpen(true); 22 }; 23 const { user, setUser, isLoading: isLoadingUser } = useFindUser({ setAlert }); 24 const { isMobile } = useIsMobile(); 25 const [isAlertOpen, setIsAlertOpen] = useState(false); 26 const [isBackdropLoaderOpen, setIsBackdropLoaderOpen] = useState(false); 27 const [isOpenDrawer, setIsOpenDrawer] = useState(false); 27 28 28 // const user = null; 29 let displayScreen; 30 if (user && (user.role === roles.admin || user.role === roles.employee)) { 31 displayScreen = <AdminEmployeeHomeScreen />; 32 } else { 33 displayScreen = <UserAndNotAuthScreen />; 34 } 29 35 30 let routes = publicRoutes; 31 if (user) { 32 switch (user.role) { 33 case roles.user: 34 routes = userRoutes; 35 break; 36 case roles.admin: 37 case roles.employee: 38 routes = adminAndEmployeeRoutes; 39 break; 40 default: 41 break; 42 } 43 } 44 console.log(publicRoutes); 45 return ( 46 <Switch> 47 {routes?.map((route, index) => ( 48 <Route key={index} path={route.path} component={route.component} /> 49 ))} 50 <Redirect to='/' /> 51 </Switch> 52 ); 36 return ( 37 <UserContext.Provider value={{ user, setUser, isLoadingUser }}> 38 <AccessoriesContext.Provider 39 value={{ 40 isMobile, 41 setAlert, 42 setIsBackdropLoaderOpen, 43 setIsOpenDrawer, 44 }} 45 > 46 <Sidedrawer 47 isOpen={isOpenDrawer} 48 setIsOpen={setIsOpenDrawer} 49 isMobile={isMobile} 50 /> 51 <BackgropLoader 52 isBackdropLoaderOpen={isBackdropLoaderOpen} 53 isMobile={isMobile} 54 /> 55 <Alert 56 isOpen={isAlertOpen} 57 setIsOpen={setIsAlertOpen} 58 type={alertData.type} 59 msg={alertData.msg} 60 /> 61 {displayScreen} 62 </AccessoriesContext.Provider> 63 </UserContext.Provider> 64 ); 53 65 } 54 66 -
sources/client/src/components/Auth/Login/index.js
re8b1076 rbc20307 1 import { Link } from 'react-router-dom'; 2 import useForm from '../../hooks/useForm'; 1 import { Link, useHistory } from 'react-router-dom'; 2 import useForm from '../../../hooks/useForm'; 3 import useLogin from '../../../hooks/useLogin'; 3 4 4 5 import { … … 15 16 16 17 const Login = () => { 18 const { loginUser } = useLogin(); 17 19 const { data, onFormChange } = useForm({ 18 20 email: '', 19 21 password: '', 20 22 }); 23 let history = useHistory(); 21 24 const handleSignIn = () => { 22 console.log(`Email: ${data.email}`); 23 console.log(`Password: ${data.password}`); 25 loginUser({ email: data.email, password: data.password }); 24 26 }; 25 27 return ( … … 28 30 <Input 29 31 name='email' 32 placeholder='Емаил' 30 33 value={data.email} 31 34 onChange={onFormChange} … … 37 40 name='password' 38 41 value={data.password} 42 placeholder='Лозинка' 39 43 onChange={onFormChange} 40 44 InputProps={{ … … 49 53 <DividerText>ИЛИ</DividerText> 50 54 </DividerButtons> 51 <SignInButton>НАЈАВА КАКО ГОСТИН</SignInButton>{' '} 55 <SignInButton onClick={() => history.push('/login-guest')}> 56 НАЈАВА КАКО ГОСТИН 57 </SignInButton> 52 58 {/* TODO Neka nosi do '/login-guest'*/} 53 59 </ButtonsWrapper> … … 55 61 {' '} 56 62 {/* TODO Neka nosi do '/register' */} 57 Немате Сметка?58 <Link to=' #'>Регистрирај се!</Link>63 Немате Профил? 64 <Link to='/register'>Регистрирај се!</Link> 59 65 </RegisterText> 60 66 </> -
sources/client/src/components/Auth/Login/styles.js
re8b1076 rbc20307 16 16 fullWidth: true, 17 17 sx: { 18 marginTop: ' 35px',18 marginTop: '25px', 19 19 }, 20 20 })` … … 26 26 font-size: 1.2rem; 27 27 padding-left: 10px; 28 29 ::placeholder { 30 opacity: 0.6; 31 } 28 32 } 29 33 30 34 fieldset { 31 35 border: 0; 32 border-bottom: 2px solid white;36 border-bottom: 4px solid white; 33 37 border-radius: 25px; 34 38 padding: 0 10px; … … 53 57 display: flex; 54 58 flex-direction: column; 55 padding: 30px 25px; 59 margin-top: 10px; 60 padding: 30px 0px; 56 61 `; 57 62 export const SignInButton = styled(Button).attrs((props) => ({ … … 77 82 sx: {}, 78 83 })` 79 align-items: flex-start;80 margin-bottom: 15px;84 align-items: center; 85 // margin-bottom: 15px; 81 86 ::before, 82 87 ::after { … … 92 97 position: absolute; 93 98 bottom: 0; 94 align-self: cetner;95 99 width: 100%; 96 100 text-align: center; … … 100 104 margin-left: 10px; 101 105 text-decoration: none; 102 color: #f65026;106 color: ${(props) => props.theme.palette.third.main}; 103 107 font-weight: bold; 104 108 font-size: 1.2rem; -
sources/client/src/components/GoogleMaps/index.js
re8b1076 rbc20307 1 1 import GoogleMapReact from 'google-map-react'; 2 import { useEffect, useRef } from 'react'; 2 3 3 4 import { Wrapper, Marker } from './styles'; 4 5 5 const ParkingSpaceMarker = ({ text, free}) => (6 <Marker $free={free}>{text}</Marker>6 const ParkingSpaceMarker = ({ text, isTaken }) => ( 7 <Marker $isTaken={isTaken}>{text}</Marker> 7 8 ); 8 9 9 const GoogleMaps = ({ location, parkingSpacesLocation, zoneAreaColor }) => { 10 const defaultProps = { 11 zoom: 18, 12 }; 13 const drawZonePolygon = (map, maps) => { 14 var zoneArea = new maps.Polygon({ 15 paths: location.coords, 16 strokeColor: zoneAreaColor, 17 strokeOpacity: 0.8, 18 strokeWeight: 2, 19 fillColor: zoneAreaColor, 20 fillOpacity: 0.2, 21 }); 22 zoneArea.setMap(map); 23 }; 24 return ( 25 <Wrapper> 26 <GoogleMapReact 27 defaultCenter={location.center} 28 defaultZoom={defaultProps.zoom} 29 yesIWantToUseGoogleMapApiInternals={true} 30 onGoogleApiLoaded={({ map, maps }) => drawZonePolygon(map, maps)} 31 > 32 {parkingSpacesLocation.map((p, index) => ( 33 <ParkingSpaceMarker 34 key={index} 35 lat={p.lat} 36 lng={p.lng} 37 free={p.free} 38 text={p.parkingSpaceNumber} 39 /> 40 ))} 41 </GoogleMapReact> 42 </Wrapper> 43 ); 10 const GoogleMaps = ({ 11 location = {}, 12 parkingSpaces = [], 13 zoneAreaColor = '', 14 zoom, 15 }) => { 16 let refMap = useRef(null); 17 let refZone = useRef(null); 18 const defaultProps = { 19 zoom: 15, 20 }; 21 const drawZonePolygon = (map, maps) => { 22 if (!maps) return; 23 var zoneArea = new maps.Polygon({ 24 paths: location?.coords ?? [], 25 strokeColor: zoneAreaColor ?? '', 26 strokeOpacity: 0.8, 27 strokeWeight: 2, 28 fillColor: zoneAreaColor ?? '', 29 fillOpacity: 0.2, 30 }); 31 refZone.current = zoneArea; 32 zoneArea.setMap(map); 33 }; 34 useEffect(() => { 35 if (refMap.current) { 36 refZone.current.setMap(null); 37 drawZonePolygon(refMap.current.map, refMap.current.maps); 38 } 39 }, [location?.centre]); 40 return ( 41 <Wrapper> 42 <GoogleMapReact 43 center={location?.centre} 44 zoom={zoom ?? defaultProps.zoom} 45 yesIWantToUseGoogleMapApiInternals={true} 46 onGoogleApiLoaded={({ map, maps, ref }) => { 47 refMap.current = { map, maps }; 48 drawZonePolygon(map, maps); 49 }} 50 > 51 {parkingSpaces.map((p, index) => ( 52 <ParkingSpaceMarker 53 key={index} 54 lat={p.lat} 55 lng={p.lng} 56 isTaken={p.taken} 57 text={p.psName} 58 /> 59 ))} 60 </GoogleMapReact> 61 </Wrapper> 62 ); 44 63 }; 45 64 -
sources/client/src/components/GoogleMaps/styles.js
re8b1076 rbc20307 1 1 import styled from 'styled-components'; 2 import { mobile_max_width } from '../../config/utilities'; 2 3 3 4 export const Wrapper = styled.div` 4 width: 100%; 5 height: 69vh; 6 padding: 0 30px; 5 width: 100%; 6 height: 69vh; 7 padding: 0 4vh; 8 9 @media (max-width: ${mobile_max_width}px) { 10 height: 50vh; 11 } 7 12 `; 8 13 … … 12 17 height: 25px; 13 18 background-color: ${(props) => 14 props.$free15 ? `${props.theme.palette.success.light}`16 : `${props.theme.palette.error.main}`}};19 props.$isTaken 20 ? `${props.theme.palette.error.main}` 21 : `${props.theme.palette.success.light}`}}; 17 22 display: inline-flex; 18 23 align-items: center; 19 24 justify-content: center; 20 25 border-radius: 100%; 26 transform: translate(-50%, -100%); 21 27 `; -
sources/client/src/components/admin/EmployeeCreate/index.js
re8b1076 rbc20307 4 4 5 5 import { 6 EmployeeEditWrapper,7 Title,8 RowWrapper,9 LabelAndInputWrapper,10 Label,11 StandardInputField,12 Dropdown,13 DropdownOption,14 SwitchRowWrapper,15 SwitchTitle,16 SwitchLabelAndInputWrapper,17 AccountSwitch,18 BackButton,19 SaveChangesButton,20 VisibilityIcon,21 VisibilityOffIcon,6 EmployeeEditWrapper, 7 Title, 8 RowWrapper, 9 LabelAndInputWrapper, 10 Label, 11 StandardInputField, 12 Dropdown, 13 DropdownOption, 14 SwitchRowWrapper, 15 SwitchTitle, 16 SwitchLabelAndInputWrapper, 17 AccountSwitch, 18 BackButton, 19 SaveChangesButton, 20 VisibilityIcon, 21 VisibilityOffIcon, 22 22 } from './styles'; 23 23 24 24 import IconButton from '@mui/material/IconButton'; 25 import Checkbox from '@mui/material/Checkbox'; 26 import ListItemText from '@mui/material/ListItemText'; 27 import AbsoluteLoader from '../../Loaders/AbsoluteLoader'; 25 28 26 29 import { employeeStatus } from '../../../config/enums'; 30 import useGetData from '../../../hooks/useGetData'; 31 import useCreateEmployee from '../../../hooks/useCreateEmployee'; 27 32 28 33 import { defaultUser } from '../../../config/defaultUser'; 29 34 35 const MenuProps = { 36 PaperProps: { 37 style: { 38 maxHeight: 220, 39 }, 40 }, 41 }; 42 30 43 const EmployeeCreate = () => { 31 const [isPasswordVisible, setIsPasswordVisible] = useState(false);32 let history = useHistory();33 34 const { data: employeeEditableData, onFormChange: setEmployeeEditableData } =35 useForm({ ...defaultUser, confirmPassword: defaultUser.password});36 const [accStatus, setAccStatus] = useState(defaultUser.accountActive);37 38 const zoneOptions = [39 {40 text: 'Nitu edna zona',41 value: 'none',42 },43 {44 text: 'Zona 1',45 value: 'zone1',46 },47 {48 text: 'Zona 2',49 value: 'zone2',50 },51 {52 text: 'Zona 3',53 value: 'zone3',54 },55 {56 text: 'Zona 4', 57 value: 'zone4',58 },59 {60 text: 'Zona 5',61 value: 'zone5',62 },63 ];64 65 const statusOptions = Object.keys(employeeStatus).map((key) => {66 return {67 text: employeeStatus[key],68 value: key,44 const [isPasswordVisible, setIsPasswordVisible] = useState(false); 45 let history = useHistory(); 46 const { data: zoneOptions, isLoading: isLoadingZonesData } = useGetData({ 47 url: `/parkingZone/parkingZoneNames`, 48 }); 49 const { createEmployee } = useCreateEmployee(); 50 const { 51 data: employeeEditableData, 52 onFormChange: setEmployeeEditableData, 53 } = useForm({ ...defaultUser }); 54 const [accStatus, setAccStatus] = useState(defaultUser.locked); 55 const { 56 data: { confirmPassword }, 57 onFormChange: setConfirmPassword, 58 } = useForm({ 59 confirmPassword: defaultUser.password, 60 }); 61 const [zones, setZones] = useState(defaultUser.parkingZones); // TODO RENAME ZONE TO ZONES 62 console.log(zones); 63 const statusOptions = Object.keys(employeeStatus).map((key) => { 64 return { 65 text: employeeStatus[key], 66 value: key, 67 }; 68 }); 69 70 const onCreateEmployee = () => { 71 if (zones.length === 1 && zones[0] === 'NONE') { 72 zones.shift(); 73 } 74 console.log(`Confirm password: ${confirmPassword}`); 75 const changedEmployee = { 76 ...employeeEditableData, 77 locked: accStatus, 78 parkingZones: zones, 79 confirmPass: confirmPassword, 80 }; 81 createEmployee({ employee: changedEmployee }); 69 82 }; 70 }); 71 72 const onCreateEmployee = () => { 73 const changedEmployee = { 74 ...employeeEditableData, 75 accountActive: accStatus, 83 const handleZonesChange = (event) => { 84 const { 85 target: { value }, 86 } = event; 87 if (value.length > 1 && value[0] === 'NONE') { 88 value.shift(); 89 } 90 setZones( 91 typeof value === 'string' 92 ? value.split(', ') 93 : value.length === 0 94 ? ['NONE'] 95 : value 96 ); 76 97 }; 77 console.log('Created employee: ', changedEmployee); 78 }; 79 80 return ( 81 <EmployeeEditWrapper> 82 <Title variant='h5'>Создади вработен</Title> 83 <RowWrapper> 84 <LabelAndInputWrapper> 85 <Label>Име</Label> 86 <StandardInputField 87 value={employeeEditableData.firstName} 88 name='firstName' 89 onChange={setEmployeeEditableData} 90 /> 91 </LabelAndInputWrapper> 92 <LabelAndInputWrapper> 93 <Label>Презиме</Label> 94 <StandardInputField 95 value={employeeEditableData.lastName} 96 name='lastName' 97 onChange={setEmployeeEditableData} 98 /> 99 </LabelAndInputWrapper> 100 </RowWrapper> 101 <RowWrapper> 102 <LabelAndInputWrapper> 103 <Label>Емаил</Label> 104 <StandardInputField 105 value={employeeEditableData.email} 106 name='email' 107 type='email' 108 onChange={setEmployeeEditableData} 109 /> 110 </LabelAndInputWrapper> 111 <LabelAndInputWrapper> 112 <Label>Телефон</Label> 113 <StandardInputField 114 value={employeeEditableData.phoneNumber} 115 name='phoneNumber' 116 onChange={setEmployeeEditableData} 117 /> 118 </LabelAndInputWrapper> 119 </RowWrapper> 120 <RowWrapper> 121 <LabelAndInputWrapper> 122 <Label>Лозинка</Label> 123 <StandardInputField 124 value={employeeEditableData.password} 125 name='password' 126 type={isPasswordVisible ? 'text' : 'password'} 127 $autoComplete={true} 128 onChange={setEmployeeEditableData} 129 /> 130 </LabelAndInputWrapper> 131 <IconButton 132 sx={{ marginTop: '23px' }} 133 onClick={() => setIsPasswordVisible(!isPasswordVisible)} 134 > 135 {isPasswordVisible ? <VisibilityIcon /> : <VisibilityOffIcon />} 136 </IconButton> 137 <LabelAndInputWrapper> 138 <Label>Потврди лозинка</Label> 139 <StandardInputField 140 value={employeeEditableData.confirmPassword} 141 name='confirmPassword' 142 type={isPasswordVisible ? 'text' : 'password'} 143 onChange={setEmployeeEditableData} 144 /> 145 </LabelAndInputWrapper> 146 </RowWrapper> 147 <RowWrapper> 148 <LabelAndInputWrapper> 149 <Label>Одговорен за</Label> 150 <Dropdown 151 value={employeeEditableData.zone} 152 name='zone' 153 onChange={setEmployeeEditableData} 154 > 155 {zoneOptions.map((option) => ( 156 <DropdownOption value={option.value} key={option.value}> 157 {option.text} 158 </DropdownOption> 159 ))} 160 </Dropdown> 161 </LabelAndInputWrapper> 162 <LabelAndInputWrapper> 163 <Label>Статус</Label> 164 <Dropdown 165 value={employeeEditableData.status} 166 name='status' 167 onChange={setEmployeeEditableData} 168 > 169 {statusOptions.map((option) => ( 170 <DropdownOption value={option.value} key={option.value}> 171 {option.text} 172 </DropdownOption> 173 ))} 174 </Dropdown> 175 </LabelAndInputWrapper> 176 </RowWrapper> 177 <SwitchRowWrapper> 178 <SwitchTitle>Акаунт</SwitchTitle> 179 <SwitchLabelAndInputWrapper> 180 <Label>Активен:</Label> 181 <AccountSwitch 182 checked={accStatus} 183 value={accStatus} 184 name='accountActive' 185 onClick={() => setAccStatus(!accStatus)} 186 /> 187 </SwitchLabelAndInputWrapper> 188 </SwitchRowWrapper> 189 <RowWrapper> 190 <BackButton onClick={() => history.push('/employees')}> 191 Врати се назад 192 </BackButton> 193 <SaveChangesButton onClick={onCreateEmployee}> 194 Создади вработен 195 </SaveChangesButton> 196 </RowWrapper> 197 </EmployeeEditWrapper> 198 ); 98 return ( 99 <EmployeeEditWrapper> 100 <Title variant='h5'>Создади вработен</Title> 101 <RowWrapper> 102 <LabelAndInputWrapper> 103 <Label>Име</Label> 104 <StandardInputField 105 value={employeeEditableData.firstName} 106 name='firstName' 107 onChange={setEmployeeEditableData} 108 /> 109 </LabelAndInputWrapper> 110 <LabelAndInputWrapper> 111 <Label>Презиме</Label> 112 <StandardInputField 113 value={employeeEditableData.lastName} 114 name='lastName' 115 onChange={setEmployeeEditableData} 116 /> 117 </LabelAndInputWrapper> 118 </RowWrapper> 119 <RowWrapper> 120 <LabelAndInputWrapper> 121 <Label>Емаил</Label> 122 <StandardInputField 123 value={employeeEditableData.email} 124 name='email' 125 type='email' 126 onChange={setEmployeeEditableData} 127 /> 128 </LabelAndInputWrapper> 129 <LabelAndInputWrapper> 130 <Label>Телефон</Label> 131 <StandardInputField 132 value={employeeEditableData.mobile} 133 name='mobile' 134 onChange={setEmployeeEditableData} 135 /> 136 </LabelAndInputWrapper> 137 </RowWrapper> 138 <RowWrapper> 139 <LabelAndInputWrapper> 140 <Label>Лозинка</Label> 141 <StandardInputField 142 value={employeeEditableData.password} 143 name='password' 144 type={isPasswordVisible ? 'text' : 'password'} 145 $autoComplete={true} 146 onChange={setEmployeeEditableData} 147 /> 148 </LabelAndInputWrapper> 149 <IconButton 150 sx={{ marginTop: '23px' }} 151 onClick={() => setIsPasswordVisible(!isPasswordVisible)} 152 > 153 {isPasswordVisible ? ( 154 <VisibilityIcon /> 155 ) : ( 156 <VisibilityOffIcon /> 157 )} 158 </IconButton> 159 <LabelAndInputWrapper> 160 <Label>Потврди лозинка</Label> 161 <StandardInputField 162 value={confirmPassword} 163 name='confirmPassword' 164 type={isPasswordVisible ? 'text' : 'password'} 165 onChange={setConfirmPassword} 166 /> 167 </LabelAndInputWrapper> 168 </RowWrapper> 169 <RowWrapper> 170 {isLoadingZonesData ? ( 171 <AbsoluteLoader 172 containerStyle={{ 173 position: 'absolute', 174 left: '310px', 175 bottom: '5px', 176 width: '40px', 177 height: '40px', 178 }} 179 /> 180 ) : null} 181 <LabelAndInputWrapper> 182 <Label>Одговорен за</Label> 183 <Dropdown 184 multiple 185 value={zones} 186 onChange={handleZonesChange} 187 renderValue={(selected) => { 188 return selected.join(', '); 189 }} 190 MenuProps={MenuProps} 191 > 192 {!isLoadingZonesData && 193 zoneOptions.map((zone) => ( 194 <DropdownOption value={zone} key={zone}> 195 <Checkbox 196 checked={zones.indexOf(zone) > -1} 197 /> 198 <ListItemText primary={zone} /> 199 </DropdownOption> 200 ))} 201 </Dropdown> 202 </LabelAndInputWrapper> 203 <LabelAndInputWrapper> 204 <Label>Статус</Label> 205 <Dropdown 206 value={employeeEditableData.status} 207 name='status' 208 onChange={setEmployeeEditableData} 209 MenuProps={MenuProps} 210 > 211 {statusOptions.map((option) => ( 212 <DropdownOption 213 value={option.value} 214 key={option.value} 215 > 216 {option.text} 217 </DropdownOption> 218 ))} 219 </Dropdown> 220 </LabelAndInputWrapper> 221 </RowWrapper> 222 <SwitchRowWrapper> 223 <SwitchTitle>Акаунт</SwitchTitle> 224 <SwitchLabelAndInputWrapper> 225 <Label>Активен:</Label> 226 <AccountSwitch 227 checked={!accStatus} 228 value={accStatus} 229 name='locked' 230 onClick={() => setAccStatus(!accStatus)} 231 /> 232 </SwitchLabelAndInputWrapper> 233 </SwitchRowWrapper> 234 <RowWrapper> 235 <BackButton onClick={() => history.push('/employees')}> 236 Врати се назад 237 </BackButton> 238 <SaveChangesButton onClick={onCreateEmployee}> 239 Создади вработен 240 </SaveChangesButton> 241 </RowWrapper> 242 </EmployeeEditWrapper> 243 ); 199 244 }; 200 245 -
sources/client/src/components/admin/EmployeeCreate/styles.js
re8b1076 rbc20307 1 import styled from "styled-components"; 2 import { Button, InputLabel, MenuItem, Select, Switch, TextField, Typography } from "@mui/material"; 1 import styled from 'styled-components'; 2 import { 3 Button, 4 InputLabel, 5 MenuItem, 6 Select, 7 Switch, 8 TextField, 9 Typography, 10 } from '@mui/material'; 3 11 import VIcon from '@mui/icons-material/Visibility'; 4 12 import VOffIcon from '@mui/icons-material/VisibilityOff'; 5 13 6 14 export const EmployeeEditWrapper = styled.div` 7 8 9 10 11 12 13 14 15 16 box-shadow: 15px 15px 10px ${props=> props.theme.palette.background.shadow};15 display: flex; 16 flex-direction: column; 17 max-width: 800px; 18 border: 1px solid whiteSmoke; 19 border-radius: 10px; 20 padding: 10px 20px; 21 margin-top: 20px; 22 margin-left: 30px; 23 background-color: white; 24 box-shadow: 15px 15px 10px ${(props) => props.theme.palette.background.shadow}; 17 25 `; 18 26 19 export const Title = styled(Typography).attrs({ 20 21 })` 22 border-bottom: 2px solid rgba(0, 0, 0, 0.12); 23 padding-bottom: 10px; 24 margin-bottom: 15px; 27 export const Title = styled(Typography).attrs({})` 28 border-bottom: 2px solid rgba(0, 0, 0, 0.12); 29 padding-bottom: 10px; 30 margin-bottom: 15px; 25 31 `; 26 32 27 33 export const RowWrapper = styled.div` 28 display: flex; 29 flex-direction: row; 30 justify-content: space-between; 31 margin-bottom: 25px; 34 display: flex; 35 flex-direction: row; 36 justify-content: space-between; 37 margin-bottom: 25px; 38 position: relative; 32 39 `; 33 40 34 export const LabelAndInputWrapper = styled.div` 41 export const LabelAndInputWrapper = styled.div``; 35 42 36 `;43 export const Label = styled(InputLabel).attrs({})``; 37 44 38 export const Label = styled(InputLabel).attrs({ 39 40 })``; 41 42 export const StandardInputField = styled(TextField).attrs({ 43 44 })` 45 width: 300px; 45 export const StandardInputField = styled(TextField).attrs({})` 46 width: 300px; 46 47 `; 47 48 48 49 export const VisibilityIcon = styled(VIcon).attrs({ 49 50 fontSize: '2rem'51 }50 sx: { 51 fontSize: '2rem', 52 }, 52 53 })``; 53 54 54 55 export const VisibilityOffIcon = styled(VOffIcon).attrs({ 55 56 fontSize: '2rem'57 }56 sx: { 57 fontSize: '2rem', 58 }, 58 59 })``; 59 60 60 export const Dropdown = styled(Select).attrs({ 61 62 })` 63 width: 300px; 61 export const Dropdown = styled(Select).attrs({})` 62 width: 300px; 64 63 `; 65 64 66 export const DropdownOption = styled(MenuItem).attrs({ 67 68 })``; 65 export const DropdownOption = styled(MenuItem).attrs({})` 66 > span { 67 color: ${(props) => props.theme.palette.primary.main}; 68 svg { 69 color: ${(props) => props.theme.palette.primary.main}; 70 } 71 } 72 > div > span { 73 font-size: 1.3rem; 74 } 75 `; 69 76 70 77 export const SwitchRowWrapper = styled.div` 71 72 73 78 text-align: center; 79 margin-top: 10px; 80 margin-bottom: 50px; 74 81 `; 75 82 76 83 export const SwitchTitle = styled.p` 77 78 79 80 84 font-size: 18px; 85 font-weight: 600; 86 margin: 0; 87 margin-bottom: 5px; 81 88 `; 82 89 83 90 export const SwitchLabelAndInputWrapper = styled.div` 84 display: flex; 85 flex-direction: row; 86 align-items: center; 87 justify-content: center; 88 91 display: flex; 92 flex-direction: row; 93 align-items: center; 94 justify-content: center; 89 95 `; 90 96 91 export const AccountSwitch = styled(Switch).attrs(props => ({ 92 }))` 93 .MuiSwitch-switchBase { 94 color: red; 95 } 96 .MuiSwitch-track { 97 background-color: red; 98 } 99 .MuiSwitch-switchBase.Mui-checked { 100 color: ${props => props.theme.palette.primary.main}; 101 } 102 .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track { 103 background-color: ${props => props.theme.palette.primary.main}; 104 } 97 export const AccountSwitch = styled(Switch).attrs((props) => ({}))` 98 .MuiSwitch-switchBase { 99 color: red; 100 } 101 .MuiSwitch-track { 102 background-color: red; 103 } 104 .MuiSwitch-switchBase.Mui-checked { 105 color: ${(props) => props.theme.palette.primary.main}; 106 } 107 .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track { 108 background-color: ${(props) => props.theme.palette.primary.main}; 109 } 105 110 `; 106 111 107 112 export const BackButton = styled(Button).attrs({ 108 variant: 'outlined'113 variant: 'outlined', 109 114 })` 110 111 112 113 114 115 margin-right: 15px; 116 :hover { 117 border: 2px solid; 118 font-weight: 600; 119 } 115 120 `; 116 121 117 export const SaveChangesButton = styled(Button).attrs( props=> ({118 119 120 backgroundColor: `${props.theme.palette.primary.main}`121 }122 export const SaveChangesButton = styled(Button).attrs((props) => ({ 123 variant: 'contained', 124 sx: { 125 backgroundColor: `${props.theme.palette.primary.main}`, 126 }, 122 127 }))` 123 124 125 background-color: ${props => props.theme.palette.primary.dark}126 128 padding: 10px 16px; 129 :hover { 130 background-color: ${(props) => props.theme.palette.primary.dark}; 131 } 127 132 `; -
sources/client/src/components/admin/EmployeeEdit/index.js
re8b1076 rbc20307 1 import { use State } from 'react';1 import { useEffect, useState } from 'react'; 2 2 import { useParams, useHistory } from 'react-router-dom'; 3 3 import useForm from '../../../hooks/useForm'; … … 21 21 SaveChangesButton, 22 22 VisibilityIcon, 23 VisibilityOffIcon 23 VisibilityOffIcon, 24 24 } from './styles'; 25 25 26 26 import IconButton from '@mui/material/IconButton'; 27 28 import { 29 employeeStatus 30 } from '../../../config/enums'; 31 32 import { 33 employees 34 } from '../EmployeesTable/mockData'; 27 import Checkbox from '@mui/material/Checkbox'; 28 import ListItemText from '@mui/material/ListItemText'; 29 import AbsoluteLoader from '../../Loaders/AbsoluteLoader'; 30 31 import { employeeStatus } from '../../../config/enums'; 32 import useGetData from '../../../hooks/useGetData'; 33 import useUpdateEmployee from '../../../hooks/useUpdateEmployee'; 34 import useDeleteEmployee from '../../../hooks/useDeleteEmployee'; 35 36 const MenuProps = { 37 PaperProps: { 38 style: { 39 maxHeight: 220, 40 }, 41 }, 42 }; 35 43 36 44 const EmployeeEdit = () => { … … 38 46 let history = useHistory(); 39 47 let { employeeId } = useParams(); 40 employeeId = parseInt(employeeId); 41 42 const employee = employees.find(e => e.id === employeeId); 43 const { data: employeeEditableData, onFormChange: setEmployeeEditableData } = useForm({ ...employee, confirmPassword: employee.password }); 44 const [accStatus, setAccStatus] = useState(employee ? employee.accountActive : false); 45 46 const zoneOptions = [ 48 const { updateEmployee } = useUpdateEmployee(); 49 const { deleteEmployee } = useDeleteEmployee(); 50 const { data: employeeData, isLoading: isLoadingEmployeeData } = useGetData( 47 51 { 48 text: 'Nitu edna zona', 49 value: 'none' 50 }, 51 { 52 text: 'Zona 1', 53 value: 'zone1' 54 }, 55 { 56 text: 'Zona 2', 57 value: 'zone2' 58 }, 59 { 60 text: 'Zona 3', 61 value: 'zone3' 62 }, 63 { 64 text: 'Zona 4', 65 value: 'zone4' 66 }, 67 { 68 text: 'Zona 5', 69 value: 'zone5' 52 url: `/vraboten/${employeeId}`, 70 53 } 71 ]; 72 73 const statusOptions = Object.keys(employeeStatus).map(key => { 54 ); 55 const { data: zoneOptions, isLoading: isLoadingZonesData } = useGetData({ 56 url: `/parkingZone/parkingZoneNames`, 57 }); 58 59 const { 60 data: employeeEditableData, 61 setNewData: setNewDataEmployee, 62 onFormChange: setEmployeeEditableData, 63 } = useForm(null); 64 const [accStatus, setAccStatus] = useState(false); 65 const { 66 data: { confirmPassword }, 67 onFormChange: setConfirmPassword, 68 } = useForm({ confirmPassword: '' }); 69 const [zones, setZones] = useState([]); 70 71 const statusOptions = Object.keys(employeeStatus).map((key) => { 74 72 return { 75 73 text: employeeStatus[key], 76 value: key 74 value: key, 75 }; 76 }); 77 78 const onSaveChanges = () => { 79 if (zones.length === 1 && zones[0] === 'NONE') { 80 zones.shift(); 77 81 } 78 });79 80 const onSaveChanges = () => {82 console.log(`Confirm password: ${confirmPassword}`); 83 const mobileNumber = employeeEditableData.mobile; 84 delete employeeEditableData.mobile; 81 85 const changedEmployee = { 82 86 ...employeeEditableData, 83 accountActive: accStatus 87 mobileNumber: mobileNumber, 88 locked: accStatus, 89 parkingZones: zones, 90 confirmPass: confirmPassword, 91 }; 92 updateEmployee({ employee: changedEmployee }); 93 }; 94 95 const onDeleteEmployee = () => { 96 deleteEmployee({ id: employeeData.workerId }); 97 }; 98 99 const handleZonesChange = (event) => { 100 const { 101 target: { value }, 102 } = event; 103 if (value.length > 1 && value[0] === 'NONE') { 104 value.shift(); 84 105 } 85 console.log('Changed employee: ', changedEmployee); 86 } 87 88 const onDeleteEmployee = () => { 89 console.log(`Empoyee with ${employee.id} is deleted`); 90 } 91 92 return <EmployeeEditWrapper> 93 <Title variant='h5'>Uredi vraboten</Title> 94 <RowWrapper> 95 <LabelAndInputWrapper> 96 <Label>Ime</Label> 97 <StandardInputField 98 value={employeeEditableData?.firstName ?? ''} 99 name='firstName' 100 onChange={setEmployeeEditableData} 106 setZones( 107 typeof value === 'string' 108 ? value.split(', ') 109 : value.length === 0 110 ? ['NONE'] 111 : value 112 ); 113 }; 114 useEffect(() => { 115 if (!employeeData) return; 116 setNewDataEmployee({...employeeData, password: ''}); 117 setAccStatus(employeeData.locked); 118 setZones( 119 employeeData.pzNames.length === 0 120 ? ['NONE'] 121 : employeeData.pzNames 122 ); 123 }, [employeeData]); 124 125 return ( 126 <EmployeeEditWrapper> 127 <Title variant='h5'>Уреди Вработен</Title> 128 {isLoadingEmployeeData ? ( 129 <AbsoluteLoader 130 containerStyle={{ 131 width: '200px', 132 height: '200px', 133 margin: 'auto', 134 }} 101 135 /> 102 </LabelAndInputWrapper> 103 <LabelAndInputWrapper> 104 <Label>Prezime</Label> 105 <StandardInputField 106 value={employeeEditableData?.lastName ?? ''} 107 name='lastName' 108 onChange={setEmployeeEditableData} 109 /> 110 </LabelAndInputWrapper> 111 </RowWrapper> 112 <RowWrapper> 113 <LabelAndInputWrapper> 114 <Label>Email</Label> 115 <StandardInputField 116 value={employeeEditableData?.email ?? ''} 117 name='email' 118 type='email' 119 onChange={setEmployeeEditableData} 120 /> 121 </LabelAndInputWrapper> 122 <LabelAndInputWrapper> 123 <Label>Telefon</Label> 124 <StandardInputField 125 value={employeeEditableData?.phoneNumber ?? ''} 126 name='phoneNumber' 127 onChange={setEmployeeEditableData} 128 /> 129 </LabelAndInputWrapper> 130 </RowWrapper> 131 <RowWrapper> 132 <LabelAndInputWrapper> 133 <Label>Lozinka</Label> 134 <StandardInputField 135 value={employeeEditableData?.password ?? ''} 136 name='password' 137 type={isPasswordVisible ? 'text' : 'password'} 138 onChange={setEmployeeEditableData} 139 /> 140 </LabelAndInputWrapper> 141 <IconButton sx={{ marginTop: '23px' }} onClick={() => setIsPasswordVisible(!isPasswordVisible)}> 142 { 143 isPasswordVisible ? 144 <VisibilityIcon /> 145 : 146 <VisibilityOffIcon /> 147 } 148 </IconButton> 149 <LabelAndInputWrapper> 150 <Label>Potvrdi lozinka</Label> 151 <StandardInputField 152 value={employeeEditableData?.confirmPassword ?? ''} 153 name='confirmPassword' 154 type={isPasswordVisible ? 'text' : 'password'} 155 onChange={setEmployeeEditableData} 156 /> 157 </LabelAndInputWrapper> 158 </RowWrapper> 159 <RowWrapper> 160 <LabelAndInputWrapper> 161 <Label>Odgovoren za</Label> 162 <Dropdown 163 value={employeeEditableData?.zone !== '' ? employeeEditableData?.zone : zoneOptions[0].value} 164 name='zone' 165 onChange={setEmployeeEditableData} 166 > 167 { 168 zoneOptions.map(option => <DropdownOption value={option.value} key={option.value}>{option.text}</DropdownOption>) 169 } 170 </Dropdown> 171 </LabelAndInputWrapper> 172 <LabelAndInputWrapper> 173 <Label>Status</Label> 174 <Dropdown 175 value={employeeEditableData?.status !== '' ? employeeEditableData?.status : statusOptions[0].value} 176 name='status' 177 onChange={setEmployeeEditableData} 178 > 179 { 180 statusOptions.map(option => <DropdownOption value={option.value} key={option.value}>{option.text}</DropdownOption>) 181 } 182 </Dropdown> 183 </LabelAndInputWrapper> 184 </RowWrapper> 185 <SwitchRowWrapper> 186 <SwitchTitle>Akaunt</SwitchTitle> 187 <SwitchLabelAndInputWrapper> 188 <Label>Aktiven:</Label> 189 <AccountSwitch 190 checked={accStatus} 191 value={accStatus} 192 name='accountActive' 193 onClick={() => setAccStatus(!accStatus)} 194 /> 195 </SwitchLabelAndInputWrapper> 196 </SwitchRowWrapper> 197 <RowWrapper> 198 <DeleteButton onClick={onDeleteEmployee}> 199 Izbrisi vraboten 200 </DeleteButton> 201 <BackAndSaveChangesButtonsWrapper> 202 <BackButton onClick={() => history.push('/employees')}> 203 Vrati se nazad 204 </BackButton> 205 <SaveChangesButton onClick={onSaveChanges}> 206 Zacuvaj gi promenti 207 </SaveChangesButton> 208 </BackAndSaveChangesButtonsWrapper> 209 </RowWrapper> 210 </EmployeeEditWrapper> 136 ) : ( 137 <> 138 <RowWrapper> 139 <LabelAndInputWrapper> 140 <Label>Име</Label> 141 <StandardInputField 142 value={employeeEditableData?.firstName ?? ''} 143 name='firstName' 144 onChange={setEmployeeEditableData} 145 /> 146 </LabelAndInputWrapper> 147 <LabelAndInputWrapper> 148 <Label>Презиме</Label> 149 <StandardInputField 150 value={employeeEditableData?.lastName ?? ''} 151 name='lastName' 152 onChange={setEmployeeEditableData} 153 /> 154 </LabelAndInputWrapper> 155 </RowWrapper> 156 <RowWrapper> 157 <LabelAndInputWrapper> 158 <Label>Емаил</Label> 159 <StandardInputField 160 value={employeeEditableData?.email ?? ''} 161 name='email' 162 type='email' 163 onChange={setEmployeeEditableData} 164 /> 165 </LabelAndInputWrapper> 166 <LabelAndInputWrapper> 167 <Label>Телефон</Label> 168 <StandardInputField 169 value={employeeEditableData?.mobile ?? ''} 170 name='mobile' 171 onChange={setEmployeeEditableData} 172 /> 173 </LabelAndInputWrapper> 174 </RowWrapper> 175 <RowWrapper> 176 <LabelAndInputWrapper> 177 <Label>Лозинка</Label> 178 <StandardInputField 179 value={employeeEditableData?.password ?? ''} 180 name='password' 181 type={isPasswordVisible ? 'text' : 'password'} 182 onChange={setEmployeeEditableData} 183 /> 184 </LabelAndInputWrapper> 185 <IconButton 186 sx={{ marginTop: '23px' }} 187 onClick={() => 188 setIsPasswordVisible(!isPasswordVisible) 189 } 190 > 191 {isPasswordVisible ? ( 192 <VisibilityIcon /> 193 ) : ( 194 <VisibilityOffIcon /> 195 )} 196 </IconButton> 197 <LabelAndInputWrapper> 198 <Label>Потврди Лозинка</Label> 199 <StandardInputField 200 value={confirmPassword} 201 name='confirmPassword' 202 type={isPasswordVisible ? 'text' : 'password'} 203 onChange={setConfirmPassword} 204 /> 205 </LabelAndInputWrapper> 206 </RowWrapper> 207 <RowWrapper> 208 {isLoadingZonesData ? ( 209 <AbsoluteLoader 210 containerStyle={{ 211 position: 'absolute', 212 left: '310px', 213 bottom: '5px', 214 width: '40px', 215 height: '40px', 216 }} 217 /> 218 ) : null} 219 <LabelAndInputWrapper> 220 <Label>Одговорен за:</Label> 221 <Dropdown 222 multiple 223 value={zones ?? []} 224 onChange={handleZonesChange} 225 renderValue={(selected) => { 226 return selected.join(', '); 227 }} 228 MenuProps={MenuProps} 229 > 230 {!isLoadingZonesData && 231 zoneOptions.map((zone) => ( 232 <DropdownOption value={zone} key={zone}> 233 <Checkbox 234 checked={ 235 zones.indexOf(zone) > -1 236 } 237 /> 238 <ListItemText primary={zone} /> 239 </DropdownOption> 240 ))} 241 </Dropdown> 242 </LabelAndInputWrapper> 243 <LabelAndInputWrapper> 244 <Label>Статус</Label> 245 <Dropdown 246 value={ 247 employeeEditableData?.status !== '' && 248 employeeEditableData?.status !== null 249 ? employeeEditableData?.status 250 : statusOptions[0].value 251 } 252 name='status' 253 onChange={setEmployeeEditableData} 254 MenuProps={MenuProps} 255 > 256 {statusOptions.map((option) => ( 257 <DropdownOption 258 value={option.value} 259 key={option.value} 260 > 261 {option.text} 262 </DropdownOption> 263 ))} 264 </Dropdown> 265 </LabelAndInputWrapper> 266 </RowWrapper> 267 <SwitchRowWrapper> 268 <SwitchTitle>Акаунт</SwitchTitle> 269 <SwitchLabelAndInputWrapper> 270 <Label>Активен:</Label> 271 <AccountSwitch 272 checked={!accStatus} 273 value={accStatus} 274 name='accountActive' 275 onClick={() => setAccStatus(!accStatus)} 276 /> 277 </SwitchLabelAndInputWrapper> 278 </SwitchRowWrapper> 279 <RowWrapper> 280 <DeleteButton onClick={onDeleteEmployee}> 281 Избриши Вработен 282 </DeleteButton> 283 <BackAndSaveChangesButtonsWrapper> 284 <BackButton 285 onClick={() => history.push('/employees')} 286 > 287 Врати се Назад 288 </BackButton> 289 <SaveChangesButton onClick={onSaveChanges}> 290 Зачувај ги Промените 291 </SaveChangesButton> 292 </BackAndSaveChangesButtonsWrapper> 293 </RowWrapper> 294 </> 295 )} 296 </EmployeeEditWrapper> 297 ); 211 298 }; 212 299 -
sources/client/src/components/admin/EmployeeEdit/styles.js
re8b1076 rbc20307 1 import styled from "styled-components"; 2 import { Button, InputLabel, MenuItem, Select, Switch, TextField, Typography } from "@mui/material"; 1 import styled from 'styled-components'; 2 import { 3 Button, 4 InputLabel, 5 MenuItem, 6 Select, 7 Switch, 8 TextField, 9 Typography, 10 } from '@mui/material'; 3 11 import VIcon from '@mui/icons-material/Visibility'; 4 12 import VOffIcon from '@mui/icons-material/VisibilityOff'; 5 13 6 14 export const EmployeeEditWrapper = styled.div` 7 display: flex; 8 flex-direction: column; 9 max-width: 800px; 10 border: 1px solid whiteSmoke; 11 border-radius: 10px; 12 padding: 10px 20px; 13 margin-top: 20px; 14 margin-left: 30px; 15 background-color: white; 16 box-shadow: 15px 15px 10px ${props => props.theme.palette.background.shadow}; 15 display: flex; 16 flex-direction: column; 17 max-width: 800px; 18 height: 693px; 19 border: 1px solid whiteSmoke; 20 border-radius: 10px; 21 padding: 10px 20px; 22 margin-top: 20px; 23 margin-left: 30px; 24 background-color: white; 25 box-shadow: 15px 15px 10px ${(props) => props.theme.palette.background.shadow}; 17 26 `; 18 27 19 export const Title = styled(Typography).attrs({ 20 21 })` 22 border-bottom: 2px solid rgba(0, 0, 0, 0.12); 23 padding-bottom: 10px; 24 margin-bottom: 15px; 28 export const Title = styled(Typography).attrs({})` 29 border-bottom: 2px solid rgba(0, 0, 0, 0.12); 30 padding-bottom: 10px; 31 margin-bottom: 15px; 25 32 `; 26 33 27 34 export const RowWrapper = styled.div` 28 display: flex; 29 flex-direction: row; 30 justify-content: space-between; 31 margin-bottom: 25px; 35 display: flex; 36 flex-direction: row; 37 justify-content: space-between; 38 margin-bottom: 25px; 39 position: relative; 32 40 `; 33 41 34 export const LabelAndInputWrapper = styled.div` 42 export const LabelAndInputWrapper = styled.div``; 35 43 36 `;44 export const Label = styled(InputLabel).attrs({})``; 37 45 38 export const Label = styled(InputLabel).attrs({ 39 40 })``; 41 42 export const StandardInputField = styled(TextField).attrs({ 43 44 })` 45 width: 300px; 46 export const StandardInputField = styled(TextField).attrs({})` 47 width: 300px; 46 48 `; 47 49 48 50 export const VisibilityIcon = styled(VIcon).attrs({ 49 50 fontSize: '2rem'51 }51 sx: { 52 fontSize: '2rem', 53 }, 52 54 })``; 53 55 54 56 export const VisibilityOffIcon = styled(VOffIcon).attrs({ 55 56 fontSize: '2rem'57 }57 sx: { 58 fontSize: '2rem', 59 }, 58 60 })``; 59 61 60 export const Dropdown = styled(Select).attrs({ 61 62 })` 63 width: 300px; 62 export const Dropdown = styled(Select).attrs({})` 63 width: 300px; 64 64 `; 65 65 66 export const DropdownOption = styled(MenuItem).attrs({ 66 export const DropdownOption = styled(MenuItem).attrs({})` 67 > span { 68 color: ${(props) => props.theme.palette.primary.main}; 69 svg { 70 color: ${(props) => props.theme.palette.primary.main}; 71 } 72 } 73 > div > span { 74 font-size: 1.3rem; 75 } 76 `; 67 77 68 })``; 78 // export const 69 79 70 80 export const SwitchRowWrapper = styled.div` 71 72 73 81 text-align: center; 82 margin-top: 10px; 83 margin-bottom: 50px; 74 84 `; 75 85 76 86 export const SwitchTitle = styled.p` 77 78 79 80 87 font-size: 18px; 88 font-weight: 600; 89 margin: 0; 90 margin-bottom: 5px; 81 91 `; 82 92 83 93 export const SwitchLabelAndInputWrapper = styled.div` 84 display: flex; 85 flex-direction: row; 86 align-items: center; 87 justify-content: center; 88 94 display: flex; 95 flex-direction: row; 96 align-items: center; 97 justify-content: center; 89 98 `; 90 99 91 export const AccountSwitch = styled(Switch).attrs(props => ({ 92 }))` 93 .MuiSwitch-switchBase { 94 color: red; 95 } 96 .MuiSwitch-track { 97 background-color: red; 98 } 99 .MuiSwitch-switchBase.Mui-checked { 100 color: ${props => props.theme.palette.primary.main}; 101 } 102 .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track { 103 background-color: ${props => props.theme.palette.primary.main}; 104 } 100 export const AccountSwitch = styled(Switch).attrs((props) => ({}))` 101 .MuiSwitch-switchBase { 102 color: red; 103 } 104 .MuiSwitch-track { 105 background-color: red; 106 } 107 .MuiSwitch-switchBase.Mui-checked { 108 color: ${(props) => props.theme.palette.primary.main}; 109 } 110 .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track { 111 background-color: ${(props) => props.theme.palette.primary.main}; 112 } 105 113 `; 106 114 115 export const BackAndSaveChangesButtonsWrapper = styled.div``; 107 116 108 export const BackAndSaveChangesButtonsWrapper = styled.div` 109 110 `; 111 112 export const DeleteButton = styled(Button).attrs(props => ({ 113 variant: 'contained', 114 sx: { 115 backgroundColor: `${props.theme.palette.error.main}` 116 } 117 export const DeleteButton = styled(Button).attrs((props) => ({ 118 variant: 'contained', 119 sx: { 120 backgroundColor: `${props.theme.palette.error.main}`, 121 }, 117 122 }))` 118 119 background-color: ${props=> props.theme.palette.error.dark};120 123 :hover { 124 background-color: ${(props) => props.theme.palette.error.dark}; 125 } 121 126 `; 122 127 123 128 export const BackButton = styled(Button).attrs({ 124 variant: 'outlined'129 variant: 'outlined', 125 130 })` 126 127 128 129 130 131 margin-right: 15px; 132 :hover { 133 border: 2px solid; 134 font-weight: 600; 135 } 131 136 `; 132 137 133 export const SaveChangesButton = styled(Button).attrs( props=> ({134 135 136 backgroundColor: `${props.theme.palette.primary.main}`137 }138 export const SaveChangesButton = styled(Button).attrs((props) => ({ 139 variant: 'contained', 140 sx: { 141 backgroundColor: `${props.theme.palette.primary.main}`, 142 }, 138 143 }))` 139 140 141 background-color: ${props => props.theme.palette.primary.dark}142 144 padding: 10px 16px; 145 :hover { 146 background-color: ${(props) => props.theme.palette.primary.dark}; 147 } 143 148 `; -
sources/client/src/components/admin/EmployeesTable/index.js
re8b1076 rbc20307 1 import { useEffect } from 'react'; 1 2 import { useHistory } from 'react-router'; 2 3 3 4 import { 4 TableContainer,5 TableHeaderWrapper,6 TableTitle,7 Table,8 TableHead,9 TableRow,10 TableBody,11 TableCell,12 ButtonTableCell,13 ToggleAccoutStatusButton,14 CreateEmployeeButton,15 AddIcon,16 SearchField,17 IdentityIcon,5 TableContainer, 6 TableHeaderWrapper, 7 TableTitle, 8 Table, 9 TableHead, 10 TableRow, 11 TableBody, 12 TableCell, 13 ButtonTableCell, 14 ToggleAccoutStatusButton, 15 CreateEmployeeButton, 16 AddIcon, 17 SearchField, 18 IdentityIcon, 18 19 } from './styles'; 19 20 20 21 import InputAdornment from '@mui/material/InputAdornment'; 22 import DropdownViewer from '../../DropdownViewer'; 21 23 22 24 import { employeeStatus, accountStatus } from '../../../config/enums'; 25 import useGetData from '../../../hooks/useGetData'; 26 import useToggleAccountStatus from '../../../hooks/useToggleAccountStatus'; 27 import AbsoluteLoader from '../../Loaders/AbsoluteLoader'; 23 28 24 import { employees } from './mockData';29 // import { employees } from './mockData'; 25 30 import { useState } from 'react'; 26 31 27 32 const EmployeesTable = () => { 28 let history = useHistory(); 29 const [filteredEmployees, setFilteredEmployees] = useState(employees); 30 const [search, setSearch] = useState(''); 33 let history = useHistory(); 34 const { toggleAccountStatus, isLoading: isLoadingAccountStatus } = 35 useToggleAccountStatus(); 36 const { data: employees, isLoading: isLoadingEmployees } = useGetData({ 37 url: `/vraboten`, 38 }); 39 const [filteredEmployees, setFilteredEmployees] = useState([]); 40 const [search, setSearch] = useState(''); 41 const onRowClick = (workerId) => { 42 history.push(`/employees/${workerId}`); 43 }; 44 const changeAccoutStatusOnEmployee = ({ workerId }) => { 45 const emp = filteredEmployees.find((e) => e.workerId === workerId); 46 emp.accountNonLocked = !emp.accountNonLocked; 47 }; 48 const onAccountStatusClick = (event, workerId) => { 49 event.stopPropagation(); 50 toggleAccountStatus({ workerId, changeAccoutStatusOnEmployee }); 51 }; 31 52 32 const onRowClick = (id) => { 33 history.push(`/employees/${id}`); 34 }; 53 const onChangeSearch = (e) => { 54 let newSearchValue = e.target.value; 55 setSearch(newSearchValue); 56 const filteredData = employees.filter((employee) => 57 employee.firstName 58 .concat(` ${employee.lastName}`) 59 .toLowerCase() 60 .includes(newSearchValue.trim().toLowerCase()) 61 ); 35 62 36 const onAccountStatusClick = (event, id) => { 37 event.stopPropagation(); 38 console.log(`Disable or activate user acc with id: ${id}`); 39 }; 63 setFilteredEmployees(filteredData); 64 }; 40 65 41 const onChangeSearch = (e) => { 42 let newSearchValue = e.target.value; 43 setSearch(newSearchValue); 44 const filteredData = employees.filter((employee) => 45 employee.firstName 46 .concat(` ${employee.lastName}`) 47 .toLowerCase() 48 .includes(newSearchValue.trim().toLowerCase()) 66 useEffect(() => { 67 setFilteredEmployees(employees); 68 }, [employees]); 69 70 return ( 71 !isLoadingEmployees && ( 72 <TableContainer> 73 <TableHeaderWrapper> 74 {isLoadingAccountStatus ? ( 75 <AbsoluteLoader 76 containerStyle={{ 77 position: 'absolute', 78 left: '65%', 79 width: '42px', 80 height: '42px', 81 }} 82 /> 83 ) : null} 84 <TableTitle variant='h5'>Вработени</TableTitle> 85 <SearchField 86 value={search} 87 onChange={(e) => onChangeSearch(e)} 88 placeholder='Пребарај...' 89 InputProps={{ 90 startAdornment: ( 91 <InputAdornment position='start'> 92 <IdentityIcon /> 93 </InputAdornment> 94 ), 95 }} 96 /> 97 <CreateEmployeeButton 98 onClick={() => history.push('/employees/create')} 99 > 100 <AddIcon /> 101 Додади вработен 102 </CreateEmployeeButton> 103 </TableHeaderWrapper> 104 <Table aria-label='simple table'> 105 <TableHead> 106 <TableRow> 107 <TableCell align='left'>Емаил</TableCell> 108 <TableCell align='center'>Име и Презиме</TableCell> 109 <TableCell align='center'>Зона</TableCell> 110 <TableCell align='center'>Телефон</TableCell> 111 <TableCell align='center'>Статус</TableCell> 112 <TableCell align='center'>Акаунт</TableCell> 113 </TableRow> 114 </TableHead> 115 <TableBody> 116 {filteredEmployees.map((employeeData) => ( 117 <TableRow 118 key={employeeData.workerId} 119 onClick={() => 120 onRowClick(employeeData.workerId) 121 } 122 > 123 <TableCell align='left'> 124 {employeeData.email} 125 </TableCell> 126 <TableCell align='center'> 127 {employeeData.firstName}{' '} 128 {employeeData.lastName} 129 </TableCell> 130 <TableCell 131 align='center' 132 style={{ padding: 0, width: '200px' }} 133 > 134 <DropdownViewer 135 data={employeeData.parkingZones.map( 136 (z) => z.pzName 137 )} // TODO DELETE - after backend fix 138 width='200px' 139 /> 140 </TableCell> 141 <TableCell align='center'> 142 {employeeData.mobile} 143 </TableCell> 144 <TableCell align='center'> 145 {employeeStatus[employeeData.status]} 146 </TableCell> 147 <ButtonTableCell align='center'> 148 <ToggleAccoutStatusButton 149 onClick={(event) => 150 onAccountStatusClick( 151 event, 152 employeeData.workerId 153 ) 154 } 155 $enabled={employeeData.accountNonLocked} 156 > 157 {/* $ added because https://styled-components.com/docs/api#transient-props*/} 158 {employeeData.accountNonLocked 159 ? accountStatus.enabled 160 : accountStatus.disabled} 161 </ToggleAccoutStatusButton> 162 </ButtonTableCell> 163 </TableRow> 164 ))} 165 </TableBody> 166 </Table> 167 </TableContainer> 168 ) 49 169 ); 50 51 setFilteredEmployees(filteredData);52 };53 54 return (55 <TableContainer>56 <TableHeaderWrapper>57 <TableTitle variant='h5'>Вработени</TableTitle>58 <SearchField59 value={search}60 onChange={(e) => onChangeSearch(e)}61 placeholder='Пребарај...'62 InputProps={{63 startAdornment: (64 <InputAdornment position='start'>65 <IdentityIcon />66 </InputAdornment>67 ),68 }}69 />70 <CreateEmployeeButton onClick={() => history.push('/employees/create')}>71 <AddIcon />72 Додади вработен73 </CreateEmployeeButton>74 </TableHeaderWrapper>75 <Table aria-label='simple table'>76 <TableHead>77 <TableRow>78 <TableCell align='left'>Емаил</TableCell>79 <TableCell align='center'>Име и Презиме</TableCell>80 <TableCell align='center'>Зона</TableCell>81 <TableCell align='center'>Телефон</TableCell>82 <TableCell align='center'>Статус</TableCell>83 <TableCell align='center'>Акаунт</TableCell>84 </TableRow>85 </TableHead>86 <TableBody>87 {filteredEmployees.map((employeeData) => (88 <TableRow89 key={employeeData.id}90 onClick={() => onRowClick(employeeData.id)}91 >92 <TableCell align='left'>{employeeData.email}</TableCell>93 <TableCell align='center'>94 {employeeData.firstName} {employeeData.lastName}95 </TableCell>96 <TableCell align='center'>{employeeData.zone}</TableCell>97 <TableCell align='center'>{employeeData.phoneNumber}</TableCell>98 <TableCell align='center'>99 {employeeStatus[employeeData.status]}100 </TableCell>101 <ButtonTableCell align='center'>102 <ToggleAccoutStatusButton103 onClick={(event) =>104 onAccountStatusClick(event, employeeData.id)105 }106 $enabled={employeeData.accountActive}107 >108 {/* $ added because https://styled-components.com/docs/api#transient-props*/}109 {employeeData.accountActive110 ? accountStatus.enabled111 : accountStatus.disabled}112 </ToggleAccoutStatusButton>113 </ButtonTableCell>114 </TableRow>115 ))}116 </TableBody>117 </Table>118 </TableContainer>119 );120 170 }; 121 171 -
sources/client/src/components/admin/EmployeesTable/mockData.js
re8b1076 rbc20307 1 1 export const employees = [ 2 3 4 5 6 7 8 zone: 'zone1',9 10 11 accountActive: true12 13 14 15 16 17 18 19 zone: 'zone2',20 21 22 accountActive: true23 24 25 26 27 28 29 30 zone: 'zone3',31 32 33 accountActive: true34 35 36 37 38 39 40 41 zone: 'zone4',42 43 44 accountActive: true45 46 47 48 49 50 51 52 zone: 'zone5',53 54 status: 'notWorking',55 accountActive: false56 }2 { 3 id: 1, 4 email: 'viktor-tasevski@hotmail.com', 5 password: '123', 6 firstName: 'Viktor', 7 lastName: 'Tasevski', 8 zones: ['Zona 1', 'Zona 2', 'Zone 4'], 9 phoneNumber: '072500000', 10 status: 'vacation', 11 accountActive: true, 12 }, 13 { 14 id: 2, 15 email: 'andrejTav@gmail.com', 16 password: '123', 17 firstName: 'Andrej', 18 lastName: 'Tavcioski', 19 zones: ['Zona 2', 'Zona 5'], 20 phoneNumber: '070350123', 21 status: 'working', 22 accountActive: true, 23 }, 24 { 25 id: 3, 26 email: 'david.trajkovski@yahoo.com', 27 password: '123', 28 firstName: 'David', 29 lastName: 'Trajkovski', 30 zones: ['Zona 3'], 31 phoneNumber: '078123321', 32 status: 'working', 33 accountActive: true, 34 }, 35 { 36 id: 4, 37 email: 'poc@gmail.com', 38 password: '123', 39 firstName: 'Nekoj od', 40 lastName: 'POC', 41 zones: ['Zona 4'], 42 phoneNumber: '223305', 43 status: 'notWorking', 44 accountActive: true, 45 }, 46 { 47 id: 5, 48 email: 'silbo@outlook.com', 49 password: '123', 50 firstName: 'Nekoj od', 51 lastName: 'Silbo', 52 zones: [], 53 phoneNumber: '071206205', 54 status: 'sick', 55 accountActive: false, 56 }, 57 57 ]; -
sources/client/src/components/admin/EmployeesTable/styles.js
re8b1076 rbc20307 18 18 component: Paper, 19 19 })` 20 max-width: 1 000px;20 max-width: 1100px; 21 21 margin-top: 20px; 22 22 margin-left: 30px; … … 33 33 border-bottom: none; 34 34 padding: 10px 16px; 35 position: relative; 35 36 background-color: ${(props) => props.theme.palette.grey[400]}; 36 37 `; -
sources/client/src/components/admin/ParkingZone/index.js
re8b1076 rbc20307 1 import { useState } from 'react';1 import { useState, useContext, useEffect } from 'react'; 2 2 import { useParams } from 'react-router-dom'; 3 3 … … 7 7 import ParkingZoneSessions from '../ParkingZoneSessions'; 8 8 import GoogleMaps from '../../GoogleMaps'; 9 9 import CircularProgress from '@mui/material/CircularProgress'; 10 10 import { 11 NamesWrapper, 12 ParkingAndZoneName, 13 DividerUnderNames, 14 NavigationIconsWrapper, 15 MainSection, 16 MapsIcon, 17 ComponentIcon, 11 NamesWrapper, 12 ParkingAndZoneName, 13 DividerUnderNames, 14 NavigationIconsWrapper, 15 MainSection, 16 MapsIcon, 17 ComponentIcon, 18 ZoneNameLoader, 18 19 } from './styles'; 19 20 21 import AbsoluteLoader from '../../Loaders/AbsoluteLoader'; 20 22 import { roles } from '../../../config/enums'; 23 import { UserContext } from '../../../context/UserContext'; 24 import useGetData from '../../../hooks/useGetData'; 21 25 22 import { parkingZones } from '../ParkingZones/mockData'; 26 const defaultLocationObj = { 27 centre: { 28 lat: 42.000629, 29 lng: 21.420525, 30 }, 31 coords: [] 32 }; 33 34 const mockResponsiblePersons = [ 35 // TODO DELTE THIS 36 { 37 id: 1, 38 email: 'viktor-tasevski@hotmail.com', 39 firstName: 'Viktor', 40 lastName: 'Tasevski', 41 }, 42 { 43 id: 3, 44 email: 'david_trajkovski@yahoo.com', 45 firstName: 'David', 46 lastName: 'Trajkovski', 47 }, 48 ]; 23 49 24 50 const activeComponentEnum = { 25 MAPS: 'maps',26 INFO: 'info',51 MAPS: 'maps', 52 INFO: 'info', 27 53 }; 28 54 29 55 const ParkingZone = () => { 30 const { zone_id } = useParams(); 31 const [activeComponent, setActiveComponent] = useState( 32 activeComponentEnum.MAPS 33 ); 56 const { user } = useContext(UserContext); 57 const { zone_id } = useParams(); 58 const url = `/parkingZone/${zone_id}`; 59 const { data: zoneData, isLoading: isLoadingZoneData } = useGetData({ 60 url: url, 61 }); 34 62 35 const user = { 36 role: 'ROLE_ADMIN', 37 }; 63 const [zoneState, setZoneState] = useState(null); 64 const [activeComponent, setActiveComponent] = useState( 65 activeComponentEnum.MAPS 66 ); 38 67 39 const zone = parkingZones.find((z) => z.id === parseInt(zone_id)); 40 const Info = 41 user.role !== roles.admin ? ParkingZoneInfo : ParkingZoneSessions; 42 return ( 43 <> 44 <NamesWrapper> 45 <ParkingAndZoneName>Паркинг - Дебар Маало</ParkingAndZoneName> 46 <ParkingAndZoneName>{zone?.zoneName}</ParkingAndZoneName> 47 </NamesWrapper> 68 const setZone = (updatedZone) => { 69 setZoneState({ ...updatedZone }); 70 }; 71 const Info = 72 user.role === roles.admin ? ParkingZoneInfo : ParkingZoneSessions; 48 73 49 <DividerUnderNames /> 74 useEffect(() => { 75 setZoneState({ 76 ...zoneData 77 }); 78 }, [zoneData]); 79 return ( 80 <> 81 <NamesWrapper> 82 <ParkingAndZoneName>Паркинг - Дебар Маало</ParkingAndZoneName> 83 <ParkingAndZoneName className='zone-name'> 84 Зона 85 {isLoadingZoneData ? ( 86 <ZoneNameLoader /> 87 ) : ( 88 ` - ${zoneState?.pzName ?? ''}` 89 )} 90 </ParkingAndZoneName> 91 </NamesWrapper> 50 92 51 <NavigationIconsWrapper> 52 <IconButton 53 onClick={() => setActiveComponent(activeComponentEnum.MAPS)} 54 > 55 <MapsIcon $isactive={activeComponent === activeComponentEnum.MAPS} /> 56 </IconButton> 57 <IconButton 58 onClick={() => setActiveComponent(activeComponentEnum.INFO)} 59 > 60 <ComponentIcon 61 $isactive={activeComponent === activeComponentEnum.INFO} 62 /> 63 </IconButton> 64 </NavigationIconsWrapper> 93 <DividerUnderNames /> 65 94 66 <MainSection> 67 {activeComponent === activeComponentEnum.MAPS ? ( 68 <GoogleMaps 69 location={zone.location} 70 parkingSpacesLocation={zone.parkingSpacesLocation} 71 zoneAreaColor={zone.areaColor} 72 /> 73 ) : ( 74 <Info zone={zone} /> 75 )} 76 </MainSection> 77 </> 78 ); 95 <NavigationIconsWrapper> 96 <IconButton 97 onClick={() => setActiveComponent(activeComponentEnum.MAPS)} 98 > 99 <MapsIcon 100 $isactive={activeComponent === activeComponentEnum.MAPS} 101 /> 102 </IconButton> 103 <IconButton 104 onClick={() => setActiveComponent(activeComponentEnum.INFO)} 105 > 106 <ComponentIcon 107 $isactive={activeComponent === activeComponentEnum.INFO} 108 /> 109 </IconButton> 110 </NavigationIconsWrapper> 111 112 <MainSection> 113 {isLoadingZoneData ? ( 114 <AbsoluteLoader 115 containerStyle={{ 116 width: '250px', 117 height: '250px', 118 margin: 'auto', 119 marginTop: '12vw', 120 }} 121 /> 122 ) : ( 123 <> 124 {activeComponent === activeComponentEnum.MAPS ? ( 125 <GoogleMaps 126 location={zoneState.parkingZoneLocation ?? defaultLocationObj} 127 parkingSpaces={zoneState.parkingSpaces} 128 zoneAreaColor={zoneState.color} 129 /> 130 ) : ( 131 <Info zone={zoneState} setZone={setZone} /> 132 )} 133 </> 134 )} 135 </MainSection> 136 </> 137 ); 79 138 }; 80 139 -
sources/client/src/components/admin/ParkingZone/styles.js
re8b1076 rbc20307 3 3 import MapOutlinedIcon from '@mui/icons-material/MapOutlined'; 4 4 import EqualizerOutlinedIcon from '@mui/icons-material/EqualizerOutlined'; 5 import { mobile_max_width } from '../../../config/utilities'; 6 import CircularProgress from '@mui/material/CircularProgress'; 5 7 6 8 export const NamesWrapper = styled.div` … … 11 13 justify-content: space-between; 12 14 align-items: center; 13 padding: 10px 175px; 15 padding: 10px 10%; 16 17 @media (max-width: ${mobile_max_width}px) { 18 flex-direction: column; 19 .zone-name { 20 margin-top: 30px; 21 } 22 } 14 23 `; 15 24 … … 19 28 margin: 0, 20 29 color: `${props.theme.palette.primary.main}`, 21 }))``; 30 }))` 31 display: flex; 32 align-items: center; 33 `; 34 35 export const ZoneNameLoader = styled(CircularProgress).attrs({ 36 size: 40, 37 sx: { 38 marginLeft: '10px', 39 }, 40 })` 41 .MuiCircularProgress-svg { 42 color: ${(props) => props.theme.palette.primary.light}; 43 } 44 `; 22 45 23 46 export const DividerUnderNames = styled(Divider).attrs({ 24 47 variant: 'middle', 25 48 sx: { 26 margin: '0 160px',49 margin: '0 9%', 27 50 borderWidth: '2px', 28 51 borderBottomWidth: 'thin', -
sources/client/src/components/admin/ParkingZoneInfo/index.js
re8b1076 rbc20307 1 const ParkingZoneInfo = () => { 2 return <h1>PARKING ZONE INFO</h1> 1 import { useState } from 'react'; 2 3 import { Wrapper } from './styles'; 4 5 import ParkingZoneInfoViewer from '../ParkingZoneInfoViewer'; 6 import ParkingZoneInfoEdit from '../ParkingZoneInfoEdit'; 7 8 const ParkingZoneInfo = ({ zone, setZone }) => { 9 const [editMode, setEditMode] = useState(false); 10 return ( 11 <Wrapper> 12 {editMode ? ( 13 <ParkingZoneInfoEdit 14 zone={zone} 15 setEditMode={setEditMode} 16 setZone={setZone} 17 /> 18 ) : ( 19 <ParkingZoneInfoViewer zone={zone} setEditMode={setEditMode} /> 20 )} 21 </Wrapper> 22 ); 3 23 }; 4 24 -
sources/client/src/components/admin/ParkingZoneInfo/styles.js
re8b1076 rbc20307 1 import styled from 'styled-components'; 2 3 export const Wrapper = styled.div` 4 display: flex; 5 flex-direction: row; 6 justify-content: space-between; 7 width: 100%; 8 max-width: 1650px; 9 height: 690px; 10 padding: 0 10%; 11 margin: auto; 12 margin-top: 25px; 13 `; -
sources/client/src/components/admin/ParkingZoneSessions/SessionCard/index.js
re8b1076 rbc20307 1 import { useState } from 'react'; 2 import moment from 'moment'; 3 1 4 import { 2 Wrapper, 3 SessionChildWrapper, 4 SessionChildTitle, 5 SeessionChildData, 6 InputAndCheckIconWrapper, 7 ParkingSpaceNumberInput, 8 CheckIcon, 9 DeleteButton, 10 ContinueButton, 5 Wrapper, 6 SessionChildWrapper, 7 SessionChildTitle, 8 SeessionChildData, 9 InputAndCheckIconWrapper, 10 ParkingSpaceNumberInput, 11 CheckIcon, 12 DeleteButton, 11 13 } from './styles'; 12 14 13 15 import { sessionStatus } from '../../../../config/enums'; 16 import { dateFormatString } from '../../../../config/utilities'; 14 17 import { IconButton } from '@mui/material'; 15 import { useState } from 'react'; 18 import AbsoluteLoader from '../../../Loaders/AbsoluteLoader'; 19 import useDeleteSession from '../../../../hooks/useDeleteSession'; 20 16 21 17 22 const sessionCardColors = { 18 active: '#389e0d', 19 idle: '#ffa940', 20 over: '#cf1322', 21 }; 22 23 const ActiveCard = ({ id, start, plate, status, parkingSpaceNumber }) => { 24 return ( 25 <Wrapper style={{ backgroundColor: sessionCardColors.active }}> 26 <SessionChildWrapper> 27 <SessionChildTitle>Почеток</SessionChildTitle> 28 <SeessionChildData>{start}</SeessionChildData> 29 </SessionChildWrapper> 30 31 <SessionChildWrapper> 32 <SessionChildTitle>Број на место</SessionChildTitle> 33 <SeessionChildData>{parkingSpaceNumber}</SeessionChildData> 34 </SessionChildWrapper> 35 36 <SessionChildWrapper> 37 <SessionChildTitle>Регистрација</SessionChildTitle> 38 <SeessionChildData>{plate}</SeessionChildData> 39 </SessionChildWrapper> 40 41 <SessionChildWrapper> 42 <DeleteButton>ИЗБРИШИ</DeleteButton> 43 </SessionChildWrapper> 44 </Wrapper> 45 ); 46 }; 47 48 const IdleCard = ({ id, start, plate, status, parkingSpaceNumber }) => { 49 const [parkingSpace, setParkingSpace] = useState(parkingSpaceNumber ?? ''); 50 return ( 51 <Wrapper style={{ backgroundColor: sessionCardColors.idle }}> 52 <SessionChildWrapper> 53 <SessionChildTitle>Почеток</SessionChildTitle> 54 <SeessionChildData>{start}</SeessionChildData> 55 </SessionChildWrapper> 56 57 <SessionChildWrapper> 58 <SessionChildTitle>Број на место</SessionChildTitle> 59 <InputAndCheckIconWrapper> 60 <ParkingSpaceNumberInput 61 value={parkingSpace} 62 onChange={(event) => setParkingSpace(event.target.value)} 63 /> 64 {parkingSpace !== '' ? ( 65 <IconButton> 66 <CheckIcon /> 67 </IconButton> 68 ) : null} 69 </InputAndCheckIconWrapper> 70 </SessionChildWrapper> 71 72 <SessionChildWrapper> 73 <SessionChildTitle>Регистрација</SessionChildTitle> 74 <SeessionChildData>{plate}</SeessionChildData> 75 </SessionChildWrapper> 76 77 <SessionChildWrapper> 78 <DeleteButton>ИЗБРИШИ</DeleteButton> 79 </SessionChildWrapper> 80 </Wrapper> 81 ); 82 }; 83 84 const OverCard = ({ id, start, end, plate, status, parkingSpaceNumber }) => { 85 return ( 86 <Wrapper style={{ backgroundColor: sessionCardColors.over }}> 87 <SessionChildWrapper> 88 <SessionChildWrapper> 89 <SessionChildTitle>Почеток</SessionChildTitle> 90 <SeessionChildData>{start}</SeessionChildData> 91 </SessionChildWrapper> 92 <SessionChildWrapper> 93 <SessionChildTitle>Крај</SessionChildTitle> 94 <SeessionChildData>{end}</SeessionChildData> 95 </SessionChildWrapper> 96 </SessionChildWrapper> 97 98 <SessionChildWrapper> 99 <SessionChildTitle>Број на место</SessionChildTitle> 100 <SeessionChildData>{parkingSpaceNumber}</SeessionChildData> 101 </SessionChildWrapper> 102 103 <SessionChildWrapper> 104 <SessionChildTitle>Регистрација</SessionChildTitle> 105 <SeessionChildData>{plate}</SeessionChildData> 106 </SessionChildWrapper> 107 108 <SessionChildWrapper> 109 <DeleteButton>ИЗБРИШИ</DeleteButton> 110 <ContinueButton>ПРОДОЛЖИ</ContinueButton> 111 </SessionChildWrapper> 112 </Wrapper> 113 ); 114 }; 115 116 const SessionCard = (props) => { 117 switch (props.status) { 118 case sessionStatus.active: 119 return <ActiveCard {...props} />; 120 case sessionStatus.idle: 121 return <IdleCard {...props} />; 122 case sessionStatus.over: 123 return <OverCard {...props} />; 124 default: 125 return null; 126 } 23 active: '#389e0d', 24 idle: '#ffa940', 25 over: '#cf1322', 26 }; 27 28 const ActiveCard = ({ 29 pssId, 30 timeStart, 31 zone, 32 plate, 33 status, 34 parkingSpace, 35 onDeleteSession, 36 }) => { 37 const { isLoading: isLoadingDeleteSession, deleteSession } = 38 useDeleteSession(); 39 const formatedTimeStart = moment(timeStart).format(dateFormatString); 40 return ( 41 <Wrapper style={{ backgroundColor: sessionCardColors.active }}> 42 <SessionChildWrapper> 43 <SessionChildTitle>Почеток</SessionChildTitle> 44 <SeessionChildData>{formatedTimeStart}</SeessionChildData> 45 </SessionChildWrapper> 46 47 <SessionChildWrapper> 48 <SessionChildTitle>Број на место</SessionChildTitle> 49 <SeessionChildData>{parkingSpace.psName}</SeessionChildData> 50 </SessionChildWrapper> 51 52 <SessionChildWrapper> 53 <SessionChildTitle>Регистрација</SessionChildTitle> 54 <SeessionChildData>{plate.plate}</SeessionChildData> 55 </SessionChildWrapper> 56 57 <SessionChildWrapper> 58 {isLoadingDeleteSession ? ( 59 <div style={{ width: '104px', textAlign: 'center' }}> 60 <AbsoluteLoader 61 containerStyle={{ 62 width: '40px', 63 height: '40px', 64 display: 'inline-block', 65 }} 66 /> 67 </div> 68 ) : ( 69 <DeleteButton 70 onClick={() => { 71 console.log(`CLICKED DELETE BUTTON ${pssId}`); 72 deleteSession({ pssId, onDeleteSession }); 73 }} 74 > 75 ИЗБРИШИ 76 </DeleteButton> 77 )} 78 </SessionChildWrapper> 79 </Wrapper> 80 ); 81 }; 82 83 const IdleCard = ({ 84 pssId, 85 timeStart, 86 zone, 87 plate, 88 status, 89 handleActivateSession, 90 isLoadingActivateSession, 91 onDeleteSession, 92 }) => { 93 const { isLoading: isLoadingDeleteSession, deleteSession } = 94 useDeleteSession(); 95 const [parkingSpaceName, setParkingSpaceName] = useState(''); 96 const formatedTimeStart = moment(timeStart).format(dateFormatString); 97 return ( 98 <Wrapper style={{ backgroundColor: sessionCardColors.idle }}> 99 <SessionChildWrapper> 100 <SessionChildTitle>Почеток</SessionChildTitle> 101 <SeessionChildData>{formatedTimeStart}</SeessionChildData> 102 </SessionChildWrapper> 103 104 <SessionChildWrapper> 105 <SessionChildTitle>Број на место</SessionChildTitle> 106 <InputAndCheckIconWrapper> 107 <ParkingSpaceNumberInput 108 value={parkingSpaceName} 109 onChange={(event) => 110 setParkingSpaceName(event.target.value.trim()) 111 } 112 /> 113 {parkingSpaceName !== '' ? ( 114 <> 115 {isLoadingActivateSession ? ( 116 <AbsoluteLoader 117 containerStyle={{ 118 width: '2rem', 119 height: '2rem', 120 display: 'inline-block', 121 }} 122 /> 123 ) : ( 124 <IconButton 125 onClick={() => 126 handleActivateSession({ 127 pssId, 128 parkingSpaceName, 129 }) 130 } 131 > 132 <CheckIcon /> 133 </IconButton> 134 )} 135 </> 136 ) : null} 137 </InputAndCheckIconWrapper> 138 </SessionChildWrapper> 139 140 <SessionChildWrapper> 141 <SessionChildTitle>Регистрација</SessionChildTitle> 142 <SeessionChildData>{plate.plate}</SeessionChildData> 143 </SessionChildWrapper> 144 145 <SessionChildWrapper> 146 {isLoadingDeleteSession ? ( 147 <div style={{ width: '104px', textAlign: 'center' }}> 148 <AbsoluteLoader 149 containerStyle={{ 150 width: '40px', 151 height: '40px', 152 display: 'inline-block', 153 }} 154 /> 155 </div> 156 ) : ( 157 <DeleteButton 158 onClick={() => { 159 console.log(`CLICKED DELETE BUTTON ${pssId}`); 160 deleteSession({ pssId, onDeleteSession }); 161 }} 162 > 163 ИЗБРИШИ 164 </DeleteButton> 165 )} 166 </SessionChildWrapper> 167 </Wrapper> 168 ); 169 }; 170 171 const OverCard = ({ 172 pssId, 173 timeStart, 174 timeEnd, 175 zone, 176 plate, 177 status, 178 parkingSpace, 179 onDeleteSession, 180 }) => { 181 const { isLoading: isLoadingDeleteSession, deleteSession } = 182 useDeleteSession(); 183 const formatedTimeStart = moment(timeStart).format(dateFormatString); 184 const formatedTimeEnd = moment(timeEnd).format(dateFormatString); 185 return ( 186 <Wrapper style={{ backgroundColor: sessionCardColors.over }}> 187 <SessionChildWrapper> 188 <SessionChildWrapper style={{ margin: 0 }}> 189 <SessionChildTitle>Почеток</SessionChildTitle> 190 <SeessionChildData>{formatedTimeStart}</SeessionChildData> 191 </SessionChildWrapper> 192 <SessionChildWrapper style={{ marginTop: '5px' }}> 193 <SessionChildTitle>Крај</SessionChildTitle> 194 <SeessionChildData>{formatedTimeEnd}</SeessionChildData> 195 </SessionChildWrapper> 196 </SessionChildWrapper> 197 198 <SessionChildWrapper> 199 <SessionChildTitle>Број на место</SessionChildTitle> 200 <SeessionChildData>{parkingSpace.psName}</SeessionChildData> 201 </SessionChildWrapper> 202 203 <SessionChildWrapper> 204 <SessionChildTitle>Регистрација</SessionChildTitle> 205 <SeessionChildData>{plate.plate}</SeessionChildData> 206 </SessionChildWrapper> 207 208 <SessionChildWrapper> 209 {isLoadingDeleteSession ? ( 210 <div style={{ width: '104px', textAlign: 'center' }}> 211 <AbsoluteLoader 212 containerStyle={{ 213 width: '40px', 214 height: '40px', 215 display: 'inline-block', 216 }} 217 /> 218 </div> 219 ) : ( 220 <DeleteButton 221 onClick={() => { 222 console.log(`CLICKED DELETE BUTTON ${pssId}`); 223 deleteSession({ pssId, onDeleteSession }); 224 }} 225 > 226 ИЗБРИШИ 227 </DeleteButton> 228 )} 229 </SessionChildWrapper> 230 </Wrapper> 231 ); 232 }; 233 234 const SessionCard = ({ 235 handleActivateSession, 236 isLoadingActivateSession, 237 ...commponProps 238 }) => { 239 switch (commponProps.status) { 240 case sessionStatus.active: 241 return <ActiveCard {...commponProps} />; 242 case sessionStatus.idle: 243 return ( 244 <IdleCard 245 {...commponProps} 246 handleActivateSession={handleActivateSession} 247 isLoadingActivateSession={isLoadingActivateSession} 248 /> 249 ); 250 case sessionStatus.over: 251 return <OverCard {...commponProps} />; 252 default: 253 return null; 254 } 127 255 }; 128 256 -
sources/client/src/components/admin/ParkingZoneSessions/SessionCard/styles.js
re8b1076 rbc20307 2 2 import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; 3 3 import Button from '@mui/material/Button'; 4 import { mobile_max_width } from '../../../../config/utilities'; 4 5 5 6 export const Wrapper = styled.div` … … 15 16 font-size: 1rem; 16 17 box-shadow: 0 10px 5px -2px ${(props) => props.theme.palette.background.shadow}; 18 19 @media (max-width: ${mobile_max_width}px) { 20 height: 350px; 21 border-radius: 100px; 22 flex-direction: column; 23 } 17 24 `; 18 25 … … 20 27 display: flex; 21 28 flex-direction: column; 29 30 @media (max-width: ${mobile_max_width}px) { 31 margin-top: 15px; 32 :last-of-type { 33 margin-top: 30px; 34 } 35 } 22 36 `; 23 37 … … 38 52 position: relative; 39 53 40 button { 54 button, 55 > div { 41 56 padding: 0; 42 57 position: absolute; … … 69 84 } 70 85 `; 71 72 export const ContinueButton = styled(Button).attrs((props) => ({73 variant: 'contained',74 size: 'small',75 sx: {76 backgroundColor: `${props.theme.palette.primary.main}`,77 },78 }))`79 margin-top: 20px;80 81 :hover {82 background-color: ${(props) => props.theme.palette.primary.dark};83 }84 `; -
sources/client/src/components/admin/ParkingZoneSessions/index.js
re8b1076 rbc20307 1 import { useEffect, use State } from 'react';1 import { useEffect, useMemo, useRef, useState } from 'react'; 2 2 import { 3 3 Wrapper, … … 9 9 StatsKey, 10 10 StatsValue, 11 SearchField, 12 SearchIcon, 11 13 } from './styles'; 12 14 import SessionCard from './SessionCard'; 13 15 import { sessionStatus } from '../../../config/enums'; 14 import { sessionsData } from './mockData'; 16 import useGetSessions from '../../../hooks/useGetSessions'; 17 import AbsoluteLoader from '../../Loaders/AbsoluteLoader'; 18 19 import useActivateSession from '../../../hooks/useActivateSession'; 20 21 const sortSessions = (a, b) => { 22 // first - yellow/idle, second - red/finished, third - green/active 23 switch (a.status) { 24 case sessionStatus.active: 25 switch (b.status) { 26 case sessionStatus.active: 27 return -1; 28 case sessionStatus.over: 29 case sessionStatus.idle: 30 return 1; 31 default: 32 return 1; 33 } 34 case sessionStatus.over: 35 switch (b.status) { 36 case sessionStatus.active: 37 case sessionStatus.over: 38 return -1; 39 case sessionStatus.idle: 40 return 1; 41 default: 42 return 1; 43 } 44 case sessionStatus.idle: 45 switch (b.status) { 46 case sessionStatus.active: 47 case sessionStatus.over: 48 case sessionStatus.idle: 49 return -1; 50 default: 51 return 1; 52 } 53 default: 54 return -1; 55 } 56 }; 15 57 16 58 const ParkingZoneSessions = ({ zone }) => { 17 const [sessions, setSession] = useState(sessionsData); 59 const { isLoading: isLoadingActivateSession, activateSession } = 60 useActivateSession(); 61 const { data: sessions, isLoading: isLoadingSessions, setData: setSessions } = useGetSessions(zone.pzId); 62 const [search, setSearch] = useState(''); 18 63 const [stats, setStats] = useState({ 19 64 activeSessions: 0, … … 21 66 overSessions: 0, 22 67 }); 68 23 69 const setSessionsStats = () => { 24 70 let aS = 0; … … 26 72 let oS = 0; 27 73 sessions.forEach((s) => { 28 switch (s.status) {29 case sessionStatus.active:30 aS += 1;31 break;32 case sessionStatus.idle:33 iS += 1;34 break;35 case sessionStatus.over:36 oS += 1;37 break;38 default:39 break;40 }74 switch (s.status) { 75 case sessionStatus.active: 76 aS += 1; 77 break; 78 case sessionStatus.idle: 79 iS += 1; 80 break; 81 case sessionStatus.over: 82 oS += 1; 83 break; 84 default: 85 break; 86 } 41 87 }); 42 88 setStats({ … … 47 93 }; 48 94 95 const onActivateSession = ({ updatedSession }) => { 96 const updatedSessions = sessions.filter( 97 (s) => s.pssId !== updatedSession.pssId 98 ); 99 setSessions([...updatedSessions, updatedSession]); 100 }; 101 102 const handleActivateSession = ({ pssId, parkingSpaceName }) => { 103 activateSession({ pssId, parkingSpaceName, onActivateSession }); 104 }; 105 106 const onDeleteSession = ({ pssId }) => { 107 setSessions((prevState) => prevState.filter((s) => s.pssId !== pssId)); 108 }; 109 110 const freeParkingSpaces = useMemo(() => { 111 if(!zone) return 0; 112 return zone.parkingSpaces.length - stats.activeSessions 113 }, [zone?.parkingSpaces, stats]) 114 49 115 useEffect(() => { 50 116 setSessionsStats(); … … 52 118 53 119 return ( 54 <Wrapper> 55 <Title>ОТВОРЕНИ ПАРКИНГ СЕСИИ</Title> 56 <StatsWrapper> 57 <Stats> 58 {/* PARKING SPACES STATS*/} 59 <KeyValueWrapper> 60 <StatsKey>Вкупно паркинг места:</StatsKey> 61 <StatsValue>{zone.parkingSpaces}</StatsValue> 62 </KeyValueWrapper> 63 <KeyValueWrapper> 64 <StatsKey>Слободни паркинг места:</StatsKey> 65 <StatsValue> 66 {zone.parkingSpaces - zone.takenParkingSpaces} 67 </StatsValue> 68 </KeyValueWrapper> 69 <KeyValueWrapper> 70 <StatsKey>Зафатени паркинг места:</StatsKey> 71 <StatsValue>{zone.takenParkingSpaces}</StatsValue> 72 </KeyValueWrapper> 73 </Stats> 74 {/* SESSIONS STATS*/} 75 <Stats> 76 <KeyValueWrapper> 77 <StatsKey>Активни паркинг сесии:</StatsKey> 78 <StatsValue>{stats.activeSessions}</StatsValue> 79 </KeyValueWrapper> 80 <KeyValueWrapper> 81 <StatsKey>Завршени паркинг сесии:</StatsKey> 82 <StatsValue>{stats.overSessions}</StatsValue> 83 </KeyValueWrapper> 84 <KeyValueWrapper> 85 <StatsKey>Неактивни паркинг сесии:</StatsKey> 86 <StatsValue>{stats.idleSessions}</StatsValue> 87 </KeyValueWrapper> 88 </Stats> 89 </StatsWrapper> 90 <SessionsWrapper> 91 {sessions.map((session) => ( 92 <SessionCard key={session.id} {...session} /> 93 ))} 94 </SessionsWrapper> 95 </Wrapper> 120 <Wrapper> 121 <Title>ОТВОРЕНИ ПАРКИНГ СЕСИИ</Title> 122 <StatsWrapper> 123 <Stats> 124 {/* PARKING SPACES STATS*/} 125 <KeyValueWrapper> 126 <StatsKey>Вкупно паркинг места:</StatsKey> 127 <StatsValue>{zone.parkingSpaces.length}</StatsValue> 128 </KeyValueWrapper> 129 <KeyValueWrapper> 130 <StatsKey>Слободни паркинг места:</StatsKey> 131 <StatsValue>{freeParkingSpaces}</StatsValue> 132 </KeyValueWrapper> 133 <KeyValueWrapper> 134 <StatsKey>Зафатени паркинг места:</StatsKey> 135 <StatsValue> 136 {zone.parkingSpaces.length - freeParkingSpaces} 137 </StatsValue> 138 </KeyValueWrapper> 139 </Stats> 140 {/* SESSIONS STATS*/} 141 <Stats> 142 <KeyValueWrapper> 143 <StatsKey>Активни паркинг сесии:</StatsKey> 144 <StatsValue>{stats.activeSessions}</StatsValue> 145 </KeyValueWrapper> 146 <KeyValueWrapper> 147 <StatsKey>Завршени паркинг сесии:</StatsKey> 148 <StatsValue>{stats.overSessions}</StatsValue> 149 </KeyValueWrapper> 150 <KeyValueWrapper> 151 <StatsKey>Неактивни паркинг сесии:</StatsKey> 152 <StatsValue>{stats.idleSessions}</StatsValue> 153 </KeyValueWrapper> 154 </Stats> 155 </StatsWrapper> 156 <SearchField 157 value={search} 158 onChange={(e) => setSearch(e.target.value)} 159 InputProps={{ 160 startAdornment: <SearchIcon />, 161 }} 162 /> 163 <SessionsWrapper> 164 {isLoadingSessions ? ( 165 <AbsoluteLoader 166 containerStyle={{ 167 width: '150px', 168 height: '150px', 169 margin: 'auto', 170 marginTop: '50px', 171 }} 172 /> 173 ) : ( 174 <> 175 {sessions 176 .filter((session) => 177 session.plate.plate 178 .concat( 179 ` ${session.parkingSpace?.psName ?? ''}` 180 ) 181 .toLowerCase() 182 .includes(search.trim().toLowerCase()) 183 ) 184 .sort(sortSessions) 185 .map((session) => ( 186 <SessionCard 187 key={session.pssId} 188 {...session} 189 handleActivateSession={handleActivateSession} 190 isLoadingActivateSession={ 191 isLoadingActivateSession 192 } 193 onDeleteSession={onDeleteSession} 194 /> 195 ))} 196 </> 197 )} 198 </SessionsWrapper> 199 </Wrapper> 96 200 ); 97 201 }; -
sources/client/src/components/admin/ParkingZoneSessions/mockData.js
re8b1076 rbc20307 5 5 id: 1, 6 6 start: moment().format('hh:mm - DD.MM.yyyy'), 7 zone: 'Zona 1', 7 8 plate: 'SK-8190-AV', 8 9 status: 'active', … … 12 13 id: 2, 13 14 start: moment().format('hh:mm - DD.MM.yyyy'), 15 zone: 'Zona 2', 14 16 plate: 'ST-9312-OK', 15 17 status: 'idle', … … 20 22 start: moment().format('hh:mm - DD.MM.yyyy'), 21 23 end: moment().format('hh:mm - DD.MM.yyyy'), 24 zone: 'Zona 1', 22 25 plate: 'SK-6511-OS', 23 26 status: 'over', -
sources/client/src/components/admin/ParkingZoneSessions/styles.js
re8b1076 rbc20307 1 1 import styled from 'styled-components'; 2 2 import { Typography } from '@mui/material'; 3 import TextField from '@mui/material/TextField'; 4 import SIcon from '@mui/icons-material/Search'; 5 6 import { mobile_max_width } from '../../../config/utilities'; 3 7 4 8 export const Wrapper = styled.div` … … 6 10 flex-direction: column; 7 11 align-items: center; 8 margin: 0; 9 padding: 30px 175px 0 175px; 12 padding: 30px 12% 0 12%; 10 13 width: 100%; 14 max-width: 1440px; 15 margin: auto; 11 16 height: 100%; 12 17 `; … … 20 25 -webkit-background-clip: text; 21 26 -webkit-text-fill-color: transparent; 27 text-align: center; 22 28 `; 23 29 … … 28 34 justify-content: space-between; 29 35 margin: 30px 0 10px 0; 36 37 @media (max-width: ${mobile_max_width}px) { 38 flex-direction: column; 39 } 30 40 `; 31 41 … … 33 43 display: flex; 34 44 flex-direction: column; 45 align-items: center; 35 46 `; 36 47 … … 38 49 display: flex; 39 50 flex-direction: row; 40 width: 2 20px;51 width: 280px; 41 52 justify-content: space-between; 42 53 `; … … 47 58 -webkit-background-clip: text; 48 59 -webkit-text-fill-color: transparent; 60 font-size: 1.2rem; 49 61 `; 50 62 … … 52 64 margin: 0; 53 65 font-weight: 600; 54 font-size: 1. 2rem;66 font-size: 1.4rem; 55 67 background: -webkit-linear-gradient(#333333, #4cc5a3); 56 68 -webkit-background-clip: text; … … 58 70 `; 59 71 72 export const SearchField = styled(TextField).attrs({ 73 width: '150px', 74 sx: { 75 marginTop: '10px', 76 marginBottom: '20px', 77 }, 78 })` 79 border: 1px solid ${(props) => props.theme.palette.primary.main}; 80 border-radius: 5px; 81 fieldset { 82 border: 0; 83 } 84 input { 85 font-size: 1.1rem; 86 font-weight: 500; 87 padding: 15px 10px; 88 color: ${(props) => props.theme.palette.primary.main}; 89 } 90 `; 91 92 export const SearchIcon = styled(SIcon).attrs((props) => ({ 93 sx: { 94 fontSize: '2rem', 95 color: `rgba(0,173,124, 0.4)`, // primary.main in rgb with opacity 0.4 96 }, 97 }))``; 98 60 99 export const SessionsWrapper = styled.div` 61 height: 45vh; 100 height: auto; 101 min-height: 240px; 102 max-height: 380px; 62 103 width: 100%; 63 104 overflow: auto; 64 padding-top: 20px;65 66 105 > div:not(div:first-of-type) { 67 106 margin-top: 24px; 68 107 } 108 109 @media (max-width: ${mobile_max_width}px) { 110 max-height: 75vh; 111 padding-bottom: 10px; 112 } 69 113 `; -
sources/client/src/components/admin/ParkingZones/ParkingZoneCard/index.js
re8b1076 rbc20307 2 2 3 3 import { 4 ParkingZoneWrapper,5 Container,6 ZoneName,7 InfoWrapper,8 Label,9 Value,10 ProgressBar,11 ProgressBarLabel,4 ParkingZoneWrapper, 5 Container, 6 ZoneName, 7 InfoWrapper, 8 Label, 9 Value, 10 ProgressBar, 11 ProgressBarLabel, 12 12 } from './styles'; 13 13 14 import DropdownViewer from '../../../DropdownViewer'; 15 14 16 const ParkingZoneCard = ({ info }) => { 15 let history = useHistory(); 17 let history = useHistory(); 18 const takenDividedByTotal = info.takenSpaces / info.capacity; 16 19 17 return ( 18 <ParkingZoneWrapper item xs={11} sm={6} md={3}> 19 <Container onClick={() => history.push(`/zone/${info.id}`)}> 20 <ZoneName>{info.zoneName}</ZoneName> 21 <InfoWrapper> 22 <Label>Одговорни лица:</Label> 23 {info.responsiblePersons.slice(0, 1).map((person, index) => ( 24 <Value key={index}>{person}</Value> 25 ))} 26 {info.responsiblePersons.length > 1 ? <Value>...</Value> : null} 27 </InfoWrapper> 28 <InfoWrapper> 29 <Label>Број на паркинг места:</Label> 30 <Value>{info.parkingSpaces}</Value> 31 </InfoWrapper> 32 <ProgressBar 33 percent={Math.floor( 34 (info.takenParkingSpaces / info.parkingSpaces) * 100 35 )} 36 label={() => ( 37 <ProgressBarLabel> 38 {info.takenParkingSpaces}/{info.parkingSpaces} 39 </ProgressBarLabel> 40 )} 41 /> 42 </Container> 43 </ParkingZoneWrapper> 44 ); 20 return ( 21 <ParkingZoneWrapper item xs={11} sm={6} md={3}> 22 <Container onClick={() => history.push(`/zone/${info.id}`)}> 23 <ZoneName>{info.pzName}</ZoneName> 24 <InfoWrapper> 25 <Label>Одговорни лица:</Label> 26 <DropdownViewer data={info?.responsibleWorkers ?? [] } /> 27 </InfoWrapper> 28 <InfoWrapper style={{ marginTop: '30px' }}> 29 <Label>Број на паркинг места:</Label> 30 <Value>{info.capacity}</Value> 31 </InfoWrapper> 32 <ProgressBar 33 percent={Math.floor( 34 (isNaN(takenDividedByTotal) ? 0 : takenDividedByTotal) * 35 100 36 )} 37 label={() => ( 38 <ProgressBarLabel> 39 {info.takenSpaces}/{info.capacity} 40 </ProgressBarLabel> 41 )} 42 /> 43 </Container> 44 </ParkingZoneWrapper> 45 ); 45 46 }; 46 47 -
sources/client/src/components/admin/ParkingZones/ParkingZoneCard/styles.js
re8b1076 rbc20307 2 2 import Grid from '@mui/material/Grid'; 3 3 import { Typography } from '@mui/material'; 4 5 import { LineProgressBar } from '@frogress/line' 6 4 import { LineProgressBar } from '@frogress/line'; 5 import { mobile_max_width } from '../../../../config/utilities'; 7 6 8 7 export const ParkingZoneWrapper = styled(Grid)` 9 height: 350px; 8 height: 350px; 9 10 @media (max-width: ${mobile_max_width}px) { 11 padding-left: 15px !important; 12 padding-right: 15px; 13 } 10 14 `; 11 15 12 16 export const Container = styled.div` 13 14 15 16 17 18 box-shadow: 15px 15px 10px ${props=> props.theme.palette.background.shadow};19 background-color: ${props=> props.theme.palette.background.white};20 21 22 23 24 17 display: flex; 18 flex-direction: column; 19 align-items: center; 20 width: 100%; 21 height: 100%; 22 box-shadow: 15px 15px 10px ${(props) => props.theme.palette.background.shadow}; 23 background-color: ${(props) => props.theme.palette.background.white}; 24 :hover { 25 opacity: 0.8; 26 cursor: pointer; 27 } 28 position: relative; 25 29 `; 26 30 27 31 export const ZoneName = styled(Typography).attrs({ 28 variant: 'h2'32 variant: 'h2', 29 33 })` 30 31 32 33 34 font-size: 2rem; 35 font-weight: 600; 36 margin-top: 30px; 37 text-align: center; 34 38 `; 35 39 36 40 export const InfoWrapper = styled.div` 37 margin-top: 20px;38 display: flex;39 flex-direction: column;40 align-items: center;41 41 width: 100%; 42 margin-top: 20px; 43 display: flex; 44 flex-direction: column; 45 align-items: center; 42 46 `; 43 47 44 48 export const Label = styled(Typography).attrs({ 45 variant: 'h4'49 variant: 'h4', 46 50 })` 47 48 51 font-size: 1rem; 52 margin-bottom: 5px; 49 53 `; 50 54 51 55 export const Value = styled(Typography).attrs({ 52 variant: 'h3'56 variant: 'h3', 53 57 })` 54 55 56 58 font-size: 1.25rem; 59 font-weight: 600; 60 margin-top: 5px; 57 61 `; 58 62 59 export const ProgressBar = styled(LineProgressBar).attrs( props=> ({60 61 62 63 export const ProgressBar = styled(LineProgressBar).attrs((props) => ({ 64 stripe: true, 65 progressColor: props.theme.palette.primary.main, 66 height: '30px', 63 67 }))` 64 65 68 position: absolute; 69 bottom: 5px; 66 70 `; 67 71 68 72 export const ProgressBarLabel = styled.p` 69 70 71 72 73 margin: 0 0 0 42.5%; 74 line-height: 30px; 75 position: absolute; 76 font-weight: 500; 73 77 `; -
sources/client/src/components/admin/ParkingZones/index.js
re8b1076 rbc20307 1 import { useState } from 'react';1 import { useState, useContext } from 'react'; 2 2 import { 3 FiltersWrapper, 4 SortingArrowsWrapper, 5 SortByTitle, 6 ArrowDown, 7 ArrowUp, 8 ClearSortIcon, 9 ParkingZonesWrapper, 10 AddParkingZoneCard, 11 AddIcon, 12 AddItem, 13 ParkingName, 14 DividerUnderFilters, 3 FiltersWrapper, 4 SortingArrowsWrapper, 5 SortByTitle, 6 ArrowDown, 7 ArrowUp, 8 ClearSortIcon, 9 ParkingZonesWrapper, 10 AddParkingZoneCard, 11 AddIcon, 12 AddItem, 13 ParkingName, 14 DividerUnderFilters, 15 ModalContainer, 16 ModalTitle, 17 ModalInputAndLabelWrapper, 18 ModalInput, 19 ModalInputLabel, 20 ModalButton, 21 ButtonWrapper, 22 CloseIcon, 15 23 } from './styles'; 16 24 25 import AbsoluteLoader from '../../Loaders/AbsoluteLoader'; 26 import Modal from '@mui/material/Modal'; 27 import Backdrop from '@mui/material/Backdrop'; 28 import { IconButton, Slide } from '@mui/material'; 17 29 import ParkingZoneCard from './ParkingZoneCard'; 18 30 19 31 import { roles } from '../../../config/enums'; 32 import { UserContext } from '../../../context/UserContext'; 33 import useGetData from '../../../hooks/useGetData'; 34 import useCreateZone from '../../../hooks/useCreateZone'; 20 35 21 36 import { parkingZones } from './mockData'; 22 37 23 38 const sortDownUp = (a, b) => { 24 const aPercent = a.takenParkingSpaces / a.parkingSpaces; 25 const bPercent = b.takenParkingSpaces / b.parkingSpaces; 26 if (aPercent > bPercent) { 27 return 1; 28 } else { 29 if (aPercent < bPercent) { 30 return -1; 39 const aPercent = a.takenParkingSpaces / a.parkingSpacesNumber; 40 const bPercent = b.takenParkingSpaces / b.parkingSpacesNumber; 41 if (aPercent > bPercent) { 42 return 1; 43 } else { 44 if (aPercent < bPercent) { 45 return -1; 46 } 47 return a.zoneName - b.zoneName; 31 48 } 32 return a.zoneName - b.zoneName;33 }34 49 }; 35 50 36 51 const sortUpDown = (a, b) => { 37 const aPercent = a.takenParkingSpaces / a.parkingSpaces; 38 const bPercent = b.takenParkingSpaces / b.parkingSpaces; 39 if (aPercent > bPercent) { 52 const aPercent = a.takenParkingSpaces / a.parkingSpacesNumber; 53 const bPercent = b.takenParkingSpaces / b.parkingSpacesNumber; 54 if (aPercent > bPercent) { 55 return -1; 56 } else { 57 if (aPercent < bPercent) { 58 return 1; 59 } 60 return a.zoneName - b.zoneName; 61 } 62 }; 63 64 const sortByName = (a, b) => { 65 if (a.zoneName >= b.zoneName) { 66 return 1; 67 } 40 68 return -1; 41 } else {42 if (aPercent < bPercent) {43 return 1;44 }45 return a.zoneName - b.zoneName;46 }47 };48 49 const sortByName = (a, b) => {50 if (a.zoneName >= b.zoneName) {51 return 1;52 }53 return -1;54 69 }; 55 70 56 71 const ParkingZones = () => { 57 const [isArrowUpUp, setIsArrowUpUp] = useState(false); 58 const [isArrowDownUp, setIsArrowDownUp] = useState(false); 59 60 const user = { 61 role: 'ROLE_ADMIN', 62 }; 63 const sortFunc = isArrowUpUp 64 ? sortDownUp 65 : isArrowDownUp 66 ? sortUpDown 67 : sortByName; 68 69 return ( 70 <> 71 <FiltersWrapper> 72 <ParkingName>Паркинг - Дебар Маало</ParkingName> 73 <SortingArrowsWrapper> 74 <SortByTitle>Сортирај:</SortByTitle> 75 <ArrowUp 76 onClick={() => { 77 if (!isArrowUpUp) { 78 setIsArrowUpUp(true); 79 setIsArrowDownUp(false); 80 } 81 }} 82 selected={isArrowUpUp} 83 /> 84 <ArrowDown 85 onClick={() => { 86 if (!isArrowDownUp) { 87 setIsArrowDownUp(true); 88 setIsArrowUpUp(false); 89 } 90 }} 91 selected={isArrowDownUp} 92 /> 93 {isArrowUpUp || isArrowDownUp ? ( 94 <ClearSortIcon 95 onClick={() => { 96 setIsArrowUpUp(false); 97 setIsArrowDownUp(false); 98 }} 99 /> 100 ) : null} 101 </SortingArrowsWrapper> 102 </FiltersWrapper> 103 104 <DividerUnderFilters /> 105 106 <ParkingZonesWrapper container spacing={{ xs: 3, md: 5 }}> 107 {user.role === roles.admin ? ( 108 <AddParkingZoneCard item xs={11} sm={6} md={3}> 109 <AddItem> 110 <AddIcon /> 111 </AddItem> 112 </AddParkingZoneCard> 113 ) : null} 114 115 {parkingZones.sort(sortFunc).map((parkingZone) => ( 116 <ParkingZoneCard key={parkingZone.id} info={parkingZone} /> 117 ))} 118 </ParkingZonesWrapper> 119 </> 120 ); 72 const { user } = useContext(UserContext); 73 const { 74 data: zones, 75 setData: setZones, 76 isLoading: isLoadingZones, 77 } = useGetData({ 78 url: '/parkingZone', 79 }); 80 const { createZone, isLoading: isCreateZoneLoading } = useCreateZone(); 81 const [isArrowUpUp, setIsArrowUpUp] = useState(false); 82 const [isArrowDownUp, setIsArrowDownUp] = useState(false); 83 const [isModalOpen, setModalOpen] = useState(false); 84 const [modalInput, setModalInput] = useState(''); 85 const sortFunc = isArrowUpUp 86 ? sortDownUp 87 : isArrowDownUp 88 ? sortUpDown 89 : sortByName; 90 91 const addNewZoneToData = (newZone) => { 92 setZones([...zones, newZone]); 93 }; 94 const handleCreateZone = () => { 95 createZone({ 96 zoneName: modalInput, 97 setModalInput, 98 setModalOpen, 99 addNewZoneToData, 100 }); 101 }; 102 103 return ( 104 <> 105 <Modal 106 open={isModalOpen} 107 onClose={() => { 108 setModalInput(''); 109 setModalOpen(false); 110 }} 111 closeAfterTransition 112 BackdropComponent={Backdrop} 113 BackdropProps={{ 114 timeout: 500, 115 }} 116 style={{ 117 display: 'flex', 118 alignItems: 'center', 119 justifyContent: 'center', 120 }} 121 > 122 <Slide in={isModalOpen}> 123 <ModalContainer> 124 <IconButton 125 style={{ 126 marginLeft: 345, 127 }} 128 onClick={() => { 129 setModalInput(''); 130 setModalOpen(false); 131 }} 132 > 133 <CloseIcon /> 134 </IconButton> 135 {isCreateZoneLoading ? ( 136 <AbsoluteLoader 137 containerStyle={{ 138 width: '200px', 139 height: '200px', 140 margin: 'auto', 141 marginTop: '30px', 142 }} 143 /> 144 ) : ( 145 <> 146 <ModalTitle>НОВА ЗОНА</ModalTitle> 147 <ModalInputAndLabelWrapper> 148 <ModalInputLabel> 149 Назив на Зона 150 </ModalInputLabel> 151 <ModalInput 152 value={modalInput} 153 onChange={(event) => 154 setModalInput(event.target.value) 155 } 156 /> 157 </ModalInputAndLabelWrapper> 158 <ButtonWrapper> 159 <ModalButton onClick={handleCreateZone}> 160 Креирај Зона 161 </ModalButton> 162 </ButtonWrapper> 163 </> 164 )} 165 </ModalContainer> 166 </Slide> 167 </Modal> 168 <FiltersWrapper> 169 <ParkingName>Паркинг - Дебар Маало</ParkingName> 170 <SortingArrowsWrapper> 171 <SortByTitle>Сортирај:</SortByTitle> 172 <ArrowUp 173 onClick={() => { 174 if (!isArrowUpUp) { 175 setIsArrowUpUp(true); 176 setIsArrowDownUp(false); 177 } 178 }} 179 selected={isArrowUpUp} 180 /> 181 <ArrowDown 182 onClick={() => { 183 if (!isArrowDownUp) { 184 setIsArrowDownUp(true); 185 setIsArrowUpUp(false); 186 } 187 }} 188 selected={isArrowDownUp} 189 /> 190 {isArrowUpUp || isArrowDownUp ? ( 191 <ClearSortIcon 192 onClick={() => { 193 setIsArrowUpUp(false); 194 setIsArrowDownUp(false); 195 }} 196 /> 197 ) : null} 198 </SortingArrowsWrapper> 199 </FiltersWrapper> 200 201 <DividerUnderFilters /> 202 203 <ParkingZonesWrapper container spacing={{ xs: 3, md: 5 }}> 204 {isLoadingZones ? ( 205 <AbsoluteLoader 206 containerStyle={{ 207 width: '250px', 208 height: '250px', 209 margin: 'auto', 210 marginTop: '12vw', 211 }} 212 /> 213 ) : ( 214 <> 215 {user.role === roles.admin ? ( 216 <AddParkingZoneCard 217 item 218 xs={11} 219 sm={6} 220 md={3} 221 onClick={() => setModalOpen(true)} 222 > 223 <AddItem> 224 <AddIcon /> 225 </AddItem> 226 </AddParkingZoneCard> 227 ) : null} 228 229 {zones.sort(sortFunc).map((parkingZone) => ( 230 <ParkingZoneCard 231 key={parkingZone.id} 232 info={parkingZone} 233 /> 234 ))} 235 </> 236 )} 237 </ParkingZonesWrapper> 238 </> 239 ); 121 240 }; 122 241 -
sources/client/src/components/admin/ParkingZones/mockData.js
re8b1076 rbc20307 1 1 export const parkingZones = [ 2 { 3 id: 1, 4 zoneName: 'Zona 1', 5 responsiblePersons: ['Viktor Tasevski', 'Dracevcanec'], 6 parkingSpaces: 69, 7 takenParkingSpaces: 34, 8 areaColor: '#FFD700', 9 location: { 10 center: { 11 lat: 41.937907, 12 lng: 21.51213, 13 }, 14 coords: [ 15 { lat: 41.937749, lng: 21.511095 }, 16 { lat: 41.936971, lng: 21.511534 }, 17 { lat: 41.93834, lng: 21.51407 }, 18 { lat: 41.939031, lng: 21.511914 }, 19 ], 2 { 3 id: 1, 4 zoneName: 'Zona 1', 5 responsiblePersons: ['Viktor Tasevski', 'David Trajkovski'], // THIS SHOULD BE IN THE ParkingZones Call Only for the Card. For the ZoneInfo We need More Data for The Employees. This should be array of employee objects. 6 parkingSpacesNumber: 79, 7 takenParkingSpaces: 34, 8 hourlyRate: 30, 9 workingHours: { 10 from: 5, 11 to: 24, 12 }, 13 zoneColor: '#FFD700', 14 location: { 15 center: { 16 lat: 42.000389, 17 lng: 21.423084, 18 }, 19 coords: [ 20 { lat: 42.00060867305611, lng: 21.424473984198578 }, 21 { lat: 42.001509615969574, lng: 21.421609384939654 }, 22 { lat: 41.99886654813535, lng: 21.42293439620515 }, 23 ], 24 }, 25 parkingSpaces: [ 26 { 27 lat: '42.00043725326595', 28 lng: '21.42263398879119', 29 parkingSpaceNumber: 'A21', 30 isTaken: true, 31 }, 32 { 33 lat: '42.00028576562848', 34 lng: '21.423680050318325', 35 parkingSpaceNumber: 'A1', 36 isTaken: false, 37 }, 38 { 39 lat: '41.99959609366812', 40 lng: '21.423374278487316', 41 parkingSpaceNumber: 'B55', 42 isTaken: true, 43 }, 44 { 45 lat: '41.99962798617793', 46 lng: '21.42275200598912', 47 parkingSpaceNumber: 'C20', 48 isTaken: false, 49 }, 50 ], 20 51 }, 21 parkingSpacesLocation: [ 22 { 23 lat: '41.938271', 24 lng: '21.512380', 25 parkingSpaceNumber: 'A21', 26 free: true, 27 }, 28 { 29 lat: '41.938284', 30 lng: '21.512387', 31 parkingSpaceNumber: 'A1', 32 free: false, 33 }, 34 { 35 lat: '41.938292', 36 lng: '21.512365', 37 parkingSpaceNumber: 'B55', 38 free: true, 39 }, 40 { 41 lat: '41.938279', 42 lng: '21.512359', 43 parkingSpaceNumber: 'C20', 44 free: false, 45 }, 46 ], 47 }, 48 { 49 id: 2, 50 zoneName: 'Zona 2', 51 responsiblePersons: ['Andrej Tavcioski', 'Vlaevec', 'Skopjaniste'], 52 parkingSpaces: 100, 53 takenParkingSpaces: 99, 54 }, 55 { 56 id: 3, 57 zoneName: 'Zona 3', 58 responsiblePersons: ['David Trajkovski', 'Kumanovecot'], 59 parkingSpaces: 36, 60 takenParkingSpaces: 5, 61 }, 62 { 63 id: 4, 64 zoneName: 'Zona 4', 65 responsiblePersons: [ 66 'Nekoj od POC', 67 'Nekoj drug od POC', 68 'Nekoj tret od POC', 69 'Nekoj cetvrt od POC', 70 ], 71 parkingSpaces: 150, 72 takenParkingSpaces: 130, 73 }, 74 { 75 id: 5, 76 zoneName: 'Zona 5', 77 responsiblePersons: ['Nekoj od Silbo'], 78 parkingSpaces: 360, 79 takenParkingSpaces: 250, 80 }, 52 { 53 id: 2, 54 zoneName: 'Zona 2', 55 responsiblePersons: ['Andrej Tavcioski', 'David Trajkovski'], // THIS SHOULD BE IN THE ParkingZones Call Only for the Card. For the ZoneInfo We need More Data for The Employees. This should be array of employee objects. 56 parkingSpacesNumber: 29, 57 takenParkingSpaces: 14, 58 hourlyRate: 30, 59 workingHours: { 60 from: 5, 61 to: 24, 62 }, 63 zoneColor: '#FF0000', 64 location: { 65 center: { 66 lat: 42.001097017606455, 67 lng: 21.421317024169447, 68 }, 69 coords: [ 70 { lat: 42.00048708466677, lng: 21.42135993951415 }, 71 { lat: 42.0006784368066, lng: 21.421837372723967 }, 72 { lat: 42.00149765665009, lng: 21.42148332113017 }, 73 { lat: 42.00134218508192, lng: 21.42078862898779 }, 74 ], 75 }, 76 parkingSpaces: [ 77 { 78 lat: '42.00043725326595', 79 lng: '21.42263398879119', 80 parkingSpaceNumber: 'A21', 81 isTaken: true, 82 }, 83 { 84 lat: '42.00028576562848', 85 lng: '21.423680050318325', 86 parkingSpaceNumber: 'A1', 87 isTaken: false, 88 }, 89 { 90 lat: '41.99959609366812', 91 lng: '21.423374278487316', 92 parkingSpaceNumber: 'B55', 93 isTaken: true, 94 }, 95 { 96 lat: '41.99962798617793', 97 lng: '21.42275200598912', 98 parkingSpaceNumber: 'C20', 99 isTaken: false, 100 }, 101 ], 102 }, 103 { 104 id: 3, 105 zoneName: 'Zona 3', 106 responsiblePersons: ['David Trajkovski', 'Kumanovecot'], 107 parkingSpacesNumber: 36, 108 takenParkingSpaces: 5, 109 hourlyRate: 10, 110 workingHours: '12:00 - 17:00', 111 }, 112 { 113 id: 4, 114 zoneName: 'Zona 4', 115 responsiblePersons: ['Nekoj od POC'], 116 parkingSpacesNumber: 150, 117 takenParkingSpaces: 130, 118 hourlyRate: 100, 119 workingHours: { 120 from: '5', 121 to: '24', 122 }, 123 }, 124 { 125 id: 5, 126 zoneName: 'Zona 5', 127 responsiblePersons: [], 128 parkingSpacesNumber: 360, 129 takenParkingSpaces: 250, 130 hourlyRate: 30, 131 workingHours: { 132 from: '5', 133 to: '24', 134 }, 135 }, 81 136 ]; -
sources/client/src/components/admin/ParkingZones/styles.js
re8b1076 rbc20307 7 7 import ClearIcon from '@mui/icons-material/Clear'; 8 8 import { ParkingZoneWrapper, Container } from './ParkingZoneCard/styles'; 9 import Box from '@mui/material/Box'; 10 import Button from '@mui/material/Button'; 11 import TextField from '@mui/material/TextField'; 12 import CIcon from '@mui/icons-material/Close'; 13 import { mobile_max_width } from '../../../config/utilities'; 9 14 10 15 export const FiltersWrapper = styled.div` 11 margin-bottom: 10px; 12 text-align: center; 13 display: flex; 14 flex-direction: row; 15 justify-content: space-between; 16 align-items: center; 17 padding: 10px 175px; 16 margin-bottom: 10px; 17 text-align: center; 18 display: flex; 19 flex-direction: row; 20 justify-content: space-between; 21 align-items: center; 22 padding: 10px 10%; 23 24 @media (max-width: ${mobile_max_width}px) { 25 flex-direction: column; 26 } 18 27 `; 19 28 20 29 export const SortingArrowsWrapper = styled.div` 21 display: flex; 22 flex-direction: row; 23 align-items: center; 24 position: relative; 30 display: flex; 31 flex-direction: row; 32 align-items: center; 33 position: relative; 34 35 @media (max-width: ${mobile_max_width}px) { 36 margin-top: 30px; 37 } 25 38 `; 26 39 27 40 export const ArrowDown = styled(ArrowDownwardIcon).attrs({})` 28 font-size: 2rem; 29 color: ${(props) => (props.selected ? props.theme.palette.primary.main : '')}; 30 :hover { 31 cursor: pointer; 32 color: ${(props) => props.theme.palette.primary.main}; 33 } 41 font-size: 2rem; 42 color: ${(props) => 43 props.selected ? props.theme.palette.primary.main : ''}; 44 :hover { 45 cursor: pointer; 46 color: ${(props) => props.theme.palette.primary.main}; 47 } 34 48 `; 35 49 36 50 export const ArrowUp = styled(ArrowUpwardIcon).attrs({})` 37 margin: 0 10px 0 15px; 38 font-size: 2rem; 39 color: ${(props) => (props.selected ? props.theme.palette.primary.main : '')}; 40 :hover { 41 cursor: pointer; 42 color: ${(props) => props.theme.palette.primary.main}; 43 } 51 margin: 0 10px 0 15px; 52 font-size: 2rem; 53 color: ${(props) => 54 props.selected ? props.theme.palette.primary.main : ''}; 55 :hover { 56 cursor: pointer; 57 color: ${(props) => props.theme.palette.primary.main}; 58 } 44 59 `; 45 60 46 61 export const ClearSortIcon = styled(ClearIcon).attrs({})` 47 color: ${(props) => props.theme.palette.error.main};48 position: absolute;49 right: 0;50 font-size: 2rem;51 right: -50px;52 :hover {53 cursor: pointer;54 }62 color: ${(props) => props.theme.palette.error.main}; 63 position: absolute; 64 right: 0; 65 font-size: 2rem; 66 right: -50px; 67 :hover { 68 cursor: pointer; 69 } 55 70 `; 56 71 57 72 export const DividerUnderFilters = styled(Divider).attrs({ 58 variant: 'middle',59 sx: {60 margin: '0 160px',61 borderWidth: '2px',62 borderBottomWidth: 'thin',63 },73 variant: 'middle', 74 sx: { 75 margin: '0 9%', 76 borderWidth: '2px', 77 borderBottomWidth: 'thin', 78 }, 64 79 })``; 65 80 66 81 export const SortByTitle = styled(Typography).attrs((props) => ({ 67 fontSize: '1rem',68 fontWeight: '600',69 margin: 0,70 color: `${props.theme.palette.primary.main}`,82 fontSize: '1rem', 83 fontWeight: '600', 84 margin: 0, 85 color: `${props.theme.palette.primary.main}`, 71 86 }))``; 72 87 73 88 export const ParkingName = styled(Typography).attrs((props) => ({ 74 fontSize: '2rem',75 fontWeight: '600',76 margin: 0,77 color: `${props.theme.palette.primary.main}`,89 fontSize: '2rem', 90 fontWeight: '600', 91 margin: 0, 92 color: `${props.theme.palette.primary.main}`, 78 93 }))``; 79 94 80 95 export const ParkingZonesWrapper = styled(Grid)` 81 margin: 0; 82 height: 100%; 83 width: 100%; 84 padding: 0 40px 0 0; 96 margin: 0; 97 height: 100%; 98 width: 100%; 99 padding: 0 40px 30px 0; 100 101 @media (max-width: ${mobile_max_width}px) { 102 padding-right: 0; 103 justify-content: center; 104 } 85 105 `; 86 106 … … 88 108 89 109 export const AddItem = styled(Container)` 90 display: flex;91 align-items: center;92 justify-content: middle;110 display: flex; 111 align-items: center; 112 justify-content: middle; 93 113 `; 94 114 95 115 export const AddIcon = styled(Add).attrs({ 96 sx: {97 fontWeight: 500,98 },116 sx: { 117 fontWeight: 500, 118 }, 99 119 })` 100 width: 100%;101 height: 100%;102 color: ${(props) => props.theme.palette.primary.main};120 width: 100%; 121 height: 100%; 122 color: ${(props) => props.theme.palette.primary.main}; 103 123 `; 124 125 export const ModalContainer = styled(Box).attrs({ 126 width: 400, 127 height: 366, 128 bgcolor: 'background.paper', 129 boxShadow: 24, 130 zIndex: 1000, 131 marginBottom: '10%', 132 })` 133 padding-bottom: 16px; 134 `; 135 136 export const ModalTitle = styled(Typography).attrs({ 137 variant: 'h4', 138 fontWeight: 600, 139 textAlign: 'center', 140 })``; 141 142 export const ModalInputAndLabelWrapper = styled.div` 143 text-align: center; 144 padding: 0 20px; 145 margin-top: 40px; 146 margin-bottom: 70px; 147 `; 148 149 export const ModalInputLabel = styled(Typography).attrs({ 150 variant: 'h6', 151 })``; 152 153 export const ModalInput = styled(TextField).attrs({ 154 fullWidth: true, 155 sx: { 156 marginTop: '10px', 157 }, 158 })` 159 fieldset { 160 border: 0; 161 } 162 input { 163 text-align: center; 164 font-size: 1.1rem; 165 font-weight: 500; 166 padding: 15px 10px; 167 border: 2px solid ${(props) => props.theme.palette.primary.main}; 168 169 :focus { 170 border: 2px dashed ${(props) => props.theme.palette.primary.main}; 171 } 172 } 173 `; 174 175 export const ButtonWrapper = styled.div` 176 width: 100%; 177 text-align: center; 178 `; 179 180 export const ModalButton = styled(Button).attrs((props) => ({ 181 variant: 'contained', 182 size: 'large', 183 sx: { 184 backgroundColor: `${props.theme.palette.primary.main}`, 185 }, 186 }))` 187 :hover { 188 background-color: ${(props) => props.theme.palette.primary.dark}; 189 } 190 `; 191 192 export const CloseIcon = styled(CIcon).attrs({ 193 sx: { 194 color: 'red', 195 fontSize: '2.5rem', 196 }, 197 })``; -
sources/client/src/config/defaultUser.js
re8b1076 rbc20307 4 4 firstName: '', 5 5 lastName: '', 6 zone: 'none',7 phoneNumber: '',8 status: ' notWorking',9 accountActive: false6 parkingZones: ['NONE'], 7 mobile: '', 8 status: 'NOT_WORKING', 9 locked: true, 10 10 }; -
sources/client/src/config/enums.js
re8b1076 rbc20307 1 1 export const employeeStatus = { 2 notWorking: 'Не работи', 3 working: 'Работи', 4 vacation: 'На одмор', 2 NOT_WORKING: 'Не работи', 3 WORKING: 'Работи', 4 VACATION: 'На одмор', 5 PAID_LEAVE: 'На боледување', 5 6 }; 6 7 7 8 export const accountStatus = { 8 enabled: 'Активен',9 disabled: 'Неактивен',9 enabled: 'Активен', 10 disabled: 'Неактивен', 10 11 }; 11 12 12 13 export const sessionStatus = { 13 active: 'active',14 idle: 'idle',15 over: 'over',14 idle: 'STARTED_UNVERIFIED', 15 active: 'STARTED_VERIFIED', 16 over: 'ENDED_UNPAID', 16 17 }; 17 18 18 19 export const roles = { 19 user: 'ROLE_USER', 20 employee: 'ROLE_EMPLOYEE', 21 admin: 'ROLE_ADMIN', 20 user: 'ROLE_REG_USER', 21 guest: 'ROLE_USER', 22 employee: 'ROLE_WORKER', 23 admin: 'ROLE_ADMIN', 22 24 }; -
sources/client/src/hooks/useForm.js
re8b1076 rbc20307 10 10 }); 11 11 }; 12 12 const setNewData = (newData) => { 13 setData({...newData}); 14 } 13 15 return { 14 16 data, 15 17 onFormChange, 18 setNewData 16 19 }; 17 20 }; -
sources/client/src/index.css
re8b1076 rbc20307 2 2 *::before, 3 3 *::after { 4 box-sizing: border-box;4 box-sizing: border-box; 5 5 } 6 6 html, 7 7 body, 8 8 #root { 9 height: 100%;9 height: 100%; 10 10 } 11 11 body { 12 margin: 0; 13 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 14 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 15 sans-serif; 16 -webkit-font-smoothing: antialiased; 17 -moz-osx-font-smoothing: grayscale; 18 height: 100%; 12 margin: 0; 13 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 14 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 15 'Helvetica Neue', sans-serif; 16 -webkit-font-smoothing: antialiased; 17 -moz-osx-font-smoothing: grayscale; 18 height: 100%; 19 } 20 21 td, 22 input { 23 overflow: hidden; 19 24 } 20 25 21 26 code { 22 font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',23 monospace;27 font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 28 monospace; 24 29 } 25 30 26 31 #root { 27 height: 100%;32 height: 100%; 28 33 } -
sources/client/src/index.js
re8b1076 rbc20307 4 4 import App from './App'; 5 5 import reportWebVitals from './reportWebVitals'; 6 6 import axios from 'axios'; 7 7 import { BrowserRouter } from 'react-router-dom'; 8 8 import { ThemeProvider } from 'styled-components'; 9 9 10 10 import { StyledEngineProvider } from '@mui/material/styles'; 11 12 11 import theme from './theme/index'; 13 12 13 axios.defaults.headers.post['Content-Type'] = 'application/json'; 14 axios.defaults.headers.post['Content-Type'] = 'application/json'; 15 axios.defaults.headers.post['Access-Control-Allow-Origin'] = '*'; 16 axios.defaults.headers.patch['Access-Control-Allow-Origin'] = '*'; 17 axios.defaults.headers.delete['Access-Control-Allow-Origin'] = '*'; 18 14 19 ReactDOM.render( 15 <React.StrictMode> 16 <BrowserRouter> 17 <StyledEngineProvider injectFirst> 18 19 <ThemeProvider theme={theme}> 20 21 <App /> 22 23 </ThemeProvider> 24 25 </StyledEngineProvider> 26 </BrowserRouter> 27 </React.StrictMode>, 28 document.getElementById('root') 20 <React.StrictMode> 21 <BrowserRouter> 22 <StyledEngineProvider injectFirst> 23 <ThemeProvider theme={theme}> 24 <App /> 25 </ThemeProvider> 26 </StyledEngineProvider> 27 </BrowserRouter> 28 </React.StrictMode>, 29 document.getElementById('root') 29 30 ); 30 31 -
sources/client/src/screens/AdminHomeScreen/index.js
re8b1076 rbc20307 1 import { useContext, useState } from 'react'; 1 2 import { Route, Switch, Redirect } from 'react-router-dom'; 2 3 import { useHistory } from 'react-router-dom'; … … 19 20 DividerUnderHeader, 20 21 UserIcon, 22 MenuBurgerIcon, 21 23 } from './styles'; 22 24 23 25 import IconButton from '@mui/material/IconButton'; 24 26 25 import logo from '../../resources/logo_2_transparent_bg. jpg';27 import logo from '../../resources/logo_2_transparent_bg.png'; 26 28 import { roles } from '../../config/enums'; 27 29 import DestinationComponent from '../../utils/DestinationComponent'; 28 30 import onClickRouting from '../../utils/onClickRouting'; 31 import AbsoluteLoader from '../../components/Loaders/AbsoluteLoader'; 29 32 30 33 import ParkingZones from '../../components/admin/ParkingZones'; … … 33 36 import EmployeeCreate from '../../components/admin/EmployeeCreate'; 34 37 import ParkingZone from '../../components/admin/ParkingZone'; 38 39 import { UserContext } from '../../context/UserContext'; 40 import { AccessoriesContext } from '../../context/AccessoriesContext'; 41 import useLogoutUser from '../../hooks/useLogoutUser'; 35 42 36 43 const ToParkingZones = new DestinationComponent('/', ParkingZones, true); … … 67 74 68 75 const AdminHomeScreen = (props) => { 76 const { user, isLoadingUser } = useContext(UserContext); 77 const { isMobile, setIsOpenDrawer } = useContext(AccessoriesContext); 69 78 let history = useHistory(); 70 const user = { 71 role: 'ROLE_ADMIN', 72 }; 73 79 const { logoutUser } = useLogoutUser(); 74 80 let routes = user.role === roles.admin ? adminRoutes : employeeRoutes; 75 81 76 82 return ( 77 83 <Container> 78 <SideMenu> 79 <IconButton onClick={() => onClickRouting('/', history)}> 80 <DashboardIcon /> 81 </IconButton> 82 {user.role === roles.admin ? ( 83 <IconButton onClick={() => onClickRouting('/employees', history)}> 84 <SupervisorAccountIcon /> 85 </IconButton> 86 ) : null} 87 </SideMenu> 84 {isLoadingUser ? ( 85 <AbsoluteLoader 86 containerStyle={{ 87 width: isMobile ? '150px' : '300px', 88 height: isMobile ? '150px' : '300px', 89 margin: 'auto', 90 }} 91 /> 92 ) : ( 93 <> 94 {!isMobile ? ( 95 <SideMenu> 96 <IconButton onClick={() => onClickRouting('/', history)}> 97 <DashboardIcon /> 98 </IconButton> 99 {user.role === roles.admin ? ( 100 <IconButton 101 onClick={() => onClickRouting('/employees', history)} 102 > 103 <SupervisorAccountIcon /> 104 </IconButton> 105 ) : null} 106 </SideMenu> 107 ) : null} 88 108 89 <HeaderAndMainSectionWrapper> 90 <Header> 91 <TitleAndLogoWrapper> 92 <LogoWrapper> 93 <Logo src={logo} /> 94 </LogoWrapper> 95 <HeaderTitle>Park Up</HeaderTitle> 96 </TitleAndLogoWrapper> 109 <HeaderAndMainSectionWrapper> 110 <Header> 111 {isMobile ? ( 112 <IconButton 113 onClick={() => setIsOpenDrawer(true)} 114 style={{ padding: 0 }} 115 > 116 <MenuBurgerIcon /> 117 </IconButton> 118 ) : null} 119 <TitleAndLogoWrapper> 120 <LogoWrapper> 121 <Logo src={logo} /> 122 </LogoWrapper> 123 <HeaderTitle>Park Up</HeaderTitle> 124 </TitleAndLogoWrapper> 97 125 98 <UserNameAndLogoutWrapper> 99 <UserIcon /> 100 <UserName>Viktor Tasevski</UserName> 101 <IconButton> 102 <LogoutIcon /> 103 </IconButton> 104 </UserNameAndLogoutWrapper> 105 </Header> 106 <DividerUnderHeader /> 107 <MainSection> 108 <Switch> 109 {routes?.map((route, index) => ( 110 <Route 111 key={index} 112 path={route.path} 113 component={route.component} 114 exact={route.exact} 115 /> 116 ))} 117 <Redirect to='/' /> 118 </Switch> 119 </MainSection> 120 </HeaderAndMainSectionWrapper> 126 {isMobile ? null : ( 127 <UserNameAndLogoutWrapper> 128 <UserIcon /> 129 <UserName> 130 {user.firstName} {user.lastName} 131 </UserName> 132 <IconButton onClick={logoutUser}> 133 <LogoutIcon /> 134 </IconButton> 135 </UserNameAndLogoutWrapper> 136 )} 137 </Header> 138 <DividerUnderHeader /> 139 <MainSection> 140 <Switch> 141 {routes?.map((route, index) => ( 142 <Route 143 key={index} 144 path={route.path} 145 component={route.component} 146 exact={route.exact} 147 /> 148 ))} 149 <Redirect to='/' /> 150 </Switch> 151 </MainSection> 152 </HeaderAndMainSectionWrapper> 153 </> 154 )} 121 155 </Container> 122 156 ); -
sources/client/src/screens/AdminHomeScreen/styles.js
re8b1076 rbc20307 7 7 import SupervisorAccount from '@mui/icons-material/SupervisorAccount'; 8 8 import AccountCircleIcon from '@mui/icons-material/AccountCircle'; 9 import Box from '@mui/material/Box'; 10 import MenuIcon from '@mui/icons-material/Menu'; 11 import CloseI from '@mui/icons-material/Close'; 12 import { mobile_max_width } from '../../config/utilities'; 13 import backgroundImage from '../../resources/login_background.jpg'; 9 14 10 15 export const Container = styled.div` 11 16 display: flex; 12 17 flex-direction: row; 13 height: 100%;18 min-height: 100vh; 14 19 flex: 1; 15 20 `; … … 20 25 flex-direction: row; 21 26 justify-content: space-between; 22 padding: 10px 30px; 27 padding: 10px 7%; 28 position: fixed; 29 z-index: 100; 30 background-color: ${(props) => props.theme.palette.background.whiteSmoke}; 31 @media (max-width: ${mobile_max_width}px) { 32 padding-left: 15px; 33 } 23 34 `; 24 35 … … 26 37 variant: 'middle', 27 38 sx: { 28 margin: '0 25px', 39 margin: '0 5%', 40 marginTop: '75px', 29 41 borderWidth: '2px', 30 42 borderBottomWidth: 'thin', … … 75 87 sx: { 76 88 fontSize: 30, 77 color: `${props.theme.palette.error. light}`,89 color: `${props.theme.palette.error.main}`, 78 90 }, 79 91 }))``; … … 82 94 display: flex; 83 95 flex-direction: column; 84 height: 100%;85 96 width: 100%; 97 padding-left: 70px; 86 98 background-color: ${(props) => props.theme.palette.background.whiteSmoke}; 99 100 @media (max-width: ${mobile_max_width}px) { 101 padding-left: 0; 102 } 87 103 `; 88 104 89 105 export const SideMenu = styled.div` 90 106 flex-direction: column; 91 height: 100%;92 107 width: 70px; 93 108 padding: 20px 10px 0 10px; 94 109 border-right: 1px solid grey; 110 position: fixed; 111 height: 100%; 95 112 background-color: ${(props) => props.theme.palette.primary.main}; 96 113 `; … … 124 141 width: 100%; 125 142 height: 100%; 126 padding : 20px0px;143 padding-top: 20px; 127 144 display: flex; 128 145 flex-direction: column; 129 146 `; 147 148 export const MenuBurgerIcon = styled(MenuIcon).attrs((props) => ({ 149 sx: { 150 fontSize: 50, 151 color: `${props.theme.palette.primary.main}`, 152 }, 153 }))``; -
sources/client/src/theme/index.js
re8b1076 rbc20307 1 import { createTheme } from "@mui/material";1 import { createTheme } from '@mui/material'; 2 2 3 3 const theme = createTheme({ 4 palette: { 5 primary: { 6 light: '#4cc5a3', 7 main: '#00AD7C', 8 dark: '#007956' 9 }, 10 secondary: { 11 light: '#DFDFDF', 12 main: '#B0B0B0', 13 dark: '#8C8C8C' 14 }, 15 background: { 16 white: '#FFFFFF', 17 whiteSmoke: '#f4f4f4', 18 shadow: '#aaaaaa' 19 } 4 palette: { 5 primary: { 6 light: '#4cc5a3', 7 main: '#00AD7C', 8 dark: '#007956', 20 9 }, 21 // overrides: { TODO 22 // MuiTypography: { 23 // h1: { 24 // fontSize: "5rem" 25 // } 26 // } 27 // } 10 secondary: { 11 light: '#DFDFDF', 12 main: '#B0B0B0', 13 dark: '#8C8C8C', 14 }, 15 third: { 16 main: '#f65026', 17 }, 18 background: { 19 white: '#FFFFFF', 20 whiteSmoke: '#f4f4f4', 21 shadow: '#aaaaaa', 22 }, 23 }, 24 // overrides: { TODO 25 // MuiTypography: { 26 // h1: { 27 // fontSize: "5rem" 28 // } 29 // } 30 // } 28 31 }); 29 32
Note:
See TracChangeset
for help on using the changeset viewer.