Changeset bc20307 for sources/client
- Timestamp:
- 02/14/22 01:41:41 (3 years ago)
- Branches:
- master
- Children:
- 747e0ab
- Parents:
- e8b1076
- Location:
- sources/client
- Files:
-
- 88 added
- 5 deleted
- 36 edited
- 2 moved
Legend:
- Unmodified
- Added
- Removed
-
sources/client/package.json
re8b1076 rbc20307 1 1 { 2 "name": "my", 3 "version": "0.1.0", 4 "private": true, 5 "dependencies": { 6 "@emotion/react": "^11.4.1", 7 "@emotion/styled": "^11.3.0", 8 "@frogress/line": "^1.0.1", 9 "@mui/icons-material": "^5.0.0", 10 "@mui/material": "^5.0.0", 11 "@mui/styled-engine-sc": "^5.0.0", 12 "@mui/styles": "^5.0.0", 13 "@testing-library/jest-dom": "^5.11.4", 14 "@testing-library/react": "^11.1.0", 15 "@testing-library/user-event": "^12.1.10", 16 "google-map-react": "^2.1.10", 17 "moment": "^2.29.1", 18 "react": "^17.0.2", 19 "react-dom": "^17.0.2", 20 "react-router-dom": "^5.3.0", 21 "react-scripts": "4.0.3", 22 "styled-components": "^5.3.1", 23 "web-vitals": "^1.0.1" 24 }, 25 "scripts": { 26 "start": "react-scripts start", 27 "build": "react-scripts build", 28 "test": "react-scripts test", 29 "eject": "react-scripts eject" 30 }, 31 "eslintConfig": { 32 "extends": [ 33 "react-app", 34 "react-app/jest" 35 ] 36 }, 37 "browserslist": { 38 "production": [ 39 ">0.2%", 40 "not dead", 41 "not op_mini all" 42 ], 43 "development": [ 44 "last 1 chrome version", 45 "last 1 firefox version", 46 "last 1 safari version" 47 ] 48 } 2 "name": "my", 3 "version": "0.1.0", 4 "private": true, 5 "dependencies": { 6 "@emotion/react": "^11.4.1", 7 "@emotion/styled": "^11.3.0", 8 "@frogress/line": "^1.0.1", 9 "@material-ui/core": "^4.12.3", 10 "@mui/icons-material": "^5.0.0", 11 "@mui/material": "^5.0.0", 12 "@mui/styled-engine-sc": "^5.0.0", 13 "@mui/styles": "^5.0.0", 14 "@testing-library/jest-dom": "^5.11.4", 15 "@testing-library/react": "^11.1.0", 16 "@testing-library/user-event": "^12.1.10", 17 "axios": "^0.24.0", 18 "google-map-react": "^2.1.10", 19 "jwt-decode": "^3.1.2", 20 "material-ui-color": "^1.2.0", 21 "moment": "^2.29.1", 22 "react": "^17.0.2", 23 "react-dnd": "^14.0.4", 24 "react-dnd-html5-backend": "^14.0.2", 25 "react-dnd-preview": "^6.0.2", 26 "react-dnd-touch-backend": "^14.1.1", 27 "react-dom": "^17.0.2", 28 "react-router-dom": "^5.3.0", 29 "react-scripts": "4.0.3", 30 "styled-components": "^5.3.1", 31 "web-vitals": "^1.0.1" 32 }, 33 "scripts": { 34 "start": "react-scripts start", 35 "build": "react-scripts build", 36 "test": "react-scripts test", 37 "eject": "react-scripts eject" 38 }, 39 "eslintConfig": { 40 "extends": [ 41 "react-app", 42 "react-app/jest" 43 ] 44 }, 45 "browserslist": { 46 "production": [ 47 ">0.2%", 48 "not dead", 49 "not op_mini all" 50 ], 51 "development": [ 52 "last 1 chrome version", 53 "last 1 firefox version", 54 "last 1 safari version" 55 ] 56 }, 57 "proxy": "http://localhost:8080" 49 58 } -
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 -
sources/client/yarn.lock
re8b1076 rbc20307 1164 1164 regenerator-runtime "^0.13.4" 1165 1165 1166 "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7. 8.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":1166 "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": 1167 1167 version "7.15.4" 1168 1168 resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" … … 1649 1649 integrity sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI= 1650 1650 1651 "@material-ui/core@^4.12.3": 1652 version "4.12.3" 1653 resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.12.3.tgz#80d665caf0f1f034e52355c5450c0e38b099d3ca" 1654 integrity sha512-sdpgI/PL56QVsEJldwEe4FFaFTLUqN+rd7sSZiRCdx2E/C7z5yK0y/khAWVBH24tXwto7I1hCzNWfJGZIYJKnw== 1655 dependencies: 1656 "@babel/runtime" "^7.4.4" 1657 "@material-ui/styles" "^4.11.4" 1658 "@material-ui/system" "^4.12.1" 1659 "@material-ui/types" "5.1.0" 1660 "@material-ui/utils" "^4.11.2" 1661 "@types/react-transition-group" "^4.2.0" 1662 clsx "^1.0.4" 1663 hoist-non-react-statics "^3.3.2" 1664 popper.js "1.16.1-lts" 1665 prop-types "^15.7.2" 1666 react-is "^16.8.0 || ^17.0.0" 1667 react-transition-group "^4.4.0" 1668 1669 "@material-ui/styles@^4.11.4": 1670 version "4.11.4" 1671 resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.4.tgz#eb9dfccfcc2d208243d986457dff025497afa00d" 1672 integrity sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew== 1673 dependencies: 1674 "@babel/runtime" "^7.4.4" 1675 "@emotion/hash" "^0.8.0" 1676 "@material-ui/types" "5.1.0" 1677 "@material-ui/utils" "^4.11.2" 1678 clsx "^1.0.4" 1679 csstype "^2.5.2" 1680 hoist-non-react-statics "^3.3.2" 1681 jss "^10.5.1" 1682 jss-plugin-camel-case "^10.5.1" 1683 jss-plugin-default-unit "^10.5.1" 1684 jss-plugin-global "^10.5.1" 1685 jss-plugin-nested "^10.5.1" 1686 jss-plugin-props-sort "^10.5.1" 1687 jss-plugin-rule-value-function "^10.5.1" 1688 jss-plugin-vendor-prefixer "^10.5.1" 1689 prop-types "^15.7.2" 1690 1691 "@material-ui/system@^4.12.1": 1692 version "4.12.1" 1693 resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.12.1.tgz#2dd96c243f8c0a331b2bb6d46efd7771a399707c" 1694 integrity sha512-lUdzs4q9kEXZGhbN7BptyiS1rLNHe6kG9o8Y307HCvF4sQxbCgpL2qi+gUk+yI8a2DNk48gISEQxoxpgph0xIw== 1695 dependencies: 1696 "@babel/runtime" "^7.4.4" 1697 "@material-ui/utils" "^4.11.2" 1698 csstype "^2.5.2" 1699 prop-types "^15.7.2" 1700 1701 "@material-ui/types@5.1.0": 1702 version "5.1.0" 1703 resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" 1704 integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== 1705 1706 "@material-ui/utils@^4.11.2": 1707 version "4.11.2" 1708 resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a" 1709 integrity sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA== 1710 dependencies: 1711 "@babel/runtime" "^7.4.4" 1712 prop-types "^15.7.2" 1713 react-is "^16.8.0 || ^17.0.0" 1714 1651 1715 "@mui/core@5.0.0-alpha.47": 1652 1716 version "5.0.0-alpha.47" … … 1811 1875 integrity sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw== 1812 1876 1877 "@react-dnd/asap@^4.0.0": 1878 version "4.0.0" 1879 resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.0.tgz#b300eeed83e9801f51bd66b0337c9a6f04548651" 1880 integrity sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ== 1881 1882 "@react-dnd/invariant@^2.0.0": 1883 version "2.0.0" 1884 resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-2.0.0.tgz#09d2e81cd39e0e767d7da62df9325860f24e517e" 1885 integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw== 1886 1887 "@react-dnd/shallowequal@^2.0.0": 1888 version "2.0.0" 1889 resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a" 1890 integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg== 1891 1813 1892 "@rollup/plugin-node-resolve@^7.1.1": 1814 1893 version "7.1.3" … … 2168 2247 "@types/react" "*" 2169 2248 2170 "@types/react-transition-group@^4. 4.2":2249 "@types/react-transition-group@^4.2.0", "@types/react-transition-group@^4.4.2": 2171 2250 version "4.4.3" 2172 2251 resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.3.tgz#b0994da0a7023d67dbb4a8910a62112bc00d5688" … … 2912 2991 resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.2.tgz#7cf783331320098bfbef620df3b3c770147bc224" 2913 2992 integrity sha512-V+Nq70NxKhYt89ArVcaNL9FDryB3vQOd+BFXZIfO3RP6rwtj+2yqqqdHEkacutglPaZLkJeuXKCjCJDMGPtPqg== 2993 2994 axios@^0.24.0: 2995 version "0.24.0" 2996 resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6" 2997 integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA== 2998 dependencies: 2999 follow-redirects "^1.14.4" 2914 3000 2915 3001 axobject-query@^2.2.0: … … 3684 3770 wrap-ansi "^6.2.0" 3685 3771 3686 clsx@^1. 1.1:3772 clsx@^1.0.4, clsx@^1.1.1: 3687 3773 version "1.1.1" 3688 3774 resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" … … 4293 4379 cssom "~0.3.6" 4294 4380 4381 csstype@^2.5.2: 4382 version "2.6.18" 4383 resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.18.tgz#980a8b53085f34af313410af064f2bd241784218" 4384 integrity sha512-RSU6Hyeg14am3Ah4VZEmeX8H7kLwEEirXe6aU2IPfKNvhXwTflK5HQRDNI0ypQXoqmm+QPyG2IaPuQE5zMwSIQ== 4385 4295 4386 csstype@^3.0.2, csstype@^3.0.8: 4296 4387 version "3.0.9" … … 4511 4602 dependencies: 4512 4603 path-type "^4.0.0" 4604 4605 dnd-core@14.0.1: 4606 version "14.0.1" 4607 resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-14.0.1.tgz#76d000e41c494983210fb20a48b835f81a203c2e" 4608 integrity sha512-+PVS2VPTgKFPYWo3vAFEA8WPbTf7/xo43TifH9G8S1KqnrQu0o77A3unrF5yOugy4mIz7K5wAVFHUcha7wsz6A== 4609 dependencies: 4610 "@react-dnd/asap" "^4.0.0" 4611 "@react-dnd/invariant" "^2.0.0" 4612 redux "^4.1.1" 4513 4613 4514 4614 dns-equal@^1.0.0: … … 5500 5600 integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== 5501 5601 5602 follow-redirects@^1.14.4: 5603 version "1.14.4" 5604 resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" 5605 integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== 5606 5502 5607 for-in@^1.0.2: 5503 5608 version "1.0.2" … … 7277 7382 verror "1.10.0" 7278 7383 7384 jss-plugin-camel-case@^10.5.1: 7385 version "10.8.1" 7386 resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.8.1.tgz#342fd406c2e8781ecdc4ac298a78b892f75581ab" 7387 integrity sha512-nOYKsvX9qh/AcUWSSRZHKyUj4RwqnhUSq4EKNFA1nHsNw0VJYwtF1yqtOPvztWEP3LTlNhcwoPINsb/eKVmYqA== 7388 dependencies: 7389 "@babel/runtime" "^7.3.1" 7390 hyphenate-style-name "^1.0.3" 7391 jss "10.8.1" 7392 7279 7393 jss-plugin-camel-case@^10.7.1: 7280 7394 version "10.8.0" … … 7286 7400 jss "10.8.0" 7287 7401 7402 jss-plugin-default-unit@^10.5.1: 7403 version "10.8.1" 7404 resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.8.1.tgz#1c35b89cd70ca5b0e01c21d89d908e75c1ea80ad" 7405 integrity sha512-W/uwVJNrFtUrVyAPfH/3ZngFYUVilMxgNbuWHYslqv3c5gnBKM6iXeoDzOnB+wtQJoSCTLzD3q77H7OeNK3oxg== 7406 dependencies: 7407 "@babel/runtime" "^7.3.1" 7408 jss "10.8.1" 7409 7288 7410 jss-plugin-default-unit@^10.7.1: 7289 7411 version "10.8.0" … … 7294 7416 jss "10.8.0" 7295 7417 7418 jss-plugin-global@^10.5.1: 7419 version "10.8.1" 7420 resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.8.1.tgz#992a14210c567178eb4cd385edcd267ae8cc6f28" 7421 integrity sha512-ERYLzD+L/v3yQL2mM5/PE+3xU/GCXcfXuGIL1kVkiEIpXnWtND/Mphf2iHQaqedx59uhiVHFZaMtv6qv+iNsDw== 7422 dependencies: 7423 "@babel/runtime" "^7.3.1" 7424 jss "10.8.1" 7425 7296 7426 jss-plugin-global@^10.7.1: 7297 7427 version "10.8.0" … … 7302 7432 jss "10.8.0" 7303 7433 7434 jss-plugin-nested@^10.5.1: 7435 version "10.8.1" 7436 resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.8.1.tgz#ac9750f8185725a0fd6ea484860767c53ec3d3dc" 7437 integrity sha512-Z15G23Fb8/br23EclH9CAq2UGdi29XgpSWXFTBusMJbWjitFdDCdYMzk7bSUJ6P7L5+WpaIDNxIJ9WrdMRqdXw== 7438 dependencies: 7439 "@babel/runtime" "^7.3.1" 7440 jss "10.8.1" 7441 tiny-warning "^1.0.2" 7442 7304 7443 jss-plugin-nested@^10.7.1: 7305 7444 version "10.8.0" … … 7311 7450 tiny-warning "^1.0.2" 7312 7451 7452 jss-plugin-props-sort@^10.5.1: 7453 version "10.8.1" 7454 resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.8.1.tgz#ee07bebf8ebeab01f8d9369973c64891cca53af9" 7455 integrity sha512-BNbKYuh4IawWr7cticlnbI+kBx01o39DNHkjAkc2CGKWVboUb2EpktDqonqVN/BjyzDgZXKOmwz36ZFkLQB45g== 7456 dependencies: 7457 "@babel/runtime" "^7.3.1" 7458 jss "10.8.1" 7459 7313 7460 jss-plugin-props-sort@^10.7.1: 7314 7461 version "10.8.0" … … 7319 7466 jss "10.8.0" 7320 7467 7468 jss-plugin-rule-value-function@^10.5.1: 7469 version "10.8.1" 7470 resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.8.1.tgz#40b19406f3cc027d9001de9026750e726c322993" 7471 integrity sha512-XrvM4bokyU1xPXr+gVEIlT9WylLQZcdC+1JDxriXDEWmKEjJgtH+w6ZicchTydLqq1qtA4fEevhdMvm4QvgIKw== 7472 dependencies: 7473 "@babel/runtime" "^7.3.1" 7474 jss "10.8.1" 7475 tiny-warning "^1.0.2" 7476 7321 7477 jss-plugin-rule-value-function@^10.7.1: 7322 7478 version "10.8.0" … … 7328 7484 tiny-warning "^1.0.2" 7329 7485 7486 jss-plugin-vendor-prefixer@^10.5.1: 7487 version "10.8.1" 7488 resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.8.1.tgz#362146c2b641aae1d29f279307aec0e2167c7ee2" 7489 integrity sha512-77b/iEFmA669s+USru2Y5eg9Hs1C1N0zE/4EaJm/fqKScCTNawHXZv5l5w6j81A9CNa63Ar7jekAIfBkoKFmLw== 7490 dependencies: 7491 "@babel/runtime" "^7.3.1" 7492 css-vendor "^2.0.8" 7493 jss "10.8.1" 7494 7330 7495 jss-plugin-vendor-prefixer@^10.7.1: 7331 7496 version "10.8.0" … … 7347 7512 tiny-warning "^1.0.2" 7348 7513 7514 jss@10.8.1, jss@^10.5.1: 7515 version "10.8.1" 7516 resolved "https://registry.yarnpkg.com/jss/-/jss-10.8.1.tgz#375797c259ffce417e56ae1a7fe703acde8de9ee" 7517 integrity sha512-P4wKxU+2m5ReGl0Mmbf9XYgVjFIVZJOZ9ylXBxdpanX+HHgj5XVaAIgYzYpKbBLPCdkAUsI/Iq1fhQPsMNu0YA== 7518 dependencies: 7519 "@babel/runtime" "^7.3.1" 7520 csstype "^3.0.2" 7521 is-in-browser "^1.1.3" 7522 tiny-warning "^1.0.2" 7523 7349 7524 "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: 7350 7525 version "3.2.0" … … 7355 7530 object.assign "^4.1.2" 7356 7531 7532 jwt-decode@^3.1.2: 7533 version "3.1.2" 7534 resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" 7535 integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== 7536 7357 7537 killable@^1.0.1: 7358 7538 version "1.0.1" … … 7623 7803 dependencies: 7624 7804 object-visit "^1.0.0" 7805 7806 material-ui-color@^1.2.0: 7807 version "1.2.0" 7808 resolved "https://registry.yarnpkg.com/material-ui-color/-/material-ui-color-1.2.0.tgz#3dde809e9f5f4a29499add83640aeadaaec36f6c" 7809 integrity sha512-bD2Rww+hakJxD2/19uxc280Vh292DnRStLke2LDFavVtGd5fzOz09zIrHO3ZHlMkJFsvwx6IwiB4/932ftv0sQ== 7625 7810 7626 7811 md5.js@^1.3.4: … … 8631 8816 dependencies: 8632 8817 ts-pnp "^1.1.6" 8818 8819 popper.js@1.16.1-lts: 8820 version "1.16.1-lts" 8821 resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05" 8822 integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA== 8633 8823 8634 8824 portfinder@^1.0.26: … … 9606 9796 text-table "0.2.0" 9607 9797 9798 react-dnd-html5-backend@^14.0.2: 9799 version "14.0.2" 9800 resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-14.0.2.tgz#25019388f6abdeeda3a6fea835dff155abb2085c" 9801 integrity sha512-QgN6rYrOm4UUj6tIvN8ovImu6uP48xBXF2rzVsp6tvj6d5XQ7OjHI4SJ/ZgGobOneRAU3WCX4f8DGCYx0tuhlw== 9802 dependencies: 9803 dnd-core "14.0.1" 9804 9805 react-dnd-preview@^6.0.2: 9806 version "6.0.2" 9807 resolved "https://registry.yarnpkg.com/react-dnd-preview/-/react-dnd-preview-6.0.2.tgz#dd34931c270853c80438e1275e6c9e77174f8afe" 9808 integrity sha512-F2+uK4Be+q+7mZfNh9kaZols7wp1hX6G7UBTVaTpDsBpMhjFvY7/v7odxYSerSFBShh23MJl33a4XOVRFj1zoQ== 9809 dependencies: 9810 prop-types "^15.7.2" 9811 9812 react-dnd-touch-backend@^14.1.1: 9813 version "14.1.1" 9814 resolved "https://registry.yarnpkg.com/react-dnd-touch-backend/-/react-dnd-touch-backend-14.1.1.tgz#d8875ef1cf8dcbf1741a4e03dd5b147c4fbda5e4" 9815 integrity sha512-ITmfzn3fJrkUBiVLO6aJZcnu7T8C+GfwZitFryGsXKn5wYcUv+oQBeh9FYcMychmVbDdeUCfvEtTk9O+DKmAaw== 9816 dependencies: 9817 "@react-dnd/invariant" "^2.0.0" 9818 dnd-core "14.0.1" 9819 9820 react-dnd@^14.0.4: 9821 version "14.0.4" 9822 resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-14.0.4.tgz#ffb4ea0e2a3a5532f9c6294d565742008a52b8b0" 9823 integrity sha512-AFJJXzUIWp5WAhgvI85ESkDCawM0lhoVvfo/lrseLXwFdH3kEO3v8I2C81QPqBW2UEyJBIPStOhPMGYGFtq/bg== 9824 dependencies: 9825 "@react-dnd/invariant" "^2.0.0" 9826 "@react-dnd/shallowequal" "^2.0.0" 9827 dnd-core "14.0.1" 9828 fast-deep-equal "^3.1.3" 9829 hoist-non-react-statics "^3.3.2" 9830 9608 9831 react-dom@^17.0.2: 9609 9832 version "17.0.2" … … 9625 9848 integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== 9626 9849 9850 "react-is@^16.8.0 || ^17.0.0", react-is@^17.0.2: 9851 version "17.0.2" 9852 resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" 9853 integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== 9854 9627 9855 react-is@^17.0.1: 9628 9856 version "17.0.1" 9629 9857 resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" 9630 9858 integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== 9631 9632 react-is@^17.0.2:9633 version "17.0.2"9634 resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"9635 integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==9636 9859 9637 9860 react-refresh@^0.8.3: … … 9735 9958 fsevents "^2.1.3" 9736 9959 9737 react-transition-group@^4.4. 2:9960 react-transition-group@^4.4.0, react-transition-group@^4.4.2: 9738 9961 version "4.4.2" 9739 9962 resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" … … 9841 10064 indent-string "^4.0.0" 9842 10065 strip-indent "^3.0.0" 10066 10067 redux@^4.1.1: 10068 version "4.1.2" 10069 resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" 10070 integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw== 10071 dependencies: 10072 "@babel/runtime" "^7.9.2" 9843 10073 9844 10074 regenerate-unicode-properties@^8.2.0:
Note:
See TracChangeset
for help on using the changeset viewer.